[PM-11741] shortcut navigation pt2 electric boogaloo (#3904)

This commit is contained in:
Dave Severns 2024-09-12 13:02:01 -04:00 committed by GitHub
parent 537c501b9b
commit f544ccc3ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 72 additions and 125 deletions

View file

@ -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)
}
}
}
}

View file

@ -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()
}
/**

View file

@ -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)
}

View file

@ -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,
)
}
}
}

View file

@ -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",
)
}
}
}

View file

@ -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,

View file

@ -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()

View file

@ -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,
),