Display bottom app bar for navigation (#24)

This commit is contained in:
Patrick Honkonen
2024-04-13 18:06:20 -04:00
committed by GitHub
parent a27002af0f
commit ea58313e31
16 changed files with 738 additions and 16 deletions
@@ -0,0 +1,106 @@
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
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.search.navigateToSearch
const val AUTHENTICATOR_GRAPH_ROUTE = "authenticator_graph"
/**
* Navigate to the authenticator graph
*/
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,
) {
navigation(
startDestination = AUTHENTICATOR_NAV_BAR_ROUTE,
route = AUTHENTICATOR_GRAPH_ROUTE
) {
authenticatorNavBarDestination(
onNavigateToSearch = { navController.navigateToSearch() },
onNavigateToQrCodeScanner = { navController.navigateToQrCodeScanScreen() },
onNavigateToManualKeyEntry = { navController.navigateToManualCodeEntryScreen() },
onNavigateToEditItem = { navController.navigateToEditItem(itemId = it) },
)
itemListingGraph(
navController = navController,
navigateToSearch = {
navController.navigateToSearch()
},
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 = {
Toast
.makeText(
navController.context,
R.string.not_yet_implemented,
Toast.LENGTH_SHORT,
)
.show()
/*navController.navigateToImportScreen()*/
},
onNavigateToSearch = { navController.navigateToSearch() },
)
editItemDestination(
onNavigateBack = { navController.popBackStack() },
)
qrCodeScanDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToManualCodeEntryScreen = {
navController.popBackStack()
navController.navigateToManualCodeEntryScreen()
},
)
manualCodeEntryDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToQrCodeScreen = {
navController.popBackStack()
navController.navigateToQrCodeScanScreen()
}
)
}
}
@@ -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
@@ -22,17 +19,21 @@ const val ITEM_LISTING_GRAPH_ROUTE = "item_listing_graph"
*/
fun NavGraphBuilder.itemListingGraph(
navController: NavController,
navigateToSearch: () -> Unit,
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() },
onNavigateToSearch = { navController.navigateToSearch() },
onNavigateToQrCodeScanner = { navController.navigateToQrCodeScanScreen() },
onNavigateToManualKeyEntry = { navController.navigateToManualCodeEntryScreen() },
onNavigateToEditItemScreen = { navController.navigateToEditItem(itemId = it) },
onNavigateToSearch = navigateToSearch,
onNavigateToQrCodeScanner = navigateToQrCodeScanner,
onNavigateToManualKeyEntry = navigateToManualKeyEntry,
onNavigateToEditItemScreen = navigateToEditItem,
onNavigateToSyncWithBitwardenScreen = {
/*navController.navigateToSyncWithBitwardenScreen()*/
},
@@ -159,7 +159,6 @@ fun ItemListingScreen(
},
floatingActionButtonPosition = FabPosition.EndOverlay,
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
@@ -0,0 +1,27 @@
package com.x8bit.bitwarden.authenticator.ui.authenticator.feature.navbar
import androidx.navigation.NavGraphBuilder
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.composableWithStayTransitions
const val AUTHENTICATOR_NAV_BAR_ROUTE: String = "AuthenticatorNavBarRoute"
/**
* Add the authenticator nav bar to the nav graph.
*/
fun NavGraphBuilder.authenticatorNavBarDestination(
onNavigateToSearch: () -> Unit,
onNavigateToQrCodeScanner: () -> Unit,
onNavigateToManualKeyEntry: () -> Unit,
onNavigateToEditItem: (itemId: String) -> Unit,
) {
composableWithStayTransitions(
route = AUTHENTICATOR_NAV_BAR_ROUTE,
) {
AuthenticatorNavBarScreen(
onNavigateToQrCodeScanner = onNavigateToQrCodeScanner,
onNavigateToManualKeyEntry = onNavigateToManualKeyEntry,
onNavigateToEditItem = onNavigateToEditItem,
onNavigateToSearch = onNavigateToSearch,
)
}
}
@@ -0,0 +1,329 @@
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
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.exclude
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
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.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onGloballyPositioned
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
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.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
/**
* Top level composable for the authenticator screens.
*/
@Composable
fun AuthenticatorNavBarScreen(
viewModel: AuthenticatorNavBarViewModel = hiltViewModel(),
navController: NavHostController = rememberNavController(),
onNavigateToSearch: () -> Unit,
onNavigateToQrCodeScanner: () -> Unit,
onNavigateToManualKeyEntry: () -> Unit,
onNavigateToEditItem: (itemId: String) -> Unit,
) {
EventsEffect(viewModel = viewModel) { event ->
navController.apply {
val navOptions = navController.authenticatorNavBarScreenNavOptions()
when (event) {
AuthenticatorNavBarEvent.NavigateToSettings -> {
Toast
.makeText(
navController.context,
R.string.not_yet_implemented,
Toast.LENGTH_SHORT
)
.show()
/* navigateToSettingGraph() */
}
AuthenticatorNavBarEvent.NavigateToVerificationCodes -> {
navigateToItemListGraph(navOptions)
}
}
}
}
LaunchedEffect(Unit) {
navController
.currentBackStackEntryFlow
.onEach {
viewModel.trySendAction(AuthenticatorNavBarAction.BackStackUpdate)
}
.launchIn(this)
}
AuthenticatorNavBarScaffold(
navController = navController,
verificationTabClickedAction = {
viewModel.trySendAction(AuthenticatorNavBarAction.VerificationCodesTabClick)
},
settingsTabClickedAction = {
viewModel.trySendAction(AuthenticatorNavBarAction.SettingsTabClick)
},
navigateToSearch = onNavigateToSearch,
navigateToQrCodeScanner = onNavigateToQrCodeScanner,
navigateToManualKeyEntry = onNavigateToManualKeyEntry,
navigateToEditItem = onNavigateToEditItem,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun AuthenticatorNavBarScaffold(
navController: NavHostController,
verificationTabClickedAction: () -> Unit,
settingsTabClickedAction: () -> Unit,
navigateToSearch: () -> Unit,
navigateToQrCodeScanner: () -> Unit,
navigateToManualKeyEntry: () -> Unit,
navigateToEditItem: (itemId: String) -> Unit,
) {
BitwardenScaffold(
contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.statusBars),
bottomBar = {
Box {
var appBarHeightPx by remember { mutableIntStateOf(0) }
AuthenticatorBottomAppBar(
modifier = Modifier
.onGloballyPositioned {
appBarHeightPx = it.size.height
},
navController = navController,
verificationCodesTabClickedAction = verificationTabClickedAction,
settingsTabClickedAction = settingsTabClickedAction,
)
BitwardenAnimatedScrim(
isVisible = false,
onClick = {
// Do nothing
},
modifier = Modifier
.fillMaxWidth()
.height(appBarHeightPx.toDp())
)
}
},
) { innerPadding ->
NavHost(
navController = navController,
startDestination = ITEM_LISTING_GRAPH_ROUTE,
modifier = Modifier
.consumeWindowInsets(WindowInsets.navigationBars)
.consumeWindowInsets(WindowInsets.ime)
.padding(innerPadding.max(WindowInsets.ime)),
enterTransition = RootTransitionProviders.Enter.fadeIn,
exitTransition = RootTransitionProviders.Exit.fadeOut,
popEnterTransition = RootTransitionProviders.Enter.fadeIn,
popExitTransition = RootTransitionProviders.Exit.fadeOut,
) {
itemListingGraph(
navController = navController,
navigateToSearch = navigateToSearch,
navigateToQrCodeScanner = navigateToQrCodeScanner,
navigateToManualKeyEntry = navigateToManualKeyEntry,
navigateToEditItem = navigateToEditItem,
)
}
}
}
@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 [AuthenticatorNavBarScreen].
*/
private fun NavController.authenticatorNavBarScreenNavOptions(): NavOptions =
navOptions {
popUpTo(graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
@@ -0,0 +1,78 @@
package com.x8bit.bitwarden.authenticator.ui.authenticator.feature.navbar
import com.x8bit.bitwarden.authenticator.data.auth.repository.AuthRepository
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,
) :
BaseViewModel<Unit, AuthenticatorNavBarEvent, AuthenticatorNavBarAction>(
initialState = Unit,
) {
override fun handleAction(action: AuthenticatorNavBarAction) {
when (action) {
AuthenticatorNavBarAction.SettingsTabClick -> {
handleSettingsClick()
}
AuthenticatorNavBarAction.VerificationCodesTabClick -> {
handleVerificationCodesTabClick()
}
AuthenticatorNavBarAction.BackStackUpdate -> {
authRepository.updateLastActiveTime()
}
}
}
private fun handleSettingsClick() {
sendEvent(AuthenticatorNavBarEvent.NavigateToSettings)
}
private fun handleVerificationCodesTabClick() {
sendEvent(AuthenticatorNavBarEvent.NavigateToVerificationCodes)
}
}
/**
* 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()
}
@@ -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.
*/
@@ -30,6 +30,27 @@ fun NavGraphBuilder.composableWithSlideTransitions(
)
}
/**
* A wrapper around [NavGraphBuilder.composable] that supplies "stay" transitions.
*/
fun NavGraphBuilder.composableWithStayTransitions(
route: String,
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
) {
this.composable(
route = route,
arguments = arguments,
deepLinks = deepLinks,
enterTransition = TransitionProviders.Enter.stay,
exitTransition = TransitionProviders.Exit.stay,
popEnterTransition = TransitionProviders.Enter.stay,
popExitTransition = TransitionProviders.Exit.stay,
content = content,
)
}
/**
* A wrapper around [NavGraphBuilder.composable] that supplies push transitions.
*
@@ -0,0 +1,48 @@
package com.x8bit.bitwarden.authenticator.ui.platform.base.util
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.LayoutDirection
/**
* Compares the top, bottom, start, and end values to another [PaddingValues] and returns a new
* 'PaddingValues' using the maximum values of each property respectively.
*
* @param other The other values to compare against.
*/
fun PaddingValues.max(
other: PaddingValues,
direction: LayoutDirection,
): PaddingValues = PaddingValues(
top = maxOf(calculateTopPadding(), other.calculateTopPadding()),
bottom = maxOf(calculateBottomPadding(), other.calculateBottomPadding()),
start = maxOf(calculateStartPadding(direction), other.calculateStartPadding(direction)),
end = maxOf(calculateEndPadding(direction), other.calculateEndPadding(direction)),
)
/**
* Compares the top, bottom, start, and end values to a [WindowInsets] and returns a new
* 'PaddingValues' using the maximum values of each property respectively.
*
* @param windowInsets The [WindowInsets] to compare against.
*/
@Composable
fun PaddingValues.max(
windowInsets: WindowInsets,
): PaddingValues = max(windowInsets.asPaddingValues())
/**
* Compares the top, bottom, start, and end values to another [PaddingValues] and returns a new
* 'PaddingValues' using the maximum values of each property respectively.
*
* @param other The other [PaddingValues] to compare against.
*/
@Composable
fun PaddingValues.max(
other: PaddingValues,
): PaddingValues = max(other, LocalLayoutDirection.current)
@@ -0,0 +1,45 @@
package com.x8bit.bitwarden.authenticator.ui.platform.components.scrim
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
/**
* A scrim that animates its visibility.
*
* @param isVisible Whether or not the scrim should be visible. This controls the animation.
* @param onClick A callback that is triggered when the scrim is clicked. No ripple will be
* performed.
* @param modifier A [Modifier] for the scrim's content.
*/
@Composable
fun BitwardenAnimatedScrim(
isVisible: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = isVisible,
enter = fadeIn(),
exit = fadeOut(),
) {
Box(
modifier = modifier
.background(Color.Black.copy(alpha = 0.40f))
.clickable(
interactionSource = remember { MutableInteractionSource() },
// Clear the ripple
indication = null,
onClick = onClick,
),
)
}
}
@@ -13,9 +13,9 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
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.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.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
@@ -26,6 +26,9 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import java.util.concurrent.atomic.AtomicReference
/**
* Controls the root level [NavHost] for the app.
*/
@Composable
fun RootNavScreen(
viewModel: RootNavViewModel = hiltViewModel(),
@@ -59,12 +62,11 @@ fun RootNavScreen(
popExitTransition = { toExitTransition()(this) },
) {
splashDestination()
itemListingGraph(navController = navController)
authenticatorGraph(navController)
}
val targetRoute = when (state) {
RootNavState.ItemListing -> ITEM_LISTING_GRAPH_ROUTE
RootNavState.ItemListing -> AUTHENTICATOR_GRAPH_ROUTE
RootNavState.Splash -> SPLASH_ROUTE
}
@@ -92,7 +94,7 @@ fun RootNavScreen(
LaunchedEffect(state) {
when (state) {
RootNavState.ItemListing -> navController.navigateToItemListGraph(rootNavOptions)
RootNavState.ItemListing -> navController.navigateToAuthenticatorGraph(rootNavOptions)
RootNavState.Splash -> navController.navigateToSplash(rootNavOptions)
}
}
+18
View File
@@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="20"
android:viewportHeight="20">
<group>
<clip-path
android:pathData="M0,0h20v20h-20z"/>
<path
android:pathData="M0,11.738V8.238C0,7.478 0.616,6.863 1.375,6.863H2.178C2.47,6.131 2.862,5.451 3.336,4.838L2.928,4.145C2.542,3.491 2.76,2.648 3.414,2.262L6.429,0.485C7.083,0.099 7.926,0.317 8.312,0.971L8.72,1.663C9.138,1.599 9.565,1.566 10,1.566C10.408,1.566 10.811,1.596 11.204,1.652L11.606,0.956C11.986,0.298 12.827,0.073 13.484,0.453L16.515,2.203C17.173,2.582 17.399,3.423 17.019,4.081L16.617,4.778C17.112,5.406 17.52,6.107 17.822,6.863H18.625C19.384,6.863 20,7.478 20,8.238V11.738C20,12.497 19.384,13.113 18.625,13.113H17.822C17.53,13.844 17.138,14.525 16.664,15.138L17.072,15.831C17.458,16.485 17.24,17.328 16.586,17.713L13.571,19.491C12.917,19.877 12.074,19.659 11.688,19.005L11.28,18.312C10.862,18.376 10.435,18.409 10,18.409C9.592,18.409 9.19,18.38 8.796,18.324L8.394,19.02C8.014,19.677 7.173,19.903 6.516,19.523L3.484,17.773C2.827,17.393 2.601,16.552 2.981,15.895L3.383,15.198C2.888,14.57 2.48,13.869 2.178,13.113H1.375C0.616,13.113 0,12.497 0,11.738ZM1.25,8.238C1.25,8.169 1.306,8.113 1.375,8.113H2.98C3.037,8.113 3.086,8.075 3.102,8.021C3.423,6.891 4.015,5.874 4.803,5.046C4.842,5.005 4.849,4.943 4.821,4.895L4.004,3.51C3.969,3.451 3.989,3.374 4.049,3.339L7.064,1.562C7.123,1.526 7.2,1.546 7.235,1.606L8.05,2.989C8.079,3.037 8.136,3.061 8.191,3.046C8.769,2.896 9.375,2.816 10,2.816C10.602,2.816 11.187,2.891 11.746,3.03C11.8,3.044 11.858,3.02 11.886,2.971L12.689,1.581C12.723,1.521 12.8,1.501 12.859,1.535L15.891,3.285C15.95,3.32 15.971,3.396 15.936,3.456L15.133,4.848C15.104,4.897 15.113,4.958 15.152,4.999C15.962,5.836 16.57,6.869 16.898,8.021C16.914,8.075 16.963,8.113 17.02,8.113H18.625C18.694,8.113 18.75,8.169 18.75,8.238V11.738C18.75,11.807 18.694,11.863 18.625,11.863H17.02C16.963,11.863 16.914,11.901 16.898,11.955C16.577,13.085 15.985,14.101 15.197,14.93C15.158,14.97 15.151,15.032 15.179,15.081L15.995,16.465C16.031,16.525 16.011,16.602 15.951,16.637L12.936,18.414C12.877,18.449 12.8,18.429 12.765,18.37L11.95,16.987C11.921,16.938 11.863,16.915 11.809,16.929C11.231,17.079 10.625,17.159 10,17.159C9.398,17.159 8.813,17.085 8.254,16.945C8.2,16.932 8.142,16.955 8.114,17.004L7.311,18.395C7.277,18.455 7.2,18.475 7.141,18.441L4.109,16.691C4.05,16.656 4.029,16.58 4.064,16.52L4.867,15.128C4.896,15.079 4.887,15.017 4.848,14.977C4.038,14.14 3.43,13.107 3.102,11.955C3.086,11.901 3.037,11.863 2.98,11.863H1.375C1.306,11.863 1.25,11.807 1.25,11.738V8.238Z"
android:fillColor="#1B1B1F"
android:fillType="evenOdd"/>
<path
android:pathData="M10,6.875C8.274,6.875 6.875,8.274 6.875,10C6.875,11.726 8.274,13.125 10,13.125C11.726,13.125 13.125,11.726 13.125,10C13.125,8.274 11.726,6.875 10,6.875ZM5.625,10C5.625,7.584 7.584,5.625 10,5.625C12.416,5.625 14.375,7.584 14.375,10C14.375,12.417 12.416,14.375 10,14.375C7.584,14.375 5.625,12.417 5.625,10Z"
android:fillColor="#1B1B1F"
android:fillType="evenOdd"/>
</group>
</vector>
@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<clip-path
android:pathData="M2,2h20v20h-20z"/>
<path
android:pathData="M20.436,13.324L20.282,13.225C20.156,13.152 20.055,13.042 19.993,12.911C19.932,12.78 19.912,12.633 19.938,12.491V11.529C19.91,11.389 19.929,11.245 19.992,11.117C20.055,10.989 20.157,10.884 20.284,10.818L20.472,10.724C20.948,10.448 21.295,9.999 21.438,9.474C21.574,8.953 21.504,8.401 21.243,7.929L20.251,6.215C19.973,5.757 19.528,5.421 19.006,5.278C18.484,5.135 17.927,5.194 17.449,5.444L17.276,5.53C17.146,5.599 16.999,5.633 16.852,5.628C16.704,5.623 16.56,5.578 16.435,5.5C16.155,5.313 15.858,5.151 15.548,5.015C15.418,4.951 15.309,4.853 15.232,4.731C15.156,4.609 15.116,4.468 15.117,4.325V4.07C15.121,3.798 15.07,3.528 14.966,3.276C14.863,3.023 14.709,2.794 14.514,2.601C14.32,2.408 14.088,2.256 13.832,2.153C13.577,2.049 13.304,1.998 13.028,2H11C10.724,1.998 10.451,2.051 10.196,2.154C9.942,2.258 9.711,2.411 9.517,2.604C9.323,2.796 9.17,3.026 9.067,3.278C8.964,3.529 8.913,3.799 8.917,4.071V4.284C8.918,4.426 8.879,4.564 8.806,4.685C8.732,4.806 8.626,4.904 8.499,4.969C8.273,5.07 8.055,5.186 7.846,5.316L7.605,5.456C7.482,5.543 7.336,5.592 7.185,5.597C7.035,5.602 6.885,5.564 6.756,5.487L6.599,5.411C6.367,5.273 6.108,5.185 5.84,5.151C5.572,5.118 5.299,5.139 5.039,5.214C4.507,5.359 4.054,5.706 3.778,6.178L2.79,7.884C2.648,8.121 2.557,8.383 2.521,8.655C2.486,8.928 2.507,9.203 2.583,9.467C2.652,9.721 2.772,9.958 2.937,10.165C3.101,10.372 3.305,10.544 3.539,10.671L3.653,10.782L3.705,10.818C3.831,10.891 3.932,11.001 3.994,11.132C4.055,11.263 4.075,11.41 4.049,11.552V12.517C4.062,12.655 4.037,12.794 3.976,12.919C3.915,13.044 3.821,13.15 3.703,13.226L3.515,13.319C3.05,13.601 2.712,14.047 2.569,14.566C2.426,15.086 2.489,15.638 2.745,16.113L3.739,17.827C4.008,18.294 4.454,18.637 4.979,18.782C5.504,18.927 6.065,18.861 6.541,18.599L6.714,18.514C6.844,18.444 6.991,18.41 7.139,18.415C7.287,18.42 7.431,18.464 7.557,18.542C7.838,18.729 8.134,18.892 8.444,19.027C8.574,19.091 8.683,19.189 8.76,19.311C8.836,19.433 8.876,19.574 8.875,19.717V19.931C8.871,20.202 8.922,20.472 9.025,20.724C9.129,20.976 9.281,21.205 9.476,21.397C9.67,21.591 9.901,21.743 10.156,21.846C10.411,21.949 10.684,22.002 10.96,22H12.988C13.263,22.002 13.536,21.95 13.791,21.846C14.046,21.743 14.277,21.59 14.471,21.397C14.665,21.204 14.819,20.976 14.922,20.724C15.025,20.472 15.076,20.202 15.072,19.93V19.716C15.071,19.575 15.109,19.436 15.183,19.316C15.257,19.195 15.363,19.096 15.49,19.031C15.715,18.931 15.933,18.816 16.142,18.685L16.196,18.654L16.383,18.545C16.506,18.459 16.652,18.409 16.804,18.404C16.955,18.399 17.104,18.437 17.233,18.514L17.39,18.59C17.628,18.729 17.892,18.819 18.166,18.855C18.439,18.891 18.718,18.871 18.984,18.798C19.245,18.727 19.489,18.604 19.7,18.436C19.911,18.268 20.085,18.059 20.209,17.823L21.198,16.116C21.337,15.886 21.426,15.629 21.46,15.363C21.493,15.096 21.472,14.826 21.396,14.569C21.254,14.046 20.909,13.598 20.435,13.324H20.436ZM11.999,15.545C11.288,15.545 10.593,15.337 10.002,14.948C9.411,14.558 8.95,14.004 8.678,13.357C8.406,12.709 8.334,11.996 8.473,11.309C8.612,10.621 8.954,9.989 9.457,9.494C9.959,8.998 10.6,8.661 11.297,8.524C11.995,8.387 12.718,8.457 13.374,8.726C14.031,8.994 14.592,9.448 14.988,10.031C15.383,10.614 15.594,11.299 15.594,12.001C15.594,12.941 15.215,13.842 14.541,14.507C13.867,15.172 12.953,15.546 11.999,15.546V15.545Z"
android:fillColor="#151B2C"/>
</group>
</vector>
@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<clip-path
android:pathData="M0,0h24v24h-24z"/>
<path
android:pathData="M6.355,5.259C10.078,1.536 16.114,1.536 19.837,5.259C23.559,8.982 23.559,15.018 19.837,18.741C17.65,20.927 14.666,21.83 11.819,21.448C11.444,21.397 11.099,21.661 11.048,22.036C10.998,22.411 11.261,22.756 11.636,22.807C14.889,23.244 18.305,22.212 20.806,19.711C25.065,15.452 25.065,8.548 20.806,4.29C16.548,0.031 9.644,0.031 5.385,4.29C4.14,5.535 3.258,7.009 2.741,8.577L1.615,6.576C1.43,6.246 1.012,6.129 0.682,6.315C0.352,6.5 0.235,6.918 0.42,7.248L2.355,10.69C2.541,11.02 2.959,11.137 3.289,10.951L6.73,9.016C7.06,8.83 7.177,8.412 6.992,8.082C6.806,7.752 6.388,7.635 6.058,7.821L4.064,8.942C4.519,7.597 5.282,6.333 6.355,5.259ZM15.372,6.408C16.315,6.408 17.084,7.178 17.084,8.121V9.394H13.659V8.121C13.659,7.174 14.428,6.408 15.372,6.408ZM12.836,9.394V8.121C12.836,6.718 13.975,5.586 15.372,5.586C16.769,5.586 17.907,6.724 17.907,8.121V9.394H18.324C18.702,9.394 19.01,9.701 19.01,10.08V15.016C19.01,15.395 18.702,15.701 18.324,15.701H15.834V18.421C15.834,18.58 15.794,18.744 15.705,18.881C15.618,19.014 15.451,19.166 15.207,19.166H0.627C0.388,19.166 0.219,19.019 0.129,18.885C0.038,18.748 -0.005,18.581 0,18.414V13.314C0,13.155 0.041,12.991 0.129,12.854C0.216,12.72 0.383,12.569 0.627,12.569H11.836V10.08C11.836,9.701 12.143,9.394 12.521,9.394H12.836ZM12.658,12.569H15.207C15.451,12.569 15.618,12.72 15.705,12.854C15.794,12.991 15.834,13.155 15.834,13.314V14.879H18.187V10.217H12.658V12.569ZM15.011,14.879V13.392H0.823V18.343H15.011V15.701H14.993V14.879H15.011ZM8.065,16.623C8.097,16.456 8.113,16.258 8.113,16.027C8.113,15.601 8.052,15.272 7.93,15.039C7.86,14.905 7.773,14.793 7.67,14.703C7.569,14.611 7.452,14.542 7.317,14.496C7.182,14.448 7.032,14.424 6.867,14.424C6.617,14.424 6.402,14.478 6.22,14.585C6.041,14.69 5.904,14.845 5.809,15.048C5.755,15.17 5.715,15.317 5.689,15.487C5.663,15.658 5.65,15.85 5.65,16.063C5.65,16.227 5.662,16.381 5.684,16.523C5.708,16.664 5.745,16.793 5.795,16.91C5.893,17.125 6.038,17.292 6.23,17.413C6.424,17.535 6.643,17.595 6.887,17.595C7.098,17.595 7.291,17.55 7.464,17.46C7.637,17.37 7.778,17.242 7.887,17.075C7.973,16.94 8.033,16.789 8.065,16.623ZM7.401,15.352C7.436,15.516 7.454,15.726 7.454,15.984C7.454,16.256 7.437,16.476 7.404,16.644C7.37,16.812 7.311,16.941 7.228,17.031C7.145,17.121 7.029,17.166 6.882,17.166C6.739,17.166 6.626,17.123 6.543,17.037C6.459,16.95 6.4,16.822 6.365,16.652C6.329,16.483 6.312,16.266 6.312,16.001C6.312,15.611 6.353,15.322 6.434,15.134C6.518,14.947 6.665,14.853 6.877,14.853C7.024,14.853 7.14,14.896 7.223,14.98C7.307,15.063 7.366,15.187 7.401,15.352ZM3.223,15.333V17.257C3.223,17.37 3.253,17.455 3.312,17.513C3.371,17.571 3.45,17.6 3.548,17.6C3.767,17.6 3.877,17.458 3.877,17.174V14.754C3.877,14.652 3.852,14.573 3.8,14.515C3.749,14.457 3.681,14.428 3.596,14.428C3.52,14.428 3.469,14.441 3.442,14.466C3.415,14.492 3.356,14.559 3.266,14.669C3.177,14.779 3.073,14.878 2.956,14.967C2.841,15.056 2.686,15.141 2.492,15.221C2.362,15.275 2.272,15.318 2.22,15.352C2.169,15.386 2.143,15.439 2.143,15.511C2.143,15.573 2.169,15.628 2.22,15.676C2.273,15.722 2.333,15.745 2.401,15.745C2.542,15.745 2.816,15.608 3.223,15.333ZM10.396,17.257V15.333C9.989,15.608 9.715,15.745 9.574,15.745C9.506,15.745 9.446,15.722 9.393,15.676C9.342,15.628 9.316,15.573 9.316,15.511C9.316,15.439 9.342,15.386 9.393,15.352C9.445,15.318 9.535,15.275 9.665,15.221C9.859,15.141 10.014,15.056 10.129,14.967C10.246,14.878 10.35,14.779 10.439,14.669C10.529,14.559 10.588,14.492 10.615,14.466C10.642,14.441 10.693,14.428 10.769,14.428C10.854,14.428 10.922,14.457 10.973,14.515C11.025,14.573 11.05,14.652 11.05,14.754V17.174C11.05,17.458 10.94,17.6 10.721,17.6C10.623,17.6 10.544,17.571 10.485,17.513C10.426,17.455 10.396,17.37 10.396,17.257ZM13.325,15.333V17.257C13.325,17.37 13.355,17.455 13.414,17.513C13.473,17.571 13.552,17.6 13.65,17.6C13.869,17.6 13.979,17.458 13.979,17.174V14.754C13.979,14.652 13.954,14.573 13.902,14.515C13.851,14.457 13.783,14.428 13.698,14.428C13.623,14.428 13.571,14.441 13.544,14.466C13.517,14.492 13.458,14.559 13.368,14.669C13.279,14.779 13.175,14.878 13.058,14.967C12.943,15.056 12.788,15.141 12.594,15.221C12.464,15.275 12.374,15.318 12.322,15.352C12.271,15.386 12.245,15.439 12.245,15.511C12.245,15.573 12.271,15.628 12.322,15.676C12.375,15.722 12.435,15.745 12.503,15.745C12.644,15.745 12.918,15.608 13.325,15.333Z"
android:fillColor="#001550"
android:fillType="evenOdd"/>
</group>
</vector>
@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<clip-path
android:pathData="M0,0h24v24h-24z"/>
<path
android:pathData="M6.355,5.259C10.078,1.536 16.114,1.536 19.837,5.259C23.559,8.982 23.559,15.018 19.837,18.741C17.65,20.927 14.666,21.83 11.819,21.448C11.444,21.397 11.099,21.661 11.048,22.036C10.998,22.411 11.261,22.756 11.636,22.807C14.889,23.244 18.305,22.212 20.806,19.711C25.065,15.452 25.065,8.548 20.806,4.29C16.548,0.031 9.644,0.031 5.385,4.29C4.14,5.535 3.258,7.009 2.741,8.577L1.615,6.576C1.43,6.246 1.012,6.129 0.682,6.315C0.352,6.5 0.235,6.918 0.42,7.248L2.355,10.69C2.541,11.02 2.959,11.137 3.289,10.951L6.73,9.016C7.06,8.83 7.177,8.412 6.992,8.082C6.806,7.752 6.388,7.635 6.058,7.821L4.064,8.942C4.519,7.597 5.282,6.333 6.355,5.259ZM15.372,6.408C16.315,6.408 17.084,7.178 17.084,8.121V9.394H13.659V8.121C13.659,7.174 14.428,6.408 15.372,6.408ZM12.836,9.394V8.121C12.836,6.718 13.975,5.586 15.372,5.586C16.769,5.586 17.907,6.724 17.907,8.121V9.394H18.324C18.702,9.394 19.01,9.701 19.01,10.08V15.016C19.01,15.395 18.702,15.701 18.324,15.701H15.834V18.421C15.834,18.58 15.794,18.744 15.705,18.881C15.618,19.014 15.451,19.166 15.207,19.166H0.627C0.388,19.166 0.219,19.019 0.129,18.885C0.038,18.748 -0.005,18.581 0,18.414V13.314C0,13.155 0.041,12.991 0.129,12.854C0.216,12.72 0.383,12.569 0.627,12.569H11.836V10.08C11.836,9.701 12.143,9.394 12.521,9.394H12.836ZM12.658,12.569H15.207C15.451,12.569 15.618,12.72 15.705,12.854C15.794,12.991 15.834,13.155 15.834,13.314V14.879H18.187V10.217H12.658V12.569ZM15.011,14.879V13.392H0.823V18.343H15.011V15.701H14.993V14.879H15.011ZM8.065,16.623C8.097,16.456 8.113,16.258 8.113,16.027C8.113,15.601 8.052,15.272 7.93,15.039C7.86,14.905 7.773,14.793 7.67,14.703C7.569,14.611 7.452,14.542 7.317,14.496C7.182,14.448 7.032,14.424 6.867,14.424C6.617,14.424 6.402,14.478 6.22,14.585C6.041,14.69 5.904,14.845 5.809,15.048C5.755,15.17 5.715,15.317 5.689,15.487C5.663,15.658 5.65,15.85 5.65,16.063C5.65,16.227 5.662,16.381 5.684,16.523C5.708,16.664 5.745,16.793 5.795,16.91C5.893,17.125 6.038,17.292 6.23,17.413C6.424,17.535 6.643,17.595 6.887,17.595C7.098,17.595 7.291,17.55 7.464,17.46C7.637,17.37 7.778,17.242 7.887,17.075C7.973,16.94 8.033,16.789 8.065,16.623ZM7.401,15.352C7.436,15.516 7.454,15.726 7.454,15.984C7.454,16.256 7.437,16.476 7.404,16.644C7.37,16.812 7.311,16.941 7.228,17.031C7.145,17.121 7.029,17.166 6.882,17.166C6.739,17.166 6.626,17.123 6.543,17.037C6.459,16.95 6.4,16.822 6.365,16.652C6.329,16.483 6.312,16.266 6.312,16.001C6.312,15.611 6.353,15.322 6.434,15.134C6.518,14.947 6.665,14.853 6.877,14.853C7.024,14.853 7.14,14.896 7.223,14.98C7.307,15.063 7.366,15.187 7.401,15.352ZM3.223,15.333V17.257C3.223,17.37 3.253,17.455 3.312,17.513C3.371,17.571 3.45,17.6 3.548,17.6C3.767,17.6 3.877,17.458 3.877,17.174V14.754C3.877,14.652 3.852,14.573 3.8,14.515C3.749,14.457 3.681,14.428 3.596,14.428C3.52,14.428 3.469,14.441 3.442,14.466C3.415,14.492 3.356,14.559 3.266,14.669C3.177,14.779 3.073,14.878 2.956,14.967C2.841,15.056 2.686,15.141 2.492,15.221C2.362,15.275 2.272,15.318 2.22,15.352C2.169,15.386 2.143,15.439 2.143,15.511C2.143,15.573 2.169,15.628 2.22,15.676C2.273,15.722 2.333,15.745 2.401,15.745C2.542,15.745 2.816,15.608 3.223,15.333ZM10.396,17.257V15.333C9.989,15.608 9.715,15.745 9.574,15.745C9.506,15.745 9.446,15.722 9.393,15.676C9.342,15.628 9.316,15.573 9.316,15.511C9.316,15.439 9.342,15.386 9.393,15.352C9.445,15.318 9.535,15.275 9.665,15.221C9.859,15.141 10.014,15.056 10.129,14.967C10.246,14.878 10.35,14.779 10.439,14.669C10.529,14.559 10.588,14.492 10.615,14.466C10.642,14.441 10.693,14.428 10.769,14.428C10.854,14.428 10.922,14.457 10.973,14.515C11.025,14.573 11.05,14.652 11.05,14.754V17.174C11.05,17.458 10.94,17.6 10.721,17.6C10.623,17.6 10.544,17.571 10.485,17.513C10.426,17.455 10.396,17.37 10.396,17.257ZM13.325,15.333V17.257C13.325,17.37 13.355,17.455 13.414,17.513C13.473,17.571 13.552,17.6 13.65,17.6C13.869,17.6 13.979,17.458 13.979,17.174V14.754C13.979,14.652 13.954,14.573 13.902,14.515C13.851,14.457 13.783,14.428 13.698,14.428C13.623,14.428 13.571,14.441 13.544,14.466C13.517,14.492 13.458,14.559 13.368,14.669C13.279,14.779 13.175,14.878 13.058,14.967C12.943,15.056 12.788,15.141 12.594,15.221C12.464,15.275 12.374,15.318 12.322,15.352C12.271,15.386 12.245,15.439 12.245,15.511C12.245,15.573 12.271,15.628 12.322,15.676C12.375,15.722 12.435,15.745 12.503,15.745C12.644,15.745 12.918,15.608 13.325,15.333Z"
android:fillColor="#001550"
android:fillType="evenOdd"/>
</group>
</vector>
+1
View File
@@ -63,4 +63,5 @@
<string name="search_codes">Search codes</string>
<string name="options">Options</string>
<string name="try_again">Try again</string>
<string name="not_yet_implemented">Not yet implemented</string>
</resources>