PM-27703: Update Authenticator navigation (#6109)

This commit is contained in:
David Perez 2025-11-03 10:28:23 -06:00 committed by GitHub
parent b1195b5f46
commit 845a5dec22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 244 additions and 294 deletions

View File

@ -36,7 +36,7 @@ import com.x8bit.bitwarden.ui.platform.composition.LocalManagerProvider
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.debugMenuDestination import com.x8bit.bitwarden.ui.platform.feature.debugmenu.debugMenuDestination
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.manager.DebugMenuLaunchManager import com.x8bit.bitwarden.ui.platform.feature.debugmenu.manager.DebugMenuLaunchManager
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.navigateToDebugMenuScreen import com.x8bit.bitwarden.ui.platform.feature.debugmenu.navigateToDebugMenuScreen
import com.x8bit.bitwarden.ui.platform.feature.rootnav.ROOT_ROUTE import com.x8bit.bitwarden.ui.platform.feature.rootnav.RootNavigationRoute
import com.x8bit.bitwarden.ui.platform.feature.rootnav.rootNavDestination import com.x8bit.bitwarden.ui.platform.feature.rootnav.rootNavDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
import com.x8bit.bitwarden.ui.platform.model.AuthTabLaunchers import com.x8bit.bitwarden.ui.platform.model.AuthTabLaunchers
@ -119,11 +119,11 @@ class MainActivity : AppCompatActivity() {
) { ) {
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = ROOT_ROUTE, startDestination = RootNavigationRoute,
) { ) {
// Nothing else should end up at this top level, we just want the ability // Both root navigation and debug menu exist at this top level.
// to have the debug menu appear on top of the rest of the app without // The debug menu can appear on top of the rest of the app without
// interacting with the state-based navigation used by the RootNavScreen. // interacting with the state-based navigation used by RootNavScreen.
rootNavDestination { shouldShowSplashScreen = false } rootNavDestination { shouldShowSplashScreen = false }
debugMenuDestination( debugMenuDestination(
onNavigateBack = { navController.popBackStack() }, onNavigateBack = { navController.popBackStack() },

View File

@ -1,12 +1,17 @@
@file:OmitFromCoverage
package com.x8bit.bitwarden.ui.platform.feature.rootnav package com.x8bit.bitwarden.ui.platform.feature.rootnav
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import com.bitwarden.annotation.OmitFromCoverage
import kotlinx.serialization.Serializable
/** /**
* The route for the root navigation screen. * The type-safe route for the root navigation screen.
*/ */
const val ROOT_ROUTE: String = "root" @Serializable
data object RootNavigationRoute
/** /**
* Add the root navigation screen to the nav graph. * Add the root navigation screen to the nav graph.
@ -14,7 +19,7 @@ const val ROOT_ROUTE: String = "root"
fun NavGraphBuilder.rootNavDestination( fun NavGraphBuilder.rootNavDestination(
onSplashScreenRemoved: () -> Unit, onSplashScreenRemoved: () -> Unit,
) { ) {
composable(route = ROOT_ROUTE) { composable<RootNavigationRoute> {
RootNavScreen(onSplashScreenRemoved = onSplashScreenRemoved) RootNavScreen(onSplashScreenRemoved = onSplashScreenRemoved)
} }
} }

View File

@ -11,15 +11,12 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavBackStackEntry import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavOptions
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.navOptions
import com.bitwarden.ui.platform.base.util.EventsEffect import com.bitwarden.ui.platform.base.util.EventsEffect
import com.bitwarden.ui.platform.base.util.navigateToTabOrRoot
import com.bitwarden.ui.platform.components.navigation.model.NavigationItem import com.bitwarden.ui.platform.components.navigation.model.NavigationItem
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.bitwarden.ui.platform.components.scaffold.model.ScaffoldNavigationData import com.bitwarden.ui.platform.components.scaffold.model.ScaffoldNavigationData
@ -29,21 +26,17 @@ import com.x8bit.bitwarden.ui.platform.components.util.rememberBitwardenNavContr
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.platform.feature.settings.about.navigateToAbout import com.x8bit.bitwarden.ui.platform.feature.settings.about.navigateToAbout
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.navigateToAutoFill import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.navigateToAutoFill
import com.x8bit.bitwarden.ui.platform.feature.settings.navigateToSettingsGraph
import com.x8bit.bitwarden.ui.platform.feature.settings.navigateToSettingsGraphRoot import com.x8bit.bitwarden.ui.platform.feature.settings.navigateToSettingsGraphRoot
import com.x8bit.bitwarden.ui.platform.feature.settings.settingsGraph import com.x8bit.bitwarden.ui.platform.feature.settings.settingsGraph
import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.model.VaultUnlockedNavBarTab import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.model.VaultUnlockedNavBarTab
import com.x8bit.bitwarden.ui.tools.feature.generator.generatorGraph import com.x8bit.bitwarden.ui.tools.feature.generator.generatorGraph
import com.x8bit.bitwarden.ui.tools.feature.generator.navigateToGeneratorGraph
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.AddEditSendRoute import com.x8bit.bitwarden.ui.tools.feature.send.addedit.AddEditSendRoute
import com.x8bit.bitwarden.ui.tools.feature.send.navigateToSendGraph
import com.x8bit.bitwarden.ui.tools.feature.send.sendGraph import com.x8bit.bitwarden.ui.tools.feature.send.sendGraph
import com.x8bit.bitwarden.ui.tools.feature.send.viewsend.ViewSendRoute import com.x8bit.bitwarden.ui.tools.feature.send.viewsend.ViewSendRoute
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
import com.x8bit.bitwarden.ui.vault.feature.importitems.navigateToImportItemsScreen import com.x8bit.bitwarden.ui.vault.feature.importitems.navigateToImportItemsScreen
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemArgs import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemArgs
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultGraphRoute import com.x8bit.bitwarden.ui.vault.feature.vault.VaultGraphRoute
import com.x8bit.bitwarden.ui.vault.feature.vault.navigateToVaultGraph
import com.x8bit.bitwarden.ui.vault.feature.vault.vaultGraph import com.x8bit.bitwarden.ui.vault.feature.vault.vaultGraph
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -81,41 +74,7 @@ fun VaultUnlockedNavBarScreen(
val state by viewModel.stateFlow.collectAsStateWithLifecycle() val state by viewModel.stateFlow.collectAsStateWithLifecycle()
EventsEffect(viewModel = viewModel) { event -> EventsEffect(viewModel = viewModel) { event ->
navController.apply { navController.navigateToTabOrRoot(target = event.tab)
when (event) {
is VaultUnlockedNavBarEvent.Shortcut.NavigateToVaultScreen,
is VaultUnlockedNavBarEvent.NavigateToVaultScreen,
-> {
navigateToTabOrRoot(tabToNavigateTo = event.tab) {
navigateToVaultGraph(navOptions = it)
}
}
VaultUnlockedNavBarEvent.Shortcut.NavigateToSendScreen,
VaultUnlockedNavBarEvent.NavigateToSendScreen,
-> {
navigateToTabOrRoot(tabToNavigateTo = event.tab) {
navigateToSendGraph(navOptions = it)
}
}
VaultUnlockedNavBarEvent.Shortcut.NavigateToGeneratorScreen,
VaultUnlockedNavBarEvent.NavigateToGeneratorScreen,
-> {
navigateToTabOrRoot(tabToNavigateTo = event.tab) {
navigateToGeneratorGraph(navOptions = it)
}
}
VaultUnlockedNavBarEvent.Shortcut.NavigateToSettingsScreen,
VaultUnlockedNavBarEvent.NavigateToSettingsScreen,
-> {
navigateToTabOrRoot(tabToNavigateTo = event.tab) {
navigateToSettingsGraph(navOptions = it)
}
}
}
}
} }
VaultUnlockedNavBarScaffold( VaultUnlockedNavBarScaffold(
@ -279,37 +238,6 @@ private fun VaultUnlockedNavBarScaffold(
} }
} }
/**
* 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].
*/
@Suppress("MaxLineLength")
private fun NavController.navigateToTabOrRoot(
tabToNavigateTo: VaultUnlockedNavBarTab,
navigate: (NavOptions) -> Unit,
) {
if (tabToNavigateTo.startDestinationRoute.toObjectNavigationRoute() == currentDestination?.route) {
// We are at the start destination already, so nothing to do.
return
} else if (currentDestination?.parent?.route == tabToNavigateTo.graphRoute.toObjectNavigationRoute()) {
// 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
},
)
}
}
/** /**
* Determine if the current destination is the same as the given tab. * Determine if the current destination is the same as the given tab.
*/ */

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<dimen name="dialogDimBackgroundAmount" format="float">0.75</dimen>
<color name="windowBackground">@android:color/transparent</color> <color name="windowBackground">@android:color/transparent</color>
</resources> </resources>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<dimen name="dialogDimBackgroundAmount" format="float">0.55</dimen>
<!-- default --> <!-- default -->
<integer name="displayCutoutMode">0</integer> <integer name="displayCutoutMode">0</integer>
<color name="windowBackground">@android:color/transparent</color> <color name="windowBackground">@android:color/transparent</color>

View File

@ -10,24 +10,26 @@ import androidx.activity.compose.setContent
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.bitwarden.authenticator.data.platform.repository.SettingsRepository import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
import com.bitwarden.authenticator.ui.platform.composition.LocalManagerProvider import com.bitwarden.authenticator.ui.platform.composition.LocalManagerProvider
import com.bitwarden.authenticator.ui.platform.feature.debugmenu.debugMenuDestination
import com.bitwarden.authenticator.ui.platform.feature.debugmenu.manager.DebugMenuLaunchManager import com.bitwarden.authenticator.ui.platform.feature.debugmenu.manager.DebugMenuLaunchManager
import com.bitwarden.authenticator.ui.platform.feature.debugmenu.navigateToDebugMenuScreen import com.bitwarden.authenticator.ui.platform.feature.debugmenu.navigateToDebugMenuScreen
import com.bitwarden.authenticator.ui.platform.feature.rootnav.RootNavScreen import com.bitwarden.authenticator.ui.platform.feature.rootnav.RootNavigationRoute
import com.bitwarden.authenticator.ui.platform.feature.rootnav.rootNavDestination
import com.bitwarden.ui.platform.base.util.EventsEffect
import com.bitwarden.ui.platform.theme.BitwardenTheme import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.bitwarden.ui.platform.util.setupEdgeToEdge import com.bitwarden.ui.platform.util.setupEdgeToEdge
import com.bitwarden.ui.platform.util.validate import com.bitwarden.ui.platform.util.validate
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -61,20 +63,28 @@ class MainActivity : AppCompatActivity() {
AppCompatDelegate.setDefaultNightMode(settingsRepository.appTheme.osValue) AppCompatDelegate.setDefaultNightMode(settingsRepository.appTheme.osValue)
setupEdgeToEdge(appThemeFlow = mainViewModel.stateFlow.map { it.theme }) setupEdgeToEdge(appThemeFlow = mainViewModel.stateFlow.map { it.theme })
setContent { setContent {
val navController = rememberNavController()
SetupEventsEffect(navController = navController)
val state by mainViewModel.stateFlow.collectAsStateWithLifecycle() val state by mainViewModel.stateFlow.collectAsStateWithLifecycle()
updateScreenCapture(isScreenCaptureAllowed = state.isScreenCaptureAllowed) updateScreenCapture(isScreenCaptureAllowed = state.isScreenCaptureAllowed)
val navController = rememberNavController()
observeViewModelEvents(navController)
LocalManagerProvider { LocalManagerProvider {
BitwardenTheme( BitwardenTheme(
theme = state.theme, theme = state.theme,
dynamicColor = state.isDynamicColorsEnabled, dynamicColor = state.isDynamicColorsEnabled,
) { ) {
RootNavScreen( NavHost(
navController = navController, navController = navController,
onSplashScreenRemoved = { shouldShowSplashScreen = false }, startDestination = RootNavigationRoute,
onExitApplication = { finishAffinity() }, ) {
) // Both root navigation and debug menu exist at this top level.
// The debug menu can appear on top of the rest of the app without
// interacting with the state-based navigation used by RootNavScreen.
rootNavDestination { shouldShowSplashScreen = false }
debugMenuDestination(
onNavigateBack = { navController.popBackStack() },
onSplashScreenRemoved = { shouldShowSplashScreen = false },
)
}
} }
} }
} }
@ -92,18 +102,16 @@ class MainActivity : AppCompatActivity() {
mainViewModel.trySendAction(MainAction.ReceiveNewIntent(intent = newIntent)) mainViewModel.trySendAction(MainAction.ReceiveNewIntent(intent = newIntent))
} }
private fun observeViewModelEvents(navController: NavHostController) { @Composable
mainViewModel private fun SetupEventsEffect(navController: NavHostController) {
.eventFlow EventsEffect(viewModel = mainViewModel) { event ->
.onEach { event -> when (event) {
when (event) { MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen()
MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen() is MainEvent.UpdateAppTheme -> {
is MainEvent.UpdateAppTheme -> { AppCompatDelegate.setDefaultNightMode(event.osTheme)
AppCompatDelegate.setDefaultNightMode(event.osTheme)
}
} }
} }
.launchIn(lifecycleScope) }
} }
override fun dispatchTouchEvent(event: MotionEvent): Boolean = debugLaunchManager override fun dispatchTouchEvent(event: MotionEvent): Boolean = debugLaunchManager

View File

@ -1,19 +1,24 @@
@file:OmitFromCoverage
package com.bitwarden.authenticator.ui.authenticator.feature.authenticator package com.bitwarden.authenticator.ui.authenticator.feature.authenticator
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.navigation import androidx.navigation.navigation
import com.bitwarden.annotation.OmitFromCoverage
import com.bitwarden.authenticator.ui.authenticator.feature.edititem.editItemDestination
import com.bitwarden.authenticator.ui.authenticator.feature.edititem.navigateToEditItem import com.bitwarden.authenticator.ui.authenticator.feature.edititem.navigateToEditItem
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.itemListingGraph import com.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry.manualCodeEntryDestination
import com.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry.navigateToManualCodeEntryScreen import com.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry.navigateToManualCodeEntryScreen
import com.bitwarden.authenticator.ui.authenticator.feature.navbar.AuthenticatorNavbarRoute import com.bitwarden.authenticator.ui.authenticator.feature.navbar.AuthenticatorNavbarRoute
import com.bitwarden.authenticator.ui.authenticator.feature.navbar.authenticatorNavBarDestination import com.bitwarden.authenticator.ui.authenticator.feature.navbar.authenticatorNavBarDestination
import com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.navigateToQrCodeScanScreen import com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.navigateToQrCodeScanScreen
import com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.qrCodeScanDestination
import com.bitwarden.authenticator.ui.authenticator.feature.search.itemSearchDestination
import com.bitwarden.authenticator.ui.authenticator.feature.search.navigateToSearch import com.bitwarden.authenticator.ui.authenticator.feature.search.navigateToSearch
import com.bitwarden.authenticator.ui.platform.feature.settings.export.navigateToExport
import com.bitwarden.authenticator.ui.platform.feature.settings.importing.navigateToImporting
import com.bitwarden.authenticator.ui.platform.feature.tutorial.navigateToSettingsTutorial import com.bitwarden.authenticator.ui.platform.feature.tutorial.navigateToSettingsTutorial
import com.bitwarden.authenticator.ui.platform.feature.tutorial.tutorialSettingsDestination
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
@ -34,39 +39,41 @@ fun NavController.navigateToAuthenticatorGraph(navOptions: NavOptions? = null) {
*/ */
fun NavGraphBuilder.authenticatorGraph( fun NavGraphBuilder.authenticatorGraph(
navController: NavController, navController: NavController,
onNavigateBack: () -> Unit,
) { ) {
navigation<AuthenticatorGraphRoute>( navigation<AuthenticatorGraphRoute>(
startDestination = AuthenticatorNavbarRoute, startDestination = AuthenticatorNavbarRoute,
) { ) {
authenticatorNavBarDestination( authenticatorNavBarDestination(
onNavigateBack = onNavigateBack, onNavigateBack = { navController.popBackStack() },
onNavigateToSearch = { navController.navigateToSearch() }, onNavigateToSearch = { navController.navigateToSearch() },
onNavigateToQrCodeScanner = { navController.navigateToQrCodeScanScreen() }, onNavigateToQrCodeScanner = { navController.navigateToQrCodeScanScreen() },
onNavigateToManualKeyEntry = { navController.navigateToManualCodeEntryScreen() }, onNavigateToManualKeyEntry = { navController.navigateToManualCodeEntryScreen() },
onNavigateToEditItem = { navController.navigateToEditItem(itemId = it) }, onNavigateToEditItem = { navController.navigateToEditItem(itemId = it) },
onNavigateToExport = { navController.navigateToExport() },
onNavigateToImport = { navController.navigateToImporting() },
onNavigateToTutorial = { navController.navigateToSettingsTutorial() }, onNavigateToTutorial = { navController.navigateToSettingsTutorial() },
) )
itemListingGraph( editItemDestination(
navController = navController, onNavigateBack = { navController.popBackStack() },
navigateBack = onNavigateBack, )
navigateToSearch = { itemSearchDestination(
navController.navigateToSearch() onNavigateBack = { navController.popBackStack() },
}, onNavigateToEdit = { navController.navigateToEditItem(itemId = it) },
navigateToQrCodeScanner = { )
navController.navigateToQrCodeScanScreen() qrCodeScanDestination(
}, onNavigateBack = { navController.popBackStack() },
navigateToManualKeyEntry = { onNavigateToManualCodeEntryScreen = {
navController.popBackStack()
navController.navigateToManualCodeEntryScreen() navController.navigateToManualCodeEntryScreen()
}, },
navigateToEditItem = { )
navController.navigateToEditItem(itemId = it) manualCodeEntryDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToQrCodeScreen = {
navController.popBackStack()
navController.navigateToQrCodeScanScreen()
}, },
navigateToExport = { navController.navigateToExport() }, )
navigateToImport = { navController.navigateToImporting() }, tutorialSettingsDestination(
navigateToTutorial = { navController.navigateToSettingsTutorial() }, onTutorialFinished = { navController.popBackStack() },
) )
} }
} }

View File

@ -5,7 +5,7 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.toRoute import androidx.navigation.toRoute
import com.bitwarden.ui.platform.base.util.composableWithPushTransitions import com.bitwarden.ui.platform.base.util.composableWithSlideTransitions
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
@ -37,7 +37,7 @@ fun SavedStateHandle.toEditItemArgs(): EditItemArgs {
fun NavGraphBuilder.editItemDestination( fun NavGraphBuilder.editItemDestination(
onNavigateBack: () -> Unit = { }, onNavigateBack: () -> Unit = { },
) { ) {
composableWithPushTransitions<EditItemRoute> { composableWithSlideTransitions<EditItemRoute> {
EditItemScreen( EditItemScreen(
onNavigateBack = onNavigateBack, onNavigateBack = onNavigateBack,
) )

View File

@ -4,13 +4,6 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.navigation import androidx.navigation.navigation
import com.bitwarden.authenticator.ui.authenticator.feature.edititem.editItemDestination
import com.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry.manualCodeEntryDestination
import com.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry.navigateToManualCodeEntryScreen
import com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.navigateToQrCodeScanScreen
import com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.qrCodeScanDestination
import com.bitwarden.authenticator.ui.authenticator.feature.search.itemSearchDestination
import com.bitwarden.authenticator.ui.platform.feature.settings.settingsGraph
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
@ -22,54 +15,22 @@ data object ItemListingGraphRoute
/** /**
* Add the item listing graph to the nav graph. * Add the item listing graph to the nav graph.
*/ */
@Suppress("LongParameterList")
fun NavGraphBuilder.itemListingGraph( fun NavGraphBuilder.itemListingGraph(
navController: NavController, onNavigateBack: () -> Unit,
navigateBack: () -> Unit, onNavigateToSearch: () -> Unit,
navigateToSearch: () -> Unit, onNavigateToQrCodeScanner: () -> Unit,
navigateToQrCodeScanner: () -> Unit, onNavigateToManualKeyEntry: () -> Unit,
navigateToManualKeyEntry: () -> Unit, onNavigateToEditItem: (String) -> Unit,
navigateToEditItem: (String) -> Unit,
navigateToExport: () -> Unit,
navigateToImport: () -> Unit,
navigateToTutorial: () -> Unit,
) { ) {
navigation<ItemListingGraphRoute>( navigation<ItemListingGraphRoute>(
startDestination = ItemListingRoute, startDestination = ItemListingRoute,
) { ) {
itemListingDestination( itemListingDestination(
onNavigateBack = navigateBack, onNavigateBack = onNavigateBack,
onNavigateToSearch = navigateToSearch, onNavigateToSearch = onNavigateToSearch,
onNavigateToQrCodeScanner = navigateToQrCodeScanner, onNavigateToQrCodeScanner = onNavigateToQrCodeScanner,
onNavigateToManualKeyEntry = navigateToManualKeyEntry, onNavigateToManualKeyEntry = onNavigateToManualKeyEntry,
onNavigateToEditItemScreen = navigateToEditItem, onNavigateToEditItemScreen = onNavigateToEditItem,
)
editItemDestination(
onNavigateBack = { navController.popBackStack() },
)
itemSearchDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToEdit = navigateToEditItem,
)
qrCodeScanDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToManualCodeEntryScreen = {
navController.popBackStack()
navController.navigateToManualCodeEntryScreen()
},
)
manualCodeEntryDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToQrCodeScreen = {
navController.popBackStack()
navController.navigateToQrCodeScanScreen()
},
)
settingsGraph(
navController = navController,
onNavigateToExport = navigateToExport,
onNavigateToImport = navigateToImport,
onNavigateToTutorial = navigateToTutorial,
) )
} }
} }

View File

@ -1,7 +1,7 @@
package com.bitwarden.authenticator.ui.authenticator.feature.itemlisting package com.bitwarden.authenticator.ui.authenticator.feature.itemlisting
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import com.bitwarden.ui.platform.base.util.composableWithPushTransitions import com.bitwarden.ui.platform.base.util.composableWithRootPushTransitions
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
@ -20,7 +20,7 @@ fun NavGraphBuilder.itemListingDestination(
onNavigateToManualKeyEntry: () -> Unit = { }, onNavigateToManualKeyEntry: () -> Unit = { },
onNavigateToEditItemScreen: (id: String) -> Unit = { }, onNavigateToEditItemScreen: (id: String) -> Unit = { },
) { ) {
composableWithPushTransitions<ItemListingRoute> { composableWithRootPushTransitions<ItemListingRoute> {
ItemListingScreen( ItemListingScreen(
onNavigateBack = onNavigateBack, onNavigateBack = onNavigateBack,
onNavigateToSearch = onNavigateToSearch, onNavigateToSearch = onNavigateToSearch,

View File

@ -20,8 +20,6 @@ fun NavGraphBuilder.authenticatorNavBarDestination(
onNavigateToQrCodeScanner: () -> Unit, onNavigateToQrCodeScanner: () -> Unit,
onNavigateToManualKeyEntry: () -> Unit, onNavigateToManualKeyEntry: () -> Unit,
onNavigateToEditItem: (itemId: String) -> Unit, onNavigateToEditItem: (itemId: String) -> Unit,
onNavigateToExport: () -> Unit,
onNavigateToImport: () -> Unit,
onNavigateToTutorial: () -> Unit, onNavigateToTutorial: () -> Unit,
) { ) {
composableWithStayTransitions<AuthenticatorNavbarRoute> { composableWithStayTransitions<AuthenticatorNavbarRoute> {
@ -31,8 +29,6 @@ fun NavGraphBuilder.authenticatorNavBarDestination(
onNavigateToQrCodeScanner = onNavigateToQrCodeScanner, onNavigateToQrCodeScanner = onNavigateToQrCodeScanner,
onNavigateToManualKeyEntry = onNavigateToManualKeyEntry, onNavigateToManualKeyEntry = onNavigateToManualKeyEntry,
onNavigateToEditItem = onNavigateToEditItem, onNavigateToEditItem = onNavigateToEditItem,
onNavigateToExport = onNavigateToExport,
onNavigateToImport = onNavigateToImport,
onNavigateToTutorial = onNavigateToTutorial, onNavigateToTutorial = onNavigateToTutorial,
) )
} }

View File

@ -2,7 +2,6 @@ package com.bitwarden.authenticator.ui.authenticator.feature.navbar
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -11,22 +10,20 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavBackStackEntry import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavOptions
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.ItemListingGraphRoute import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.ItemListingGraphRoute
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.ItemListingRoute import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.ItemListingRoute
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.itemListingGraph import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.itemListingGraph
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.navigateToItemListGraph
import com.bitwarden.authenticator.ui.platform.feature.settings.SettingsGraphRoute import com.bitwarden.authenticator.ui.platform.feature.settings.SettingsGraphRoute
import com.bitwarden.authenticator.ui.platform.feature.settings.SettingsRoute import com.bitwarden.authenticator.ui.platform.feature.settings.SettingsRoute
import com.bitwarden.authenticator.ui.platform.feature.settings.navigateToSettingsGraph import com.bitwarden.authenticator.ui.platform.feature.settings.export.navigateToExport
import com.bitwarden.authenticator.ui.platform.feature.settings.importing.navigateToImporting
import com.bitwarden.authenticator.ui.platform.feature.settings.settingsGraph
import com.bitwarden.ui.platform.base.util.EventsEffect import com.bitwarden.ui.platform.base.util.EventsEffect
import com.bitwarden.ui.platform.base.util.navigateToTabOrRoot
import com.bitwarden.ui.platform.components.navigation.model.NavigationItem import com.bitwarden.ui.platform.components.navigation.model.NavigationItem
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.bitwarden.ui.platform.components.scaffold.model.ScaffoldNavigationData import com.bitwarden.ui.platform.components.scaffold.model.ScaffoldNavigationData
@ -52,23 +49,10 @@ fun AuthenticatorNavBarScreen(
onNavigateToQrCodeScanner: () -> Unit, onNavigateToQrCodeScanner: () -> Unit,
onNavigateToManualKeyEntry: () -> Unit, onNavigateToManualKeyEntry: () -> Unit,
onNavigateToEditItem: (itemId: String) -> Unit, onNavigateToEditItem: (itemId: String) -> Unit,
onNavigateToExport: () -> Unit,
onNavigateToImport: () -> Unit,
onNavigateToTutorial: () -> Unit, onNavigateToTutorial: () -> Unit,
) { ) {
EventsEffect(viewModel = viewModel) { event -> EventsEffect(viewModel = viewModel) { event ->
navController.apply { navController.navigateToTabOrRoot(target = event.tab)
val navOptions = navController.authenticatorNavBarScreenNavOptions()
when (event) {
AuthenticatorNavBarEvent.NavigateToSettings -> {
navigateToSettingsGraph(navOptions)
}
AuthenticatorNavBarEvent.NavigateToVerificationCodes -> {
navigateToItemListGraph(navOptions)
}
}
}
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@ -93,13 +77,10 @@ fun AuthenticatorNavBarScreen(
navigateToQrCodeScanner = onNavigateToQrCodeScanner, navigateToQrCodeScanner = onNavigateToQrCodeScanner,
navigateToManualKeyEntry = onNavigateToManualKeyEntry, navigateToManualKeyEntry = onNavigateToManualKeyEntry,
navigateToEditItem = onNavigateToEditItem, navigateToEditItem = onNavigateToEditItem,
navigateToExport = onNavigateToExport, onNavigateToTutorial = onNavigateToTutorial,
navigateToImport = onNavigateToImport,
navigateToTutorial = onNavigateToTutorial,
) )
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun AuthenticatorNavBarScaffold( private fun AuthenticatorNavBarScaffold(
navController: NavHostController, navController: NavHostController,
@ -110,9 +91,7 @@ private fun AuthenticatorNavBarScaffold(
navigateToQrCodeScanner: () -> Unit, navigateToQrCodeScanner: () -> Unit,
navigateToManualKeyEntry: () -> Unit, navigateToManualKeyEntry: () -> Unit,
navigateToEditItem: (itemId: String) -> Unit, navigateToEditItem: (itemId: String) -> Unit,
navigateToExport: () -> Unit, onNavigateToTutorial: () -> Unit,
navigateToImport: () -> Unit,
navigateToTutorial: () -> Unit,
) { ) {
var shouldDimNavBar by rememberSaveable { mutableStateOf(value = false) } var shouldDimNavBar by rememberSaveable { mutableStateOf(value = false) }
@ -144,15 +123,17 @@ private fun AuthenticatorNavBarScaffold(
popExitTransition = RootTransitionProviders.Exit.fadeOut, popExitTransition = RootTransitionProviders.Exit.fadeOut,
) { ) {
itemListingGraph( itemListingGraph(
navController = navController, onNavigateBack = navigateBack,
navigateBack = navigateBack, onNavigateToSearch = navigateToSearch,
navigateToSearch = navigateToSearch, onNavigateToQrCodeScanner = navigateToQrCodeScanner,
navigateToQrCodeScanner = navigateToQrCodeScanner, onNavigateToManualKeyEntry = navigateToManualKeyEntry,
navigateToManualKeyEntry = navigateToManualKeyEntry, onNavigateToEditItem = navigateToEditItem,
navigateToEditItem = navigateToEditItem, )
navigateToExport = navigateToExport, settingsGraph(
navigateToImport = navigateToImport, onNavigateBack = { navController.popBackStack() },
navigateToTutorial = navigateToTutorial, onNavigateToTutorial = onNavigateToTutorial,
onNavigateToExport = { navController.navigateToExport() },
onNavigateToImport = { navController.navigateToImporting() },
) )
} }
} }
@ -171,8 +152,9 @@ private fun AuthenticatorNavBarScaffold(
* @property iconResSelected The resource ID for the icon representing the tab when it's selected. * @property iconResSelected The resource ID for the icon representing the tab when it's selected.
*/ */
@Parcelize @Parcelize
private sealed class AuthenticatorNavBarTab : NavigationItem, Parcelable { sealed class AuthenticatorNavBarTab : NavigationItem, Parcelable {
@Suppress("UndocumentedPublicClass")
companion object { companion object {
/** /**
* The list of navigation tabs available in the authenticator. * The list of navigation tabs available in the authenticator.
@ -214,18 +196,6 @@ private sealed class AuthenticatorNavBarTab : NavigationItem, Parcelable {
} }
} }
/**
* Helper function to generate [NavOptions] for [AuthenticatorNavBarScreen].
*/
private fun NavController.authenticatorNavBarScreenNavOptions(): NavOptions =
navOptions {
popUpTo(graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
/** /**
* Determine if the current destination is the same as the given tab. * Determine if the current destination is the same as the given tab.
*/ */

View File

@ -46,15 +46,25 @@ class AuthenticatorNavBarViewModel @Inject constructor(
* Models events for the [AuthenticatorNavBarViewModel]. * Models events for the [AuthenticatorNavBarViewModel].
*/ */
sealed class AuthenticatorNavBarEvent { sealed class AuthenticatorNavBarEvent {
/**
* The [AuthenticatorNavBarTab] to be associated with the event.
*/
abstract val tab: AuthenticatorNavBarTab
/** /**
* Navigate to the verification codes screen. * Navigate to the verification codes screen.
*/ */
data object NavigateToVerificationCodes : AuthenticatorNavBarEvent() data object NavigateToVerificationCodes : AuthenticatorNavBarEvent() {
override val tab: AuthenticatorNavBarTab = AuthenticatorNavBarTab.VerificationCodes
}
/** /**
* Navigate to the settings screen. * Navigate to the settings screen.
*/ */
data object NavigateToSettings : AuthenticatorNavBarEvent() data object NavigateToSettings : AuthenticatorNavBarEvent() {
override val tab: AuthenticatorNavBarTab = AuthenticatorNavBarTab.Settings
}
} }
/** /**

View File

@ -23,10 +23,13 @@ fun NavController.navigateToDebugMenuScreen() {
/** /**
* Add the setup unlock screen to the nav graph. * Add the setup unlock screen to the nav graph.
*/ */
fun NavGraphBuilder.setupDebugMenuDestination( fun NavGraphBuilder.debugMenuDestination(
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
onSplashScreenRemoved: () -> Unit,
) { ) {
composableWithPushTransitions<DebugRoute> { composableWithPushTransitions<DebugRoute> {
DebugMenuScreen(onNavigateBack = onNavigateBack) DebugMenuScreen(onNavigateBack = onNavigateBack)
// If we are displaying the debug screen, then we can just hide the splash screen.
onSplashScreenRemoved()
} }
} }

View File

@ -0,0 +1,25 @@
@file:OmitFromCoverage
package com.bitwarden.authenticator.ui.platform.feature.rootnav
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.bitwarden.annotation.OmitFromCoverage
import kotlinx.serialization.Serializable
/**
* The type-safe route for the root navigation screen.
*/
@Serializable
data object RootNavigationRoute
/**
* Add the root navigation screen to the nav graph.
*/
fun NavGraphBuilder.rootNavDestination(
onSplashScreenRemoved: () -> Unit,
) {
composable<RootNavigationRoute> {
RootNavScreen(onSplashScreenRemoved = onSplashScreenRemoved)
}
}

View File

@ -19,7 +19,6 @@ import com.bitwarden.authenticator.ui.auth.unlock.unlockDestination
import com.bitwarden.authenticator.ui.authenticator.feature.authenticator.AuthenticatorGraphRoute import com.bitwarden.authenticator.ui.authenticator.feature.authenticator.AuthenticatorGraphRoute
import com.bitwarden.authenticator.ui.authenticator.feature.authenticator.authenticatorGraph import com.bitwarden.authenticator.ui.authenticator.feature.authenticator.authenticatorGraph
import com.bitwarden.authenticator.ui.authenticator.feature.authenticator.navigateToAuthenticatorGraph import com.bitwarden.authenticator.ui.authenticator.feature.authenticator.navigateToAuthenticatorGraph
import com.bitwarden.authenticator.ui.platform.feature.debugmenu.setupDebugMenuDestination
import com.bitwarden.authenticator.ui.platform.feature.splash.SplashRoute import com.bitwarden.authenticator.ui.platform.feature.splash.SplashRoute
import com.bitwarden.authenticator.ui.platform.feature.splash.navigateToSplash import com.bitwarden.authenticator.ui.platform.feature.splash.navigateToSplash
import com.bitwarden.authenticator.ui.platform.feature.splash.splashDestination import com.bitwarden.authenticator.ui.platform.feature.splash.splashDestination
@ -42,8 +41,7 @@ import java.util.concurrent.atomic.AtomicReference
fun RootNavScreen( fun RootNavScreen(
viewModel: RootNavViewModel = hiltViewModel(), viewModel: RootNavViewModel = hiltViewModel(),
navController: NavHostController = rememberNavController(), navController: NavHostController = rememberNavController(),
onSplashScreenRemoved: () -> Unit = {}, onSplashScreenRemoved: () -> Unit,
onExitApplication: () -> Unit,
) { ) {
val state by viewModel.stateFlow.collectAsState() val state by viewModel.stateFlow.collectAsState()
val previousStateReference = remember { AtomicReference(state) } val previousStateReference = remember { AtomicReference(state) }
@ -82,15 +80,7 @@ fun RootNavScreen(
viewModel.trySendAction(RootNavAction.Internal.AppUnlocked) viewModel.trySendAction(RootNavAction.Internal.AppUnlocked)
}, },
) )
setupDebugMenuDestination( authenticatorGraph(navController = navController)
onNavigateBack = {
navController.popBackStack()
},
)
authenticatorGraph(
navController = navController,
onNavigateBack = onExitApplication,
)
} }
val targetRoute = when (state.navState) { val targetRoute = when (state.navState) {

View File

@ -6,7 +6,6 @@ import androidx.navigation.NavOptions
import androidx.navigation.navigation import androidx.navigation.navigation
import com.bitwarden.authenticator.ui.platform.feature.settings.export.exportDestination import com.bitwarden.authenticator.ui.platform.feature.settings.export.exportDestination
import com.bitwarden.authenticator.ui.platform.feature.settings.importing.importingDestination import com.bitwarden.authenticator.ui.platform.feature.settings.importing.importingDestination
import com.bitwarden.authenticator.ui.platform.feature.tutorial.tutorialSettingsDestination
import com.bitwarden.ui.platform.base.util.composableWithRootPushTransitions import com.bitwarden.ui.platform.base.util.composableWithRootPushTransitions
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -23,32 +22,44 @@ data object SettingsGraphRoute
data object SettingsRoute data object SettingsRoute
/** /**
* Add settings graph to the nav graph. * Add settings destination to the nav graph.
*/ */
fun NavGraphBuilder.settingsGraph( fun NavGraphBuilder.settingsDestination(
navController: NavController,
onNavigateToExport: () -> Unit, onNavigateToExport: () -> Unit,
onNavigateToImport: () -> Unit, onNavigateToImport: () -> Unit,
onNavigateToTutorial: () -> Unit, onNavigateToTutorial: () -> Unit,
) {
composableWithRootPushTransitions<SettingsRoute> {
SettingsScreen(
onNavigateToTutorial = onNavigateToTutorial,
onNavigateToExport = onNavigateToExport,
onNavigateToImport = onNavigateToImport,
)
}
}
/**
* Add settings graph to the nav graph.
*/
fun NavGraphBuilder.settingsGraph(
onNavigateBack: () -> Unit,
onNavigateToTutorial: () -> Unit,
onNavigateToExport: () -> Unit,
onNavigateToImport: () -> Unit,
) { ) {
navigation<SettingsGraphRoute>( navigation<SettingsGraphRoute>(
startDestination = SettingsRoute, startDestination = SettingsRoute,
) { ) {
composableWithRootPushTransitions<SettingsRoute> { settingsDestination(
SettingsScreen( onNavigateToTutorial = onNavigateToTutorial,
onNavigateToTutorial = onNavigateToTutorial, onNavigateToExport = onNavigateToExport,
onNavigateToExport = onNavigateToExport, onNavigateToImport = onNavigateToImport,
onNavigateToImport = onNavigateToImport,
)
}
tutorialSettingsDestination(
onTutorialFinished = { navController.popBackStack() },
) )
exportDestination( exportDestination(
onNavigateBack = { navController.popBackStack() }, onNavigateBack = onNavigateBack,
) )
importingDestination( importingDestination(
onNavigateBack = { navController.popBackStack() }, onNavigateBack = onNavigateBack,
) )
} }
} }

View File

@ -57,6 +57,7 @@ import com.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton
import com.bitwarden.ui.platform.components.header.BitwardenListHeaderText import com.bitwarden.ui.platform.components.header.BitwardenListHeaderText
import com.bitwarden.ui.platform.components.model.CardStyle import com.bitwarden.ui.platform.components.model.CardStyle
import com.bitwarden.ui.platform.components.row.BitwardenExternalLinkRow import com.bitwarden.ui.platform.components.row.BitwardenExternalLinkRow
import com.bitwarden.ui.platform.components.row.BitwardenPushRow
import com.bitwarden.ui.platform.components.row.BitwardenTextRow import com.bitwarden.ui.platform.components.row.BitwardenTextRow
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.bitwarden.ui.platform.components.toggle.BitwardenSwitch import com.bitwarden.ui.platform.components.toggle.BitwardenSwitch
@ -333,37 +334,21 @@ private fun ColumnScope.VaultSettings(
label = stringResource(id = BitwardenString.data), label = stringResource(id = BitwardenString.data),
) )
Spacer(modifier = Modifier.height(height = 8.dp)) Spacer(modifier = Modifier.height(height = 8.dp))
BitwardenTextRow( BitwardenPushRow(
text = stringResource(id = BitwardenString.import_vault), text = stringResource(id = BitwardenString.import_vault),
onClick = onImportClick, onClick = onImportClick,
cardStyle = CardStyle.Top(),
modifier = Modifier modifier = Modifier
.standardHorizontalMargin() .standardHorizontalMargin()
.testTag("Import"), .testTag("Import"),
cardStyle = CardStyle.Top(),
content = {
Icon(
modifier = Modifier.mirrorIfRtl(),
painter = painterResource(id = BitwardenDrawable.ic_chevron_right),
contentDescription = null,
tint = BitwardenTheme.colorScheme.icon.primary,
)
},
) )
BitwardenTextRow( BitwardenPushRow(
text = stringResource(id = BitwardenString.export), text = stringResource(id = BitwardenString.export),
onClick = onExportClick, onClick = onExportClick,
cardStyle = CardStyle.Middle(),
modifier = Modifier modifier = Modifier
.standardHorizontalMargin() .standardHorizontalMargin()
.testTag("Export"), .testTag("Export"),
cardStyle = CardStyle.Middle(),
content = {
Icon(
modifier = Modifier.mirrorIfRtl(),
painter = painterResource(id = BitwardenDrawable.ic_chevron_right),
contentDescription = null,
tint = BitwardenTheme.colorScheme.icon.primary,
)
},
) )
BitwardenExternalLinkRow( BitwardenExternalLinkRow(
text = stringResource(BitwardenString.backup), text = stringResource(BitwardenString.backup),

View File

@ -3,7 +3,7 @@ package com.bitwarden.authenticator.ui.platform.feature.settings.export
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import com.bitwarden.ui.platform.base.util.composableWithSlideTransitions import com.bitwarden.ui.platform.base.util.composableWithPushTransitions
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
@ -18,7 +18,7 @@ data object ExportRoute
fun NavGraphBuilder.exportDestination( fun NavGraphBuilder.exportDestination(
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
) { ) {
composableWithSlideTransitions<ExportRoute> { composableWithPushTransitions<ExportRoute> {
ExportScreen( ExportScreen(
onNavigateBack = onNavigateBack, onNavigateBack = onNavigateBack,
) )

View File

@ -139,8 +139,8 @@ fun ExportScreen(
BitwardenTopAppBar( BitwardenTopAppBar(
title = stringResource(id = BitwardenString.export), title = stringResource(id = BitwardenString.export),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = BitwardenDrawable.ic_close), navigationIcon = painterResource(id = BitwardenDrawable.ic_back),
navigationIconContentDescription = stringResource(id = BitwardenString.close), navigationIconContentDescription = stringResource(id = BitwardenString.back),
onNavigationIconClick = remember(viewModel) { onNavigationIconClick = remember(viewModel) {
{ {
viewModel.trySendAction(ExportAction.CloseButtonClick) viewModel.trySendAction(ExportAction.CloseButtonClick)

View File

@ -3,7 +3,7 @@ package com.bitwarden.authenticator.ui.platform.feature.settings.importing
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import com.bitwarden.ui.platform.base.util.composableWithSlideTransitions import com.bitwarden.ui.platform.base.util.composableWithPushTransitions
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
@ -18,7 +18,7 @@ data object ImportRoute
fun NavGraphBuilder.importingDestination( fun NavGraphBuilder.importingDestination(
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
) { ) {
composableWithSlideTransitions<ImportRoute> { composableWithPushTransitions<ImportRoute> {
ImportingScreen( ImportingScreen(
onNavigateBack = onNavigateBack, onNavigateBack = onNavigateBack,
) )

View File

@ -123,8 +123,8 @@ fun ImportingScreen(
BitwardenTopAppBar( BitwardenTopAppBar(
title = stringResource(id = BitwardenString.import_vault), title = stringResource(id = BitwardenString.import_vault),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = BitwardenDrawable.ic_close), navigationIcon = painterResource(id = BitwardenDrawable.ic_back),
navigationIconContentDescription = stringResource(id = BitwardenString.close), navigationIconContentDescription = stringResource(id = BitwardenString.back),
onNavigationIconClick = remember(viewModel) { onNavigationIconClick = remember(viewModel) {
{ {
viewModel.trySendAction(ImportAction.CloseButtonClick) viewModel.trySendAction(ImportAction.CloseButtonClick)

View File

@ -3,7 +3,8 @@ package com.bitwarden.authenticator.ui.platform.feature.tutorial
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.compose.composable import com.bitwarden.ui.platform.base.util.composableWithSlideTransitions
import com.bitwarden.ui.platform.base.util.composableWithStayTransitions
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
@ -22,7 +23,7 @@ data object SettingsTutorialRoute
* Add the top level Tutorial screen to the nav graph. * Add the top level Tutorial screen to the nav graph.
*/ */
fun NavGraphBuilder.tutorialDestination(onTutorialFinished: () -> Unit) { fun NavGraphBuilder.tutorialDestination(onTutorialFinished: () -> Unit) {
composable<TutorialRoute> { composableWithStayTransitions<TutorialRoute> {
TutorialScreen( TutorialScreen(
onTutorialFinished = onTutorialFinished, onTutorialFinished = onTutorialFinished,
) )
@ -33,7 +34,7 @@ fun NavGraphBuilder.tutorialDestination(onTutorialFinished: () -> Unit) {
* Add the Settings Tutorial screen to the nav graph. * Add the Settings Tutorial screen to the nav graph.
*/ */
fun NavGraphBuilder.tutorialSettingsDestination(onTutorialFinished: () -> Unit) { fun NavGraphBuilder.tutorialSettingsDestination(onTutorialFinished: () -> Unit) {
composable<SettingsTutorialRoute> { composableWithSlideTransitions<SettingsTutorialRoute> {
TutorialScreen( TutorialScreen(
onTutorialFinished = onTutorialFinished, onTutorialFinished = onTutorialFinished,
) )

View File

@ -7,6 +7,8 @@
<item name="android:windowNoTitle">true</item> <item name="android:windowNoTitle">true</item>
<item name="android:windowActionModeOverlay">true</item> <item name="android:windowActionModeOverlay">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">default</item> <item name="android:windowLayoutInDisplayCutoutMode">default</item>
<item name="android:backgroundDimAmount">@dimen/dialogDimBackgroundAmount</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style> </style>
<!-- Launch theme (for auto dark/light based on system) --> <!-- Launch theme (for auto dark/light based on system) -->

View File

@ -0,0 +1,42 @@
package com.bitwarden.ui.platform.base.util
import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavOptions
import androidx.navigation.navOptions
import com.bitwarden.ui.platform.components.navigation.model.NavigationItem
import com.bitwarden.ui.platform.util.toObjectNavigationRoute
/**
* A helper function to determine how to navigate to a specified [NavigationItem].
*
* This function intelligently handles navigation based on the current destination:
* - If already at the target start destination, no action is taken.
* - If in the correct graph but not at start, pops back to start destination of the graph.
* - Otherwise, navigates to the target graph with appropriate [NavOptions].
*
* @param target The [NavigationItem] representing the desired navigation target
*/
fun NavController.navigateToTabOrRoot(target: NavigationItem) {
if (target.startDestinationRoute.toObjectNavigationRoute() == currentDestination?.route) {
// We are at the start destination already, so nothing to do.
return
} else if (target.graphRoute.toObjectNavigationRoute() == currentDestination?.parent?.route) {
// We are not at the start destination but we are in the correct graph,
// so lets pop up to the start destination.
popBackStack(route = target.startDestinationRoute, inclusive = false)
return
} else {
// We are not in correct graph at all, so navigate there.
navigate(
route = target.graphRoute,
navOptions = navOptions {
popUpTo(id = graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
},
)
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="dialogDimBackgroundAmount">0.75</dimen>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="dialogDimBackgroundAmount">0.55</dimen>
</resources>