Skip to content

Commit

Permalink
Add bottom app bar for navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
SaintPatrck committed Apr 12, 2024
1 parent 8ad7c18 commit 314cdb9
Show file tree
Hide file tree
Showing 16 changed files with 711 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.x8bit.bitwarden.authenticator.ui.authenticator.feature.authenticator

import android.widget.Toast
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.navigation
import com.x8bit.bitwarden.authenticator.R
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.edititem.editItemDestination
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.edititem.navigateToEditItem
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.itemListingDestination
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.itemListingGraph
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry.manualCodeEntryDestination
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry.navigateToManualCodeEntryScreen
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.navbar.AUTHENTICATOR_NAV_BAR_ROUTE
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.navbar.authenticatorNavBarDestination
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.navigateToQrCodeScanScreen
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.qrCodeScanDestination

const val AUTHENTICATOR_GRAPH_ROUTE = "authenticator_graph"

/**
* Navigate to the authenticator graph
*/
fun NavController.navigateToAuthenticatorGraph(navOptions: NavOptions? = null) {
navigate(AUTHENTICATOR_NAV_BAR_ROUTE, navOptions)
}

fun NavGraphBuilder.authenticatorGraph(
navController: NavController,
) {
navigation(
startDestination = AUTHENTICATOR_NAV_BAR_ROUTE,
route = AUTHENTICATOR_GRAPH_ROUTE
) {
authenticatorNavBarDestination(
onNavigateToQrCodeScanner = { navController.navigateToQrCodeScanScreen() },
onNavigateToManualKeyEntry = { navController.navigateToManualCodeEntryScreen() },
onNavigateToEditItem = { navController.navigateToEditItem(itemId = it) },
)
itemListingGraph(
navController = navController,
navigateToQrCodeScanner = {
navController.navigateToQrCodeScanScreen()
},
navigateToManualKeyEntry = {
navController.navigateToManualCodeEntryScreen()
},
navigateToEditItem = {
navController.navigateToEditItem(itemId = it)
}
)
itemListingDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToQrCodeScanner = { navController.navigateToQrCodeScanScreen() },
onNavigateToManualKeyEntry = { navController.navigateToManualCodeEntryScreen() },
onNavigateToEditItemScreen = { navController.navigateToEditItem(itemId = it) },
onNavigateToSyncWithBitwardenScreen = {
Toast
.makeText(
navController.context,
R.string.not_yet_implemented,
Toast.LENGTH_SHORT
)
.show()
/*navController.navigateToSyncWithBitwardenScreen()*/
},
onNavigateToImportScreen = { /*navController.navigateToImportScreen()*/ }
)
editItemDestination(
onNavigateBack = { navController.popBackStack() },
)
qrCodeScanDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToManualCodeEntryScreen = {
navController.popBackStack()
navController.navigateToManualCodeEntryScreen()
},
)
manualCodeEntryDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToQrCodeScreen = {
navController.popBackStack()
navController.navigateToQrCodeScanScreen()
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.navigation
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.edititem.editItemDestination
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.edititem.navigateToEditItem
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.edititem.editItemDestination
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.edititem.navigateToEditItem
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry.manualCodeEntryDestination
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry.navigateToManualCodeEntryScreen
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.navigateToQrCodeScanScreen
Expand All @@ -20,16 +17,19 @@ const val ITEM_LISTING_GRAPH_ROUTE = "item_listing_graph"
*/
fun NavGraphBuilder.itemListingGraph(
navController: NavController,
navigateToQrCodeScanner: () -> Unit,
navigateToManualKeyEntry: () -> Unit,
navigateToEditItem: (String) -> Unit,
) {
navigation(
route = ITEM_LISTING_GRAPH_ROUTE,
startDestination = ITEM_LIST_ROUTE
startDestination = ITEM_LIST_ROUTE,
) {
itemListingDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToQrCodeScanner = { navController.navigateToQrCodeScanScreen() },
onNavigateToManualKeyEntry = { navController.navigateToManualCodeEntryScreen() },
onNavigateToEditItemScreen = { navController.navigateToEditItem(itemId = it) },
onNavigateToQrCodeScanner = navigateToQrCodeScanner,
onNavigateToManualKeyEntry = navigateToManualKeyEntry,
onNavigateToEditItemScreen = navigateToEditItem,
onNavigateToSyncWithBitwardenScreen = {
/*navController.navigateToSyncWithBitwardenScreen()*/
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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
Expand All @@ -19,6 +20,9 @@ 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
Expand All @@ -38,6 +42,7 @@ 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
Expand Down Expand Up @@ -142,7 +147,6 @@ fun ItemListingScreen(
},
floatingActionButtonPosition = FabPosition.EndOverlay,
) { paddingValues ->

Column(
modifier = Modifier
.fillMaxSize()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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
) {
composableWithStayTransitions(
route = AUTHENTICATOR_NAV_BAR_ROUTE,
) {
AuthenticatorNavBarScreen(
onNavigateToQrCodeScanner = onNavigateToQrCodeScanner,
onNavigateToManualKeyEntry = onNavigateToManualKeyEntry,
onNavigateToEditItem = onNavigateToEditItem,
)
}
}
Loading

0 comments on commit 314cdb9

Please sign in to comment.