From 6ac510a156e6f08bd6b7be59fdf131a25661e41b Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Tue, 6 Aug 2024 16:22:12 -0300 Subject: [PATCH] Add error state for pin entry during backups flow. --- .../MessageBackupsFlowFragment.kt | 1 + .../subscription/MessageBackupsFlowState.kt | 3 +- .../MessageBackupsFlowViewModel.kt | 54 +++++++++++-------- .../MessageBackupsPinConfirmationScreen.kt | 10 +++- 4 files changed, 44 insertions(+), 24 deletions(-) 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 8faaf49cd2..fd638c038d 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 @@ -91,6 +91,7 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega composable(route = MessageBackupsScreen.PIN_CONFIRMATION.name) { MessageBackupsPinConfirmationScreen( pin = pin, + isPinIncorrect = state.displayIncorrectPinError, onPinChanged = viewModel::onPinEntryUpdated, pinKeyboardType = state.pinKeyboardType, onPinKeyboardTypeSelected = viewModel::onPinKeyboardTypeUpdated, diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowState.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowState.kt index b4390acb7c..e6b12d47cb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowState.kt @@ -20,5 +20,6 @@ data class MessageBackupsFlowState( val pinKeyboardType: PinKeyboardType = SignalStore.pin.keyboardType, val inAppPayment: InAppPaymentTable.InAppPayment? = null, val startScreen: MessageBackupsScreen, - val screen: MessageBackupsScreen = startScreen + val screen: MessageBackupsScreen = startScreen, + val displayIncorrectPinError: Boolean = false ) 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 02c3b7033b..ef1ed84d13 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 @@ -64,23 +64,20 @@ class MessageBackupsFlowViewModel : ViewModel() { fun goToNextScreen() { val pinSnapshot = pinState.value - internalPinState.value = "" internalStateFlow.update { - val nextScreen = when (it.screen) { - MessageBackupsScreen.EDUCATION -> MessageBackupsScreen.PIN_EDUCATION - MessageBackupsScreen.PIN_EDUCATION -> MessageBackupsScreen.PIN_CONFIRMATION - MessageBackupsScreen.PIN_CONFIRMATION -> validatePinAndUpdateState(pinSnapshot) - MessageBackupsScreen.TYPE_SELECTION -> validateTypeAndUpdateState(it.selectedMessageBackupTier!!) + when (it.screen) { + MessageBackupsScreen.EDUCATION -> it.copy(screen = MessageBackupsScreen.PIN_EDUCATION) + MessageBackupsScreen.PIN_EDUCATION -> it.copy(screen = MessageBackupsScreen.PIN_CONFIRMATION) + MessageBackupsScreen.PIN_CONFIRMATION -> validatePinAndUpdateState(it, pinSnapshot) + MessageBackupsScreen.TYPE_SELECTION -> validateTypeAndUpdateState(it) MessageBackupsScreen.CHECKOUT_SHEET -> validateGatewayAndUpdateState(it) MessageBackupsScreen.CREATING_IN_APP_PAYMENT -> error("This is driven by an async coroutine.") - MessageBackupsScreen.CANCELLATION_DIALOG -> MessageBackupsScreen.PROCESS_CANCELLATION - MessageBackupsScreen.PROCESS_PAYMENT -> MessageBackupsScreen.COMPLETED - MessageBackupsScreen.PROCESS_CANCELLATION -> MessageBackupsScreen.COMPLETED + MessageBackupsScreen.CANCELLATION_DIALOG -> it.copy(screen = MessageBackupsScreen.PROCESS_CANCELLATION) + MessageBackupsScreen.PROCESS_PAYMENT -> it.copy(screen = MessageBackupsScreen.COMPLETED) + MessageBackupsScreen.PROCESS_CANCELLATION -> it.copy(screen = MessageBackupsScreen.COMPLETED) MessageBackupsScreen.COMPLETED -> error("Unsupported state transition from terminal state COMPLETED") } - - it.copy(screen = nextScreen) } } @@ -139,36 +136,49 @@ class MessageBackupsFlowViewModel : ViewModel() { } } - private fun validatePinAndUpdateState(pin: String): MessageBackupsScreen { + private fun validatePinAndUpdateState(state: MessageBackupsFlowState, pin: String): MessageBackupsFlowState { val pinHash = SignalStore.svr.localPinHash - if (pinHash == null || TextUtils.isEmpty(pin) || pin.length < SvrConstants.MINIMUM_PIN_LENGTH) return MessageBackupsScreen.PIN_CONFIRMATION + if (pinHash == null || TextUtils.isEmpty(pin) || pin.length < SvrConstants.MINIMUM_PIN_LENGTH) { + return state.copy( + screen = MessageBackupsScreen.PIN_CONFIRMATION, + displayIncorrectPinError = true + ) + } if (!verifyLocalPinHash(pinHash, pin)) { - return MessageBackupsScreen.PIN_CONFIRMATION + return state.copy( + screen = MessageBackupsScreen.PIN_CONFIRMATION, + displayIncorrectPinError = true + ) } - return MessageBackupsScreen.TYPE_SELECTION + + internalPinState.value = "" + return state.copy( + screen = MessageBackupsScreen.TYPE_SELECTION, + displayIncorrectPinError = false + ) } - private fun validateTypeAndUpdateState(tier: MessageBackupTier): MessageBackupsScreen { - return when (tier) { + private fun validateTypeAndUpdateState(state: MessageBackupsFlowState): MessageBackupsFlowState { + return when (state.selectedMessageBackupTier!!) { MessageBackupTier.FREE -> { if (SignalStore.backup.backupTier == MessageBackupTier.PAID) { isDowngrading = true - MessageBackupsScreen.PROCESS_CANCELLATION + state.copy(screen = MessageBackupsScreen.PROCESS_CANCELLATION) } else { SignalStore.backup.areBackupsEnabled = true SignalStore.backup.backupTier = MessageBackupTier.FREE // TODO [message-backups] -- Trigger backup now? - MessageBackupsScreen.COMPLETED + state.copy(screen = MessageBackupsScreen.COMPLETED) } } - MessageBackupTier.PAID -> MessageBackupsScreen.CHECKOUT_SHEET + MessageBackupTier.PAID -> state.copy(screen = MessageBackupsScreen.CHECKOUT_SHEET) } } - private fun validateGatewayAndUpdateState(state: MessageBackupsFlowState): MessageBackupsScreen { + private fun validateGatewayAndUpdateState(state: MessageBackupsFlowState): MessageBackupsFlowState { val backupsType = state.availableBackupTypes.first { it.tier == state.selectedMessageBackupTier } viewModelScope.launch(Dispatchers.IO) { @@ -202,6 +212,6 @@ class MessageBackupsFlowViewModel : ViewModel() { } } - return MessageBackupsScreen.CREATING_IN_APP_PAYMENT + return state.copy(screen = MessageBackupsScreen.CREATING_IN_APP_PAYMENT) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsPinConfirmationScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsPinConfirmationScreen.kt index c54ec97e5f..8fa6c97831 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsPinConfirmationScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsPinConfirmationScreen.kt @@ -50,6 +50,7 @@ import org.thoughtcrime.securesms.lock.v2.PinKeyboardType @Composable fun MessageBackupsPinConfirmationScreen( pin: String, + isPinIncorrect: Boolean, onPinChanged: (String) -> Unit, pinKeyboardType: PinKeyboardType, onPinKeyboardTypeSelected: (PinKeyboardType) -> Unit, @@ -107,7 +108,13 @@ fun MessageBackupsPinConfirmationScreen( .padding(top = 72.dp) .fillMaxWidth() .focusRequester(focusRequester), - visualTransformation = PasswordVisualTransformation() + visualTransformation = PasswordVisualTransformation(), + isError = isPinIncorrect, + supportingText = { + if (isPinIncorrect) { + Text(text = stringResource(id = R.string.PinRestoreEntryFragment_incorrect_pin)) + } + } ) } @@ -154,6 +161,7 @@ private fun MessageBackupsPinConfirmationScreenPreview() { Previews.Preview { MessageBackupsPinConfirmationScreen( pin = "", + isPinIncorrect = true, onPinChanged = {}, pinKeyboardType = PinKeyboardType.ALPHA_NUMERIC, onPinKeyboardTypeSelected = {},