diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/BackgroundEvent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/BackgroundEvent.kt new file mode 100644 index 000000000..f9f2e9d6f --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/BackgroundEvent.kt @@ -0,0 +1,8 @@ +package com.x8bit.bitwarden.ui.platform.base.util + +/** + * Almost all the events in the app involve navigation or toasts. To prevent accidentally + * navigating to the same view twice, by default, events are ignored if the view is not currently + * resumed. To avoid that restriction, specific events can implement [BackgroundEvent]. + */ +interface BackgroundEvent diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/EventsEffect.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/EventsEffect.kt index ccfb4d240..146deb3fe 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/EventsEffect.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/EventsEffect.kt @@ -2,20 +2,32 @@ package com.x8bit.bitwarden.ui.platform.base.util import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.Lifecycle import com.x8bit.bitwarden.ui.platform.base.BaseViewModel +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach /** * Convenience method for observing event flow from [BaseViewModel]. + * + * By default, events will only be consumed when the associated screen is + * resumed, to avoid bugs like duplicate navigation calls. To override + * this behavior, a given event type can implement [BackgroundEvent]. */ @Composable fun EventsEffect( viewModel: BaseViewModel<*, E, *>, + lifecycleOwner: Lifecycle = LocalLifecycleOwner.current.lifecycle, handler: suspend (E) -> Unit, ) { LaunchedEffect(key1 = Unit) { viewModel.eventFlow + .filter { + it is BackgroundEvent || + lifecycleOwner.currentState.isAtLeast(Lifecycle.State.RESUMED) + } .onEach { handler.invoke(it) } .launchIn(this) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt index cc023483b..99e451178 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt @@ -37,6 +37,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.ui.platform.base.BaseViewModel +import com.x8bit.bitwarden.ui.platform.base.util.BackgroundEvent import com.x8bit.bitwarden.ui.platform.base.util.Text import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.base.util.concat @@ -2358,8 +2359,7 @@ sealed class VaultAddEditEvent { /** * Navigate the user to the tooltip URI. */ - data object NavigateToTooltipUri : - VaultAddEditEvent() + data object NavigateToTooltipUri : VaultAddEditEvent() /** * Navigate to the QR code scan screen. @@ -2385,7 +2385,7 @@ sealed class VaultAddEditEvent { */ data class CompleteFido2Registration( val result: Fido2RegisterCredentialResult, - ) : VaultAddEditEvent() + ) : BackgroundEvent, VaultAddEditEvent() /** * Perform user verification for a FIDO 2 credential operation. @@ -2394,7 +2394,7 @@ sealed class VaultAddEditEvent { */ data class Fido2UserVerification( val isRequired: Boolean, - ) : VaultAddEditEvent() + ) : BackgroundEvent, VaultAddEditEvent() } /** diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index a3f9f169b..29b35ef3a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -46,6 +46,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.ui.platform.base.BaseViewModel +import com.x8bit.bitwarden.ui.platform.base.util.BackgroundEvent import com.x8bit.bitwarden.ui.platform.base.util.Text import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.base.util.concat @@ -2102,7 +2103,7 @@ sealed class VaultItemListingEvent { */ data class CompleteFido2Registration( val result: Fido2RegisterCredentialResult, - ) : VaultItemListingEvent() + ) : BackgroundEvent, VaultItemListingEvent() /** * Perform user verification for a FIDO 2 credential operation. @@ -2110,7 +2111,7 @@ sealed class VaultItemListingEvent { data class Fido2UserVerification( val isRequired: Boolean, val selectedCipherView: CipherView, - ) : VaultItemListingEvent() + ) : BackgroundEvent, VaultItemListingEvent() /** * FIDO 2 credential assertion result has been received and the process is ready to be @@ -2120,7 +2121,7 @@ sealed class VaultItemListingEvent { */ data class CompleteFido2Assertion( val result: Fido2CredentialAssertionResult, - ) : VaultItemListingEvent() + ) : BackgroundEvent, VaultItemListingEvent() /** * FIDO 2 credential lookup result has been received and the process is ready to be completed. @@ -2129,7 +2130,7 @@ sealed class VaultItemListingEvent { */ data class CompleteFido2GetCredentialsRequest( val result: Fido2GetCredentialsResult, - ) : VaultItemListingEvent() + ) : BackgroundEvent, VaultItemListingEvent() } /**