diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/authenticator/AuthenticatorNavigation.kt b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/authenticator/AuthenticatorNavigation.kt index 4aa3cd944..3054aa99c 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/authenticator/AuthenticatorNavigation.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/authenticator/AuthenticatorNavigation.kt @@ -26,6 +26,9 @@ fun NavController.navigateToAuthenticatorGraph(navOptions: NavOptions? = null) { navigate(AUTHENTICATOR_NAV_BAR_ROUTE, navOptions) } +/** + * Add the top authenticator graph to the nav graph. + */ fun NavGraphBuilder.authenticatorGraph( navController: NavController, ) { diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt index 6422bc7d7..cea116c99 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt @@ -3,7 +3,6 @@ package com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting import android.widget.Toast import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -20,9 +19,6 @@ import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -42,7 +38,6 @@ import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.mo import com.x8bit.bitwarden.authenticator.ui.platform.base.util.EventsEffect import com.x8bit.bitwarden.authenticator.ui.platform.base.util.asText import com.x8bit.bitwarden.authenticator.ui.platform.components.appbar.BitwardenTopAppBar -import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.navbar.AuthenticatorBottomAppBar import com.x8bit.bitwarden.authenticator.ui.platform.components.button.BitwardenFilledTonalButton import com.x8bit.bitwarden.authenticator.ui.platform.components.button.BitwardenTextButton import com.x8bit.bitwarden.authenticator.ui.platform.components.dialog.BasicDialogState diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorBottomAppBar.kt b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorBottomAppBar.kt deleted file mode 100644 index e35ec3fa2..000000000 --- a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorBottomAppBar.kt +++ /dev/null @@ -1,179 +0,0 @@ -package com.x8bit.bitwarden.authenticator.ui.authenticator.feature.navbar - -import android.os.Parcelable -import androidx.compose.material3.BottomAppBar -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.NavigationBarItemDefaults -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag -import androidx.compose.ui.text.style.TextOverflow -import androidx.navigation.NavController -import androidx.navigation.NavDestination.Companion.hierarchy -import androidx.navigation.NavGraph.Companion.findStartDestination -import androidx.navigation.NavOptions -import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.navOptions -import com.x8bit.bitwarden.authenticator.R -import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.ITEM_LISTING_GRAPH_ROUTE -import kotlinx.parcelize.Parcelize - -@Suppress("LongMethod") -@Composable -fun AuthenticatorBottomAppBar( - navController: NavController, - verificationCodesTabClickedAction: () -> Unit, - settingsTabClickedAction: () -> Unit, - modifier: Modifier = Modifier, -) { - BottomAppBar( - containerColor = MaterialTheme.colorScheme.surfaceContainer, - modifier = modifier, - ) { - val destinations = listOf( - AuthenticatorNavBarTab.VerificationCodes, - AuthenticatorNavBarTab.Settings, - ) - val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentDestination = navBackStackEntry?.destination - destinations.forEach { destination -> - val isSelected = currentDestination?.hierarchy?.any { - it.route == destination.route - } == true - - NavigationBarItem( - icon = { - Icon( - painter = painterResource( - id = if (isSelected) { - destination.iconResSelected - } else { - destination.iconRes - }, - ), - contentDescription = stringResource( - id = destination.contentDescriptionRes, - ), - ) - }, - label = { - Text( - text = stringResource(id = destination.labelRes), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - }, - selected = isSelected, - onClick = { - when (destination) { - AuthenticatorNavBarTab.VerificationCodes -> { - verificationCodesTabClickedAction() - } - - AuthenticatorNavBarTab.Settings -> { - settingsTabClickedAction() - } - } - }, - colors = NavigationBarItemDefaults.colors( - indicatorColor = MaterialTheme.colorScheme.secondaryContainer, - selectedIconColor = MaterialTheme.colorScheme.onSecondaryContainer, - unselectedIconColor = MaterialTheme.colorScheme.onSurface, - selectedTextColor = MaterialTheme.colorScheme.onSecondaryContainer, - unselectedTextColor = MaterialTheme.colorScheme.onSurface, - ), - modifier = Modifier.semantics { testTag = destination.testTag }, - ) - } - } -} - -/** - * Represents the different tabs available in the navigation bar - * for the authenticator screens. - * - * Each tab is modeled with properties that provide information on: - * - Regular icon resource - * - Icon resource when selected - * and other essential UI and navigational data. - * - * @property iconRes The resource ID for the regular (unselected) icon representing the tab. - * @property iconResSelected The resource ID for the icon representing the tab when it's selected. - */ -@Parcelize -private sealed class AuthenticatorNavBarTab : Parcelable { - /** - * The resource ID for the icon representing the tab when it is selected. - */ - abstract val iconResSelected: Int - - /** - * Resource id for the icon representing the tab. - */ - abstract val iconRes: Int - - /** - * Resource id for the label describing the tab. - */ - abstract val labelRes: Int - - /** - * Resource id for the content description describing the tab. - */ - abstract val contentDescriptionRes: Int - - /** - * Route of the tab. - */ - abstract val route: String - - /** - * The test tag of the tab. - */ - abstract val testTag: String - - /** - * Show the Verification Codes screen. - */ - @Parcelize - data object VerificationCodes : AuthenticatorNavBarTab() { - override val iconResSelected get() = R.drawable.ic_verification_codes_filled - override val iconRes get() = R.drawable.ic_verification_codes - override val labelRes get() = R.string.verification_codes - override val contentDescriptionRes get() = R.string.verification_codes - override val route get() = ITEM_LISTING_GRAPH_ROUTE - override val testTag get() = "SettingsTab" - } - - /** - * Show the Settings screen. - */ - @Parcelize - data object Settings : AuthenticatorNavBarTab() { - override val iconResSelected get() = R.drawable.ic_settings_filled - 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" - override val testTag get() = "SettingsTab" - } -} - -/** - * Helper function to generate [NavOptions] for [VaultUnlockedNavBarScreen]. - */ -private fun NavController.vaultUnlockedNavBarScreenNavOptions(): NavOptions = - navOptions { - popUpTo(graph.findStartDestination().id) { - saveState = true - } - launchSingleTop = true - restoreState = true - } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarNavigation.kt b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarNavigation.kt index d67ca28b3..a174c1458 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarNavigation.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarNavigation.kt @@ -1,20 +1,14 @@ package com.x8bit.bitwarden.authenticator.ui.authenticator.feature.navbar -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavOptions import com.x8bit.bitwarden.authenticator.ui.platform.base.util.composableWithStayTransitions const val AUTHENTICATOR_NAV_BAR_ROUTE: String = "AuthenticatorNavBarRoute" -fun NavController.navigateToAuthenticatorNavBar(navOptions: NavOptions? = null) { - navigate(AUTHENTICATOR_NAV_BAR_ROUTE, navOptions) -} - fun NavGraphBuilder.authenticatorNavBarDestination( onNavigateToQrCodeScanner: () -> Unit, onNavigateToManualKeyEntry: () -> Unit, - onNavigateToEditItem: (itemId: String) -> Unit + onNavigateToEditItem: (itemId: String) -> Unit, ) { composableWithStayTransitions( route = AUTHENTICATOR_NAV_BAR_ROUTE, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarScreen.kt index e652a235e..c9faa3d1a 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarScreen.kt @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.authenticator.ui.authenticator.feature.navbar +import android.os.Parcelable import android.widget.Toast import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets @@ -11,24 +12,35 @@ import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars +import androidx.compose.material3.BottomAppBar import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.ScaffoldDefaults +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.unit.dp +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag +import androidx.compose.ui.text.style.TextOverflow import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController +import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.NavHost +import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions import com.x8bit.bitwarden.authenticator.R @@ -37,11 +49,13 @@ import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.it import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.navigateToItemListGraph import com.x8bit.bitwarden.authenticator.ui.platform.base.util.EventsEffect import com.x8bit.bitwarden.authenticator.ui.platform.base.util.max +import com.x8bit.bitwarden.authenticator.ui.platform.base.util.toDp import com.x8bit.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold import com.x8bit.bitwarden.authenticator.ui.platform.components.scrim.BitwardenAnimatedScrim import com.x8bit.bitwarden.authenticator.ui.platform.theme.RootTransitionProviders import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.parcelize.Parcelize @Composable fun AuthenticatorNavBarScreen( @@ -106,8 +120,6 @@ private fun AuthenticatorNavBarScaffold( navigateToManualKeyEntry: () -> Unit, navigateToEditItem: (itemId: String) -> Unit, ) { - var shouldDimNavBar by remember { mutableStateOf(false) } - BitwardenScaffold( contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.statusBars), bottomBar = { @@ -123,13 +135,13 @@ private fun AuthenticatorNavBarScaffold( settingsTabClickedAction = settingsTabClickedAction, ) BitwardenAnimatedScrim( - isVisible = shouldDimNavBar, + isVisible = false, onClick = { // Do nothing }, modifier = Modifier .fillMaxWidth() - .height(appBarHeightPx.dp) + .height(appBarHeightPx.toDp()) ) } }, @@ -156,8 +168,149 @@ private fun AuthenticatorNavBarScaffold( } } +@Composable +private fun AuthenticatorBottomAppBar( + navController: NavController, + verificationCodesTabClickedAction: () -> Unit, + settingsTabClickedAction: () -> Unit, + modifier: Modifier = Modifier, +) { + BottomAppBar( + containerColor = MaterialTheme.colorScheme.surfaceContainer, + modifier = modifier, + ) { + val destinations = listOf( + AuthenticatorNavBarTab.VerificationCodes, + AuthenticatorNavBarTab.Settings, + ) + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + destinations.forEach { destination -> + val isSelected = currentDestination?.hierarchy?.any { + it.route == destination.route + } == true + + NavigationBarItem( + icon = { + Icon( + painter = painterResource( + id = if (isSelected) { + destination.iconResSelected + } else { + destination.iconRes + }, + ), + contentDescription = stringResource( + id = destination.contentDescriptionRes, + ), + ) + }, + label = { + Text( + text = stringResource(id = destination.labelRes), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + }, + selected = isSelected, + onClick = { + when (destination) { + AuthenticatorNavBarTab.VerificationCodes -> { + verificationCodesTabClickedAction() + } + + AuthenticatorNavBarTab.Settings -> { + settingsTabClickedAction() + } + } + }, + colors = NavigationBarItemDefaults.colors( + indicatorColor = MaterialTheme.colorScheme.secondaryContainer, + selectedIconColor = MaterialTheme.colorScheme.onSecondaryContainer, + unselectedIconColor = MaterialTheme.colorScheme.onSurface, + selectedTextColor = MaterialTheme.colorScheme.onSecondaryContainer, + unselectedTextColor = MaterialTheme.colorScheme.onSurface, + ), + modifier = Modifier.semantics { testTag = destination.testTag }, + ) + } + } +} + +/** + * Represents the different tabs available in the navigation bar + * for the authenticator screens. + * + * Each tab is modeled with properties that provide information on: + * - Regular icon resource + * - Icon resource when selected + * and other essential UI and navigational data. + * + * @property iconRes The resource ID for the regular (unselected) icon representing the tab. + * @property iconResSelected The resource ID for the icon representing the tab when it's selected. + */ +@Parcelize +private sealed class AuthenticatorNavBarTab : Parcelable { + /** + * The resource ID for the icon representing the tab when it is selected. + */ + abstract val iconResSelected: Int + + /** + * Resource id for the icon representing the tab. + */ + abstract val iconRes: Int + + /** + * Resource id for the label describing the tab. + */ + abstract val labelRes: Int + + /** + * Resource id for the content description describing the tab. + */ + abstract val contentDescriptionRes: Int + + /** + * Route of the tab. + */ + abstract val route: String + + /** + * The test tag of the tab. + */ + abstract val testTag: String + + /** + * Show the Verification Codes screen. + */ + @Parcelize + data object VerificationCodes : AuthenticatorNavBarTab() { + override val iconResSelected get() = R.drawable.ic_verification_codes_filled + override val iconRes get() = R.drawable.ic_verification_codes + override val labelRes get() = R.string.verification_codes + override val contentDescriptionRes get() = R.string.verification_codes + override val route get() = ITEM_LISTING_GRAPH_ROUTE + override val testTag get() = "VerificationCodesTab" + } + + /** + * Show the Settings screen. + */ + @Parcelize + data object Settings : AuthenticatorNavBarTab() { + override val iconResSelected get() = R.drawable.ic_settings_filled + override val iconRes get() = R.drawable.ic_settings + override val labelRes get() = R.string.settings + override val contentDescriptionRes get() = R.string.settings + // TODO: Replace with constant when settings screen is complete. + override val route get() = "settings_graph" + override val testTag get() = "SettingsTab" + } +} + /** - * Helper function to generate [NavOptions] for [VaultUnlockedNavBarScreen]. + * Helper function to generate [NavOptions] for [AuthenticatorNavBarScreen]. */ private fun NavController.authenticatorNavBarScreenNavOptions(): NavOptions = navOptions { diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarViewModel.kt index 002dfb9ba..c7d9c488c 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarViewModel.kt @@ -5,6 +5,10 @@ import com.x8bit.bitwarden.authenticator.ui.platform.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject +/** + * View model for the authenticator nav bar screen. Manages bottom tab navigation within the + * application. + */ @HiltViewModel class AuthenticatorNavBarViewModel @Inject constructor( private val authRepository: AuthRepository, @@ -38,16 +42,37 @@ class AuthenticatorNavBarViewModel @Inject constructor( } } +/** + * Models events for the [AuthenticatorNavBarViewModel]. + */ sealed class AuthenticatorNavBarEvent { + /** + * Navigate to the verification codes screen. + */ data object NavigateToVerificationCodes : AuthenticatorNavBarEvent() + /** + * Navigate to the settings screen. + */ data object NavigateToSettings : AuthenticatorNavBarEvent() } +/** + * Models actions for the bottom tab of. + */ sealed class AuthenticatorNavBarAction { + /** + * User clicked the verification codes tab. + */ data object VerificationCodesTabClick : AuthenticatorNavBarAction() + /** + * User clicked the settings tab. + */ data object SettingsTabClick : AuthenticatorNavBarAction() + /** + * Indicates the backstack has changed. + */ data object BackStackUpdate : AuthenticatorNavBarAction() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/platform/base/util/DensityExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/platform/base/util/DensityExtensions.kt index 1bc64041f..6408ee2c7 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/platform/base/util/DensityExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/platform/base/util/DensityExtensions.kt @@ -4,6 +4,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp +/** + * A function for converting pixels to [Dp] within a composable function. + */ +@Composable +fun Int.toDp(): Dp = with(LocalDensity.current) { this@toDp.toDp() } + /** * A function for converting [Dp] to pixels within a composable function. */ diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/platform/feature/rootnav/RootNavScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/platform/feature/rootnav/RootNavScreen.kt index 325376423..29fd4d93c 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/platform/feature/rootnav/RootNavScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/platform/feature/rootnav/RootNavScreen.kt @@ -16,10 +16,6 @@ import androidx.navigation.navOptions import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.authenticator.AUTHENTICATOR_GRAPH_ROUTE import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.authenticator.authenticatorGraph import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.authenticator.navigateToAuthenticatorGraph -import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.ITEM_LISTING_GRAPH_ROUTE -import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.itemListingGraph -import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.navigateToItemListGraph -import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.navbar.navigateToAuthenticatorNavBar import com.x8bit.bitwarden.authenticator.ui.platform.feature.splash.SPLASH_ROUTE import com.x8bit.bitwarden.authenticator.ui.platform.feature.splash.navigateToSplash import com.x8bit.bitwarden.authenticator.ui.platform.feature.splash.splashDestination @@ -64,7 +60,6 @@ fun RootNavScreen( ) { splashDestination() authenticatorGraph(navController) -// itemListingGraph(navController = navController) } val targetRoute = when (state) {