diff --git a/feature/registration/src/main/java/org/signal/registration/screens/signallogincredentials/AccountIdFormat.kt b/feature/registration/src/main/java/org/signal/registration/screens/signallogincredentials/AccountIdFormat.kt index e7f2cadded..c6a0230ca0 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/signallogincredentials/AccountIdFormat.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/signallogincredentials/AccountIdFormat.kt @@ -16,14 +16,17 @@ internal object AccountIdFormat { /** Offsets in a raw account ID that a dash is inserted in front of. */ private val DASH_OFFSETS = intArrayOf(8, 12, 16, 20) - /** Rewrites a raw account ID with the dashes a UUID is normally written with. */ + /** + * Rewrites a raw account ID with the dashes and uppercasing a UUID is normally written with. + */ fun dashed(accountId: String): String { return buildString { for ((index, character) in accountId.withIndex()) { if (index in DASH_OFFSETS) { append('-') } - append(character) + // Uppercase on a per-character basis so as to not screw up offsets + append(character.uppercaseChar()) } } } diff --git a/feature/registration/src/main/java/org/signal/registration/screens/signallogincredentials/SignalLoginCredentialEntryScreen.kt b/feature/registration/src/main/java/org/signal/registration/screens/signallogincredentials/SignalLoginCredentialEntryScreen.kt index b2e68a4477..ad5b5fc5ea 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/signallogincredentials/SignalLoginCredentialEntryScreen.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/signallogincredentials/SignalLoginCredentialEntryScreen.kt @@ -470,7 +470,7 @@ internal object AccountIdVisualTransformation : VisualTransformation { override fun filter(text: AnnotatedString): TransformedText { return TransformedText( - text = AnnotatedString(AccountIdFormat.dashed(text.text).uppercase()), + text = AnnotatedString(AccountIdFormat.dashed(text.text)), offsetMapping = AccountIdOffsetMapping(text.length) ) } diff --git a/feature/registration/src/test/java/org/signal/registration/screens/signallogincredentials/AccountIdVisualTransformationTest.kt b/feature/registration/src/test/java/org/signal/registration/screens/signallogincredentials/AccountIdVisualTransformationTest.kt index 82ac0a539c..cef07fb3bb 100644 --- a/feature/registration/src/test/java/org/signal/registration/screens/signallogincredentials/AccountIdVisualTransformationTest.kt +++ b/feature/registration/src/test/java/org/signal/registration/screens/signallogincredentials/AccountIdVisualTransformationTest.kt @@ -15,6 +15,9 @@ class AccountIdVisualTransformationTest { companion object { private const val FULL_KEY = "a6b284822e3283d07f2391360a4c2b91" private const val FULL_KEY_FORMATTED = "A6B28482-2E32-83D0-7F23-91360A4C2B91" + + /** Characters whose whole-string uppercase mapping is longer than the character itself. */ + private val LENGTH_CHANGING_CHARACTERS = listOf('ß', 'ʼn', 'ff', 'fi', 'ǰ') } @Test @@ -54,5 +57,33 @@ class AccountIdVisualTransformationTest { } } + @Test + fun `characters that uppercase into multiple characters stay one character long`() { + for (character in LENGTH_CHANGING_CHARACTERS) { + assertThat(transform(character.toString()).length).isEqualTo(1) + assertThat(transform("a6b2848$character")).isEqualTo("A6B2848${character.uppercaseChar()}") + } + } + + @Test + fun `every cursor position round trips even when the key holds characters that normally expand`() { + for (character in LENGTH_CHANGING_CHARACTERS) { + val key = character + FULL_KEY.drop(1) + val mapping = AccountIdVisualTransformation.filter(AnnotatedString(key)).offsetMapping + val transformedLength = transform(key).length + + for (offset in 0..key.length) { + val transformed = mapping.originalToTransformed(offset) + + assertThat(transformed in 0..transformedLength).isEqualTo(true) + assertThat(mapping.transformedToOriginal(transformed)).isEqualTo(offset) + } + + for (offset in 0..transformedLength) { + assertThat(mapping.transformedToOriginal(offset) in 0..key.length).isEqualTo(true) + } + } + } + private fun transform(text: String): String = AccountIdVisualTransformation.filter(AnnotatedString(text)).text.text }