Skip to content

Instantly share code, notes, and snippets.

@hrules6872
Created April 3, 2023 09:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hrules6872/8d24c67cbce69ea5230809d841e14bc5 to your computer and use it in GitHub Desktop.
Save hrules6872/8d24c67cbce69ea5230809d841e14bc5 to your computer and use it in GitHub Desktop.
Compose PhoneNumber VisualTransformation implementation
class PhoneNumberVisualTransformation(countryCode: String) : SeparatorVisualTransformation() {
private val phoneNumberFormatter = PhoneNumberFormatter(countryCode)
override fun transform(input: CharSequence): CharSequence = phoneNumberFormatter.format(input.toString())
override fun isSeparator(char: Char): Boolean = !PhoneNumberUtils.isNonSeparator(char)
}
private class PhoneNumberFormatter(countryCode: String) {
private val formatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode)
fun format(number: String): String = number
.takeIf(String::isNotBlank)
?.replaceIndexIf(0, '+') { c -> c == '0' }
?.filter { it.isDigit() || it == '+' }
?.let { sanitized ->
formatter.clear()
sanitized.map(formatter::inputDigit).lastOrNull().orEmpty()
} ?: number
}
private fun String.replaceIndexIf(index: Int, value: Char, predicate: (Char) -> Boolean): String = when {
predicate(get(index)) -> this.toCharArray()
.apply { set(index, value) }
.let(::String)
else -> this
}
fun String.phoneAllowedCharsOnly(): String = this.trim().filter { it.isDigit() || it in arrayOf('+', '*', '#', '(', ')') }
abstract class SeparatorVisualTransformation : VisualTransformation {
abstract fun transform(input: CharSequence): CharSequence
abstract fun isSeparator(char: Char): Boolean
override fun filter(text: AnnotatedString): TransformedText {
val formatted = transform(text)
return TransformedText(
text = AnnotatedString(text = formatted.toString()),
offsetMapping = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int = formatted
.mapIndexedNotNull { index, c ->
index.takeIf { !isSeparator(c) }?.plus(1) // convert index to an offset
}
// 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 }
.getOrNull(offset) ?: formatted.length
override fun transformedToOriginal(offset: Int): Int = formatted
.mapIndexedNotNull { index, c ->
index.takeIf { isSeparator(c) }
}
.count { separatorIndex -> // count how many separators precede the transformed offset
separatorIndex < offset
}
.let { separatorCount -> // find the original offset by subtracting the number of separators
offset - separatorCount
}
}
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment