Add support for a better AEP character set.

This commit is contained in:
Greyson Parrelli
2026-05-13 12:45:06 -04:00
committed by Michelle Tang
parent db4def45f9
commit 10b0221e98
14 changed files with 255 additions and 20 deletions
@@ -93,7 +93,8 @@ fun EnterKeyScreen(
val updateEnteredBackupKey = { input: String ->
enteredBackupKey = AccountEntropyPool.removeIllegalCharacters(input).uppercase()
isBackupKeyValid = enteredBackupKey == backupKey
val normalized = AccountEntropyPool.formatForStorage(enteredBackupKey)
isBackupKeyValid = normalized.equals(AccountEntropyPool.formatForStorage(backupKey), ignoreCase = true)
showError = !isBackupKeyValid && enteredBackupKey.length >= backupKey.length
}
@@ -503,7 +503,7 @@ fun Screen(
text = "Copy Account Entropy Pool (AEP)",
label = "Copies the Account Entropy Pool (AEP) to the clipboard, which is labeled as the \"Backup Key\" in the designs.",
onClick = {
Util.copyToClipboard(context, SignalStore.account.accountEntropyPool.value)
Util.copyToClipboard(context, SignalStore.account.accountEntropyPool.displayValue)
Toast.makeText(context, "Copied!", Toast.LENGTH_SHORT).show()
}
)
@@ -11,14 +11,15 @@ import androidx.compose.ui.text.input.TransformedText
import androidx.compose.ui.text.input.VisualTransformation
/**
* Visual formatter for backup keys.
* Visual formatter for backup keys. Preserves whatever the user typed verbatim (no character
* swapping) and just groups characters with spaces.
*
* @param chunkSize character count per group
*/
class BackupKeyVisualTransformation(private val chunkSize: Int) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
var output = ""
for ((i, c) in text.withIndex()) {
for ((i, c) in text.text.withIndex()) {
output += c
if (i % chunkSize == chunkSize - 1) {
output += " "
@@ -96,7 +96,7 @@ class EnterBackupKeyFragment : ComposeFragment() {
EnterBackupKeyScreen(
isDisplayedDuringManualRestore = true,
backupKey = viewModel.backupKey,
enteredText = viewModel.enteredText,
inProgress = sharedState.inProgress,
isBackupKeyValid = state.backupKeyValid,
chunkLength = state.chunkLength,
@@ -68,7 +68,7 @@ import org.thoughtcrime.securesms.registration.ui.shared.RegistrationScreen
@Composable
fun EnterBackupKeyScreen(
isDisplayedDuringManualRestore: Boolean,
backupKey: String,
enteredText: String,
inProgress: Boolean,
isBackupKeyValid: Boolean,
chunkLength: Int,
@@ -140,7 +140,7 @@ fun EnterBackupKeyScreen(
val autoFillHelper = backupKeyAutoFillHelper { onBackupKeyChanged(it) }
TextField(
value = backupKey,
value = enteredText,
onValueChange = {
onBackupKeyChanged(it)
autoFillHelper.onValueChanged(it)
@@ -222,7 +222,7 @@ private fun AccountEntropyPoolVerification.AEPValidationError.ValidationErrorMes
private fun EnterBackupKeyScreenPreview() {
Previews.Preview {
EnterBackupKeyScreen(
backupKey = "UY38jh2778hjjhj8lk19ga61s672jsj089r023s6a57809bap92j2yh5t326vv7t".uppercase(),
enteredText = "UY38jh2778hjjhj8lk19ga61s672jsj089r023s6a57809bap92j2yh5t326vv7t".uppercase(),
isBackupKeyValid = true,
inProgress = false,
chunkLength = 4,
@@ -237,7 +237,7 @@ private fun EnterBackupKeyScreenPreview() {
private fun EnterBackupKeyScreenErrorPreview() {
Previews.Preview {
EnterBackupKeyScreen(
backupKey = "UY38jh2778hjjhj8lk19ga61s672jsj089r023s6a57809bap92j2yh5t326vv7t".uppercase(),
enteredText = "UY38jh2778hjjhj8lk19ga61s672jsj089r023s6a57809bap92j2yh5t326vv7t".uppercase(),
isBackupKeyValid = true,
inProgress = false,
chunkLength = 4,
@@ -41,14 +41,21 @@ class EnterBackupKeyViewModel : ViewModel() {
private val verifyGeneration = AtomicInteger(0)
/** Raw user-typed text (illegal chars stripped, length-capped). Bound to the TextField so #/= stay visible. */
var enteredText by mutableStateOf("")
private set
/** Storage-normalized lowercase form of [enteredText], used for parseOrNull and submit. */
var backupKey by mutableStateOf("")
private set
val state: StateFlow<EnterBackupKeyState> = store
fun updateBackupKey(key: String) {
val newKey = AccountEntropyPool.removeIllegalCharacters(key).take(AccountEntropyPool.LENGTH + 16).lowercase()
val newEnteredText = AccountEntropyPool.removeIllegalCharacters(key).take(AccountEntropyPool.LENGTH + 16)
val newKey = AccountEntropyPool.formatForStorage(newEnteredText).lowercase()
val changed = newKey != backupKey
enteredText = newEnteredText
backupKey = newKey
store.update {
val (isValid, updatedError) = AccountEntropyPoolVerification.verifyAEP(
@@ -65,7 +65,7 @@ class PostRegistrationEnterBackupKeyFragment : ComposeFragment() {
EnterBackupKeyScreen(
isDisplayedDuringManualRestore = false,
backupKey = viewModel.backupKey,
enteredText = viewModel.enteredText,
isBackupKeyValid = state.backupKeyValid,
inProgress = state.inProgress,
chunkLength = 4,
@@ -32,14 +32,21 @@ class PostRegistrationEnterBackupKeyViewModel : ViewModel() {
PostRegistrationEnterBackupKeyState()
)
/** Raw user-typed text (illegal chars stripped, length-capped). Bound to the TextField so #/= stay visible. */
var enteredText by mutableStateOf("")
private set
/** Storage-normalized lowercase form of [enteredText], used for parseOrNull and submit. */
var backupKey by mutableStateOf("")
private set
val state: StateFlow<PostRegistrationEnterBackupKeyState> = store
fun updateBackupKey(key: String) {
val newKey = AccountEntropyPool.removeIllegalCharacters(key).take(AccountEntropyPool.LENGTH + 16).lowercase()
val newEnteredText = AccountEntropyPool.removeIllegalCharacters(key).take(AccountEntropyPool.LENGTH + 16)
val newKey = AccountEntropyPool.formatForStorage(newEnteredText).lowercase()
val changed = newKey != backupKey
enteredText = newEnteredText
backupKey = newKey
store.update {
val (isValid, updatedError) = AccountEntropyPoolVerification.verifyAEP(