From f544ccc3ef00915befdeeea13b4f8d236656bd13 Mon Sep 17 00:00:00 2001 From: Dave Severns <149429124+dseverns-livefront@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:02:01 -0400 Subject: [PATCH] [PM-11741] shortcut navigation pt2 electric boogaloo (#3904) --- .../platform/feature/rootnav/RootNavScreen.kt | 8 -- .../feature/rootnav/RootNavViewModel.kt | 10 +- .../VaultUnlockedNavBarScreen.kt | 8 +- .../VaultUnlockedNavBarViewModel.kt | 31 ++++++- .../feature/rootnav/RootNavScreenTest.kt | 9 -- .../feature/rootnav/RootNavViewModelTest.kt | 91 ------------------- .../VaultUnlockedNavBarScreenTest.kt | 32 +++++++ .../VaultUnlockedNavBarViewModelTest.kt | 8 +- 8 files changed, 72 insertions(+), 125 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt index 322e52b04..374a328ed 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt @@ -47,8 +47,6 @@ import com.x8bit.bitwarden.ui.platform.feature.vaultunlocked.vaultUnlockedGraph import com.x8bit.bitwarden.ui.platform.theme.NonNullEnterTransitionProvider import com.x8bit.bitwarden.ui.platform.theme.NonNullExitTransitionProvider import com.x8bit.bitwarden.ui.platform.theme.RootTransitionProviders -import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode -import com.x8bit.bitwarden.ui.tools.feature.generator.navigateToGeneratorModal import com.x8bit.bitwarden.ui.tools.feature.send.addsend.model.AddSendType import com.x8bit.bitwarden.ui.tools.feature.send.addsend.navigateToAddSend import com.x8bit.bitwarden.ui.vault.feature.addedit.navigateToVaultAddEdit @@ -115,7 +113,6 @@ fun RootNavScreen( is RootNavState.VaultUnlockedForFido2Save, is RootNavState.VaultUnlockedForFido2Assertion, is RootNavState.VaultUnlockedForFido2GetCredentials, - is RootNavState.GeneratorShortcut, -> VAULT_UNLOCKED_GRAPH_ROUTE } val currentRoute = navController.currentDestination?.rootLevelRoute() @@ -220,11 +217,6 @@ fun RootNavScreen( navOptions = rootNavOptions, ) } - - RootNavState.GeneratorShortcut -> { - navController.navigateToVaultUnlockedGraph(rootNavOptions) - navController.navigateToGeneratorModal(mode = GeneratorMode.Modal.Password) - } } } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt index 19f548fb6..8d2edc9bc 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt @@ -131,9 +131,7 @@ class RootNavViewModel @Inject constructor( ) } - SpecialCircumstance.GeneratorShortcut -> { - RootNavState.GeneratorShortcut - } + SpecialCircumstance.GeneratorShortcut, SpecialCircumstance.VaultShortcut, null, -> RootNavState.VaultUnlocked(activeUserId = userState.activeAccount.userId) @@ -322,12 +320,6 @@ sealed class RootNavState : Parcelable { */ @Parcelize data object ExpiredRegistrationLink : RootNavState() - - /** - * App should show the password generator modal. - */ - @Parcelize - data object GeneratorShortcut : RootNavState() } /** diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt index 1895387f7..d181a431e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt @@ -88,7 +88,9 @@ fun VaultUnlockedNavBarScreen( navController.apply { val navOptions = vaultUnlockedNavBarScreenNavOptions(tabToNavigateTo = event.tab) when (event) { - is VaultUnlockedNavBarEvent.NavigateToVaultScreen -> { + is VaultUnlockedNavBarEvent.Shortcut.NavigateToVaultScreen, + is VaultUnlockedNavBarEvent.NavigateToVaultScreen, + -> { navigateToVaultGraph(navOptions) } @@ -96,7 +98,9 @@ fun VaultUnlockedNavBarScreen( navigateToSendGraph(navOptions) } - VaultUnlockedNavBarEvent.NavigateToGeneratorScreen -> { + VaultUnlockedNavBarEvent.Shortcut.NavigateToGeneratorScreen, + VaultUnlockedNavBarEvent.NavigateToGeneratorScreen, + -> { navigateToGeneratorGraph(navOptions) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarViewModel.kt index fd452ae14..1e544170a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarViewModel.kt @@ -8,6 +8,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance import com.x8bit.bitwarden.ui.platform.base.BaseViewModel +import com.x8bit.bitwarden.ui.platform.base.util.BackgroundEvent import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.model.VaultUnlockedNavBarTab import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.launchIn @@ -38,13 +39,13 @@ class VaultUnlockedNavBarViewModel @Inject constructor( when (specialCircumstancesManager.specialCircumstance) { SpecialCircumstance.GeneratorShortcut -> { - sendEvent(VaultUnlockedNavBarEvent.NavigateToGeneratorScreen) + sendEvent(VaultUnlockedNavBarEvent.Shortcut.NavigateToGeneratorScreen) specialCircumstancesManager.specialCircumstance = null } SpecialCircumstance.VaultShortcut -> { sendEvent( - VaultUnlockedNavBarEvent.NavigateToVaultScreen( + VaultUnlockedNavBarEvent.Shortcut.NavigateToVaultScreen( labelRes = state.vaultNavBarLabelRes, contentDescRes = state.vaultNavBarContentDescriptionRes, ), @@ -218,4 +219,30 @@ sealed class VaultUnlockedNavBarEvent { data object NavigateToSettingsScreen : VaultUnlockedNavBarEvent() { override val tab: VaultUnlockedNavBarTab = VaultUnlockedNavBarTab.Settings } + + /** + * Shortcut events should to be considered [BackgroundEvent] as they are fired + * outside of normal lifecycle aware events and should not be ignored by filter. + */ + sealed class Shortcut : VaultUnlockedNavBarEvent(), BackgroundEvent { + /** + * Navigate to the Generator screen via a shortcut. + */ + data object NavigateToGeneratorScreen : Shortcut() { + override val tab: VaultUnlockedNavBarTab = VaultUnlockedNavBarTab.Generator + } + + /** + * Navigate to the Vault screen via a shortcut. + */ + data class NavigateToVaultScreen( + val labelRes: Int, + val contentDescRes: Int, + ) : Shortcut() { + override val tab: VaultUnlockedNavBarTab = VaultUnlockedNavBarTab.Vault( + labelRes = labelRes, + contentDescriptionRes = contentDescRes, + ) + } + } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt index 25366b823..9736a2243 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt @@ -225,15 +225,6 @@ class RootNavScreenTest : BaseComposeTest() { navOptions = expectedNavOptions, ) } - - // Make sure navigating to the generator shortcut works as expected: - rootNavStateFlow.value = RootNavState.GeneratorShortcut - composeTestRule - .runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "generator_modal/password_generator", - ) - } } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt index c27ea5ac4..873c9947d 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt @@ -906,97 +906,6 @@ class RootNavViewModelTest : BaseViewModelTest() { assertEquals(RootNavState.VaultLocked, viewModel.stateFlow.value) } - @Suppress("MaxLineLength") - @Test - fun `when there are no accounts but there is a GeneratorShortcut special circumstance the nav state should be Auth`() { - every { authRepository.hasPendingAccountAddition } returns false - - specialCircumstanceManager.specialCircumstance = - SpecialCircumstance.GeneratorShortcut - mutableUserStateFlow.tryEmit(null) - val viewModel = createViewModel() - assertEquals( - RootNavState.Auth, - viewModel.stateFlow.value, - ) - } - - @Suppress("MaxLineLength") - @Test - fun `when the active user has an unlocked vault and there is a GeneratorShortcut special circumstance the nav state should be GeneratorShortcut`() { - every { authRepository.hasPendingAccountAddition } returns true - - specialCircumstanceManager.specialCircumstance = - SpecialCircumstance.GeneratorShortcut - mutableUserStateFlow.tryEmit( - UserState( - activeUserId = "activeUserId", - accounts = listOf( - UserState.Account( - userId = "activeUserId", - name = "name", - email = "email", - avatarColorHex = "avatarHexColor", - environment = Environment.Us, - isPremium = true, - isLoggedIn = true, - isVaultUnlocked = true, - needsPasswordReset = false, - isBiometricsEnabled = false, - organizations = emptyList(), - needsMasterPassword = false, - trustedDevice = null, - hasMasterPassword = true, - isUsingKeyConnector = false, - ), - ), - ), - ) - val viewModel = createViewModel() - assertEquals( - RootNavState.GeneratorShortcut, - viewModel.stateFlow.value, - ) - } - - @Suppress("MaxLineLength") - @Test - fun `when the active user has a locked vault and there is a GeneratorShortcut special circumstance the nav state should be VaultLocked`() { - every { authRepository.hasPendingAccountAddition } returns true - - specialCircumstanceManager.specialCircumstance = - SpecialCircumstance.GeneratorShortcut - mutableUserStateFlow.tryEmit( - UserState( - activeUserId = "activeUserId", - accounts = listOf( - UserState.Account( - userId = "activeUserId", - name = "name", - email = "email", - avatarColorHex = "avatarColorHex", - environment = Environment.Us, - isPremium = true, - isLoggedIn = true, - isVaultUnlocked = false, - needsPasswordReset = false, - isBiometricsEnabled = false, - organizations = emptyList(), - needsMasterPassword = false, - trustedDevice = null, - hasMasterPassword = true, - isUsingKeyConnector = false, - ), - ), - ), - ) - val viewModel = createViewModel() - assertEquals( - RootNavState.VaultLocked, - viewModel.stateFlow.value, - ) - } - private fun createViewModel(): RootNavViewModel = RootNavViewModel( authRepository = authRepository, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreenTest.kt index 67f0f2ec3..a840a5432 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreenTest.kt @@ -81,6 +81,24 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() { } } + @Test + fun `NavigateToVaultScreen shortcut event should navigate to VaultScreen`() { + mutableEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToSendScreen) + composeTestRule.runOnIdle { fakeNavHostController.assertCurrentRoute("send_graph") } + mutableEventFlow.tryEmit( + VaultUnlockedNavBarEvent.Shortcut.NavigateToVaultScreen( + labelRes = R.string.my_vault, + contentDescRes = R.string.my_vault, + ), + ) + composeTestRule.runOnIdle { + fakeNavHostController.assertLastNavigation( + route = "vault_graph", + navOptions = expectedNavOptions, + ) + } + } + @Test fun `send tab click should send SendTabClick action`() { composeTestRule.onNodeWithText("Send").performClick() @@ -121,6 +139,20 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() { } } + @Test + fun `NavigateToGeneratorScreen shortcut event should navigate to GeneratorScreen`() { + composeTestRule.apply { + runOnIdle { fakeNavHostController.assertCurrentRoute("vault_graph") } + mutableEventFlow.tryEmit(VaultUnlockedNavBarEvent.Shortcut.NavigateToGeneratorScreen) + runOnIdle { + fakeNavHostController.assertLastNavigation( + route = "generator_graph", + navOptions = expectedNavOptions, + ) + } + } + } + @Test fun `settings tab click should send SendTabClick action`() { composeTestRule.onNodeWithText("Settings").performClick() diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarViewModelTest.kt index e9e248a4a..70958886a 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarViewModelTest.kt @@ -29,7 +29,7 @@ class VaultUnlockedNavBarViewModelTest : BaseViewModelTest() { @Suppress("MaxLineLength") @Test - fun `on init with GeneratorShortcut special circumstance should navigate to the generator screen`() = + fun `on init with GeneratorShortcut special circumstance should navigate to the generator screen with shortcut event`() = runTest { every { specialCircumstancesManager.specialCircumstance @@ -38,7 +38,7 @@ class VaultUnlockedNavBarViewModelTest : BaseViewModelTest() { val viewModel = createViewModel() viewModel.eventFlow.test { - assertEquals(VaultUnlockedNavBarEvent.NavigateToGeneratorScreen, awaitItem()) + assertEquals(VaultUnlockedNavBarEvent.Shortcut.NavigateToGeneratorScreen, awaitItem()) } verify(exactly = 1) { specialCircumstancesManager.specialCircumstance @@ -48,7 +48,7 @@ class VaultUnlockedNavBarViewModelTest : BaseViewModelTest() { @Suppress("MaxLineLength") @Test - fun `on init with VaultShortcut special circumstance should navigate to the generator screen`() = + fun `on init with VaultShortcut special circumstance should navigate to the vault screen with shortcut event`() = runTest { every { specialCircumstancesManager.specialCircumstance @@ -58,7 +58,7 @@ class VaultUnlockedNavBarViewModelTest : BaseViewModelTest() { viewModel.eventFlow.test { assertEquals( - VaultUnlockedNavBarEvent.NavigateToVaultScreen( + VaultUnlockedNavBarEvent.Shortcut.NavigateToVaultScreen( labelRes = R.string.my_vault, contentDescRes = R.string.my_vault, ),