1

I'd want to add phone number formatting as the user types in the field. So far, it's functioning, but the cursor is in the wrong location when the field is empty. When we have the initial value, it moves the cursor to the right location.

Note: I would like to provide support to add number in between characters.

Reference here

Example: 1234567890 -> +1 (123) 456-7890

When empty -> +1
Type 1 -> +1 (1
Type 2 -> +1 (12
Type 3 -> +1 (123)

I would like to display just +1 when field is empty and if user type first character I would like to add (.

Here is my effort.

    /**
 * The visual filter for phone number.
 *
 * This filter converts up to 10 digits to phone number form.
 * For example, "1234567890" will be shown as "+1 (123) 456-7890".
 */
private val phoneNumberFilter = VisualTransformation { text ->


    // +1 (XXX) XXX-XXXX
    val trimmed = if (text.text.length >= 10) text.text.substring(0..9) else text.text
    var out = ""

    for (i in trimmed.indices) {
        out += trimmed[i]
        when (i) {
//                0 -> out+= "$out"
            2 -> out += ") "
            5 -> out += "-"
        }
    }
    out = "+1 ($out"


    val mapping = object : OffsetMapping {
        override fun originalToTransformed(offset: Int): Int {

            Log.d("TAG", "Out: $out")

            val transformedOffsets = out
                .mapIndexedNotNull { index, c ->
                    index
                        .takeIf { c.isDigit() }
                        // convert index to an offset
                        ?.plus(1)
                }
                // We want to support an offset of 0 and shift everything to the right,
                // so we prepend that index by default
                .let { offsetList ->
                    listOf(0) + offsetList
                }


//            val transformedOffsets = offset + countSpecialChars(out) + 1
            Log.d("TAG", "originalToTransformed: $transformedOffsets")
            return transformedOffsets[offset+1]
        }

        override fun transformedToOriginal(offset: Int): Int {

            val originalOffset = out
                // This creates a list of all separator offsets
                .mapIndexedNotNull { index, c ->
                    index.takeIf { !c.isDigit() }
                }
                // We want to count how many separators precede the transformed offset
                .count { separatorIndex ->
                    separatorIndex < offset
                }
                // We find the original offset by subtracting the number of separators
                .let { separatorCount ->
                    offset - separatorCount
                }


//            val originalOffset = offset - countSpecialChars(out) - 1
            Log.d("TAG", "transformedToOriginal: $originalOffset")
            return originalOffset-1
        }

    }

UI

OutlinedTextField(
        modifier = Modifier.fillMaxWidth(),
        value = phone,
        onValueChange = { value ->
            if (value.length <= 10) {
                phone = value.takeWhile { it.isDigit() }
            }

            Log.d("TAG", "PhoneNumber: $phone")
        },
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
        visualTransformation = remember { phoneNumberFilter }
    )

Check visual here

Arpit Patel
  • 7,212
  • 5
  • 56
  • 67

1 Answers1

2

You can use:

private val phoneNumberFilter = VisualTransformation { text ->


    // +1 (XXX) XXX-XXXX
    val trimmed = if (text.text.length >= 10) text.text.substring(0..9) else text.text
    var out = ""
    if (text.isNotEmpty()) out = "("
    for (i in trimmed.indices) {
        out += trimmed[i]
        when (i) {
            2 -> out += ") "
            5 -> out += "-"
        }
    }
    out = "+1 $out"

    val mapping = object : OffsetMapping {
        override fun originalToTransformed(offset: Int): Int {

            if (offset == 0) return 3
            if (offset <= 2) return offset + 4
            if (offset <= 5) return offset + 6
            return offset + 7

        }

        override fun transformedToOriginal(offset: Int): Int {
            if (offset <=3) return 0
            if (offset <=7) return offset -4
            if (offset <=12) return offset -6
            return offset -7
        }

    }
    TransformedText(AnnotatedString(out), mapping)
}

enter image description here

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
  • Thank you for the quick response @Gabriele Can you make `offsetMapping` more generic? I would like to use that in other parts of the project. – Arpit Patel Mar 25 '23 at 22:54