mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-08-04 12:24:00 +01:00
Reset regV5 flow state when re-registering.
This commit is contained in:
+1
-1
@@ -97,7 +97,7 @@ class RegistrationActivity : BaseActivity() {
|
||||
@JvmStatic
|
||||
fun newIntentForReRegistration(context: Context): Intent {
|
||||
return if (Environment.USE_NEW_REGISTRATION) {
|
||||
org.signal.registration.RegistrationActivity.createIntent(context, nextIntent = MainActivity.clearTop(context))
|
||||
org.signal.registration.RegistrationActivity.createIntent(context, nextIntent = MainActivity.clearTop(context), startFresh = true)
|
||||
} else {
|
||||
Intent(context, RegistrationActivity::class.java).apply {
|
||||
putExtra(RE_REGISTRATION_EXTRA, true)
|
||||
|
||||
@@ -27,6 +27,7 @@ class RegistrationActivity : ComponentActivity() {
|
||||
companion object {
|
||||
private const val NEXT_INTENT_EXTRA = "next_intent"
|
||||
private const val START_DESTINATION_EXTRA = "start_destination"
|
||||
private const val START_FRESH_EXTRA = "start_fresh"
|
||||
|
||||
/**
|
||||
* @param nextIntent An optional intent to launch once registration completes successfully. This is how the caller
|
||||
@@ -34,10 +35,12 @@ class RegistrationActivity : ComponentActivity() {
|
||||
* typically have finished itself.
|
||||
* @param startDestination An optional route to open directly instead of resuming a previous flow. Used, for example,
|
||||
* to send a deregistered linked device straight to the link-device screen.
|
||||
* @param startFresh When true, any persisted registration data is not restored and the user starts the flow fresh
|
||||
* from the beginning.
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun createIntent(context: Context, nextIntent: Intent? = null, startDestination: RegistrationRoute? = null): Intent {
|
||||
fun createIntent(context: Context, nextIntent: Intent? = null, startDestination: RegistrationRoute? = null, startFresh: Boolean = false): Intent {
|
||||
return Intent(context, RegistrationActivity::class.java).apply {
|
||||
if (nextIntent != null) {
|
||||
putExtra(NEXT_INTENT_EXTRA, nextIntent)
|
||||
@@ -45,6 +48,7 @@ class RegistrationActivity : ComponentActivity() {
|
||||
if (startDestination != null) {
|
||||
putExtra(START_DESTINATION_EXTRA, startDestination)
|
||||
}
|
||||
putExtra(START_FRESH_EXTRA, startFresh)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +68,7 @@ class RegistrationActivity : ComponentActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val startDestination = IntentCompat.getParcelableExtra(intent, START_DESTINATION_EXTRA, RegistrationRoute::class.java)
|
||||
val startFresh = intent.getBooleanExtra(START_FRESH_EXTRA, false)
|
||||
|
||||
setContent {
|
||||
SignalTheme(incognitoKeyboardEnabled = false) {
|
||||
@@ -71,6 +76,7 @@ class RegistrationActivity : ComponentActivity() {
|
||||
RegistrationNavHost(
|
||||
registrationRepository = repository,
|
||||
startDestination = startDestination,
|
||||
startFresh = startFresh,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.navigationBarsPadding(),
|
||||
|
||||
+4
-1
@@ -261,6 +261,8 @@ private const val PIN_LEARN_MORE_URL = "https://support.signal.org/hc/articles/3
|
||||
*
|
||||
* @param registrationRepository The repository for registration data.
|
||||
* @param registrationViewModel Optional ViewModel for testing. If null, creates one internally.
|
||||
* @param startFresh When true, any persisted registration data is not restored and the user starts the flow fresh from
|
||||
* the beginning.
|
||||
* @param permissionsState Optional permissions state for testing. If null, creates one internally.
|
||||
* @param startDestination Optional route to open directly as the sole start destination, instead of showing [RegistrationRoute.Welcome] or restoring a previous flow.
|
||||
* @param modifier Modifier to be applied to the NavDisplay.
|
||||
@@ -271,13 +273,14 @@ private const val PIN_LEARN_MORE_URL = "https://support.signal.org/hc/articles/3
|
||||
fun RegistrationNavHost(
|
||||
registrationRepository: RegistrationRepository,
|
||||
registrationViewModel: RegistrationViewModel? = null,
|
||||
startFresh: Boolean = false,
|
||||
permissionsState: MultiplePermissionsState? = null,
|
||||
startDestination: RegistrationRoute? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
onRegistrationComplete: () -> Unit = {}
|
||||
) {
|
||||
val viewModel: RegistrationViewModel = registrationViewModel ?: viewModel(
|
||||
factory = RegistrationViewModel.Factory(registrationRepository, startDestination)
|
||||
factory = RegistrationViewModel.Factory(registrationRepository, startDestination, startFresh)
|
||||
)
|
||||
|
||||
val registrationState by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
+23
-4
@@ -32,12 +32,20 @@ import kotlin.reflect.KClass
|
||||
*/
|
||||
class RegistrationViewModel(
|
||||
private val repository: RegistrationRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
startDestination: RegistrationRoute? = null
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
startDestination: RegistrationRoute? = null,
|
||||
private val startFresh: Boolean = false
|
||||
) : EventDrivenViewModel<RegistrationFlowEvent>(TAG) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(RegistrationViewModel::class)
|
||||
|
||||
/**
|
||||
* Marks that a start-fresh reset has already been performed for this ViewModel. Persisted in the
|
||||
* [SavedStateHandle] so that a process death mid-flow (which re-delivers the start-fresh intent) does not
|
||||
* wipe the fresh in-progress data the user has since built up.
|
||||
*/
|
||||
private const val RESET_PERFORMED_KEY = "start_fresh_reset_performed"
|
||||
}
|
||||
|
||||
private var _state: MutableStateFlow<RegistrationFlowState> = savedStateHandle.getMutableStateFlow(
|
||||
@@ -57,6 +65,17 @@ class RegistrationViewModel(
|
||||
} else {
|
||||
_state.value = _state.value.copy(isRestoringNavigationState = true)
|
||||
viewModelScope.launch {
|
||||
if (startFresh && savedStateHandle.get<Boolean>(RESET_PERFORMED_KEY) != true) {
|
||||
Log.i(TAG, "[init] Start-fresh requested. Clearing any persisted in-progress registration data so the user starts fresh.")
|
||||
repository.clearInProgressRegistrationData()
|
||||
savedStateHandle[RESET_PERFORMED_KEY] = true
|
||||
_state.value = RegistrationFlowState(
|
||||
preExistingRegistrationData = repository.getPreExistingRegistrationData(),
|
||||
isRestoringNavigationState = false
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val restored = repository.restoreFlowState()
|
||||
if (restored != null) {
|
||||
Log.i(TAG, "[init] Restored flow state from disk. Backstack size: ${restored.backStack.size}, hasSession: ${restored.sessionMetadata != null}")
|
||||
@@ -214,9 +233,9 @@ class RegistrationViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(private val repository: RegistrationRepository, private val startDestination: RegistrationRoute? = null) : ViewModelProvider.Factory {
|
||||
class Factory(private val repository: RegistrationRepository, private val startDestination: RegistrationRoute? = null, private val startFresh: Boolean = false) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: KClass<T>, extras: CreationExtras): T {
|
||||
return RegistrationViewModel(repository, extras.createSavedStateHandle(), startDestination) as T
|
||||
return RegistrationViewModel(repository, extras.createSavedStateHandle(), startDestination, startFresh) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+46
@@ -184,6 +184,52 @@ class RegistrationViewModelTest {
|
||||
assertThat(viewModel.state.value.isRestoringNavigationState).isEqualTo(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `start fresh clears in-progress data and starts fresh from Welcome`() = runTest(testDispatcher) {
|
||||
val preExisting = mockk<PreExistingRegistrationData>(relaxed = true)
|
||||
val savedState = RegistrationFlowState(
|
||||
backStack = listOf(
|
||||
RegistrationRoute.Welcome,
|
||||
RegistrationRoute.Permissions(nextRoute = RegistrationRoute.PhoneNumberEntry),
|
||||
RegistrationRoute.PhoneNumberEntry,
|
||||
RegistrationRoute.VerificationCodeEntry
|
||||
),
|
||||
sessionMetadata = createSessionMetadata("stale-session")
|
||||
)
|
||||
coEvery { mockRepository.restoreFlowState() } returns savedState
|
||||
coEvery { mockRepository.getPreExistingRegistrationData() } returns preExisting
|
||||
|
||||
val viewModel = RegistrationViewModel(mockRepository, SavedStateHandle(), startFresh = true)
|
||||
advanceUntilIdle()
|
||||
|
||||
val state = viewModel.state.value
|
||||
assertThat(state.backStack).isEqualTo(listOf(RegistrationRoute.Welcome))
|
||||
assertThat(state.sessionMetadata).isNull()
|
||||
assertThat(state.preExistingRegistrationData).isEqualTo(preExisting)
|
||||
assertThat(state.isRestoringNavigationState).isEqualTo(false)
|
||||
|
||||
coVerify { mockRepository.clearInProgressRegistrationData() }
|
||||
coVerify(exactly = 0) { mockRepository.restoreFlowState() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `start fresh does not clear again once reset has been performed`() = runTest(testDispatcher) {
|
||||
val savedState = RegistrationFlowState(
|
||||
backStack = listOf(RegistrationRoute.Welcome, RegistrationRoute.PhoneNumberEntry),
|
||||
sessionMetadata = null
|
||||
)
|
||||
coEvery { mockRepository.restoreFlowState() } returns savedState
|
||||
|
||||
val savedStateHandle = SavedStateHandle(mapOf("start_fresh_reset_performed" to true))
|
||||
val viewModel = RegistrationViewModel(mockRepository, savedStateHandle, startFresh = true)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertThat(viewModel.state.value.backStack).isEqualTo(savedState.backStack)
|
||||
|
||||
coVerify(exactly = 0) { mockRepository.clearInProgressRegistrationData() }
|
||||
coVerify { mockRepository.restoreFlowState() }
|
||||
}
|
||||
|
||||
// ==================== Persistence Side-Effect Tests ====================
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user