Skip to content

Commit 845a5de

Browse files
PM-27703: Update Authenticator navigation (#6109)
1 parent b1195b5 commit 845a5de

File tree

27 files changed

+243
-293
lines changed

27 files changed

+243
-293
lines changed

app/src/main/kotlin/com/x8bit/bitwarden/MainActivity.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import com.x8bit.bitwarden.ui.platform.composition.LocalManagerProvider
3636
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.debugMenuDestination
3737
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.manager.DebugMenuLaunchManager
3838
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.navigateToDebugMenuScreen
39-
import com.x8bit.bitwarden.ui.platform.feature.rootnav.ROOT_ROUTE
39+
import com.x8bit.bitwarden.ui.platform.feature.rootnav.RootNavigationRoute
4040
import com.x8bit.bitwarden.ui.platform.feature.rootnav.rootNavDestination
4141
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
4242
import com.x8bit.bitwarden.ui.platform.model.AuthTabLaunchers
@@ -119,11 +119,11 @@ class MainActivity : AppCompatActivity() {
119119
) {
120120
NavHost(
121121
navController = navController,
122-
startDestination = ROOT_ROUTE,
122+
startDestination = RootNavigationRoute,
123123
) {
124-
// Nothing else should end up at this top level, we just want the ability
125-
// to have the debug menu appear on top of the rest of the app without
126-
// interacting with the state-based navigation used by the RootNavScreen.
124+
// Both root navigation and debug menu exist at this top level.
125+
// The debug menu can appear on top of the rest of the app without
126+
// interacting with the state-based navigation used by RootNavScreen.
127127
rootNavDestination { shouldShowSplashScreen = false }
128128
debugMenuDestination(
129129
onNavigateBack = { navController.popBackStack() },
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1+
@file:OmitFromCoverage
2+
13
package com.x8bit.bitwarden.ui.platform.feature.rootnav
24

35
import androidx.navigation.NavGraphBuilder
46
import androidx.navigation.compose.composable
7+
import com.bitwarden.annotation.OmitFromCoverage
8+
import kotlinx.serialization.Serializable
59

610
/**
7-
* The route for the root navigation screen.
11+
* The type-safe route for the root navigation screen.
812
*/
9-
const val ROOT_ROUTE: String = "root"
13+
@Serializable
14+
data object RootNavigationRoute
1015

1116
/**
1217
* Add the root navigation screen to the nav graph.
1318
*/
1419
fun NavGraphBuilder.rootNavDestination(
1520
onSplashScreenRemoved: () -> Unit,
1621
) {
17-
composable(route = ROOT_ROUTE) {
22+
composable<RootNavigationRoute> {
1823
RootNavScreen(onSplashScreenRemoved = onSplashScreenRemoved)
1924
}
2025
}

app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt

Lines changed: 2 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,12 @@ import androidx.compose.ui.unit.dp
1111
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
1212
import androidx.lifecycle.compose.collectAsStateWithLifecycle
1313
import androidx.navigation.NavBackStackEntry
14-
import androidx.navigation.NavController
1514
import androidx.navigation.NavDestination.Companion.hierarchy
16-
import androidx.navigation.NavGraph.Companion.findStartDestination
1715
import androidx.navigation.NavHostController
18-
import androidx.navigation.NavOptions
1916
import androidx.navigation.compose.NavHost
2017
import androidx.navigation.compose.currentBackStackEntryAsState
21-
import androidx.navigation.navOptions
2218
import com.bitwarden.ui.platform.base.util.EventsEffect
19+
import com.bitwarden.ui.platform.base.util.navigateToTabOrRoot
2320
import com.bitwarden.ui.platform.components.navigation.model.NavigationItem
2421
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
2522
import com.bitwarden.ui.platform.components.scaffold.model.ScaffoldNavigationData
@@ -29,21 +26,17 @@ import com.x8bit.bitwarden.ui.platform.components.util.rememberBitwardenNavContr
2926
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
3027
import com.x8bit.bitwarden.ui.platform.feature.settings.about.navigateToAbout
3128
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.navigateToAutoFill
32-
import com.x8bit.bitwarden.ui.platform.feature.settings.navigateToSettingsGraph
3329
import com.x8bit.bitwarden.ui.platform.feature.settings.navigateToSettingsGraphRoot
3430
import com.x8bit.bitwarden.ui.platform.feature.settings.settingsGraph
3531
import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.model.VaultUnlockedNavBarTab
3632
import com.x8bit.bitwarden.ui.tools.feature.generator.generatorGraph
37-
import com.x8bit.bitwarden.ui.tools.feature.generator.navigateToGeneratorGraph
3833
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.AddEditSendRoute
39-
import com.x8bit.bitwarden.ui.tools.feature.send.navigateToSendGraph
4034
import com.x8bit.bitwarden.ui.tools.feature.send.sendGraph
4135
import com.x8bit.bitwarden.ui.tools.feature.send.viewsend.ViewSendRoute
4236
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
4337
import com.x8bit.bitwarden.ui.vault.feature.importitems.navigateToImportItemsScreen
4438
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemArgs
4539
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultGraphRoute
46-
import com.x8bit.bitwarden.ui.vault.feature.vault.navigateToVaultGraph
4740
import com.x8bit.bitwarden.ui.vault.feature.vault.vaultGraph
4841
import kotlinx.collections.immutable.persistentListOf
4942

@@ -81,41 +74,7 @@ fun VaultUnlockedNavBarScreen(
8174
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
8275

8376
EventsEffect(viewModel = viewModel) { event ->
84-
navController.apply {
85-
when (event) {
86-
is VaultUnlockedNavBarEvent.Shortcut.NavigateToVaultScreen,
87-
is VaultUnlockedNavBarEvent.NavigateToVaultScreen,
88-
-> {
89-
navigateToTabOrRoot(tabToNavigateTo = event.tab) {
90-
navigateToVaultGraph(navOptions = it)
91-
}
92-
}
93-
94-
VaultUnlockedNavBarEvent.Shortcut.NavigateToSendScreen,
95-
VaultUnlockedNavBarEvent.NavigateToSendScreen,
96-
-> {
97-
navigateToTabOrRoot(tabToNavigateTo = event.tab) {
98-
navigateToSendGraph(navOptions = it)
99-
}
100-
}
101-
102-
VaultUnlockedNavBarEvent.Shortcut.NavigateToGeneratorScreen,
103-
VaultUnlockedNavBarEvent.NavigateToGeneratorScreen,
104-
-> {
105-
navigateToTabOrRoot(tabToNavigateTo = event.tab) {
106-
navigateToGeneratorGraph(navOptions = it)
107-
}
108-
}
109-
110-
VaultUnlockedNavBarEvent.Shortcut.NavigateToSettingsScreen,
111-
VaultUnlockedNavBarEvent.NavigateToSettingsScreen,
112-
-> {
113-
navigateToTabOrRoot(tabToNavigateTo = event.tab) {
114-
navigateToSettingsGraph(navOptions = it)
115-
}
116-
}
117-
}
118-
}
77+
navController.navigateToTabOrRoot(target = event.tab)
11978
}
12079

12180
VaultUnlockedNavBarScaffold(
@@ -279,37 +238,6 @@ private fun VaultUnlockedNavBarScaffold(
279238
}
280239
}
281240

282-
/**
283-
* Helper function to determine how to navigate to a specified [VaultUnlockedNavBarTab].
284-
* If direct navigation is required, the [navigate] lambda will be invoked with the appropriate
285-
* [NavOptions].
286-
*/
287-
@Suppress("MaxLineLength")
288-
private fun NavController.navigateToTabOrRoot(
289-
tabToNavigateTo: VaultUnlockedNavBarTab,
290-
navigate: (NavOptions) -> Unit,
291-
) {
292-
if (tabToNavigateTo.startDestinationRoute.toObjectNavigationRoute() == currentDestination?.route) {
293-
// We are at the start destination already, so nothing to do.
294-
return
295-
} else if (currentDestination?.parent?.route == tabToNavigateTo.graphRoute.toObjectNavigationRoute()) {
296-
// We are not at the start destination but we are in the correct graph,
297-
// so lets pop up to the start destination.
298-
popBackStack(route = tabToNavigateTo.startDestinationRoute, inclusive = false)
299-
} else {
300-
// We are not in correct graph at all, so navigate there.
301-
navigate(
302-
navOptions {
303-
popUpTo(id = graph.findStartDestination().id) {
304-
saveState = true
305-
}
306-
launchSingleTop = true
307-
restoreState = true
308-
},
309-
)
310-
}
311-
}
312-
313241
/**
314242
* Determine if the current destination is the same as the given tab.
315243
*/
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
3-
<dimen name="dialogDimBackgroundAmount" format="float">0.75</dimen>
43
<color name="windowBackground">@android:color/transparent</color>
54
</resources>

app/src/main/res/values/values.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
3-
<dimen name="dialogDimBackgroundAmount" format="float">0.55</dimen>
43
<!-- default -->
54
<integer name="displayCutoutMode">0</integer>
65
<color name="windowBackground">@android:color/transparent</color>

authenticator/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,26 @@ import androidx.activity.compose.setContent
1010
import androidx.activity.viewModels
1111
import androidx.appcompat.app.AppCompatActivity
1212
import androidx.appcompat.app.AppCompatDelegate
13+
import androidx.compose.runtime.Composable
1314
import androidx.compose.runtime.getValue
1415
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
1516
import androidx.lifecycle.compose.collectAsStateWithLifecycle
16-
import androidx.lifecycle.lifecycleScope
1717
import androidx.navigation.NavHostController
18+
import androidx.navigation.compose.NavHost
1819
import androidx.navigation.compose.rememberNavController
1920
import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
2021
import com.bitwarden.authenticator.ui.platform.composition.LocalManagerProvider
22+
import com.bitwarden.authenticator.ui.platform.feature.debugmenu.debugMenuDestination
2123
import com.bitwarden.authenticator.ui.platform.feature.debugmenu.manager.DebugMenuLaunchManager
2224
import com.bitwarden.authenticator.ui.platform.feature.debugmenu.navigateToDebugMenuScreen
23-
import com.bitwarden.authenticator.ui.platform.feature.rootnav.RootNavScreen
25+
import com.bitwarden.authenticator.ui.platform.feature.rootnav.RootNavigationRoute
26+
import com.bitwarden.authenticator.ui.platform.feature.rootnav.rootNavDestination
27+
import com.bitwarden.ui.platform.base.util.EventsEffect
2428
import com.bitwarden.ui.platform.theme.BitwardenTheme
2529
import com.bitwarden.ui.platform.util.setupEdgeToEdge
2630
import com.bitwarden.ui.platform.util.validate
2731
import dagger.hilt.android.AndroidEntryPoint
28-
import kotlinx.coroutines.flow.launchIn
2932
import kotlinx.coroutines.flow.map
30-
import kotlinx.coroutines.flow.onEach
3133
import javax.inject.Inject
3234

3335
/**
@@ -61,20 +63,28 @@ class MainActivity : AppCompatActivity() {
6163
AppCompatDelegate.setDefaultNightMode(settingsRepository.appTheme.osValue)
6264
setupEdgeToEdge(appThemeFlow = mainViewModel.stateFlow.map { it.theme })
6365
setContent {
66+
val navController = rememberNavController()
67+
SetupEventsEffect(navController = navController)
6468
val state by mainViewModel.stateFlow.collectAsStateWithLifecycle()
6569
updateScreenCapture(isScreenCaptureAllowed = state.isScreenCaptureAllowed)
66-
val navController = rememberNavController()
67-
observeViewModelEvents(navController)
6870
LocalManagerProvider {
6971
BitwardenTheme(
7072
theme = state.theme,
7173
dynamicColor = state.isDynamicColorsEnabled,
7274
) {
73-
RootNavScreen(
75+
NavHost(
7476
navController = navController,
75-
onSplashScreenRemoved = { shouldShowSplashScreen = false },
76-
onExitApplication = { finishAffinity() },
77-
)
77+
startDestination = RootNavigationRoute,
78+
) {
79+
// Both root navigation and debug menu exist at this top level.
80+
// The debug menu can appear on top of the rest of the app without
81+
// interacting with the state-based navigation used by RootNavScreen.
82+
rootNavDestination { shouldShowSplashScreen = false }
83+
debugMenuDestination(
84+
onNavigateBack = { navController.popBackStack() },
85+
onSplashScreenRemoved = { shouldShowSplashScreen = false },
86+
)
87+
}
7888
}
7989
}
8090
}
@@ -92,18 +102,16 @@ class MainActivity : AppCompatActivity() {
92102
mainViewModel.trySendAction(MainAction.ReceiveNewIntent(intent = newIntent))
93103
}
94104

95-
private fun observeViewModelEvents(navController: NavHostController) {
96-
mainViewModel
97-
.eventFlow
98-
.onEach { event ->
99-
when (event) {
100-
MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen()
101-
is MainEvent.UpdateAppTheme -> {
102-
AppCompatDelegate.setDefaultNightMode(event.osTheme)
103-
}
105+
@Composable
106+
private fun SetupEventsEffect(navController: NavHostController) {
107+
EventsEffect(viewModel = mainViewModel) { event ->
108+
when (event) {
109+
MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen()
110+
is MainEvent.UpdateAppTheme -> {
111+
AppCompatDelegate.setDefaultNightMode(event.osTheme)
104112
}
105113
}
106-
.launchIn(lifecycleScope)
114+
}
107115
}
108116

109117
override fun dispatchTouchEvent(event: MotionEvent): Boolean = debugLaunchManager
Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1+
@file:OmitFromCoverage
2+
13
package com.bitwarden.authenticator.ui.authenticator.feature.authenticator
24

35
import androidx.navigation.NavController
46
import androidx.navigation.NavGraphBuilder
57
import androidx.navigation.NavOptions
68
import androidx.navigation.navigation
9+
import com.bitwarden.annotation.OmitFromCoverage
10+
import com.bitwarden.authenticator.ui.authenticator.feature.edititem.editItemDestination
711
import com.bitwarden.authenticator.ui.authenticator.feature.edititem.navigateToEditItem
8-
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.itemListingGraph
12+
import com.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry.manualCodeEntryDestination
913
import com.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry.navigateToManualCodeEntryScreen
1014
import com.bitwarden.authenticator.ui.authenticator.feature.navbar.AuthenticatorNavbarRoute
1115
import com.bitwarden.authenticator.ui.authenticator.feature.navbar.authenticatorNavBarDestination
1216
import com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.navigateToQrCodeScanScreen
17+
import com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.qrCodeScanDestination
18+
import com.bitwarden.authenticator.ui.authenticator.feature.search.itemSearchDestination
1319
import com.bitwarden.authenticator.ui.authenticator.feature.search.navigateToSearch
14-
import com.bitwarden.authenticator.ui.platform.feature.settings.export.navigateToExport
15-
import com.bitwarden.authenticator.ui.platform.feature.settings.importing.navigateToImporting
1620
import com.bitwarden.authenticator.ui.platform.feature.tutorial.navigateToSettingsTutorial
21+
import com.bitwarden.authenticator.ui.platform.feature.tutorial.tutorialSettingsDestination
1722
import kotlinx.serialization.Serializable
1823

1924
/**
@@ -34,39 +39,41 @@ fun NavController.navigateToAuthenticatorGraph(navOptions: NavOptions? = null) {
3439
*/
3540
fun NavGraphBuilder.authenticatorGraph(
3641
navController: NavController,
37-
onNavigateBack: () -> Unit,
3842
) {
3943
navigation<AuthenticatorGraphRoute>(
4044
startDestination = AuthenticatorNavbarRoute,
4145
) {
4246
authenticatorNavBarDestination(
43-
onNavigateBack = onNavigateBack,
47+
onNavigateBack = { navController.popBackStack() },
4448
onNavigateToSearch = { navController.navigateToSearch() },
4549
onNavigateToQrCodeScanner = { navController.navigateToQrCodeScanScreen() },
4650
onNavigateToManualKeyEntry = { navController.navigateToManualCodeEntryScreen() },
4751
onNavigateToEditItem = { navController.navigateToEditItem(itemId = it) },
48-
onNavigateToExport = { navController.navigateToExport() },
49-
onNavigateToImport = { navController.navigateToImporting() },
5052
onNavigateToTutorial = { navController.navigateToSettingsTutorial() },
5153
)
52-
itemListingGraph(
53-
navController = navController,
54-
navigateBack = onNavigateBack,
55-
navigateToSearch = {
56-
navController.navigateToSearch()
57-
},
58-
navigateToQrCodeScanner = {
59-
navController.navigateToQrCodeScanScreen()
60-
},
61-
navigateToManualKeyEntry = {
54+
editItemDestination(
55+
onNavigateBack = { navController.popBackStack() },
56+
)
57+
itemSearchDestination(
58+
onNavigateBack = { navController.popBackStack() },
59+
onNavigateToEdit = { navController.navigateToEditItem(itemId = it) },
60+
)
61+
qrCodeScanDestination(
62+
onNavigateBack = { navController.popBackStack() },
63+
onNavigateToManualCodeEntryScreen = {
64+
navController.popBackStack()
6265
navController.navigateToManualCodeEntryScreen()
6366
},
64-
navigateToEditItem = {
65-
navController.navigateToEditItem(itemId = it)
67+
)
68+
manualCodeEntryDestination(
69+
onNavigateBack = { navController.popBackStack() },
70+
onNavigateToQrCodeScreen = {
71+
navController.popBackStack()
72+
navController.navigateToQrCodeScanScreen()
6673
},
67-
navigateToExport = { navController.navigateToExport() },
68-
navigateToImport = { navController.navigateToImporting() },
69-
navigateToTutorial = { navController.navigateToSettingsTutorial() },
74+
)
75+
tutorialSettingsDestination(
76+
onTutorialFinished = { navController.popBackStack() },
7077
)
7178
}
7279
}

authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemNavigation.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import androidx.navigation.NavController
55
import androidx.navigation.NavGraphBuilder
66
import androidx.navigation.NavOptions
77
import androidx.navigation.toRoute
8-
import com.bitwarden.ui.platform.base.util.composableWithPushTransitions
8+
import com.bitwarden.ui.platform.base.util.composableWithSlideTransitions
99
import kotlinx.serialization.Serializable
1010

1111
/**
@@ -37,7 +37,7 @@ fun SavedStateHandle.toEditItemArgs(): EditItemArgs {
3737
fun NavGraphBuilder.editItemDestination(
3838
onNavigateBack: () -> Unit = { },
3939
) {
40-
composableWithPushTransitions<EditItemRoute> {
40+
composableWithSlideTransitions<EditItemRoute> {
4141
EditItemScreen(
4242
onNavigateBack = onNavigateBack,
4343
)

0 commit comments

Comments
 (0)