Skip to content

Instantly share code, notes, and snippets.

@Zhuinden
Last active May 3, 2024 16:50
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Zhuinden/ab065534bbf73d7e6de83b5a39366c24 to your computer and use it in GitHub Desktop.
Save Zhuinden/ab065534bbf73d7e6de83b5a39366c24 to your computer and use it in GitHub Desktop.
A Compose TextField that initializes the cursor position at the end of the text, rather than the start, when focused without tapping the TextField manually.
/*
* 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.
*
* ![Outlined text field image](https://developer.android.com/images/reference/androidx/compose/material3/outlined-text-field.png)
*
* 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.
*
* ![Outlined text field image](https://developer.android.com/images/reference/androidx/compose/material/outlined-text-field.png)
*
* 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.
*
* ![Filled text field image](https://developer.android.com/images/reference/androidx/compose/material/filled-text-field.png)
*
* 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
Copy link

Its not working

@Zhuinden
Copy link
Author

@AhmerAfzal1 It worked last time I used it so you might want to be more specific if you're looking for support.

@vanzar-codigo
Copy link

Outline text field desn't work !!

@Zhuinden
Copy link
Author

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment