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]")