Clean up back-pressed behavior which could result in an empty backstack and crash.

Co-authored-by: Greyson Parrelli <greyson@signal.org>
Co-authored-by: jeffrey-signal <jeffrey@signal.org>
This commit is contained in:
Alex Hart
2026-04-30 17:37:47 -03:00
committed by Greyson Parrelli
parent 07329c5b0d
commit 1d36ecafe1
4 changed files with 48 additions and 3 deletions
@@ -6,6 +6,7 @@
package org.thoughtcrime.securesms.registration.olddevice
import android.os.Parcelable
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -49,6 +50,13 @@ fun TransferAccountNavHost(
) {
val backStack by viewModel.backStack.collectAsStateWithLifecycle()
val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
LaunchedEffect(viewModel, backDispatcher) {
viewModel.finishRequests.collect {
backDispatcher?.onBackPressed()
}
}
val entryProvider = entryProvider {
navigationEntries(
viewModel = viewModel,
@@ -8,8 +8,12 @@ package org.thoughtcrime.securesms.registration.olddevice
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.signal.core.util.logging.Log
@@ -41,8 +45,11 @@ class QuickTransferOldDeviceViewModel(reRegisterUri: String) : ViewModel() {
private val _backStack: MutableStateFlow<List<TransferAccountRoute>> = MutableStateFlow(listOf(TransferAccountRoute.Transfer))
val backStack: StateFlow<List<TransferAccountRoute>> = _backStack
private val finishChannel = Channel<Unit>(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
val finishRequests: Flow<Unit> = finishChannel.receiveAsFlow()
fun goBack() {
_backStack.update { it.dropLast(1) }
popOrFinish()
}
fun onEvent(event: PrepareDeviceScreenEvents) {
@@ -51,7 +58,7 @@ class QuickTransferOldDeviceViewModel(reRegisterUri: String) : ViewModel() {
store.update { it.copy(navigateToBackupCreation = true) }
}
PrepareDeviceScreenEvents.NavigateBack -> {
_backStack.update { it.dropLast(1) }
popOrFinish()
}
PrepareDeviceScreenEvents.SkipAndContinue -> {
_backStack.update { listOf(TransferAccountRoute.Transfer) }
@@ -96,6 +103,14 @@ class QuickTransferOldDeviceViewModel(reRegisterUri: String) : ViewModel() {
store.update { it.copy(navigateToBackupCreation = false) }
}
private fun popOrFinish() {
if (_backStack.value.size > 1) {
_backStack.update { it.dropLast(1) }
} else {
finishChannel.trySend(Unit)
}
}
private fun transferAccount() {
viewModelScope.launch(Dispatchers.IO) {
val restoreMethodToken = UUID.randomUUID().toString()
@@ -8,6 +8,7 @@
package org.signal.registration
import android.os.Parcelable
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
@@ -231,6 +232,13 @@ fun RegistrationNavHost(
return
}
val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
LaunchedEffect(viewModel, backDispatcher) {
viewModel.finishRequests.collect {
backDispatcher?.onBackPressed()
}
}
val entryProvider = entryProvider {
navigationEntries(
registrationRepository = registrationRepository,
@@ -13,9 +13,13 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import org.signal.core.ui.navigation.ResultEventBus
import org.signal.core.util.logging.Log
@@ -35,6 +39,9 @@ class RegistrationViewModel(private val repository: RegistrationRepository, save
private var _state: MutableStateFlow<RegistrationFlowState> = savedStateHandle.getMutableStateFlow("registration_state", initialValue = RegistrationFlowState())
val state: StateFlow<RegistrationFlowState> = _state.asStateFlow()
private val finishChannel = Channel<Unit>(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
val finishRequests: Flow<Unit> = finishChannel.receiveAsFlow()
val resultBus = ResultEventBus()
init {
@@ -66,7 +73,14 @@ class RegistrationViewModel(private val repository: RegistrationRepository, save
is RegistrationFlowEvent.Registered -> state.copy(accountEntropyPool = event.accountEntropyPool)
is RegistrationFlowEvent.MasterKeyRestoredFromSvr -> state.copy(temporaryMasterKey = event.masterKey)
is RegistrationFlowEvent.NavigateToScreen -> applyNavigationToScreenEvent(state, event)
is RegistrationFlowEvent.NavigateBack -> state.copy(backStack = state.backStack.dropLast(1))
is RegistrationFlowEvent.NavigateBack -> {
if (state.backStack.size > 1) {
state.copy(backStack = state.backStack.dropLast(1))
} else {
finishChannel.trySend(Unit)
state
}
}
is RegistrationFlowEvent.RecoveryPasswordInvalid -> state.copy(doNotAttemptRecoveryPassword = true)
is RegistrationFlowEvent.PendingRestoreOptionSelected -> state.copy(pendingRestoreOption = event.option)
is RegistrationFlowEvent.UserSuppliedAepSubmitted -> state.copy(unverifiedRestoredAep = event.aep)