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(