mirror of
https://github.com/bitwarden/android.git
synced 2026-03-12 05:04:17 -05:00
PM-20422: Update tab navigation (#5076)
This commit is contained in:
@@ -25,9 +25,14 @@ interface NavigationItem {
|
||||
val contentDescriptionRes: Int
|
||||
|
||||
/**
|
||||
* Route of the tab.
|
||||
* Route of the tab's graph.
|
||||
*/
|
||||
val route: String
|
||||
val graphRoute: String
|
||||
|
||||
/**
|
||||
* Route of the tab's start destination.
|
||||
*/
|
||||
val startDestinationRoute: String
|
||||
|
||||
/**
|
||||
* The test tag of the tab.
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.vault.vaultSettingsDesti
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
|
||||
const val SETTINGS_GRAPH_ROUTE: String = "settings_graph"
|
||||
private const val SETTINGS_ROUTE: String = "settings"
|
||||
const val SETTINGS_ROUTE: String = "settings"
|
||||
|
||||
/**
|
||||
* Add settings destinations to the nav graph.
|
||||
|
||||
@@ -13,7 +13,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.NavGraph
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavOptions
|
||||
@@ -73,32 +72,37 @@ fun VaultUnlockedNavBarScreen(
|
||||
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
navController.apply {
|
||||
val navOptions = vaultUnlockedNavBarScreenNavOptions(tabToNavigateTo = event.tab)
|
||||
when (event) {
|
||||
is VaultUnlockedNavBarEvent.Shortcut.NavigateToVaultScreen,
|
||||
is VaultUnlockedNavBarEvent.NavigateToVaultScreen,
|
||||
-> {
|
||||
navigateToVaultGraph(navOptions)
|
||||
navigateToTabOrRoot(tabToNavigateTo = event.tab) {
|
||||
navigateToVaultGraph(navOptions = it)
|
||||
}
|
||||
}
|
||||
|
||||
VaultUnlockedNavBarEvent.Shortcut.NavigateToSendScreen,
|
||||
VaultUnlockedNavBarEvent.NavigateToSendScreen,
|
||||
-> {
|
||||
navigateToSendGraph(navOptions)
|
||||
navigateToTabOrRoot(tabToNavigateTo = event.tab) {
|
||||
navigateToSendGraph(navOptions = it)
|
||||
}
|
||||
}
|
||||
|
||||
VaultUnlockedNavBarEvent.Shortcut.NavigateToGeneratorScreen,
|
||||
VaultUnlockedNavBarEvent.NavigateToGeneratorScreen,
|
||||
-> {
|
||||
navigateToGeneratorGraph(navOptions)
|
||||
navigateToTabOrRoot(tabToNavigateTo = event.tab) {
|
||||
navigateToGeneratorGraph(navOptions = it)
|
||||
}
|
||||
}
|
||||
|
||||
VaultUnlockedNavBarEvent.NavigateToSettingsScreen -> {
|
||||
navigateToSettingsGraph(navOptions)
|
||||
}
|
||||
|
||||
VaultUnlockedNavBarEvent.Shortcut.NavigateToSettingsScreen -> {
|
||||
navigateToSettingsGraph(navOptions)
|
||||
VaultUnlockedNavBarEvent.Shortcut.NavigateToSettingsScreen,
|
||||
VaultUnlockedNavBarEvent.NavigateToSettingsScreen,
|
||||
-> {
|
||||
navigateToTabOrRoot(tabToNavigateTo = event.tab) {
|
||||
navigateToSettingsGraph(navOptions = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,7 +194,7 @@ private fun VaultUnlockedNavBarScaffold(
|
||||
navigationData = ScaffoldNavigationData(
|
||||
navigationItems = navigationItems,
|
||||
selectedNavigationItem = navigationItems.find {
|
||||
navBackStackEntry.isCurrentRoute(route = it.route)
|
||||
navBackStackEntry.isCurrentRoute(route = it.graphRoute)
|
||||
},
|
||||
onNavigationClick = { navigationItem ->
|
||||
when (navigationItem) {
|
||||
@@ -251,34 +255,32 @@ private fun VaultUnlockedNavBarScaffold(
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to generate [NavOptions] for [VaultUnlockedNavBarScreen].
|
||||
*
|
||||
* @param tabToNavigateTo The [VaultUnlockedNavBarTab] to prepare the NavOptions for.
|
||||
* NavOptions are determined on whether or not the tab is already selected.
|
||||
* Helper function to determine how to navigate to a specified [VaultUnlockedNavBarTab].
|
||||
* If direct navigation is required, the [navigate] lambda will be invoked with the appropriate
|
||||
* [NavOptions].
|
||||
*/
|
||||
private fun NavController.vaultUnlockedNavBarScreenNavOptions(
|
||||
private fun NavController.navigateToTabOrRoot(
|
||||
tabToNavigateTo: VaultUnlockedNavBarTab,
|
||||
): NavOptions {
|
||||
val returnToCurrentSubRoot = currentBackStackEntry.isCurrentRoute(tabToNavigateTo.route)
|
||||
val currentSubRootGraph = currentDestination?.parent?.id
|
||||
// determine the destination to navigate to, if we are navigating to the same sub-root for the
|
||||
// selected tab we want to find the start destination of the sub-root and pop up to it, which
|
||||
// will maintain its state (i.e. scroll position). If we are navigating to a different sub-root,
|
||||
// we can safely pop up to the start of the graph, the "home" tab destination.
|
||||
val popUpToDestination = graph
|
||||
.getSubgraphStartDestinationOrNull(currentSubRootGraph)
|
||||
.takeIf { returnToCurrentSubRoot }
|
||||
?: graph.findStartDestination().id
|
||||
// If we are popping up the start of the whole nav graph we want to maintain the state of the
|
||||
// the popped destinations in the other sub-roots. If we are navigating to the same sub-root,
|
||||
// we want to pop off the nested destinations without maintaining their state.
|
||||
val maintainStateOfPoppedDestinations = !returnToCurrentSubRoot
|
||||
return navOptions {
|
||||
popUpTo(popUpToDestination) {
|
||||
saveState = maintainStateOfPoppedDestinations
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = maintainStateOfPoppedDestinations
|
||||
navigate: (NavOptions) -> Unit,
|
||||
) {
|
||||
if (tabToNavigateTo.startDestinationRoute == currentDestination?.route) {
|
||||
// We are at the start destination already, so nothing to do.
|
||||
return
|
||||
} else if (currentDestination?.parent?.route == tabToNavigateTo.graphRoute) {
|
||||
// We are not at the start destination but we are in the correct graph,
|
||||
// so lets pop up to the start destination.
|
||||
popBackStack(route = tabToNavigateTo.startDestinationRoute, inclusive = false)
|
||||
} else {
|
||||
// We are not in correct graph at all, so navigate there.
|
||||
navigate(
|
||||
navOptions {
|
||||
popUpTo(id = graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,17 +292,3 @@ private fun NavBackStackEntry?.isCurrentRoute(route: String): Boolean =
|
||||
?.destination
|
||||
?.hierarchy
|
||||
?.any { it.route == route } == true
|
||||
|
||||
/**
|
||||
* Helper function to determine the start destination of a subgraph.
|
||||
*
|
||||
* @param subgraphId the id of the subgraph to find the start destination of.
|
||||
*
|
||||
* @return the ID of the start destination of the subgraph, or null if the subgraph does not exist.
|
||||
*/
|
||||
private fun NavGraph.getSubgraphStartDestinationOrNull(subgraphId: Int?): Int? {
|
||||
subgraphId ?: return null
|
||||
return nodes[subgraphId]?.let {
|
||||
(it as? NavGraph)?.findStartDestination()?.id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,13 @@ import android.os.Parcelable
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.NavigationItem
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.SETTINGS_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.SETTINGS_ROUTE
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GENERATOR_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GENERATOR_ROUTE
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.SEND_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.SEND_ROUTE
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VAULT_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VAULT_ROUTE
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
@@ -29,7 +33,8 @@ sealed class VaultUnlockedNavBarTab : NavigationItem, Parcelable {
|
||||
override val iconRes get() = R.drawable.ic_generator
|
||||
override val labelRes get() = R.string.generator
|
||||
override val contentDescriptionRes get() = R.string.generator
|
||||
override val route get() = GENERATOR_GRAPH_ROUTE
|
||||
override val graphRoute: String get() = GENERATOR_GRAPH_ROUTE
|
||||
override val startDestinationRoute get() = GENERATOR_ROUTE
|
||||
override val testTag get() = "GeneratorTab"
|
||||
override val notificationCount get() = 0
|
||||
}
|
||||
@@ -43,7 +48,8 @@ sealed class VaultUnlockedNavBarTab : NavigationItem, Parcelable {
|
||||
override val iconRes get() = R.drawable.ic_send
|
||||
override val labelRes get() = R.string.send
|
||||
override val contentDescriptionRes get() = R.string.send
|
||||
override val route get() = SEND_GRAPH_ROUTE
|
||||
override val graphRoute: String get() = SEND_GRAPH_ROUTE
|
||||
override val startDestinationRoute get() = SEND_ROUTE
|
||||
override val testTag get() = "SendTab"
|
||||
override val notificationCount get() = 0
|
||||
}
|
||||
@@ -58,7 +64,8 @@ sealed class VaultUnlockedNavBarTab : NavigationItem, Parcelable {
|
||||
) : VaultUnlockedNavBarTab() {
|
||||
override val iconResSelected get() = R.drawable.ic_vault_filled
|
||||
override val iconRes get() = R.drawable.ic_vault
|
||||
override val route get() = VAULT_GRAPH_ROUTE
|
||||
override val graphRoute: String get() = VAULT_GRAPH_ROUTE
|
||||
override val startDestinationRoute get() = VAULT_ROUTE
|
||||
override val testTag get() = "VaultTab"
|
||||
override val notificationCount get() = 0
|
||||
}
|
||||
@@ -74,7 +81,8 @@ sealed class VaultUnlockedNavBarTab : NavigationItem, Parcelable {
|
||||
override val iconRes get() = R.drawable.ic_settings
|
||||
override val labelRes get() = R.string.settings
|
||||
override val contentDescriptionRes get() = R.string.settings
|
||||
override val route get() = SETTINGS_GRAPH_ROUTE
|
||||
override val graphRoute: String get() = SETTINGS_GRAPH_ROUTE
|
||||
override val startDestinationRoute get() = SETTINGS_ROUTE
|
||||
override val testTag get() = "SettingsTab"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user