Created
April 3, 2023 09:49
-
-
Save hector6872/8d24c67cbce69ea5230809d841e14bc5 to your computer and use it in GitHub Desktop.
Compose PhoneNumber VisualTransformation implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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('+', '*', '#', '(', ')') } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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