mirror of
https://github.com/bitwarden/android.git
synced 2026-08-29 10:17:56 -05:00
Display bottom app bar for navigation (#24)
This commit is contained in:
+106
@@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
+9
-8
@@ -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()*/
|
||||
},
|
||||
|
||||
-1
@@ -159,7 +159,6 @@ fun ItemListingScreen(
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.EndOverlay,
|
||||
) { paddingValues ->
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
||||
+27
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
+329
@@ -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
|
||||
}
|
||||
+78
@@ -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()
|
||||
}
|
||||
+6
@@ -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.
|
||||
*/
|
||||
|
||||
+21
@@ -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.
|
||||
*
|
||||
|
||||
+48
@@ -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)
|
||||
+45
@@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user