DIsplay base settings screen (#25)

This commit is contained in:
Patrick Honkonen
2024-04-13 20:39:03 -04:00
committed by GitHub
parent ea58313e31
commit 6b025832d7
10 changed files with 358 additions and 64 deletions

View File

@@ -58,49 +58,5 @@ fun NavGraphBuilder.authenticatorGraph(
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()
}
)
}
}

View File

@@ -11,6 +11,7 @@ import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.nav
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.qrCodeScanDestination
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.search.itemSearchDestination
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.search.navigateToSearch
import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.settingsGraph
const val ITEM_LISTING_GRAPH_ROUTE = "item_listing_graph"
@@ -59,6 +60,7 @@ fun NavGraphBuilder.itemListingGraph(
navController.navigateToQrCodeScanScreen()
}
)
settingsGraph(navController)
}
}

View File

@@ -45,6 +45,7 @@ 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.ITEM_LIST_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
@@ -52,6 +53,8 @@ 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.feature.settings.SETTINGS_GRAPH_ROUTE
import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.navigateToSettingsGraph
import com.x8bit.bitwarden.authenticator.ui.platform.theme.RootTransitionProviders
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -74,14 +77,7 @@ fun AuthenticatorNavBarScreen(
val navOptions = navController.authenticatorNavBarScreenNavOptions()
when (event) {
AuthenticatorNavBarEvent.NavigateToSettings -> {
Toast
.makeText(
navController.context,
R.string.not_yet_implemented,
Toast.LENGTH_SHORT
)
.show()
/* navigateToSettingGraph() */
navigateToSettingsGraph(navOptions)
}
AuthenticatorNavBarEvent.NavigateToVerificationCodes -> {
@@ -297,7 +293,7 @@ private sealed class AuthenticatorNavBarTab : Parcelable {
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 route get() = ITEM_LIST_ROUTE
override val testTag get() = "VerificationCodesTab"
}
@@ -311,7 +307,7 @@ private sealed class AuthenticatorNavBarTab : Parcelable {
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 route get() = SETTINGS_GRAPH_ROUTE
override val testTag get() = "SettingsTab"
}
}

View File

@@ -0,0 +1,87 @@
package com.x8bit.bitwarden.authenticator.ui.platform.components.appbar
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.tooling.preview.Preview
import com.x8bit.bitwarden.authenticator.R
/**
* A custom Bitwarden-themed medium top app bar with support for actions.
*
* This app bar wraps around Material 3's [MediumTopAppBar] and customizes its appearance
* and behavior according to the app theme.
* It provides a title and an optional set of actions on the trailing side.
* These actions are arranged within a custom action row tailored to the app's design requirements.
*
* @param title The text to be displayed as the title of the app bar.
* @param scrollBehavior Defines the scrolling behavior of the app bar. It controls how the app bar
* behaves in conjunction with scrolling content.
* @param actions A lambda containing the set of actions (usually icons or similar) to display
* in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in
* defining the layout of the actions.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BitwardenMediumTopAppBar(
title: String,
scrollBehavior: TopAppBarScrollBehavior,
modifier: Modifier = Modifier,
actions: @Composable RowScope.() -> Unit = {},
) {
MediumTopAppBar(
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surface,
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
navigationIconContentColor = MaterialTheme.colorScheme.onSurface,
titleContentColor = MaterialTheme.colorScheme.onSurface,
actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
scrollBehavior = scrollBehavior,
title = {
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.semantics { testTag = "PageTitleLabel" },
)
},
modifier = modifier.semantics { testTag = "HeaderBarComponent" },
actions = actions,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Preview(showBackground = true)
@Composable
private fun BitwardenMediumTopAppBar_preview() {
MaterialTheme {
BitwardenMediumTopAppBar(
title = "Preview Title",
scrollBehavior = TopAppBarDefaults
.exitUntilCollapsedScrollBehavior(
rememberTopAppBarState(),
),
actions = {
IconButton(onClick = { }) {
Icon(
painter = painterResource(id = R.drawable.ic_more),
contentDescription = "",
tint = MaterialTheme.colorScheme.onSurface,
)
}
},
)
}
}

View File

@@ -0,0 +1,35 @@
package com.x8bit.bitwarden.authenticator.ui.platform.feature.settings
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.navigation
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.composableWithRootPushTransitions
const val SETTINGS_GRAPH_ROUTE = "settings_graph"
private const val SETTINGS_ROUTE = "settings"
/**
* Add settings graph to the nav graph.
*/
fun NavGraphBuilder.settingsGraph(
navController: NavController,
) {
navigation(
startDestination = SETTINGS_ROUTE,
route = SETTINGS_GRAPH_ROUTE
) {
composableWithRootPushTransitions(
route = SETTINGS_ROUTE
) {
SettingsScreen()
}
}
}
/**
* Navigate to the settings screen.
*/
fun NavController.navigateToSettingsGraph(navOptions: NavOptions? = null) {
navigate(SETTINGS_GRAPH_ROUTE, navOptions)
}

View File

@@ -0,0 +1,141 @@
package com.x8bit.bitwarden.authenticator.ui.platform.feature.settings
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.x8bit.bitwarden.authenticator.R
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.Text
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.bottomDivider
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.mirrorIfRtl
import com.x8bit.bitwarden.authenticator.ui.platform.components.appbar.BitwardenMediumTopAppBar
import com.x8bit.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold
import com.x8bit.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme
/**
* Display the settings screen.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(
viewModel: SettingsViewModel = hiltViewModel(),
) {
val scrollBehavior =
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
BitwardenScaffold(
topBar = {
BitwardenMediumTopAppBar(
title = stringResource(id = R.string.settings),
scrollBehavior = scrollBehavior
)
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.verticalScroll(state = rememberScrollState())
) {
Settings.entries.forEach {
SettingsRow(
text = it.text,
onClick = remember(viewModel) {
{ viewModel.trySendAction(SettingsAction.SettingsClick(it)) }
},
modifier = Modifier
.semantics { testTag = it.testTag }
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
}
}
}
}
@Composable
private fun SettingsRow(
text: Text,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(color = MaterialTheme.colorScheme.primary),
onClick = onClick,
)
.bottomDivider(paddingStart = 16.dp)
.defaultMinSize(minHeight = 56.dp)
.padding(end = 8.dp, top = 8.dp, bottom = 8.dp)
.then(modifier),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier
.padding(end = 16.dp)
.weight(1f),
text = text(),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
)
Icon(
painter = painterResource(id = R.drawable.ic_navigate_next),
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurface,
modifier = Modifier
.mirrorIfRtl()
.size(24.dp),
)
}
}
@Preview
@Composable
private fun SettingsRows_preview() {
AuthenticatorTheme {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
) {
Settings.entries.forEach {
SettingsRow(
text = it.text,
onClick = { },
)
}
}
}
}

View File

@@ -0,0 +1,54 @@
package com.x8bit.bitwarden.authenticator.ui.platform.feature.settings
import androidx.compose.material3.Text
import com.x8bit.bitwarden.authenticator.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.Text
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
/**
* View model for the settings screen.
*/
@HiltViewModel
class SettingsViewModel @Inject constructor(
) : BaseViewModel<Unit, SettingsEvent, SettingsAction>(
initialState = Unit
) {
override fun handleAction(action: SettingsAction) {
when (action) {
is SettingsAction.SettingsClick -> handleSettingClick(action)
}
}
private fun handleSettingClick(action: SettingsAction.SettingsClick) {
when (action.setting) {
else -> {}
}
}
}
/**
* Models events for the settings screen.
*/
sealed class SettingsEvent
/**
* Models actions for the settings screen.
*/
sealed class SettingsAction {
/**
* User clicked a settings row.
*/
class SettingsClick(val setting: Settings) : SettingsAction()
}
/**
* Enum representing the settings rows, such as "import" or "export".
*
* @property text The [Text] of the string that represents the label of each setting.
* @property testTag The value that should be set for the test tag. This is used in Appium testing.
*/
enum class Settings(
val text: Text,
val testTag: String,
)

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#1B1B1F"
android:pathData="M13.25,5.75C13.25,6.44 12.69,7 12,7C11.31,7 10.75,6.44 10.75,5.75C10.75,5.06 11.31,4.5 12,4.5C12.69,4.5 13.25,5.06 13.25,5.75Z" />
<path
android:fillColor="#1B1B1F"
android:pathData="M13.25,12C13.25,12.69 12.69,13.25 12,13.25C11.31,13.25 10.75,12.69 10.75,12C10.75,11.31 11.31,10.75 12,10.75C12.69,10.75 13.25,11.31 13.25,12Z" />
<path
android:fillColor="#1B1B1F"
android:pathData="M13.25,18.25C13.25,18.94 12.69,19.5 12,19.5C11.31,19.5 10.75,18.94 10.75,18.25C10.75,17.56 11.31,17 12,17C12.69,17 13.25,17.56 13.25,18.25Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportHeight="20"
android:viewportWidth="20">
<path
android:fillColor="#1B1B1F"
android:pathData="M14.697,10.431C15.082,10.052 15.014,9.888 14.634,9.519C14.634,9.519 7.04,1.619 6.524,1.062C6.008,0.504 6.764,-0.522 7.536,0.317C8.309,1.157 15.759,8.887 15.759,8.887C16.301,9.553 16.301,10.461 15.759,11.126L7.644,19.589C6.792,20.527 5.843,19.68 6.628,18.859C9.647,15.7 14.697,10.431 14.697,10.431Z" />
</vector>

View File

@@ -1,14 +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="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>
android:viewportHeight="24"
android:viewportWidth="24">
<group>
<clip-path android:pathData="M0,0h24v24h-24z" />
<path
android:fillColor="#001550"
android:fillType="evenOdd"
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" />
</group>
</vector>