0

M3 TextField has a placeholder parameter where it shows a text when it's focused and empty. But I want to show a placeholder or a hint always as the user is typing. Something similar to search suggestions but more like Github Copilot suggestions which suggest text as you type.

current effort

Visually I achieved what I want but currently, when the cursor is moved it's not moving logically I suspect OffsetMapping is responsible for that but I can't fix it. The problem is when you try to move the cursor it moves but sometimes jumps to the end of the green text instead of moving smoothly.

class MyMaskTransformation(
    private val suggestion: String
) : VisualTransformation {

    private var suffix: String = ""

    override fun filter(text: AnnotatedString): TransformedText {
        suffix = if (suggestion.length > text.length)
            suggestion.takeLast(suggestion.length - text.length)
        else ""
        return TransformedText(
            offsetMapping = offsetMapping,
            text = buildAnnotatedString {
                withStyle(SpanStyle(Color.Unspecified)) {
                    append(text)
                }
                withStyle(SpanStyle(Color.Gray)) {
                    append(suffix)
                }
            }
        )
    }

    private val offsetMapping = object : OffsetMapping {
        override fun originalToTransformed(offset: Int): Int {
            if (offset < suggestion.length || suggestion.isEmpty()) return offset
            return suggestion.length - suffix.length
        }

        override fun transformedToOriginal(offset: Int): Int {
            offset.toString().log()
            if (offset == 0) return 0
            if (suggestion.length - suffix.length in 1 until offset)
                return suggestion.length - suffix.length
            return offset
        }
    }
}
YaMiN
  • 3,194
  • 2
  • 12
  • 36

1 Answers1

1

To add an extra view to a text field, you can use a BasicTextField with one of the standard decoration boxes.

For example, instead of OutlinedTextField you can use TextFieldDefaults.OutlinedTextFieldDecorationBox, and in innerTextField you can decorate the text field according to your requirements:

var value by remember {
    mutableStateOf("")
}
val suggestion = "Suggestion"
val interactionSource = remember { MutableInteractionSource() }
val colors = TextFieldDefaults.outlinedTextFieldColors()
val textStyle = TextStyle.Default
BasicTextField(
    value = value,
    onValueChange = { value = it },
    interactionSource = interactionSource,
    textStyle = textStyle,
    decorationBox = { innerTextField ->
        TextFieldDefaults.OutlinedTextFieldDecorationBox(
            value = value,
            innerTextField = {
                Box {
                    Text(suggestion, style = textStyle.copy(color = Color.Gray))
                    innerTextField()
                }
            },
            interactionSource = interactionSource,
            label = { Text("chat") },
            trailingIcon = { Icon(Icons.Default.Send, null) },
            colors = colors,
            enabled = true,
            singleLine = true,
            visualTransformation = VisualTransformation.None,
            border = {
                TextFieldDefaults.BorderBox(
                    enabled = true,
                    isError = false,
                    interactionSource = interactionSource,
                    colors = colors,
                )
            }
        )
    }
)

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • Which version of Material3 are you using? I'm using `1.1.0-beta01` and its `OutlinedTextFieldDecorationBox` has no border parameter. – YaMiN Mar 29 '23 at 01:19
  • 1
    @YaMiN I didn't notice that you are using the M3, but you should be able to use the same principle there. Let me know if you think I should updated my code to M3. – Phil Dukhov Mar 29 '23 at 01:21