From cef839d300809d7b6b4842feae7cd4ef800c18bc Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Fri, 21 Mar 2025 11:26:49 -0400 Subject: [PATCH] Add verify AEP screen. --- .../MessageBackupsFlowFragment.kt | 12 +- .../MessageBackupsFlowViewModel.kt | 4 +- .../MessageBackupsKeyRecordScreen.kt | 140 +------- .../MessageBackupsKeyVerifyScreen.kt | 314 ++++++++++++++++++ .../v2/ui/subscription/MessageBackupsStage.kt | 2 + .../remote/BackupKeyDisplayFragment.kt | 2 +- app/src/main/res/values/strings.xml | 13 + .../signalservice/api/AccountEntropyPool.kt | 5 +- 8 files changed, 348 insertions(+), 144 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyVerifyScreen.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt index dd63fd58e8..6258362a69 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt @@ -111,7 +111,7 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega val context = LocalContext.current MessageBackupsKeyRecordScreen( - backupKey = state.accountEntropyPool.value, + backupKey = state.accountEntropyPool.displayValue, onNavigationClick = viewModel::goToPreviousStage, onNextClick = viewModel::goToNextStage, onCopyToClipboardClick = { @@ -120,6 +120,14 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega ) } + composable(route = MessageBackupsStage.Route.BACKUP_KEY_VERIFY.name) { + MessageBackupsKeyVerifyScreen( + backupKey = state.accountEntropyPool.displayValue, + onNavigationClick = viewModel::goToPreviousStage, + onNextClick = viewModel::goToNextStage + ) + } + composable(route = MessageBackupsStage.Route.TYPE_SELECTION.name) { MessageBackupsTypeSelectionScreen( stage = state.stage, @@ -140,7 +148,7 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega val currentRoute = navController.currentDestination?.route if (currentRoute != newRoute) { if (currentRoute != null && MessageBackupsStage.Route.valueOf(currentRoute).isAfter(state.stage.route)) { - navController.popBackStack() + navController.popBackStack(newRoute, inclusive = false) } else { navController.navigate(newRoute) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt index 283b7d8c9b..3445a3ecf8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt @@ -164,7 +164,8 @@ class MessageBackupsFlowViewModel( MessageBackupsStage.CANCEL -> error("Unsupported state transition from terminal state CANCEL") MessageBackupsStage.EDUCATION -> it.copy(stage = MessageBackupsStage.BACKUP_KEY_EDUCATION) MessageBackupsStage.BACKUP_KEY_EDUCATION -> it.copy(stage = MessageBackupsStage.BACKUP_KEY_RECORD) - MessageBackupsStage.BACKUP_KEY_RECORD -> it.copy(stage = MessageBackupsStage.TYPE_SELECTION) + MessageBackupsStage.BACKUP_KEY_RECORD -> it.copy(stage = MessageBackupsStage.BACKUP_KEY_VERIFY) + MessageBackupsStage.BACKUP_KEY_VERIFY -> it.copy(stage = MessageBackupsStage.TYPE_SELECTION) MessageBackupsStage.TYPE_SELECTION -> validateTypeAndUpdateState(it) MessageBackupsStage.CHECKOUT_SHEET -> it.copy(stage = MessageBackupsStage.PROCESS_PAYMENT) MessageBackupsStage.CREATING_IN_APP_PAYMENT -> error("This is driven by an async coroutine.") @@ -186,6 +187,7 @@ class MessageBackupsFlowViewModel( MessageBackupsStage.EDUCATION -> MessageBackupsStage.CANCEL MessageBackupsStage.BACKUP_KEY_EDUCATION -> MessageBackupsStage.EDUCATION MessageBackupsStage.BACKUP_KEY_RECORD -> MessageBackupsStage.BACKUP_KEY_EDUCATION + MessageBackupsStage.BACKUP_KEY_VERIFY -> MessageBackupsStage.BACKUP_KEY_RECORD MessageBackupsStage.TYPE_SELECTION -> MessageBackupsStage.BACKUP_KEY_RECORD MessageBackupsStage.CHECKOUT_SHEET -> MessageBackupsStage.TYPE_SELECTION MessageBackupsStage.CREATING_IN_APP_PAYMENT -> MessageBackupsStage.CREATING_IN_APP_PAYMENT diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyRecordScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyRecordScreen.kt index 4ea8041d5e..5a2a66b174 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyRecordScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyRecordScreen.kt @@ -7,33 +7,20 @@ package org.thoughtcrime.securesms.backup.v2.ui.subscription import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Checkbox -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource @@ -43,8 +30,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import kotlinx.coroutines.launch -import org.signal.core.ui.compose.BottomSheets import org.signal.core.ui.compose.Buttons import org.signal.core.ui.compose.Previews import org.signal.core.ui.compose.Scaffolds @@ -59,7 +44,6 @@ import org.signal.core.ui.R as CoreUiR * Screen displaying the backup key allowing the user to write it down * or copy it. */ -@OptIn(ExperimentalMaterial3Api::class) @Composable fun MessageBackupsKeyRecordScreen( backupKey: String, @@ -67,11 +51,6 @@ fun MessageBackupsKeyRecordScreen( onCopyToClipboardClick: (String) -> Unit = {}, onNextClick: () -> Unit = {} ) { - val coroutineScope = rememberCoroutineScope() - val sheetState = rememberModalBottomSheetState( - skipPartiallyExpanded = true - ) - val backupKeyString = remember(backupKey) { backupKey.chunked(4).joinToString(" ") } @@ -164,11 +143,7 @@ fun MessageBackupsKeyRecordScreen( .padding(bottom = 24.dp) ) { Buttons.LargeTonal( - onClick = { - coroutineScope.launch { - sheetState.show() - } - }, + onClick = onNextClick, modifier = Modifier.align(Alignment.BottomEnd) ) { Text( @@ -177,111 +152,6 @@ fun MessageBackupsKeyRecordScreen( } } } - - if (sheetState.isVisible) { - ModalBottomSheet( - dragHandle = null, - onDismissRequest = { - coroutineScope.launch { - sheetState.hide() - } - } - ) { - BottomSheetContent( - onContinueClick = onNextClick, - onSeeKeyAgainClick = { - coroutineScope.launch { - sheetState.hide() - } - } - ) - } - } - } -} - -@Composable -private fun BottomSheetContent( - onContinueClick: () -> Unit, - onSeeKeyAgainClick: () -> Unit -) { - var checked by remember { mutableStateOf(false) } - - LazyColumn( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimensionResource(CoreUiR.dimen.gutter)) - .testTag("message-backups-key-record-screen-sheet-content") - ) { - item { - BottomSheets.Handle() - } - - item { - Text( - text = stringResource(R.string.MessageBackupsKeyRecordScreen__keep_your_key_safe), - style = MaterialTheme.typography.titleLarge, - textAlign = TextAlign.Center, - modifier = Modifier.padding(top = 30.dp) - ) - } - - item { - Text( - text = stringResource(R.string.MessageBackupsKeyRecordScreen__signal_will_not), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center, - modifier = Modifier.padding(top = 12.dp) - ) - } - - item { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .padding(vertical = 24.dp) - .defaultMinSize(minWidth = 220.dp) - .clip(shape = RoundedCornerShape(percent = 50)) - .clickable(onClick = { checked = !checked }) - ) { - Checkbox( - checked = checked, - onCheckedChange = { checked = it } - ) - - Text( - text = stringResource(R.string.MessageBackupsKeyRecordScreen__ive_recorded_my_key), - style = MaterialTheme.typography.bodyLarge - ) - } - } - - item { - Buttons.LargeTonal( - enabled = checked, - onClick = onContinueClick, - modifier = Modifier - .padding(bottom = 16.dp) - .defaultMinSize(minWidth = 220.dp) - ) { - Text(text = stringResource(R.string.MessageBackupsKeyRecordScreen__continue)) - } - } - - item { - TextButton( - onClick = onSeeKeyAgainClick, - modifier = Modifier - .padding(bottom = 24.dp) - .defaultMinSize(minWidth = 220.dp) - ) { - Text( - text = stringResource(R.string.MessageBackupsKeyRecordScreen__see_key_again) - ) - } - } } } @@ -294,11 +164,3 @@ private fun MessageBackupsKeyRecordScreenPreview() { ) } } - -@SignalPreview -@Composable -private fun BottomSheetContentPreview() { - Previews.BottomSheetPreview { - BottomSheetContent({}, {}) - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyVerifyScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyVerifyScreen.kt new file mode 100644 index 0000000000..1684e71ddb --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyVerifyScreen.kt @@ -0,0 +1,314 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.ui.subscription + +import android.graphics.Typeface +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TextField +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kotlinx.coroutines.launch +import org.signal.core.ui.compose.BottomSheets +import org.signal.core.ui.compose.Buttons +import org.signal.core.ui.compose.Previews +import org.signal.core.ui.compose.Scaffolds +import org.signal.core.ui.compose.SignalPreview +import org.signal.core.ui.compose.horizontalGutters +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.registrationv3.ui.restore.BackupKeyVisualTransformation +import org.whispersystems.signalservice.api.AccountEntropyPool +import kotlin.random.Random +import kotlin.random.nextInt +import org.signal.core.ui.R as CoreUiR + +/** + * Prompt user to re-enter backup key (AEP) to confirm they have it still. + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MessageBackupsKeyVerifyScreen( + backupKey: String, + onNavigationClick: () -> Unit = {}, + onNextClick: () -> Unit = {} +) { + val coroutineScope = rememberCoroutineScope() + val sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) + + Scaffolds.Settings( + title = stringResource(R.string.MessageBackupsKeyVerifyScreen__confirm_your_backup_key), + navigationIconPainter = painterResource(R.drawable.symbol_arrow_left_24), + onNavigationClick = onNavigationClick + ) { paddingValues -> + + Column( + verticalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + ) { + val scrollState = rememberScrollState() + + val focusRequester = remember { FocusRequester() } + val visualTransform = remember { BackupKeyVisualTransformation(chunkSize = 4) } + val keyboardController = LocalSoftwareKeyboardController.current + + var enteredBackupKey by remember { mutableStateOf("") } + var isBackupKeyValid by remember { mutableStateOf(false) } + var showError by remember { mutableStateOf(false) } + + Column( + modifier = Modifier + .verticalScroll(scrollState) + .weight(weight = 1f, fill = false) + .horizontalGutters(), + horizontalAlignment = Alignment.Start + ) { + Text( + text = stringResource(R.string.MessageBackupsKeyVerifyScreen__enter_the_backup_key_that_you_just_recorded), + style = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurfaceVariant) + ) + + Spacer(modifier = Modifier.height(48.dp)) + + TextField( + value = enteredBackupKey, + onValueChange = { + enteredBackupKey = AccountEntropyPool.removeIllegalCharacters(it).uppercase() + isBackupKeyValid = enteredBackupKey == backupKey + showError = !isBackupKeyValid && enteredBackupKey.length >= backupKey.length + }, + label = { + Text(text = stringResource(id = R.string.MessageBackupsKeyVerifyScreen__backup_key)) + }, + textStyle = LocalTextStyle.current.copy( + fontFamily = FontFamily(typeface = Typeface.MONOSPACE), + lineHeight = 36.sp + ), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + capitalization = KeyboardCapitalization.None, + imeAction = ImeAction.Next, + autoCorrectEnabled = false + ), + keyboardActions = KeyboardActions( + onNext = { + if (isBackupKeyValid) { + keyboardController?.hide() + coroutineScope.launch { sheetState.show() } + } + } + ), + supportingText = { if (showError) Text(text = stringResource(R.string.MessageBackupsKeyVerifyScreen__incorrect_backup_key)) }, + isError = showError, + minLines = 4, + visualTransformation = visualTransform, + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester) + ) + } + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } + + Surface( + shadowElevation = if (scrollState.canScrollForward) 8.dp else 0.dp, + modifier = Modifier.fillMaxWidth() + ) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .padding(top = 8.dp, bottom = 24.dp) + .horizontalGutters() + .fillMaxWidth() + ) { + TextButton( + onClick = onNavigationClick + ) { + Text( + text = stringResource(id = R.string.MessageBackupsKeyVerifyScreen__see_key_again) + ) + } + + Buttons.LargeTonal( + enabled = isBackupKeyValid, + onClick = { + coroutineScope.launch { sheetState.show() } + } + ) { + Text( + text = stringResource(id = R.string.RegistrationActivity_next) + ) + } + } + } + } + + if (sheetState.isVisible) { + ModalBottomSheet( + sheetState = sheetState, + dragHandle = null, + onDismissRequest = { + coroutineScope.launch { + sheetState.hide() + } + } + ) { + BottomSheetContent( + onContinueClick = { + coroutineScope.launch { + sheetState.hide() + } + onNextClick() + }, + onSeeKeyAgainClick = { + coroutineScope.launch { + sheetState.hide() + } + onNavigationClick() + } + ) + } + } + } +} + +@Composable +private fun BottomSheetContent( + onContinueClick: () -> Unit, + onSeeKeyAgainClick: () -> Unit +) { + LazyColumn( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimensionResource(CoreUiR.dimen.gutter)) + .testTag("message-backups-key-record-screen-sheet-content") + ) { + item { + BottomSheets.Handle() + } + + item { + Image( + painter = painterResource(R.drawable.image_signal_backups_key), + contentDescription = null, + modifier = Modifier + .padding(top = 26.dp) + .size(80.dp) + ) + } + + item { + Text( + text = stringResource(R.string.MessageBackupsKeyRecordScreen__keep_your_key_safe), + style = MaterialTheme.typography.titleLarge, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 16.dp) + ) + } + + item { + Text( + text = stringResource(R.string.MessageBackupsKeyRecordScreen__signal_will_not), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 12.dp) + ) + } + + item { + Spacer(modifier = Modifier.height(54.dp)) + Buttons.LargeTonal( + onClick = onContinueClick, + modifier = Modifier + .padding(bottom = 16.dp) + .defaultMinSize(minWidth = 220.dp) + ) { + Text(text = stringResource(R.string.MessageBackupsKeyRecordScreen__continue)) + } + } + + item { + TextButton( + onClick = onSeeKeyAgainClick, + modifier = Modifier + .padding(bottom = 24.dp) + .defaultMinSize(minWidth = 220.dp) + ) { + Text( + text = stringResource(R.string.MessageBackupsKeyRecordScreen__see_key_again) + ) + } + } + } +} + +@SignalPreview +@Composable +private fun MessageBackupsKeyRecordScreenPreview() { + Previews.Preview { + MessageBackupsKeyVerifyScreen( + backupKey = (0 until 64).map { Random.nextInt(65..90).toChar() }.joinToString("").uppercase() + ) + } +} + +@SignalPreview +@Composable +private fun BottomSheetContentPreview() { + Previews.BottomSheetPreview { + BottomSheetContent({}, {}) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsStage.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsStage.kt index 4a2de20819..c07523cee7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsStage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsStage.kt @@ -15,6 +15,7 @@ enum class MessageBackupsStage( EDUCATION(route = Route.EDUCATION), BACKUP_KEY_EDUCATION(route = Route.BACKUP_KEY_EDUCATION), BACKUP_KEY_RECORD(route = Route.BACKUP_KEY_RECORD), + BACKUP_KEY_VERIFY(route = Route.BACKUP_KEY_VERIFY), TYPE_SELECTION(route = Route.TYPE_SELECTION), CREATING_IN_APP_PAYMENT(route = Route.TYPE_SELECTION), CHECKOUT_SHEET(route = Route.TYPE_SELECTION), @@ -31,6 +32,7 @@ enum class MessageBackupsStage( EDUCATION, BACKUP_KEY_EDUCATION, BACKUP_KEY_RECORD, + BACKUP_KEY_VERIFY, TYPE_SELECTION; fun isAfter(other: Route): Boolean = ordinal > other.ordinal diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/BackupKeyDisplayFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/BackupKeyDisplayFragment.kt index 8ce22a2a99..1d59e839eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/BackupKeyDisplayFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/BackupKeyDisplayFragment.kt @@ -19,7 +19,7 @@ class BackupKeyDisplayFragment : ComposeFragment() { @Composable override fun FragmentContent() { MessageBackupsKeyRecordScreen( - backupKey = SignalStore.account.accountEntropyPool.value, + backupKey = SignalStore.account.accountEntropyPool.displayValue, onNavigationClick = { findNavController().popBackStack() }, onCopyToClipboardClick = { Util.copyToClipboard(requireContext(), it) }, onNextClick = { findNavController().popBackStack() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9868c45f17..b465a40b41 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8140,6 +8140,19 @@ See key again + + Confirm your backup key + + Enter the backup key that you just recorded + + Backup key + + Incorrect backup key + + See key again + + Next + Choose your backup plan diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/AccountEntropyPool.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/AccountEntropyPool.kt index bf919c339c..11d737533c 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/AccountEntropyPool.kt +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/AccountEntropyPool.kt @@ -12,7 +12,10 @@ import org.signal.libsignal.messagebackup.AccountEntropyPool as LibSignalAccount /** * The Root of All Entropy. You can use this to derive the [MasterKey] or [MessageBackupKey]. */ -class AccountEntropyPool(val value: String) { +class AccountEntropyPool(value: String) { + + val value = value.lowercase() + val displayValue = value.uppercase() companion object { private val INVALID_CHARACTERS = Regex("[^0-9a-zA-Z]")