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 374a328ed0..322e52b04e 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,6 +47,8 @@ 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 @@ -113,6 +115,7 @@ fun RootNavScreen( is RootNavState.VaultUnlockedForFido2Save, is RootNavState.VaultUnlockedForFido2Assertion, is RootNavState.VaultUnlockedForFido2GetCredentials, + is RootNavState.GeneratorShortcut, -> VAULT_UNLOCKED_GRAPH_ROUTE } val currentRoute = navController.currentDestination?.rootLevelRoute() @@ -217,6 +220,11 @@ 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 8d2edc9bc5..19f548fb63 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,7 +131,9 @@ class RootNavViewModel @Inject constructor( ) } - SpecialCircumstance.GeneratorShortcut, + SpecialCircumstance.GeneratorShortcut -> { + RootNavState.GeneratorShortcut + } SpecialCircumstance.VaultShortcut, null, -> RootNavState.VaultUnlocked(activeUserId = userState.activeAccount.userId) @@ -320,6 +322,12 @@ 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/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 9736a2243e..25366b823a 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,6 +225,15 @@ 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 9081b17749..f7665ab78d 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 @@ -905,6 +905,97 @@ 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,