diff --git a/feature/registration/src/main/java/org/signal/registration/RegistrationRepository.kt b/feature/registration/src/main/java/org/signal/registration/RegistrationRepository.kt index 5a9f62e57a..10ce844621 100644 --- a/feature/registration/src/main/java/org/signal/registration/RegistrationRepository.kt +++ b/feature/registration/src/main/java/org/signal/registration/RegistrationRepository.kt @@ -289,6 +289,8 @@ class RegistrationRepository(val context: Context, val networkController: Networ backupSizeBytes = provisioningMessage.backupSizeBytes ?: 0, backupVersion = provisioningMessage.backupVersion ) + pin = provisioningMessage.pin ?: "" + pinIsAlphanumeric = provisioningMessage.pin?.any { !it.isDigit() } == true } val aep = AccountEntropyPool(provisioningMessage.accountEntropyPool) diff --git a/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryForRegistrationLockViewModel.kt b/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryForRegistrationLockViewModel.kt index 9389d7004c..997d142c51 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryForRegistrationLockViewModel.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryForRegistrationLockViewModel.kt @@ -69,6 +69,8 @@ class PinEntryForRegistrationLockViewModel( is PinEntryScreenEvents.Skip -> { handleSkip() } + is PinEntryScreenEvents.CreateNewPin, + is PinEntryScreenEvents.ContactSupport -> Unit is PinEntryScreenEvents.ToggleKeyboard, is PinEntryScreenEvents.NeedHelp -> { stateEmitter(PinEntryScreenEventHandler.applyEvent(state, event)) diff --git a/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryForSmsBypassViewModel.kt b/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryForSmsBypassViewModel.kt index e1a8fb255d..0ff7c89607 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryForSmsBypassViewModel.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryForSmsBypassViewModel.kt @@ -76,6 +76,8 @@ class PinEntryForSmsBypassViewModel( is PinEntryScreenEvents.Skip -> { handleSkip() } + is PinEntryScreenEvents.CreateNewPin, + is PinEntryScreenEvents.ContactSupport -> Unit is PinEntryScreenEvents.ToggleKeyboard, is PinEntryScreenEvents.NeedHelp -> { stateEmitter(PinEntryScreenEventHandler.applyEvent(state, event)) diff --git a/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryForSvrRestoreViewModel.kt b/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryForSvrRestoreViewModel.kt index 4a1bdd6b8d..e78f3cefdf 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryForSvrRestoreViewModel.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryForSvrRestoreViewModel.kt @@ -72,6 +72,15 @@ class PinEntryForSvrRestoreViewModel( is PinEntryScreenEvents.Skip -> { handleSkip() } + is PinEntryScreenEvents.CreateNewPin -> { + Log.i(TAG, "[CreateNewPin] User opted to create a new PIN after no data was found. Navigating to PIN creation.") + stateEmitter(state.copy(showNoDataToRestoreDialog = false)) + parentEventEmitter.navigateTo(RegistrationRoute.PinCreate) + } + is PinEntryScreenEvents.ContactSupport -> { + Log.i(TAG, "[ContactSupport] User opted to contact support after no data was found.") + stateEmitter(state.copy(showNoDataToRestoreDialog = false)) + } is PinEntryScreenEvents.ToggleKeyboard, is PinEntryScreenEvents.NeedHelp -> { stateEmitter(PinEntryScreenEventHandler.applyEvent(state, event)) @@ -128,9 +137,8 @@ class PinEntryForSvrRestoreViewModel( state.copy(triesRemaining = error.triesRemaining) } is NetworkController.RestoreMasterKeyError.NoDataFound -> { - Log.w(TAG, "[PinEntered] No SVR data found. Need to create a PIN instead.") - parentEventEmitter.navigateTo(RegistrationRoute.PinCreate) - state + Log.w(TAG, "[PinEntered] No SVR data found. Prompting user to create a new PIN.") + state.copy(showNoDataToRestoreDialog = true) } } } diff --git a/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryScreen.kt b/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryScreen.kt index df43bd73ca..ffb7b97438 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryScreen.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryScreen.kt @@ -45,6 +45,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogProperties import org.signal.core.ui.compose.AllDevicePreviews import org.signal.core.ui.compose.Buttons import org.signal.core.ui.compose.Dialogs @@ -111,6 +112,22 @@ fun PinEntryScreen( ) } + if (state.showNoDataToRestoreDialog) { + Dialogs.SimpleAlertDialog( + title = "", + body = stringResource(R.string.PinEntryScreen__no_data_could_be_found), + confirm = stringResource(R.string.PinEntryScreen__create_new_pin), + dismiss = stringResource(R.string.PinEntryScreen__contact_support), + onConfirm = { onEvent(PinEntryScreenEvents.CreateNewPin) }, + onDeny = { onEvent(PinEntryScreenEvents.ContactSupport) }, + onDismissRequest = { onEvent(PinEntryScreenEvents.ContactSupport) }, + properties = DialogProperties( + dismissOnBackPress = false, + dismissOnClickOutside = false + ) + ) + } + // autofocus PIN field on initial composition LaunchedEffect(Unit) { focusRequester.requestFocus() diff --git a/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryScreenEvents.kt b/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryScreenEvents.kt index ffa42f11a2..6957617ba1 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryScreenEvents.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryScreenEvents.kt @@ -12,4 +12,6 @@ sealed class PinEntryScreenEvents { data object ToggleKeyboard : PinEntryScreenEvents() data object NeedHelp : PinEntryScreenEvents() data object Skip : PinEntryScreenEvents() + data object CreateNewPin : PinEntryScreenEvents() + data object ContactSupport : PinEntryScreenEvents() } diff --git a/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryState.kt b/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryState.kt index 5a7d9ab027..9394da2722 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryState.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/pinentry/PinEntryState.kt @@ -11,6 +11,7 @@ data class PinEntryState( val showNeedHelp: Boolean = false, val isAlphanumericKeyboard: Boolean = false, val loading: Boolean = false, + val showNoDataToRestoreDialog: Boolean = false, val triesRemaining: Int? = null, val mode: Mode = Mode.SvrRestore, val oneTimeEvent: OneTimeEvent? = null, diff --git a/feature/registration/src/main/res/values/strings.xml b/feature/registration/src/main/res/values/strings.xml index b9fee67a5a..f34853f66f 100644 --- a/feature/registration/src/main/res/values/strings.xml +++ b/feature/registration/src/main/res/values/strings.xml @@ -373,6 +373,10 @@ Create New PIN Cancel + + No data could be found to restore your account. Please create a new PIN. + + Contact support Allow Notifications diff --git a/feature/registration/src/test/java/org/signal/registration/screens/pinentry/PinEntryForSvrRestoreViewModelTest.kt b/feature/registration/src/test/java/org/signal/registration/screens/pinentry/PinEntryForSvrRestoreViewModelTest.kt index 7c8de18395..d491468747 100644 --- a/feature/registration/src/test/java/org/signal/registration/screens/pinentry/PinEntryForSvrRestoreViewModelTest.kt +++ b/feature/registration/src/test/java/org/signal/registration/screens/pinentry/PinEntryForSvrRestoreViewModelTest.kt @@ -163,7 +163,7 @@ class PinEntryForSvrRestoreViewModelTest { } @Test - fun `PinEntered with no SVR data navigates to PinCreate`() = runTest { + fun `PinEntered with no SVR data shows the no-data-to-restore dialog without navigating`() = runTest { val svrCredentials = NetworkController.SvrCredentials( username = "test-username", password = "test-password" @@ -179,6 +179,19 @@ class PinEntryForSvrRestoreViewModelTest { viewModel.applyEvent(initialState, PinEntryScreenEvents.PinEntered("123456"), parentEventEmitter, stateEmitter) + assertThat(emittedParentEvents).hasSize(0) + assertThat(emittedStates.last().showNoDataToRestoreDialog).isEqualTo(true) + } + + // ==================== No Data To Restore Dialog Tests ==================== + + @Test + fun `CreateNewPin dismisses the dialog and navigates to PinCreate`() = runTest { + val initialState = PinEntryState(mode = PinEntryState.Mode.SvrRestore, showNoDataToRestoreDialog = true) + + viewModel.applyEvent(initialState, PinEntryScreenEvents.CreateNewPin, parentEventEmitter, stateEmitter) + + assertThat(emittedStates.last().showNoDataToRestoreDialog).isEqualTo(false) assertThat(emittedParentEvents).hasSize(1) assertThat(emittedParentEvents.first()) .isInstanceOf() @@ -186,6 +199,16 @@ class PinEntryForSvrRestoreViewModelTest { .isInstanceOf() } + @Test + fun `ContactSupport dismisses the dialog without navigating`() = runTest { + val initialState = PinEntryState(mode = PinEntryState.Mode.SvrRestore, showNoDataToRestoreDialog = true) + + viewModel.applyEvent(initialState, PinEntryScreenEvents.ContactSupport, parentEventEmitter, stateEmitter) + + assertThat(emittedStates.last().showNoDataToRestoreDialog).isEqualTo(false) + assertThat(emittedParentEvents).hasSize(0) + } + @Test fun `PinEntered with network error restoring master key returns NetworkError event`() = runTest { val svrCredentials = NetworkController.SvrCredentials(