-
-
Save Zhuinden/ab065534bbf73d7e6de83b5a39366c24 to your computer and use it in GitHub Desktop.
| /* | |
| * Copyright 2020 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| /** | |
| * Basic composable that enables users to edit text via hardware or software keyboard, but | |
| * provides no decorations like hint or placeholder. | |
| * | |
| * Whenever the user edits the text, [onValueChange] is called with the most up to date state | |
| * represented by [String] with which developer is expected to update their state. | |
| * | |
| * Unlike [TextFieldValue] overload, this composable does not let the developer to control | |
| * selection, cursor and text composition information. Please check [TextFieldValue] and | |
| * corresponding [BasicTextField] overload for more information. | |
| * | |
| * It is crucial that the value provided in the [onValueChange] is fed back into [BasicTextField] in | |
| * order to have the final state of the text being displayed. | |
| * | |
| * Example usage: | |
| * @sample androidx.compose.foundation.samples.BasicTextFieldWithStringSample | |
| * | |
| * Please keep in mind that [onValueChange] is useful to be informed about the latest state of the | |
| * text input by users, however it is generally not recommended to modify the value that you get | |
| * via [onValueChange] callback. Any change to this value may result in a context reset and end | |
| * up with input session restart. Such a scenario would cause glitches in the UI or text input | |
| * experience for users. | |
| * | |
| * This composable provides basic text editing functionality, however does not include any | |
| * decorations such as borders, hints/placeholder. A design system based implementation such as | |
| * Material Design Filled text field is typically what is needed to cover most of the needs. This | |
| * composable is designed to be used when a custom implementation for different design system is | |
| * needed. | |
| * | |
| * For example, if you need to include a placeholder in your TextField, you can write a composable | |
| * using the decoration box like this: | |
| * @sample androidx.compose.foundation.samples.PlaceholderBasicTextFieldSample | |
| * | |
| * If you want to add decorations to your text field, such as icon or similar, and increase the | |
| * hit target area, use the decoration box: | |
| * @sample androidx.compose.foundation.samples.TextFieldWithIconSample | |
| * | |
| * In order to create formatted text field, for example for entering a phone number or a social | |
| * security number, use a [visualTransformation] parameter. Below is the example of the text field | |
| * for entering a credit card number: | |
| * @sample androidx.compose.foundation.samples.CreditCardSample | |
| * | |
| * @param value the input [String] text to be shown in the text field | |
| * @param onValueChange the callback that is triggered when the input service updates the text. An | |
| * updated text comes as a parameter of the callback | |
| * @param modifier optional [Modifier] for this text field. | |
| * @param enabled controls the enabled state of the [BasicTextField]. When `false`, the text | |
| * field will be neither editable nor focusable, the input of the text field will not be selectable | |
| * @param readOnly controls the editable state of the [BasicTextField]. When `true`, the text | |
| * field can not be modified, however, a user can focus it and copy text from it. Read-only text | |
| * fields are usually used to display pre-filled forms that user can not edit | |
| * @param textStyle Style configuration that applies at character level such as color, font etc. | |
| * @param keyboardOptions software keyboard options that contains configuration such as | |
| * [KeyboardType] and [ImeAction]. | |
| * @param keyboardActions when the input service emits an IME action, the corresponding callback | |
| * is called. Note that this IME action may be different from what you specified in | |
| * [KeyboardOptions.imeAction]. | |
| * @param singleLine when set to true, this text field becomes a single horizontally scrolling | |
| * text field instead of wrapping onto multiple lines. The keyboard will be informed to not show | |
| * the return key as the [ImeAction]. [maxLines] and [minLines] are ignored as both are | |
| * automatically set to 1. | |
| * @param maxLines the maximum height in terms of maximum number of visible lines. It is required | |
| * that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true. | |
| * @param minLines the minimum height in terms of minimum number of visible lines. It is required | |
| * that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true. | |
| * @param visualTransformation The visual transformation filter for changing the visual | |
| * representation of the input. By default no visual transformation is applied. | |
| * @param onTextLayout Callback that is executed when a new text layout is calculated. A | |
| * [TextLayoutResult] object that callback provides contains paragraph information, size of the | |
| * text, baselines and other details. The callback can be used to add additional decoration or | |
| * functionality to the text. For example, to draw a cursor or selection around the text. | |
| * @param interactionSource the [MutableInteractionSource] representing the stream of | |
| * [Interaction]s for this TextField. You can create and pass in your own remembered | |
| * [MutableInteractionSource] if you want to observe [Interaction]s and customize the | |
| * appearance / behavior of this TextField in different [Interaction]s. | |
| * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified] | |
| * provided, there will be no cursor drawn | |
| * @param decorationBox Composable lambda that allows to add decorations around text field, such | |
| * as icon, placeholder, helper messages or similar, and automatically increase the hit target area | |
| * of the text field. To allow you to control the placement of the inner text field relative to your | |
| * decorations, the text field implementation will pass in a framework-controlled composable | |
| * parameter "innerTextField" to the decorationBox lambda you provide. You must call | |
| * innerTextField exactly once. | |
| */ | |
| @Composable | |
| fun BasicTextFieldWithCursorAtEnd( | |
| value: String, | |
| onValueChange: (String) -> Unit, | |
| modifier: Modifier = Modifier, | |
| enabled: Boolean = true, | |
| readOnly: Boolean = false, | |
| textStyle: TextStyle = TextStyle.Default, | |
| keyboardOptions: KeyboardOptions = KeyboardOptions.Default, | |
| keyboardActions: KeyboardActions = KeyboardActions.Default, | |
| singleLine: Boolean = false, | |
| maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, | |
| minLines: Int = 1, | |
| visualTransformation: VisualTransformation = VisualTransformation.None, | |
| onTextLayout: (TextLayoutResult) -> Unit = {}, | |
| interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, | |
| cursorBrush: Brush = SolidColor(Color.Black), | |
| decorationBox: @Composable (innerTextField: @Composable () -> Unit) -> Unit = | |
| @Composable { innerTextField -> innerTextField() } | |
| ) { | |
| // Holds the latest internal TextFieldValue state. We need to keep it to have the correct value | |
| // of the composition. | |
| var textFieldValueState by remember { | |
| mutableStateOf( | |
| TextFieldValue( | |
| text = value, selection = when { | |
| value.isEmpty() -> TextRange.Zero | |
| else -> TextRange(value.length, value.length) | |
| } | |
| ) | |
| ) | |
| } | |
| // Holds the latest TextFieldValue that BasicTextField was recomposed with. We couldn't simply | |
| // pass `TextFieldValue(text = value)` to the CoreTextField because we need to preserve the | |
| // composition. | |
| val textFieldValue = textFieldValueState.copy(text = value) | |
| SideEffect { | |
| if (textFieldValue.selection != textFieldValueState.selection || | |
| textFieldValue.composition != textFieldValueState.composition | |
| ) { | |
| textFieldValueState = textFieldValue | |
| } | |
| } | |
| // Last String value that either text field was recomposed with or updated in the onValueChange | |
| // callback. We keep track of it to prevent calling onValueChange(String) for same String when | |
| // CoreTextField's onValueChange is called multiple times without recomposition in between. | |
| var lastTextValue by remember(value) { mutableStateOf(value) } | |
| BasicTextField( | |
| value = textFieldValue, | |
| onValueChange = { newTextFieldValueState -> | |
| textFieldValueState = newTextFieldValueState | |
| val stringChangedSinceLastInvocation = lastTextValue != newTextFieldValueState.text | |
| lastTextValue = newTextFieldValueState.text | |
| if (stringChangedSinceLastInvocation) { | |
| onValueChange(newTextFieldValueState.text) | |
| } | |
| }, | |
| modifier = modifier, | |
| enabled = enabled, | |
| readOnly = readOnly, | |
| textStyle = textStyle, | |
| keyboardOptions = keyboardOptions, | |
| keyboardActions = keyboardActions, | |
| singleLine = singleLine, | |
| maxLines = maxLines, | |
| minLines = minLines, | |
| visualTransformation = visualTransformation, | |
| onTextLayout = onTextLayout, | |
| interactionSource = interactionSource, | |
| cursorBrush = cursorBrush, | |
| decorationBox = decorationBox, | |
| ) | |
| } |
| /* | |
| * Copyright 2022 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| /** | |
| * <a href="https://m3.material.io/components/text-fields/overview" class="external" target="_blank">Material Design outlined text field</a>. | |
| * | |
| * Text fields allow users to enter text into a UI. They typically appear in forms and dialogs. | |
| * Outlined text fields have less visual emphasis than filled text fields. When they appear in | |
| * places like forms, where many text fields are placed together, their reduced emphasis helps | |
| * simplify the layout. | |
| * | |
| *  | |
| * | |
| * See example usage: | |
| * @sample androidx.compose.material3.samples.SimpleOutlinedTextFieldSample | |
| * | |
| * If apart from input text change you also want to observe the cursor location, selection range, | |
| * or IME composition use the OutlinedTextField overload with the [TextFieldValue] parameter | |
| * instead. | |
| * | |
| * @param value the input text to be shown in the text field | |
| * @param onValueChange the callback that is triggered when the input service updates the text. An | |
| * updated text comes as a parameter of the callback | |
| * @param modifier the [Modifier] to be applied to this text field | |
| * @param enabled controls the enabled state of this text field. When `false`, this component will | |
| * not respond to user input, and it will appear visually disabled and disabled to accessibility | |
| * services. | |
| * @param readOnly controls the editable state of the text field. When `true`, the text field cannot | |
| * be modified. However, a user can focus it and copy text from it. Read-only text fields are | |
| * usually used to display pre-filled forms that a user cannot edit. | |
| * @param textStyle the style to be applied to the input text. Defaults to [LocalTextStyle]. | |
| * @param label the optional label to be displayed inside the text field container. The default | |
| * text style for internal [Text] is [Typography.bodySmall] when the text field is in focus and | |
| * [Typography.bodyLarge] when the text field is not in focus | |
| * @param placeholder the optional placeholder to be displayed when the text field is in focus and | |
| * the input text is empty. The default text style for internal [Text] is [Typography.bodyLarge] | |
| * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field | |
| * container | |
| * @param trailingIcon the optional trailing icon to be displayed at the end of the text field | |
| * container | |
| * @param prefix the optional prefix to be displayed before the input text in the text field | |
| * @param suffix the optional suffix to be displayed after the input text in the text field | |
| * @param supportingText the optional supporting text to be displayed below the text field | |
| * @param isError indicates if the text field's current value is in error. If set to true, the | |
| * label, bottom indicator and trailing icon by default will be displayed in error color | |
| * @param visualTransformation transforms the visual representation of the input [value] | |
| * For example, you can use | |
| * [PasswordVisualTransformation][androidx.compose.ui.text.input.PasswordVisualTransformation] to | |
| * create a password text field. By default, no visual transformation is applied. | |
| * @param keyboardOptions software keyboard options that contains configuration such as | |
| * [KeyboardType] and [ImeAction] | |
| * @param keyboardActions when the input service emits an IME action, the corresponding callback | |
| * is called. Note that this IME action may be different from what you specified in | |
| * [KeyboardOptions.imeAction] | |
| * @param singleLine when `true`, this text field becomes a single horizontally scrolling text field | |
| * instead of wrapping onto multiple lines. The keyboard will be informed to not show the return key | |
| * as the [ImeAction]. Note that [maxLines] parameter will be ignored as the maxLines attribute will | |
| * be automatically set to 1. | |
| * @param maxLines the maximum height in terms of maximum number of visible lines. It is required | |
| * that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true. | |
| * @param minLines the minimum height in terms of minimum number of visible lines. It is required | |
| * that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true. | |
| * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s | |
| * for this text field. You can create and pass in your own `remember`ed instance to observe | |
| * [Interaction]s and customize the appearance / behavior of this text field in different states. | |
| * @param shape defines the shape of this text field's border | |
| * @param colors [TextFieldColors] that will be used to resolve the colors used for this text field | |
| * in different states. See [OutlinedTextFieldDefaults.colors]. | |
| */ | |
| @Composable | |
| fun M3OutlinedTextField( | |
| value: String, | |
| onValueChange: (String) -> Unit, | |
| modifier: Modifier = Modifier, | |
| enabled: Boolean = true, | |
| readOnly: Boolean = false, | |
| textStyle: TextStyle = LocalTextStyle.current, | |
| label: @Composable (() -> Unit)? = null, | |
| placeholder: @Composable (() -> Unit)? = null, | |
| leadingIcon: @Composable (() -> Unit)? = null, | |
| trailingIcon: @Composable (() -> Unit)? = null, | |
| prefix: @Composable (() -> Unit)? = null, | |
| suffix: @Composable (() -> Unit)? = null, | |
| supportingText: @Composable (() -> Unit)? = null, | |
| isError: Boolean = false, | |
| visualTransformation: VisualTransformation = VisualTransformation.None, | |
| keyboardOptions: KeyboardOptions = KeyboardOptions.Default, | |
| keyboardActions: KeyboardActions = KeyboardActions.Default, | |
| singleLine: Boolean = false, | |
| maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, | |
| minLines: Int = 1, | |
| interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, | |
| shape: Shape = OutlinedTextFieldDefaults.shape, | |
| colors: TextFieldColors = OutlinedTextFieldDefaults.colors() | |
| ) { | |
| var textFieldValueState by remember { | |
| mutableStateOf( | |
| TextFieldValue( | |
| text = value, selection = when { | |
| value.isEmpty() -> TextRange.Zero | |
| else -> TextRange(value.length, value.length) | |
| } | |
| ) | |
| ) | |
| } | |
| val textFieldValue = textFieldValueState.copy(text = value) | |
| SideEffect { | |
| if (textFieldValue.selection != textFieldValueState.selection || | |
| textFieldValue.composition != textFieldValueState.composition | |
| ) { | |
| textFieldValueState = textFieldValue | |
| } | |
| } | |
| var lastTextValue by remember(value) { mutableStateOf(value) } | |
| androidx.compose.material3.OutlinedTextField( | |
| value = textFieldValue, | |
| onValueChange = { newTextFieldValueState -> | |
| textFieldValueState = newTextFieldValueState | |
| val stringChangedSinceLastInvocation = lastTextValue != newTextFieldValueState.text | |
| lastTextValue = newTextFieldValueState.text | |
| if (stringChangedSinceLastInvocation) { | |
| onValueChange(newTextFieldValueState.text) | |
| } | |
| }, | |
| modifier = modifier, | |
| enabled = enabled, | |
| readOnly = readOnly, | |
| textStyle = textStyle, | |
| label = label, | |
| placeholder = placeholder, | |
| leadingIcon = leadingIcon, | |
| trailingIcon = trailingIcon, | |
| prefix = prefix, | |
| suffix = suffix, | |
| supportingText = supportingText, | |
| isError = isError, | |
| visualTransformation = visualTransformation, | |
| keyboardOptions = keyboardOptions, | |
| keyboardActions = keyboardActions, | |
| singleLine = singleLine, | |
| maxLines = maxLines, | |
| minLines = minLines, | |
| interactionSource = interactionSource, | |
| shape = shape, | |
| colors = colors, | |
| ) | |
| } |
| /* | |
| * Copyright 2020 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| import androidx.compose.foundation.background | |
| import androidx.compose.foundation.interaction.Interaction | |
| import androidx.compose.foundation.interaction.MutableInteractionSource | |
| import androidx.compose.foundation.layout.defaultMinSize | |
| import androidx.compose.foundation.layout.padding | |
| import androidx.compose.foundation.text.BasicTextField | |
| import androidx.compose.foundation.text.KeyboardActions | |
| import androidx.compose.foundation.text.KeyboardOptions | |
| import androidx.compose.material.ExperimentalMaterialApi | |
| import androidx.compose.material.LocalTextStyle | |
| import androidx.compose.material.MaterialTheme | |
| import androidx.compose.material.OutlinedTextField | |
| import androidx.compose.material.Text | |
| import androidx.compose.material.TextFieldColors | |
| import androidx.compose.material.TextFieldDefaults | |
| import androidx.compose.material.Typography | |
| import androidx.compose.runtime.Composable | |
| import androidx.compose.runtime.SideEffect | |
| import androidx.compose.runtime.getValue | |
| import androidx.compose.runtime.mutableStateOf | |
| import androidx.compose.runtime.remember | |
| import androidx.compose.runtime.setValue | |
| import androidx.compose.ui.Modifier | |
| import androidx.compose.ui.graphics.Shape | |
| import androidx.compose.ui.graphics.SolidColor | |
| import androidx.compose.ui.graphics.takeOrElse | |
| import androidx.compose.ui.semantics.semantics | |
| import androidx.compose.ui.text.TextRange | |
| import androidx.compose.ui.text.TextStyle | |
| import androidx.compose.ui.text.input.ImeAction | |
| import androidx.compose.ui.text.input.KeyboardType | |
| import androidx.compose.ui.text.input.TextFieldValue | |
| import androidx.compose.ui.text.input.VisualTransformation | |
| import androidx.compose.ui.unit.dp | |
| /** | |
| * <a href="https://material.io/components/text-fields#outlined-text-field" class="external" target="_blank">Material Design outlined text field</a>. | |
| * | |
| * Outlined text fields have less visual emphasis than filled text fields. When they appear in | |
| * places like forms, where many text fields are placed together, their reduced emphasis helps | |
| * simplify the layout. | |
| * | |
| *  | |
| * | |
| * See example usage: | |
| * @sample androidx.compose.material.samples.SimpleOutlinedTextFieldSample | |
| * | |
| * If apart from input text change you also want to observe the cursor location, selection range, | |
| * or IME composition use the OutlinedTextField overload with the [TextFieldValue] parameter | |
| * instead. | |
| * | |
| * @param value the input text to be shown in the text field | |
| * @param onValueChange the callback that is triggered when the input service updates the text. An | |
| * updated text comes as a parameter of the callback | |
| * @param modifier a [Modifier] for this text field | |
| * @param enabled controls the enabled state of the [OutlinedTextField]. When `false`, the text field will | |
| * be neither editable nor focusable, the input of the text field will not be selectable, | |
| * visually text field will appear in the disabled UI state | |
| * @param readOnly controls the editable state of the [OutlinedTextField]. When `true`, the text | |
| * field can not be modified, however, a user can focus it and copy text from it. Read-only text | |
| * fields are usually used to display pre-filled forms that user can not edit | |
| * @param textStyle the style to be applied to the input text. The default [textStyle] uses the | |
| * [LocalTextStyle] defined by the theme | |
| * @param label the optional label to be displayed inside the text field container. The default | |
| * text style for internal [Text] is [Typography.caption] when the text field is in focus and | |
| * [Typography.subtitle1] when the text field is not in focus | |
| * @param placeholder the optional placeholder to be displayed when the text field is in focus and | |
| * the input text is empty. The default text style for internal [Text] is [Typography.subtitle1] | |
| * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field | |
| * container | |
| * @param trailingIcon the optional trailing icon to be displayed at the end of the text field | |
| * container | |
| * @param isError indicates if the text field's current value is in error. If set to true, the | |
| * label, bottom indicator and trailing icon by default will be displayed in error color | |
| * @param visualTransformation transforms the visual representation of the input [value] | |
| * For example, you can use | |
| * [PasswordVisualTransformation][androidx.compose.ui.text.input.PasswordVisualTransformation] to | |
| * create a password text field. By default no visual transformation is applied | |
| * @param keyboardOptions software keyboard options that contains configuration such as | |
| * [KeyboardType] and [ImeAction] | |
| * @param keyboardActions when the input service emits an IME action, the corresponding callback | |
| * is called. Note that this IME action may be different from what you specified in | |
| * [KeyboardOptions.imeAction] | |
| * @param singleLine when set to true, this text field becomes a single horizontally scrolling | |
| * text field instead of wrapping onto multiple lines. The keyboard will be informed to not show | |
| * the return key as the [ImeAction]. Note that [maxLines] parameter will be ignored as the | |
| * maxLines attribute will be automatically set to 1 | |
| * @param maxLines the maximum height in terms of maximum number of visible lines. It is required | |
| * that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true. | |
| * @param minLines the minimum height in terms of minimum number of visible lines. It is required | |
| * that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true. | |
| * @param interactionSource the [MutableInteractionSource] representing the stream of | |
| * [Interaction]s for this OutlinedTextField. You can create and pass in your own remembered | |
| * [MutableInteractionSource] if you want to observe [Interaction]s and customize the | |
| * appearance / behavior of this OutlinedTextField in different [Interaction]s. | |
| * @param shape the shape of the text field's border | |
| * @param colors [TextFieldColors] that will be used to resolve color of the text and content | |
| * (including label, placeholder, leading and trailing icons, border) for this text field in | |
| * different states. See [TextFieldDefaults.outlinedTextFieldColors] | |
| */ | |
| @Composable | |
| fun OutlinedTextFieldWithCursorAtEnd( | |
| value: String, | |
| onValueChange: (String) -> Unit, | |
| modifier: Modifier = Modifier, | |
| enabled: Boolean = true, | |
| readOnly: Boolean = false, | |
| textStyle: TextStyle = LocalTextStyle.current, | |
| label: @Composable (() -> Unit)? = null, | |
| placeholder: @Composable (() -> Unit)? = null, | |
| leadingIcon: @Composable (() -> Unit)? = null, | |
| trailingIcon: @Composable (() -> Unit)? = null, | |
| isError: Boolean = false, | |
| visualTransformation: VisualTransformation = VisualTransformation.None, | |
| keyboardOptions: KeyboardOptions = KeyboardOptions.Default, | |
| keyboardActions: KeyboardActions = KeyboardActions.Default, | |
| singleLine: Boolean = false, | |
| maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, | |
| minLines: Int = 1, | |
| interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, | |
| shape: Shape = MaterialTheme.shapes.small, | |
| colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors() | |
| ) { | |
| var textFieldValueState by remember { | |
| mutableStateOf( | |
| TextFieldValue( | |
| text = value, selection = when { | |
| value.isEmpty() -> TextRange.Zero | |
| else -> TextRange(value.length, value.length) | |
| } | |
| ) | |
| ) | |
| } | |
| val textFieldValue = textFieldValueState.copy(text = value) | |
| SideEffect { | |
| if (textFieldValue.selection != textFieldValueState.selection || | |
| textFieldValue.composition != textFieldValueState.composition | |
| ) { | |
| textFieldValueState = textFieldValue | |
| } | |
| } | |
| var lastTextValue by remember(value) { mutableStateOf(value) } | |
| OutlinedTextField( | |
| value = textFieldValue, | |
| onValueChange = { newTextFieldValueState -> | |
| textFieldValueState = newTextFieldValueState | |
| val stringChangedSinceLastInvocation = lastTextValue != newTextFieldValueState.text | |
| lastTextValue = newTextFieldValueState.text | |
| if (stringChangedSinceLastInvocation) { | |
| onValueChange(newTextFieldValueState.text) | |
| } | |
| }, | |
| modifier = modifier, | |
| enabled = enabled, | |
| readOnly = readOnly, | |
| textStyle = textStyle, | |
| label = label, | |
| placeholder = placeholder, | |
| leadingIcon = leadingIcon, | |
| trailingIcon = trailingIcon, | |
| isError = isError, | |
| visualTransformation = visualTransformation, | |
| keyboardOptions = keyboardOptions, | |
| keyboardActions = keyboardActions, | |
| singleLine = singleLine, | |
| maxLines = maxLines, | |
| minLines = minLines, | |
| interactionSource = interactionSource, | |
| shape = shape, | |
| colors = colors, | |
| ) | |
| } |
| /* | |
| * Copyright 2020 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| import androidx.compose.foundation.background | |
| import androidx.compose.foundation.interaction.Interaction | |
| import androidx.compose.foundation.interaction.MutableInteractionSource | |
| import androidx.compose.foundation.layout.defaultMinSize | |
| import androidx.compose.foundation.shape.ZeroCornerSize | |
| import androidx.compose.foundation.text.BasicTextField | |
| import androidx.compose.foundation.text.KeyboardActions | |
| import androidx.compose.foundation.text.KeyboardOptions | |
| import androidx.compose.material.ExperimentalMaterialApi | |
| import androidx.compose.material.LocalTextStyle | |
| import androidx.compose.material.MaterialTheme | |
| import androidx.compose.material.OutlinedTextField | |
| import androidx.compose.material.Text | |
| import androidx.compose.material.TextField | |
| import androidx.compose.material.TextFieldColors | |
| import androidx.compose.material.TextFieldDefaults | |
| import androidx.compose.material.TextFieldDefaults.indicatorLine | |
| import androidx.compose.material.Typography | |
| import androidx.compose.runtime.Composable | |
| import androidx.compose.runtime.SideEffect | |
| import androidx.compose.runtime.getValue | |
| import androidx.compose.runtime.mutableStateOf | |
| import androidx.compose.runtime.remember | |
| import androidx.compose.runtime.setValue | |
| import androidx.compose.ui.Modifier | |
| import androidx.compose.ui.graphics.Shape | |
| import androidx.compose.ui.graphics.SolidColor | |
| import androidx.compose.ui.graphics.takeOrElse | |
| import androidx.compose.ui.text.TextRange | |
| import androidx.compose.ui.text.TextStyle | |
| import androidx.compose.ui.text.input.ImeAction | |
| import androidx.compose.ui.text.input.KeyboardType | |
| import androidx.compose.ui.text.input.TextFieldValue | |
| import androidx.compose.ui.text.input.VisualTransformation | |
| // based on Material TextField 1.5.1 | |
| /** | |
| * <a href="https://material.io/components/text-fields#filled-text-field" class="external" target="_blank">Material Design filled text field</a>. | |
| * | |
| * Filled text fields have more visual emphasis than outlined text fields, making them stand out | |
| * when surrounded by other content and components. | |
| * | |
| *  | |
| * | |
| * If you are looking for an outlined version, see [OutlinedTextField]. | |
| * | |
| * A simple single line text field looks like: | |
| * | |
| * @sample androidx.compose.material.samples.SimpleTextFieldSample | |
| * | |
| * You may provide a placeholder: | |
| * | |
| * @sample androidx.compose.material.samples.TextFieldWithPlaceholder | |
| * | |
| * You can also provide leading and trailing icons: | |
| * | |
| * @sample androidx.compose.material.samples.TextFieldWithIcons | |
| * | |
| * To handle the error input state, use [isError] parameter: | |
| * | |
| * @sample androidx.compose.material.samples.TextFieldWithErrorState | |
| * | |
| * Additionally, you may provide additional message at the bottom: | |
| * | |
| * @sample androidx.compose.material.samples.TextFieldWithHelperMessage | |
| * | |
| * Password text field example: | |
| * | |
| * @sample androidx.compose.material.samples.PasswordTextField | |
| * | |
| * Hiding a software keyboard on IME action performed: | |
| * | |
| * @sample androidx.compose.material.samples.TextFieldWithHideKeyboardOnImeAction | |
| * | |
| * If apart from input text change you also want to observe the cursor location, selection range, | |
| * or IME composition use the TextField overload with the [TextFieldValue] parameter instead. | |
| * | |
| * @param value the input text to be shown in the text field | |
| * @param onValueChange the callback that is triggered when the input service updates the text. An | |
| * updated text comes as a parameter of the callback | |
| * @param modifier a [Modifier] for this text field | |
| * @param enabled controls the enabled state of the [TextField]. When `false`, the text field will | |
| * be neither editable nor focusable, the input of the text field will not be selectable, | |
| * visually text field will appear in the disabled UI state | |
| * @param readOnly controls the editable state of the [TextField]. When `true`, the text | |
| * field can not be modified, however, a user can focus it and copy text from it. Read-only text | |
| * fields are usually used to display pre-filled forms that user can not edit | |
| * @param textStyle the style to be applied to the input text. The default [textStyle] uses the | |
| * [LocalTextStyle] defined by the theme | |
| * @param label the optional label to be displayed inside the text field container. The default | |
| * text style for internal [Text] is [Typography.caption] when the text field is in focus and | |
| * [Typography.subtitle1] when the text field is not in focus | |
| * @param placeholder the optional placeholder to be displayed when the text field is in focus and | |
| * the input text is empty. The default text style for internal [Text] is [Typography.subtitle1] | |
| * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field | |
| * container | |
| * @param trailingIcon the optional trailing icon to be displayed at the end of the text field | |
| * container | |
| * @param isError indicates if the text field's current value is in error. If set to true, the | |
| * label, bottom indicator and trailing icon by default will be displayed in error color | |
| * @param visualTransformation transforms the visual representation of the input [value] | |
| * For example, you can use | |
| * [PasswordVisualTransformation][androidx.compose.ui.text.input.PasswordVisualTransformation] to | |
| * create a password text field. By default no visual transformation is applied | |
| * @param keyboardOptions software keyboard options that contains configuration such as | |
| * [KeyboardType] and [ImeAction]. | |
| * @param keyboardActions when the input service emits an IME action, the corresponding callback | |
| * is called. Note that this IME action may be different from what you specified in | |
| * [KeyboardOptions.imeAction]. | |
| * @param singleLine when set to true, this text field becomes a single horizontally scrolling | |
| * text field instead of wrapping onto multiple lines. The keyboard will be informed to not show | |
| * the return key as the [ImeAction]. Note that [maxLines] parameter will be ignored as the | |
| * maxLines attribute will be automatically set to 1. | |
| * @param maxLines the maximum height in terms of maximum number of visible lines. It is required | |
| * that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true. | |
| * @param minLines the minimum height in terms of minimum number of visible lines. It is required | |
| * that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true. | |
| * @param interactionSource the [MutableInteractionSource] representing the stream of | |
| * [Interaction]s for this TextField. You can create and pass in your own remembered | |
| * [MutableInteractionSource] if you want to observe [Interaction]s and customize the | |
| * appearance / behavior of this TextField in different [Interaction]s. | |
| * @param shape the shape of the text field's container | |
| * @param colors [TextFieldColors] that will be used to resolve color of the text, content | |
| * (including label, placeholder, leading and trailing icons, indicator line) and background for | |
| * this text field in different states. See [TextFieldDefaults.textFieldColors] | |
| */ | |
| @Composable | |
| fun TextFieldWithCursorAtEnd( | |
| value: String, | |
| onValueChange: (String) -> Unit, | |
| modifier: Modifier = Modifier, | |
| enabled: Boolean = true, | |
| readOnly: Boolean = false, | |
| textStyle: TextStyle = LocalTextStyle.current, | |
| label: @Composable (() -> Unit)? = null, | |
| placeholder: @Composable (() -> Unit)? = null, | |
| leadingIcon: @Composable (() -> Unit)? = null, | |
| trailingIcon: @Composable (() -> Unit)? = null, | |
| isError: Boolean = false, | |
| visualTransformation: VisualTransformation = VisualTransformation.None, | |
| keyboardOptions: KeyboardOptions = KeyboardOptions.Default, | |
| keyboardActions: KeyboardActions = KeyboardActions(), | |
| singleLine: Boolean = false, | |
| maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, | |
| minLines: Int = 1, | |
| interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, | |
| shape: Shape = | |
| MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize), | |
| colors: TextFieldColors = TextFieldDefaults.textFieldColors() | |
| ) { | |
| var textFieldValueState by remember { | |
| mutableStateOf( | |
| TextFieldValue( | |
| text = value, selection = when { | |
| value.isEmpty() -> TextRange.Zero | |
| else -> TextRange(value.length, value.length) | |
| } | |
| ) | |
| ) | |
| } | |
| val textFieldValue = textFieldValueState.copy(text = value) | |
| SideEffect { | |
| if (textFieldValue.selection != textFieldValueState.selection || | |
| textFieldValue.composition != textFieldValueState.composition | |
| ) { | |
| textFieldValueState = textFieldValue | |
| } | |
| } | |
| var lastTextValue by remember(value) { mutableStateOf(value) } | |
| TextField( | |
| value = textFieldValue, | |
| onValueChange = { newTextFieldValueState -> | |
| textFieldValueState = newTextFieldValueState | |
| val stringChangedSinceLastInvocation = lastTextValue != newTextFieldValueState.text | |
| lastTextValue = newTextFieldValueState.text | |
| if (stringChangedSinceLastInvocation) { | |
| onValueChange(newTextFieldValueState.text) | |
| } | |
| }, | |
| modifier = modifier, | |
| enabled = enabled, | |
| readOnly = readOnly, | |
| textStyle = textStyle, | |
| label = label, | |
| placeholder = placeholder, | |
| leadingIcon = leadingIcon, | |
| trailingIcon = trailingIcon, | |
| isError = isError, | |
| visualTransformation = visualTransformation, | |
| keyboardOptions = keyboardOptions, | |
| keyboardActions = keyboardActions, | |
| singleLine = singleLine, | |
| maxLines = maxLines, | |
| minLines = minLines, | |
| interactionSource = interactionSource, | |
| shape = shape, | |
| colors = colors, | |
| ) | |
| } |
@AhmerAfzal1 It worked last time I used it so you might want to be more specific if you're looking for support.
Outline text field desn't work !!
@vanzar-codigo you might want to be either more specific about the issue you found, or you'll need to post the fix yourself and then the gist will be updated.
jfc, i was looking for a quick fix and it turns out you need an entire framework just to put the fricking cursor at the end β thanks a lot compost! thanks for sharing tho
yeah you have to copy it to pass an initial state because Google didn't bother making that a possibility out of the box
FWIW, what I ended up doing was hiding the cursor if the text was empty and showing a Divider to indicate to the user that the text field has focus π
It was the only way I could (without tons of code) workaround the cursor issue, otherwise the freaking cursor is always at the start of the text field π
geez, thanks compost!
There's a good chance that the solution here would be to update the GIST with new version of TextField.
I heard that if you don't use the new TextFieldState which btw did not exist at the time of creating the gist, then double-tapping to select is broken anyway.
Oh this is definitely outdated, now that they've come out with BasicTextField2. The old string/TextFieldValue-based solutions will inevitably be deprecated.
Its not working