'Phone number visual transformation in Jetpack compose

How can I implement phone number visual transformation in jetpack compose? I have read this article for the card number.

And I want to format my phone number like this xx xxx xx xx



Solution 1:[1]

You can just modify some params from the example link that you provided according to the pattern you need. You need to consider the max length you want, how many spaces you need between each section.

For example in your given link here: http://zenandroid.io/using-the-jetpack-composes-visualtransformation-to-create-a-credit-card-text-input/

  1. They're adding space after every 4th character in AnnotatedString.Builder() You need it on 1, 4, 6.
  2. then they've added 2 spaces that's why they're adding spaces like 2,4,6 in originalToTransformed but you need 1,2,3 & same for deducting in transformedToOriginal

Complete code example:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            AppTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    Test()
                }
            }
        }
    }
}


@Composable
fun Test() {

    var mobileNumber by rememberSaveable { mutableStateOf("") }
    Column {
        Row(modifier = Modifier.padding(all = 10.dp)) {
            Text(
                text = "Mobile number",
                fontSize = 14.sp,
                modifier = Modifier.weight(1f)
            )
            BasicTextField(
                value = mobileNumber,
                onValueChange = { mobileNumber = it },
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
                visualTransformation = { mobileNumberFilter(it) }
            )
        }
        Box(
            modifier = Modifier
                .height(1.dp)
                .padding(start = 10.dp)
                .fillMaxWidth()
                .background(Color.Gray)
        )

        Spacer(modifier = Modifier.height(20.dp))
        Text(text = "Actual value:\n$mobileNumber")
    }

}

const val mask = "xx xxx xx xx"

fun mobileNumberFilter(text: AnnotatedString): TransformedText {
    // change the length
    val trimmed =
        if (text.text.length >= 9) text.text.substring(0..8) else text.text

    val annotatedString = AnnotatedString.Builder().run {
        for (i in trimmed.indices) {
            append(trimmed[i])
            if (i == 1 || i == 4 || i == 6) {
                append(" ")
            }
        }
        pushStyle(SpanStyle(color = Color.LightGray))
        append(mask.takeLast(mask.length - length))
        toAnnotatedString()
    }

    val phoneNumberOffsetTranslator = object : OffsetMapping {
        override fun originalToTransformed(offset: Int): Int {
            if (offset <= 1) return offset
            if (offset <= 4) return offset + 1
            if (offset <= 6) return offset + 2
            if (offset <= 9) return offset + 3
            return 12
        }

        override fun transformedToOriginal(offset: Int): Int {
            if (offset <= 1) return offset
            if (offset <= 4) return offset - 1
            if (offset <= 6) return offset - 2
            if (offset <= 9) return offset - 3
            return 9
        }
    }

    return TransformedText(annotatedString, phoneNumberOffsetTranslator)
}

Output:

visual transformation

Solution 2:[2]

For a north American version:

const val mask = "(xxx) xxx-xxxx"
fun mobileNumberFilter(text: AnnotatedString, formType: FormType): 
TransformedText {
    if (formType != FormType.SHIPPING_PHONE) {
        return VisualTransformation.None.filter(text)
    }

    // change the length
    val trimmed =
    if (text.text.length >= 14) text.text.substring(0..13) else text.text

    val annotatedString = AnnotatedString.Builder().run {
        for (i in trimmed.indices) {
            val trimmedPortion = trimmed[i]
            if (i == 0) {
               append("($trimmedPortion")
            } else {
               append(trimmedPortion)
            }
            if (i == 2) {
               append(") ")
            }
            if (i == 5) {
               append("-")
            }
        }
        pushStyle(
            SpanStyle(color = Color.LightGray)
        )
        try {
            append(mask.takeLast(mask.length - length))
        } catch (e: IllegalArgumentException) {
            Timber.d(e.localizedMessage?.plus(" reached end of phone number"))
        }

        toAnnotatedString()
    }

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

            if (offset <= 1) return offset
            if (offset <= 4) return offset + 1
            if (offset <= 9) return offset + 2
            return 14

        }

        override fun transformedToOriginal(offset: Int): Int {

            if (offset <= 1) return offset
            if (offset <= 4) return offset - 1
            if (offset <= 9) return offset - 2
            return 14

        }
    }

    return TransformedText(annotatedString, translator)
}

For an even cleaner version, see this Medium Article:

Adding Phone Number Visual transformation

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mayur Gajra
Solution 2