feat(device): add visibility/luftdata/narodmon settings, device model icons, SDK refactor

- Add SetDeviceVisibilityUseCase, SetLuftdataUseCase, SetNarodmonUseCase

- Introduce DeviceModel enum (Basic, Mobile, Solar, Radiation, Custom)

- Add device-type drawables (active/inactive icons for each model)

- Refactor Device SDK: repository, DAO, database, local/remote data sources

- Update DeviceSettingsScreen, ManageViewModel, theme colors

- Add/update string resources (default, ru, be)

Made-with: Cursor
This commit is contained in:
2026-03-06 20:01:24 +01:00
parent 7815f151f1
commit 0519936531
41 changed files with 1251 additions and 138 deletions

View File

@@ -45,3 +45,14 @@ Use this stack as the default foundation for all implementation work in `airmq-a
5. Apollo GraphQL for GraphQL schema integration, client generation, and network operations. 5. Apollo GraphQL for GraphQL schema integration, client generation, and network operations.
If a requested change conflicts with this baseline, ask for explicit approval before introducing an alternative. If a requested change conflicts with this baseline, ask for explicit approval before introducing an alternative.
## Temporary or implicit implementations
When implementing something implicitly or obviously temporary, document it in `temp.md` at the project root with all important details:
- What was implemented
- Why it is temporary or implicit
- Location (files, components, modules)
- Any follow-up work or conditions for proper replacement
Create `temp.md` if it does not exist.

8
.idea/markdown.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<option name="previewPanelProviderInfo">
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
</option>
</component>
</project>

View File

@@ -1,25 +1,33 @@
package org.db3.airmq.features.device package org.db3.airmq.features.device
import android.R as AndroidR
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
@@ -27,24 +35,44 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import org.db3.airmq.R import org.db3.airmq.R
import org.db3.airmq.sdk.device.domain.DeviceModel
import org.db3.airmq.features.common.AirMQButton import org.db3.airmq.features.common.AirMQButton
import org.db3.airmq.ui.theme.AirMQTheme
import org.db3.airmq.features.common.AirMQButtonStyle import org.db3.airmq.features.common.AirMQButtonStyle
import org.db3.airmq.sdk.device.domain.OnlineStatus
import org.db3.airmq.ui.theme.LegacyBackground import org.db3.airmq.ui.theme.LegacyBackground
import org.db3.airmq.ui.theme.LegacyBlack12
import androidx.core.net.toUri
private const val INFO_URL = "https://docs.google.com/document/d/1JC41nHE5foYhS6beDFTA5a8dQqX3IQoqHJYZbPRtmuM/edit"
private fun roundLocation(d: Double): String =
"%.5f".format(d).replace(',', '.')
private fun DeviceModel.iconRes(): Int = when (this) {
DeviceModel.Basic -> R.drawable.ic_device_basic_active_10
DeviceModel.Mobile -> R.drawable.ic_device_mobile_active_10
DeviceModel.Solar -> R.drawable.ic_device_solar_active_10
DeviceModel.Radiation -> R.drawable.ic_device_radiation_active_10
DeviceModel.Custom -> R.drawable.ic_device_custom_active_10
}
@Composable @Composable
fun DeviceSettingsScreen( fun DeviceSettingsScreen(
@@ -52,11 +80,15 @@ fun DeviceSettingsScreen(
onOpenLocation: () -> Unit, onOpenLocation: () -> Unit,
onShowOnMap: () -> Unit, onShowOnMap: () -> Unit,
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
viewModel: DeviceSettingsViewModel = hiltViewModel() viewModel: DeviceSettingsViewModel = hiltViewModel(
creationCallback = { factory: DeviceSettingsViewModel.Factory ->
factory.create(deviceId)
}
)
) { ) {
val uiState by viewModel.uiState.collectAsState() val uiState by viewModel.uiState.collectAsState()
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
val toastDoneText = stringResource(R.string.toast_done) val context = LocalContext.current
LaunchedEffect(viewModel) { LaunchedEffect(viewModel) {
viewModel.actions.collectLatest { action -> viewModel.actions.collectLatest { action ->
@@ -64,21 +96,23 @@ fun DeviceSettingsScreen(
is DeviceSettingsScreenContract.Action.ShowError -> { is DeviceSettingsScreenContract.Action.ShowError -> {
snackbarHostState.showSnackbar(action.message) snackbarHostState.showSnackbar(action.message)
} }
is DeviceSettingsScreenContract.Action.ShowSuccess -> { is DeviceSettingsScreenContract.Action.ShowSuccess -> { /* no snackbar for success */ }
snackbarHostState.showSnackbar(toastDoneText)
}
is DeviceSettingsScreenContract.Action.OpenLocation -> onOpenLocation() is DeviceSettingsScreenContract.Action.OpenLocation -> onOpenLocation()
is DeviceSettingsScreenContract.Action.ShowOnMap -> onShowOnMap() is DeviceSettingsScreenContract.Action.ShowOnMap -> onShowOnMap()
is DeviceSettingsScreenContract.Action.OpenInfoUrl -> {
context.startActivity(Intent(Intent.ACTION_VIEW, INFO_URL.toUri()))
}
} }
} }
} }
Scaffold( Scaffold(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxWidth(),
topBar = { topBar = {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.statusBarsPadding()
.background(LegacyBackground) .background(LegacyBackground)
.padding(16.dp), .padding(16.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@@ -90,26 +124,42 @@ fun DeviceSettingsScreen(
) )
} }
Text( Text(
text = stringResource(R.string.title_device), text = stringResource(R.string.title_device_config),
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge.copy(
modifier = Modifier.padding(start = 8.dp) fontSize = 24.sp,
fontWeight = FontWeight.Medium
),
modifier = Modifier
.weight(1f)
.padding(start = 8.dp),
textAlign = androidx.compose.ui.text.style.TextAlign.Center
) )
Spacer(modifier = Modifier.size(48.dp))
} }
}, },
snackbarHost = { SnackbarHost(snackbarHostState) } snackbarHost = {
SnackbarHost(
hostState = snackbarHostState,
snackbar = { data ->
Snackbar(
snackbarData = data,
containerColor = MaterialTheme.colorScheme.inverseSurface,
contentColor = MaterialTheme.colorScheme.inverseOnSurface
)
}
)
}
) { innerPadding -> ) { innerPadding ->
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxWidth()
.padding(innerPadding) .padding(innerPadding)
) { ) {
when { when {
uiState.isLoading -> { uiState.device == null && uiState.isLoading -> {
CircularProgressIndicator( CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
modifier = Modifier.align(Alignment.Center)
)
} }
uiState.device == null -> { uiState.device == null && !uiState.isLoading -> {
Text( Text(
text = stringResource(R.string.text_nothing_to_show), text = stringResource(R.string.text_nothing_to_show),
modifier = Modifier modifier = Modifier
@@ -118,26 +168,48 @@ fun DeviceSettingsScreen(
) )
} }
else -> { else -> {
DeviceSettingsContent( Box(modifier = Modifier.fillMaxWidth()) {
state = uiState, DeviceSettingsContent(
onEvent = viewModel::onEvent, state = uiState,
deviceName = uiState.device?.name ?: "" onEvent = viewModel::onEvent
) )
if (uiState.isLoading) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.3f)),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
}
} }
} }
} }
} }
} }
private val PreviewHeaderColor = Color(0xFF1F5DA5)
private val PreviewGreyColor = Color(0x61000000)
private val PreviewBlack54 = Color(0x8A000000)
private val PreviewOrange = Color(0xFFFF6F00)
@Composable @Composable
private fun DeviceSettingsContent( private fun DeviceSettingsContent(
state: DeviceSettingsScreenContract.State, state: DeviceSettingsScreenContract.State,
onEvent: (DeviceSettingsScreenContract.Event) -> Unit, onEvent: (DeviceSettingsScreenContract.Event) -> Unit,
deviceName: String labelColorOverride: Color? = null,
greyColorOverride: Color? = null,
iconResOverride: Int? = null
) { ) {
val device = state.device!! val device = state.device!!
var showRenameDialog by remember { mutableStateOf(false) } var showRenameDialog by remember { mutableStateOf(false) }
var renameText by remember(deviceName) { mutableStateOf(deviceName) } var showConfigComingSoonDialog by remember { mutableStateOf(false) }
var renameText by remember(device.name) { mutableStateOf(device.name) }
val headerColor = labelColorOverride ?: colorResource(R.color.headerColor)
val onSurfaceVariant = MaterialTheme.colorScheme.onSurfaceVariant
val greyColor = greyColorOverride ?: colorResource(R.color.black38)
if (showRenameDialog) { if (showRenameDialog) {
AlertDialog( AlertDialog(
@@ -178,9 +250,23 @@ private fun DeviceSettingsContent(
} }
) )
} }
if (showConfigComingSoonDialog) {
AlertDialog(
onDismissRequest = { showConfigComingSoonDialog = false },
title = { Text(stringResource(R.string.coming_soon)) },
text = { Text(stringResource(R.string.coming_soon)) },
confirmButton = {
androidx.compose.material3.TextButton(onClick = { showConfigComingSoonDialog = false }) {
Text(stringResource(R.string.button_ok))
}
}
)
}
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
val statusIcon = when (device.toOnlineStatus()) { val statusIcon = when {
OnlineStatus.Online -> R.drawable.device_chip_online state.isConnected -> R.drawable.device_chip_online
else -> R.drawable.device_chip_offline else -> R.drawable.device_chip_offline
} }
@@ -189,115 +275,458 @@ private fun DeviceSettingsContent(
.fillMaxWidth() .fillMaxWidth()
.verticalScroll(scrollState) .verticalScroll(scrollState)
.background(LegacyBackground) .background(LegacyBackground)
.padding(16.dp)
) { ) {
HorizontalDivider(color = LegacyBlack12)
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.fillMaxWidth()
.height(72.dp)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
painter = painterResource(R.drawable.ic_chip), painter = painterResource(
iconResOverride ?: device.model.iconRes()
),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(48.dp) modifier = Modifier.size(40.dp)
) )
Spacer(modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.size(16.dp))
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
Text( Text(
text = device.name, text = device.name,
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.bodyLarge.copy(fontSize = 16.sp)
) )
Text( Text(
text = device.model, text = device.model.displayName,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium.copy(fontSize = 14.sp),
color = Color.Gray color = greyColor
) )
} }
Icon(
painter = painterResource(statusIcon),
contentDescription = device.toOnlineStatus().toString(),
modifier = Modifier.size(24.dp)
)
}
if (state.isPendingSync) {
Text( Text(
text = stringResource(R.string.text_pending_sync), text = if (state.isConnected) stringResource(R.string.text_device_connected) else "",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.labelSmall.copy(fontSize = 11.sp),
color = Color.Gray, color = greyColor
modifier = Modifier.padding(top = 8.dp)
) )
if (!state.isConnected) {
Icon(
painter = painterResource(statusIcon),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
}
} }
HorizontalDivider(color = LegacyBlack12)
Spacer(modifier = Modifier.height(24.dp)) ConfigRow(
label = stringResource(R.string.text_device_name),
AirMQButton( value = device.name,
text = stringResource(R.string.button_rename),
onClick = { showRenameDialog = true }, onClick = { showRenameDialog = true },
style = AirMQButtonStyle.Outlined, trailingIcon = R.drawable.ic_edit,
modifier = Modifier.fillMaxWidth() iconTint = (labelColorOverride?.let { PreviewBlack54 } ?: colorResource(R.color.black54)),
labelColor = headerColor
) )
Spacer(modifier = Modifier.height(12.dp)) ConfigRowReadOnly(
label = stringResource(R.string.text_device_id),
value = device.id,
labelColor = headerColor
)
if (device.hasLocation()) { ConfigRowReadOnly(
Text( label = stringResource(R.string.text_device_ip),
text = stringResource( value = device.deviceAddress,
labelColor = headerColor
)
ConfigRowWithIndicator(
label = stringResource(R.string.text_device_config_version),
value = device.configVersion,
showIndicator = state.configVersionNeedsUpdate,
indicatorColor = labelColorOverride?.let { PreviewOrange } ?: colorResource(R.color.colorOrange),
onClick = { if (state.configVersionNeedsUpdate) showConfigComingSoonDialog = true },
trailingIcon = if (state.configVersionNeedsUpdate) {
R.drawable.ic_upload
} else null,
iconTint = labelColorOverride?.let { PreviewBlack54 } ?: colorResource(R.color.black54),
labelColor = headerColor,
indicatorDrawableRes = null
)
ConfigRowReadOnly(
label = stringResource(R.string.text_device_wifi),
value = state.wifiSsid,
labelColor = headerColor
)
HorizontalDivider(color = LegacyBlack12)
ConfigRowWithIndicator(
label = stringResource(R.string.text_device_location),
value = if (device.hasLocation()) {
stringResource(
R.string.text_device_location_set, R.string.text_device_location_set,
device.latitude!!, roundLocation(device.latitude!!),
device.longitude!! roundLocation(device.longitude!!)
),
style = MaterialTheme.typography.bodyMedium
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
AirMQButton(
text = stringResource(R.string.title_location),
onClick = { onEvent(DeviceSettingsScreenContract.Event.OpenLocationClicked) },
style = AirMQButtonStyle.Outlined,
modifier = Modifier.weight(1f)
) )
AirMQButton( } else {
text = stringResource(R.string.button_view_on_map), stringResource(R.string.text_device_location_unset)
onClick = { onEvent(DeviceSettingsScreenContract.Event.ShowOnMapClicked) }, },
style = AirMQButtonStyle.Outlined, showIndicator = !device.hasLocation(),
modifier = Modifier.weight(1f) indicatorColor = labelColorOverride?.let { PreviewOrange } ?: colorResource(R.color.colorOrange),
) onClick = { onEvent(DeviceSettingsScreenContract.Event.OpenLocationClicked) },
} trailingIcon = if (device.hasLocation()) null else R.drawable.ic_edit,
} else { iconTint = labelColorOverride?.let { PreviewBlack54 } ?: colorResource(R.color.black54),
AirMQButton( labelColor = headerColor,
text = stringResource(R.string.button_register), indicatorDrawableRes = null
onClick = { onEvent(DeviceSettingsScreenContract.Event.OpenLocationClicked) }, )
style = AirMQButtonStyle.Outlined,
modifier = Modifier.fillMaxWidth()
)
}
Spacer(modifier = Modifier.height(12.dp)) ToggleRow(
title = stringResource(R.string.text_device_narodmon_title),
subtitle = stringResource(R.string.text_device_narodmon),
checked = device.isNarodmonOn,
onCheckedChange = { onEvent(DeviceSettingsScreenContract.Event.NarodmonToggled(it)) },
onInfoClick = { onEvent(DeviceSettingsScreenContract.Event.NarodmonInfoClicked) },
labelColor = headerColor,
infoIconTint = labelColorOverride?.let { PreviewBlack54 } ?: colorResource(R.color.black54),
infoIconResOverride = null
)
Row( ToggleRow(
modifier = Modifier.fillMaxWidth(), title = stringResource(R.string.text_device_luftdata_title),
verticalAlignment = Alignment.CenterVertically, subtitle = stringResource(R.string.text_device_luftdata),
horizontalArrangement = Arrangement.SpaceBetween checked = device.isLuftdataOn,
) { onCheckedChange = { onEvent(DeviceSettingsScreenContract.Event.LuftdataToggled(it)) },
Text( onInfoClick = { onEvent(DeviceSettingsScreenContract.Event.LuftdataInfoClicked) },
text = stringResource(R.string.text_data_sharing), labelColor = headerColor,
style = MaterialTheme.typography.bodyLarge infoIconTint = labelColorOverride?.let { PreviewBlack54 } ?: colorResource(R.color.black54),
) infoIconResOverride = null
Switch( )
checked = device.dataSharingEnabled,
onCheckedChange = { onEvent(DeviceSettingsScreenContract.Event.DataSharingToggled(it)) }
)
}
Spacer(modifier = Modifier.height(12.dp)) VisibilityRow(
label = stringResource(R.string.text_device_visibility),
visibilityText = when {
!device.hasLocation() -> stringResource(R.string.text_device_visibility_not_registered)
device.isPublic -> stringResource(R.string.text_device_visibility_visible)
else -> stringResource(R.string.text_device_visibility_private)
},
publishButtonText = if (device.isPublic) {
stringResource(R.string.button_hide)
} else {
stringResource(R.string.button_publish)
},
isPublishButtonEnabled = device.hasLocation(),
onPublishClick = {
if (device.isPublic) {
onEvent(DeviceSettingsScreenContract.Event.VisibilityHideClicked)
} else {
onEvent(DeviceSettingsScreenContract.Event.VisibilityPublishClicked)
}
},
labelColor = greyColor
)
Spacer(modifier = Modifier.height(20.dp))
AirMQButton( AirMQButton(
text = stringResource(R.string.button_firmware_update), text = stringResource(R.string.button_view_on_map),
onClick = { onEvent(DeviceSettingsScreenContract.Event.FirmwareUpdateClicked) }, onClick = { onEvent(DeviceSettingsScreenContract.Event.ShowOnMapClicked) },
style = AirMQButtonStyle.Gradient, style = AirMQButtonStyle.Contained,
modifier = Modifier.fillMaxWidth() enabled = device.hasLocation(),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 80.dp)
)
Spacer(modifier = Modifier.height(20.dp))
}
}
@Composable
private fun VisibilityRow(
label: String,
visibilityText: String,
publishButtonText: String,
isPublishButtonEnabled: Boolean,
onPublishClick: () -> Unit,
labelColor: Color
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = label,
style = MaterialTheme.typography.bodyMedium.copy(fontSize = 14.sp),
color = labelColor
)
Text(
text = visibilityText,
style = MaterialTheme.typography.bodyLarge.copy(fontSize = 16.sp)
)
}
AirMQButton(
text = publishButtonText,
onClick = onPublishClick,
enabled = isPublishButtonEnabled,
style = AirMQButtonStyle.Text,
modifier = Modifier.width(170.dp)
)
}
}
@Composable
private fun ConfigRow(
label: String,
value: String,
onClick: () -> Unit,
trailingIcon: Int,
iconTint: Color,
labelColor: Color
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.clickable(onClick = onClick)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = label,
style = MaterialTheme.typography.labelMedium.copy(fontSize = 12.sp, fontWeight = FontWeight.Medium),
color = labelColor
)
Text(
text = value,
style = MaterialTheme.typography.bodyMedium.copy(fontSize = 14.sp)
)
}
Icon(
painter = painterResource(trailingIcon),
contentDescription = null,
modifier = Modifier.size(24.dp),
tint = iconTint
)
}
}
@Composable
private fun ConfigRowReadOnly(
label: String,
value: String,
labelColor: Color
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
text = label,
style = MaterialTheme.typography.labelMedium.copy(fontSize = 12.sp, fontWeight = FontWeight.Medium),
color = labelColor
)
Text(
text = value,
style = MaterialTheme.typography.bodyMedium.copy(fontSize = 14.sp)
)
}
}
}
@Composable
private fun ConfigRowWithIndicator(
label: String,
value: String,
showIndicator: Boolean,
indicatorColor: Color,
onClick: () -> Unit,
trailingIcon: Int?,
iconTint: Color,
labelColor: Color,
indicatorDrawableRes: Int? = null
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.clickable(onClick = onClick)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = label,
style = MaterialTheme.typography.labelMedium.copy(fontSize = 12.sp, fontWeight = FontWeight.Medium),
color = labelColor
)
if (showIndicator) {
Spacer(modifier = Modifier.size(5.dp))
Icon(
painter = painterResource(indicatorDrawableRes ?: R.drawable.circle_indicator),
contentDescription = null,
modifier = Modifier.size(10.dp),
tint = indicatorColor
)
}
}
Text(
text = value,
style = MaterialTheme.typography.bodyMedium.copy(fontSize = 14.sp)
)
}
if (trailingIcon != null) {
Icon(
painter = painterResource(trailingIcon),
contentDescription = null,
modifier = Modifier.size(24.dp),
tint = iconTint
)
}
}
}
@Composable
private fun ToggleRow(
title: String,
subtitle: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
onInfoClick: () -> Unit,
labelColor: Color,
infoIconTint: Color,
infoIconResOverride: Int? = null
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.clickable { onCheckedChange(!checked) }
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
Column {
Text(
text = title,
style = MaterialTheme.typography.labelMedium.copy(fontSize = 12.sp, fontWeight = FontWeight.Medium),
color = labelColor
)
Text(
text = subtitle,
style = MaterialTheme.typography.bodyMedium.copy(fontSize = 14.sp)
)
}
IconButton(
onClick = onInfoClick,
modifier = Modifier
.size(32.dp)
.padding(8.dp)
) {
Icon(
painter = painterResource(infoIconResOverride ?: R.drawable.ic_pref_info),
contentDescription = null,
tint = infoIconTint
)
}
}
Switch(
checked = checked,
onCheckedChange = onCheckedChange
)
}
}
@Preview(showBackground = true, name = "Device config no location")
@Composable
private fun PreviewDeviceSettingsContentDefault() {
AirMQTheme {
DeviceSettingsContent(
state = DeviceSettingsScreenContract.previewState(),
onEvent = {},
labelColorOverride = PreviewHeaderColor,
greyColorOverride = PreviewGreyColor,
iconResOverride = AndroidR.drawable.ic_menu_mylocation
)
}
}
@Preview(showBackground = true, name = "Device config with location")
@Composable
private fun PreviewDeviceSettingsContentWithLocation() {
AirMQTheme {
DeviceSettingsContent(
state = DeviceSettingsScreenContract.previewState(
device = DeviceSettingsScreenContract.previewDevice(hasLocation = true, isPublic = false)
),
onEvent = {},
labelColorOverride = PreviewHeaderColor,
greyColorOverride = PreviewGreyColor,
iconResOverride = AndroidR.drawable.ic_menu_mylocation
)
}
}
@Preview(showBackground = true, name = "Device config public visibility")
@Composable
private fun PreviewDeviceSettingsContentPublicVisibility() {
AirMQTheme {
DeviceSettingsContent(
state = DeviceSettingsScreenContract.previewState(
device = DeviceSettingsScreenContract.previewDevice(hasLocation = true, isPublic = true)
),
onEvent = {},
labelColorOverride = PreviewHeaderColor,
greyColorOverride = PreviewGreyColor,
iconResOverride = AndroidR.drawable.ic_menu_mylocation
)
}
}
@Preview(showBackground = true, name = "Device config config needs update")
@Composable
private fun PreviewDeviceSettingsContentConfigNeedsUpdate() {
AirMQTheme {
DeviceSettingsContent(
state = DeviceSettingsScreenContract.previewState(
configVersionNeedsUpdate = true
),
onEvent = {},
labelColorOverride = PreviewHeaderColor,
greyColorOverride = PreviewGreyColor,
iconResOverride = AndroidR.drawable.ic_menu_mylocation
)
}
}
@Preview(showBackground = true, name = "Device config Narodmon/Luftdata on")
@Composable
private fun PreviewDeviceSettingsContentTogglesOn() {
AirMQTheme {
DeviceSettingsContent(
state = DeviceSettingsScreenContract.previewState(
device = DeviceSettingsScreenContract.previewDevice(
isNarodmonOn = true,
isLuftdataOn = true
)
),
onEvent = {},
labelColorOverride = PreviewHeaderColor,
greyColorOverride = PreviewGreyColor,
iconResOverride = AndroidR.drawable.ic_menu_mylocation
) )
} }
} }

View File

@@ -1,14 +1,65 @@
package org.db3.airmq.features.device package org.db3.airmq.features.device
import org.db3.airmq.sdk.device.domain.Device import org.db3.airmq.sdk.device.domain.Device
import org.db3.airmq.sdk.device.domain.DeviceModel
import org.db3.airmq.sdk.device.domain.OnlineFreshness
object DeviceSettingsScreenContract { object DeviceSettingsScreenContract {
fun previewState(
device: Device? = previewDevice(),
isLoading: Boolean = false,
isPendingSync: Boolean = false,
configVersionNeedsUpdate: Boolean = false,
isConnected: Boolean = true,
wifiSsid: String = "Захарова 42"
): State = State(
device = device,
isLoading = isLoading,
isPendingSync = isPendingSync,
configVersionNeedsUpdate = configVersionNeedsUpdate,
isConnected = isConnected,
wifiSsid = wifiSsid
)
fun previewDevice(
id: String = "12312",
name: String = "Захарова 42",
model: DeviceModel = DeviceModel.Mobile,
deviceAddress: String = "127.0.0.1",
configVersion: String = "42",
isNarodmonOn: Boolean = false,
isLuftdataOn: Boolean = false,
hasLocation: Boolean = false,
isPublic: Boolean = false
): Device = Device(
id = id,
name = name,
model = model,
firmwareVersion = "1.0",
deviceAddress = deviceAddress,
configVersion = configVersion,
isNarodmonOn = isNarodmonOn,
isLuftdataOn = isLuftdataOn,
locationId = null,
latitude = if (hasLocation) 53.9 else null,
longitude = if (hasLocation) 27.5 else null,
isPublic = isPublic,
city = null,
dataSharingEnabled = false,
isOnline = true,
onlineFreshness = OnlineFreshness.Fresh,
ownerId = null
)
data class State( data class State(
val device: Device? = null, val device: Device? = null,
val isLoading: Boolean = false, val isLoading: Boolean = false,
val errorMessage: String? = null, val errorMessage: String? = null,
val isPendingSync: Boolean = false val isPendingSync: Boolean = false,
val configVersionNeedsUpdate: Boolean = false,
val isConnected: Boolean = false,
val wifiSsid: String = ""
) )
sealed interface Action { sealed interface Action {
@@ -16,15 +67,23 @@ object DeviceSettingsScreenContract {
data object ShowSuccess : Action data object ShowSuccess : Action
data object OpenLocation : Action data object OpenLocation : Action
data object ShowOnMap : Action data object ShowOnMap : Action
data object OpenInfoUrl : Action
} }
sealed interface Event { sealed interface Event {
data class RenameSubmitted(val name: String) : Event data class RenameSubmitted(val name: String) : Event
data object RenameRowClicked : Event
data object ConfigUpdateClicked : Event
data class NarodmonToggled(val enabled: Boolean) : Event
data class LuftdataToggled(val enabled: Boolean) : Event
data object NarodmonInfoClicked : Event
data object LuftdataInfoClicked : Event
data class LocationSet(val latitude: Double, val longitude: Double) : Event data class LocationSet(val latitude: Double, val longitude: Double) : Event
data object LocationRemoved : Event data object LocationRemoved : Event
data class DataSharingToggled(val enabled: Boolean) : Event
data object FirmwareUpdateClicked : Event data object FirmwareUpdateClicked : Event
data object OpenLocationClicked : Event data object OpenLocationClicked : Event
data object ShowOnMapClicked : Event data object ShowOnMapClicked : Event
data object VisibilityPublishClicked : Event
data object VisibilityHideClicked : Event
} }
} }

View File

@@ -1,9 +1,12 @@
package org.db3.airmq.features.device package org.db3.airmq.features.device
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
@@ -16,23 +19,32 @@ import kotlinx.coroutines.launch
import org.db3.airmq.features.device.usecases.GetDeviceUseCase import org.db3.airmq.features.device.usecases.GetDeviceUseCase
import org.db3.airmq.features.device.usecases.ObservePendingSyncUseCase import org.db3.airmq.features.device.usecases.ObservePendingSyncUseCase
import org.db3.airmq.features.device.usecases.RenameDeviceUseCase import org.db3.airmq.features.device.usecases.RenameDeviceUseCase
import org.db3.airmq.features.device.usecases.SetDataSharingUseCase
import org.db3.airmq.features.device.usecases.SetDeviceLocationUseCase import org.db3.airmq.features.device.usecases.SetDeviceLocationUseCase
import org.db3.airmq.features.device.usecases.SetDeviceVisibilityUseCase
import org.db3.airmq.features.device.usecases.SetLuftdataUseCase
import org.db3.airmq.features.device.usecases.SetNarodmonUseCase
import org.db3.airmq.features.device.usecases.TriggerFirmwareUpdateUseCase import org.db3.airmq.features.device.usecases.TriggerFirmwareUpdateUseCase
import javax.inject.Inject import org.db3.airmq.sdk.device.domain.Device
import org.db3.airmq.sdk.device.domain.DeviceModel
import org.db3.airmq.sdk.device.domain.OnlineFreshness
@HiltViewModel @HiltViewModel(assistedFactory = DeviceSettingsViewModel.Factory::class)
class DeviceSettingsViewModel @Inject constructor( class DeviceSettingsViewModel @AssistedInject constructor(
savedStateHandle: SavedStateHandle, @Assisted private val deviceId: String,
private val getDeviceUseCase: GetDeviceUseCase, private val getDeviceUseCase: GetDeviceUseCase,
private val renameDeviceUseCase: RenameDeviceUseCase, private val renameDeviceUseCase: RenameDeviceUseCase,
private val setDeviceLocationUseCase: SetDeviceLocationUseCase, private val setDeviceLocationUseCase: SetDeviceLocationUseCase,
private val setDataSharingUseCase: SetDataSharingUseCase, private val setNarodmonUseCase: SetNarodmonUseCase,
private val setLuftdataUseCase: SetLuftdataUseCase,
private val setDeviceVisibilityUseCase: SetDeviceVisibilityUseCase,
private val triggerFirmwareUpdateUseCase: TriggerFirmwareUpdateUseCase, private val triggerFirmwareUpdateUseCase: TriggerFirmwareUpdateUseCase,
private val observePendingSyncUseCase: ObservePendingSyncUseCase private val observePendingSyncUseCase: ObservePendingSyncUseCase
) : ViewModel() { ) : ViewModel() {
private val deviceId: String = checkNotNull(savedStateHandle.get<String>("deviceId")) @AssistedFactory
interface Factory {
fun create(deviceId: String): DeviceSettingsViewModel
}
private val _uiState = MutableStateFlow(DeviceSettingsScreenContract.State()) private val _uiState = MutableStateFlow(DeviceSettingsScreenContract.State())
val uiState: StateFlow<DeviceSettingsScreenContract.State> = _uiState.asStateFlow() val uiState: StateFlow<DeviceSettingsScreenContract.State> = _uiState.asStateFlow()
@@ -48,22 +60,60 @@ class DeviceSettingsViewModel @Inject constructor(
) { device, hasPending -> ) { device, hasPending ->
device to hasPending device to hasPending
}.collect { (device, hasPending) -> }.collect { (device, hasPending) ->
val displayDevice = device ?: stubDevice()
val configVersionNeedsUpdate = deriveConfigVersionNeedsUpdate(displayDevice)
val isConnected = true // Stub: always connected for UI testing
_uiState.update { _uiState.update {
it.copy( it.copy(
device = device, device = displayDevice,
isPendingSync = hasPending isPendingSync = hasPending,
configVersionNeedsUpdate = configVersionNeedsUpdate,
isConnected = isConnected,
wifiSsid = if (device == null) "Захарова 42" else it.wifiSsid.takeIf { s -> s.isNotEmpty() } ?: ""
) )
} }
} }
} }
} }
private fun stubDevice(): Device = Device(
id = "12312",
name = "Захарова 42",
model = DeviceModel.Mobile,
firmwareVersion = "1.0",
deviceAddress = "127.0.0.1",
configVersion = "42",
isNarodmonOn = false,
isLuftdataOn = false,
locationId = null,
latitude = null,
longitude = null,
isPublic = false,
city = null,
dataSharingEnabled = false,
isOnline = true,
onlineFreshness = OnlineFreshness.Fresh,
ownerId = null
)
private fun deriveConfigVersionNeedsUpdate(device: Device): Boolean {
// Stub: compare configVersion to expected (e.g. 42); for testing assume up-to-date
return false
}
fun onEvent(event: DeviceSettingsScreenContract.Event) { fun onEvent(event: DeviceSettingsScreenContract.Event) {
when (event) { when (event) {
is DeviceSettingsScreenContract.Event.RenameSubmitted -> renameDevice(event.name) is DeviceSettingsScreenContract.Event.RenameSubmitted -> renameDevice(event.name)
is DeviceSettingsScreenContract.Event.RenameRowClicked -> { /* UI opens dialog */ }
is DeviceSettingsScreenContract.Event.ConfigUpdateClicked -> configUpdateStub()
is DeviceSettingsScreenContract.Event.NarodmonToggled -> setNarodmon(event.enabled)
is DeviceSettingsScreenContract.Event.LuftdataToggled -> setLuftdata(event.enabled)
is DeviceSettingsScreenContract.Event.NarodmonInfoClicked,
is DeviceSettingsScreenContract.Event.LuftdataInfoClicked -> _actions.tryEmit(
DeviceSettingsScreenContract.Action.OpenInfoUrl
)
is DeviceSettingsScreenContract.Event.LocationSet -> setLocation(event.latitude, event.longitude) is DeviceSettingsScreenContract.Event.LocationSet -> setLocation(event.latitude, event.longitude)
is DeviceSettingsScreenContract.Event.LocationRemoved -> removeLocation() is DeviceSettingsScreenContract.Event.LocationRemoved -> removeLocation()
is DeviceSettingsScreenContract.Event.DataSharingToggled -> setDataSharing(event.enabled)
is DeviceSettingsScreenContract.Event.FirmwareUpdateClicked -> triggerFirmwareUpdate() is DeviceSettingsScreenContract.Event.FirmwareUpdateClicked -> triggerFirmwareUpdate()
is DeviceSettingsScreenContract.Event.OpenLocationClicked -> _actions.tryEmit( is DeviceSettingsScreenContract.Event.OpenLocationClicked -> _actions.tryEmit(
DeviceSettingsScreenContract.Action.OpenLocation DeviceSettingsScreenContract.Action.OpenLocation
@@ -71,12 +121,24 @@ class DeviceSettingsViewModel @Inject constructor(
is DeviceSettingsScreenContract.Event.ShowOnMapClicked -> _actions.tryEmit( is DeviceSettingsScreenContract.Event.ShowOnMapClicked -> _actions.tryEmit(
DeviceSettingsScreenContract.Action.ShowOnMap DeviceSettingsScreenContract.Action.ShowOnMap
) )
is DeviceSettingsScreenContract.Event.VisibilityPublishClicked -> setVisibility(true)
is DeviceSettingsScreenContract.Event.VisibilityHideClicked -> setVisibility(false)
}
}
private fun configUpdateStub() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
delay(800)
_uiState.update { it.copy(isLoading = false) }
_actions.tryEmit(DeviceSettingsScreenContract.Action.ShowSuccess)
} }
} }
private fun renameDevice(name: String) { private fun renameDevice(name: String) {
viewModelScope.launch { viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, errorMessage = null) } _uiState.update { it.copy(isLoading = true, errorMessage = null) }
delay(500) // Stub simulation
val result = renameDeviceUseCase(deviceId, name) val result = renameDeviceUseCase(deviceId, name)
_uiState.update { it.copy(isLoading = false) } _uiState.update { it.copy(isLoading = false) }
if (result.isSuccess) { if (result.isSuccess) {
@@ -91,6 +153,42 @@ class DeviceSettingsViewModel @Inject constructor(
} }
} }
private fun setNarodmon(enabled: Boolean) {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
delay(300)
val result = setNarodmonUseCase(deviceId, enabled)
_uiState.update { it.copy(isLoading = false) }
if (result.isSuccess) {
_actions.tryEmit(DeviceSettingsScreenContract.Action.ShowSuccess)
} else {
_actions.tryEmit(
DeviceSettingsScreenContract.Action.ShowError(
result.exceptionOrNull()?.message ?: "Failed to update Narodmon"
)
)
}
}
}
private fun setLuftdata(enabled: Boolean) {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
delay(300)
val result = setLuftdataUseCase(deviceId, enabled)
_uiState.update { it.copy(isLoading = false) }
if (result.isSuccess) {
_actions.tryEmit(DeviceSettingsScreenContract.Action.ShowSuccess)
} else {
_actions.tryEmit(
DeviceSettingsScreenContract.Action.ShowError(
result.exceptionOrNull()?.message ?: "Failed to update Sensor.community"
)
)
}
}
}
private fun setLocation(latitude: Double, longitude: Double) { private fun setLocation(latitude: Double, longitude: Double) {
viewModelScope.launch { viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, errorMessage = null) } _uiState.update { it.copy(isLoading = true, errorMessage = null) }
@@ -125,17 +223,16 @@ class DeviceSettingsViewModel @Inject constructor(
} }
} }
private fun setDataSharing(enabled: Boolean) { private fun setVisibility(isPublic: Boolean) {
viewModelScope.launch { viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, errorMessage = null) } _uiState.update { it.copy(isLoading = true, errorMessage = null) }
val result = setDataSharingUseCase(deviceId, enabled) delay(300)
val result = setDeviceVisibilityUseCase(deviceId, isPublic)
_uiState.update { it.copy(isLoading = false) } _uiState.update { it.copy(isLoading = false) }
if (result.isSuccess) { if (result.isFailure) {
_actions.tryEmit(DeviceSettingsScreenContract.Action.ShowSuccess)
} else {
_actions.tryEmit( _actions.tryEmit(
DeviceSettingsScreenContract.Action.ShowError( DeviceSettingsScreenContract.Action.ShowError(
result.exceptionOrNull()?.message ?: "Failed to update data sharing" result.exceptionOrNull()?.message ?: "Failed to update visibility"
) )
) )
} }

View File

@@ -0,0 +1,15 @@
package org.db3.airmq.features.device.usecases
import org.db3.airmq.sdk.device.domain.DeviceRepository
import javax.inject.Inject
/**
* Use case to set device visibility (publish/hide location on map).
* Only effective when device has location.
*/
class SetDeviceVisibilityUseCase @Inject constructor(
private val repository: DeviceRepository
) {
suspend operator fun invoke(deviceId: String, isPublic: Boolean): Result<Unit> =
repository.setVisibility(deviceId, isPublic)
}

View File

@@ -0,0 +1,14 @@
package org.db3.airmq.features.device.usecases
import org.db3.airmq.sdk.device.domain.DeviceRepository
import javax.inject.Inject
/**
* Use case to enable or disable Sensor.community (Luftdata) data sharing for a device.
*/
class SetLuftdataUseCase @Inject constructor(
private val repository: DeviceRepository
) {
suspend operator fun invoke(deviceId: String, enabled: Boolean): Result<Unit> =
repository.setLuftdata(deviceId, enabled)
}

View File

@@ -0,0 +1,14 @@
package org.db3.airmq.features.device.usecases
import org.db3.airmq.sdk.device.domain.DeviceRepository
import javax.inject.Inject
/**
* Use case to enable or disable Narodmon.ru data sharing for a device.
*/
class SetNarodmonUseCase @Inject constructor(
private val repository: DeviceRepository
) {
suspend operator fun invoke(deviceId: String, enabled: Boolean): Result<Unit> =
repository.setNarodmon(deviceId, enabled)
}

View File

@@ -108,7 +108,7 @@ class ManageViewModel @Inject constructor(
return DeviceItem( return DeviceItem(
id = id, id = id,
name = name, name = name,
extra = model.ifEmpty { "mobile" }, extra = model.displayName,
status = statusText, status = statusText,
hasLocation = hasLocation() hasLocation = hasLocation()
) )

View File

@@ -13,6 +13,8 @@ val LegacyOnBackground = Color(0xFF000000)
val LegacyOnSurface = Color(0xFF000000) val LegacyOnSurface = Color(0xFF000000)
val LegacyOutline = Color(0x61000000) val LegacyOutline = Color(0x61000000)
val LegacyOutlineLight = Color(0x3B000000) val LegacyOutlineLight = Color(0x3B000000)
val LegacyBlack12 = Color(0x1F000000)
val LegacyBlack38 = Color(0x61000000)
val LegacyNavSelected = Color(0xFFFFFFFF) val LegacyNavSelected = Color(0xFFFFFFFF)
val LegacyNavUnselected = Color(0x8AFFFFFF) val LegacyNavUnselected = Color(0x8AFFFFFF)
val LegacyNavContainer = Color(0xFF295989) val LegacyNavContainer = Color(0xFF295989)

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="10dp"
android:height="10dp"
android:viewportWidth="10"
android:viewportHeight="10">
<path
android:fillColor="@color/sensorOrange"
android:pathData="M5,5m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"/>
</vector>

View File

@@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:fillColor="#FF000000"
android:pathData="M14.29,11.28C1,11.28 0,12.28 0,25.58c0,13.29 1,14.29 14.29,14.29c13.3,0 14.3,-1 14.3,-14.29C28.59,12.28 27.59,11.28 14.29,11.28zM24.56,18.44c-0.78,0.79 -2.07,0.79 -2.86,0c-0.79,-0.79 -0.79,-2.07 0,-2.86c0.79,-0.79 2.08,-0.79 2.86,0C25.35,16.37 25.35,17.65 24.56,18.44z"
android:strokeAlpha="0.54"
android:fillAlpha="0.54"/>
<path
android:fillColor="#FF000000"
android:pathData="M39.85,18.81v0.05c0,0.03 -0.01,0.05 -0.01,0.08c0,0.02 -0.01,0.04 -0.02,0.07c-0.17,0.76 -0.94,1.23 -1.7,1.05c-0.07,-0.01 -0.13,-0.04 -0.19,-0.06c-0.56,-0.22 -0.92,-0.76 -0.9,-1.35v-0.08c0,-0.01 0,-0.01 0,-0.01c0.01,-0.05 0.02,-0.1 0.02,-0.15c0.31,-2.26 0.03,-4.5 -0.73,-6.54c-0.12,-0.32 -0.25,-0.64 -0.4,-0.96c-0.06,-0.13 -0.12,-0.26 -0.19,-0.38c-0.04,-0.1 -0.09,-0.19 -0.14,-0.28c-0.05,-0.1 -0.1,-0.2 -0.16,-0.29c-0.01,-0.03 -0.02,-0.05 -0.03,-0.07c-0.05,-0.07 -0.09,-0.15 -0.14,-0.23c-0.04,-0.08 -0.1,-0.17 -0.15,-0.25s-0.1,-0.16 -0.15,-0.24C34.91,9.08 34.85,9 34.8,8.92c-0.33,-0.48 -0.68,-0.94 -1.07,-1.37c-0.06,-0.07 -0.12,-0.14 -0.19,-0.21c-0.14,-0.15 -0.29,-0.3 -0.44,-0.45c-0.05,-0.06 -0.11,-0.12 -0.17,-0.17c-0.07,-0.07 -0.14,-0.13 -0.21,-0.2c-0.1,-0.09 -0.2,-0.17 -0.29,-0.26C32.32,6.17 32.22,6.09 32.11,6C32,5.91 31.9,5.83 31.79,5.75c-0.86,-0.66 -1.81,-1.22 -2.83,-1.66c-0.12,-0.06 -0.25,-0.11 -0.38,-0.16c-0.12,-0.05 -0.25,-0.1 -0.38,-0.15c-0.11,-0.04 -0.23,-0.08 -0.34,-0.11c-0.02,-0.01 -0.03,-0.02 -0.05,-0.02c-1.36,-0.46 -2.81,-0.7 -4.28,-0.7c-0.79,0 -1.58,0.07 -2.37,0.21c-0.02,0 -0.05,0.01 -0.07,0.01c-0.68,0.1 -1.36,-0.31 -1.56,-0.99l-0.02,-0.07c-0.19,-0.74 0.25,-1.5 1,-1.71c0.03,0 0.06,-0.01 0.09,-0.02c0.03,0 0.06,-0.01 0.08,-0.01c2.77,-0.49 5.53,-0.24 8.06,0.61c0.03,0 0.05,0.01 0.07,0.02c0.13,0.04 0.26,0.09 0.38,0.13c0.13,0.05 0.27,0.1 0.4,0.16c0.03,0.01 0.07,0.02 0.1,0.04c1.98,0.8 3.79,1.98 5.32,3.47l0.37,0.37c0.11,0.11 0.21,0.22 0.32,0.34c0.01,0.01 0.03,0.03 0.04,0.05c0.07,0.07 0.14,0.15 0.2,0.22c0.05,0.06 0.1,0.11 0.15,0.17c0.07,0.09 0.15,0.18 0.22,0.27c0.07,0.09 0.15,0.18 0.22,0.27c0.07,0.09 0.14,0.19 0.21,0.28C36.87,6.94 37,7.12 37.12,7.3c0.02,0.02 0.03,0.05 0.05,0.07c0.05,0.09 0.11,0.17 0.16,0.26c0.13,0.19 0.25,0.39 0.37,0.59c0.06,0.1 0.12,0.2 0.18,0.3c0.08,0.15 0.16,0.3 0.24,0.45c0.05,0.09 0.1,0.19 0.15,0.29c0.01,0.01 0.01,0.02 0.02,0.03c0.07,0.15 0.14,0.3 0.21,0.45c0,0.01 0.01,0.02 0.01,0.03c0.14,0.3 0.27,0.62 0.39,0.93c0.01,0.01 0.01,0.02 0.01,0.03c0.01,0.01 0.01,0.01 0.01,0.02c0.01,0.02 0.02,0.05 0.03,0.07C39.88,13.31 40.23,16.04 39.85,18.81z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
<path
android:fillColor="#FF000000"
android:pathData="M34.12,17.63v0.07c-0.01,0.06 -0.02,0.13 -0.03,0.19c-0.18,0.77 -0.95,1.24 -1.71,1.06c-0.06,-0.02 -0.13,-0.04 -0.19,-0.06c-0.56,-0.22 -0.91,-0.76 -0.9,-1.35c0.01,-0.06 0.01,-0.12 0.02,-0.18c0.12,-1.83 -0.39,-3.6 -1.38,-5.05c-0.23,-0.35 -0.49,-0.67 -0.77,-0.97l-0.18,-0.18c-0.09,-0.1 -0.19,-0.18 -0.28,-0.27c-0.07,-0.07 -0.15,-0.13 -0.22,-0.19c-0.06,-0.06 -0.13,-0.11 -0.2,-0.16c-0.04,-0.03 -0.08,-0.07 -0.12,-0.1c-0.11,-0.09 -0.23,-0.17 -0.35,-0.25c-0.06,-0.04 -0.12,-0.08 -0.18,-0.12c-0.01,0 -0.01,0 -0.01,0c-0.19,-0.12 -0.38,-0.24 -0.58,-0.34c-0.06,-0.03 -0.13,-0.07 -0.19,-0.1c-0.01,0 -0.02,-0.01 -0.03,-0.01c-0.13,-0.06 -0.25,-0.12 -0.38,-0.17c-0.01,-0.01 -0.02,-0.01 -0.02,-0.01c-1.25,-0.53 -2.63,-0.74 -4.03,-0.58c-0.69,0.1 -1.35,-0.33 -1.54,-1.02c-0.21,-0.76 0.23,-1.54 0.99,-1.75c0.03,-0.01 0.07,-0.01 0.1,-0.02c0.08,-0.01 0.15,-0.02 0.22,-0.02c1.77,-0.2 3.51,0.05 5.1,0.68c0.01,0 0.01,0 0.03,0.01c0.18,0.07 0.36,0.14 0.54,0.22c1.02,0.47 1.97,1.1 2.81,1.87c0.15,0.14 0.3,0.28 0.44,0.43c0.07,0.07 0.14,0.14 0.2,0.21c0.24,0.26 0.46,0.53 0.68,0.81c0.08,0.11 0.16,0.22 0.24,0.34c0.04,0.05 0.08,0.11 0.12,0.17s0.08,0.11 0.12,0.17c0.03,0.06 0.07,0.12 0.11,0.18c0.03,0.05 0.06,0.11 0.09,0.16c0.1,0.16 0.19,0.33 0.28,0.5c0.05,0.09 0.09,0.17 0.13,0.26c0,0 0,0 0.01,0.01c0.04,0.09 0.09,0.19 0.13,0.29c0.01,0.02 0.02,0.04 0.03,0.06c0.01,0.03 0.02,0.06 0.04,0.09C33.94,14.09 34.26,15.84 34.12,17.63z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:fillColor="#FF000000"
android:pathData="M14.29,11.41C1,11.41 0,12.41 0,25.71C0,39 1,40 14.29,40c13.3,0 14.3,-1 14.3,-14.29C28.59,12.41 27.59,11.41 14.29,11.41zM24.56,18.57c-0.78,0.79 -2.07,0.79 -2.86,0c-0.79,-0.79 -0.79,-2.07 0,-2.86c0.79,-0.79 2.08,-0.79 2.86,0C25.35,16.5 25.35,17.78 24.56,18.57z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
</vector>

View File

@@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:fillColor="#FF000000"
android:pathData="M14.29,11.28C1,11.28 0,12.28 0,25.58c0,13.29 1,14.29 14.29,14.29c13.3,0 14.3,-1 14.3,-14.29C28.59,12.28 27.59,11.28 14.29,11.28zM18.79,27.57c0,0.21 -0.02,0.4 -0.04,0.59l1.27,0.99c0.11,0.09 0.14,0.25 0.07,0.38l-1.2,2.08c-0.07,0.13 -0.23,0.19 -0.37,0.13l-1.49,-0.6c-0.31,0.24 -0.65,0.44 -1.02,0.59l-0.22,1.59c-0.02,0.14 -0.15,0.25 -0.3,0.25h-2.4c-0.15,0 -0.27,-0.11 -0.29,-0.25l-0.23,-1.59c-0.37,-0.15 -0.7,-0.35 -1.01,-0.59l-1.5,0.6c-0.13,0.05 -0.29,0 -0.36,-0.13l-1.2,-2.08c-0.08,-0.13 -0.05,-0.29 0.07,-0.38l1.26,-0.99c-0.02,-0.19 -0.04,-0.39 -0.04,-0.59c0,-0.2 0.02,-0.39 0.04,-0.59l-1.26,-0.99c-0.12,-0.09 -0.15,-0.25 -0.07,-0.38l1.2,-2.08c0.07,-0.13 0.22,-0.18 0.36,-0.13l1.5,0.6c0.31,-0.23 0.64,-0.44 1.01,-0.59l0.23,-1.59c0.02,-0.14 0.14,-0.25 0.29,-0.25h2.4c0.15,0 0.28,0.11 0.3,0.25l0.22,1.59c0.37,0.15 0.71,0.35 1.02,0.59l1.49,-0.6c0.13,-0.05 0.3,0 0.37,0.13l1.2,2.08c0.07,0.13 0.04,0.29 -0.07,0.38l-1.27,0.99C18.77,27.18 18.79,27.37 18.79,27.57zM24.56,18.44c-0.78,0.79 -2.07,0.79 -2.86,0c-0.79,-0.79 -0.79,-2.07 0,-2.86c0.79,-0.79 2.08,-0.79 2.86,0C25.35,16.37 25.35,17.65 24.56,18.44zM14.29,25.47c-1.16,0 -2.1,0.94 -2.1,2.1c0,1.16 0.94,2.1 2.1,2.1s2.1,-0.94 2.1,-2.1C16.39,26.41 15.45,25.47 14.29,25.47z"
android:strokeAlpha="0.54"
android:fillAlpha="0.54"/>
<path
android:fillColor="#FF000000"
android:pathData="M39.84,18.81v0.05c0,0.03 -0.01,0.05 -0.01,0.08c0,0.02 -0.01,0.04 -0.02,0.07c-0.17,0.76 -0.94,1.23 -1.7,1.05c-0.07,-0.01 -0.13,-0.04 -0.19,-0.06c-0.56,-0.22 -0.92,-0.76 -0.9,-1.35v-0.08c0,-0.01 0,-0.01 0,-0.01c0.01,-0.05 0.02,-0.1 0.02,-0.15c0.31,-2.26 0.03,-4.5 -0.73,-6.54c-0.12,-0.32 -0.25,-0.64 -0.4,-0.96c-0.06,-0.13 -0.12,-0.26 -0.19,-0.38c-0.04,-0.1 -0.09,-0.19 -0.14,-0.28c-0.05,-0.1 -0.1,-0.2 -0.16,-0.29c-0.01,-0.03 -0.02,-0.05 -0.03,-0.07c-0.05,-0.07 -0.09,-0.15 -0.14,-0.23c-0.04,-0.08 -0.1,-0.17 -0.15,-0.25S35,9.25 34.95,9.17c-0.05,-0.08 -0.11,-0.16 -0.16,-0.24c-0.33,-0.48 -0.68,-0.94 -1.07,-1.37c-0.06,-0.07 -0.12,-0.14 -0.19,-0.21c-0.14,-0.15 -0.29,-0.3 -0.44,-0.45c-0.05,-0.06 -0.11,-0.12 -0.17,-0.17c-0.07,-0.07 -0.14,-0.13 -0.21,-0.2c-0.1,-0.09 -0.2,-0.17 -0.29,-0.26c-0.11,-0.09 -0.21,-0.17 -0.32,-0.26c-0.11,-0.09 -0.21,-0.17 -0.32,-0.25c-0.86,-0.66 -1.81,-1.22 -2.83,-1.66c-0.12,-0.06 -0.25,-0.11 -0.38,-0.16c-0.12,-0.05 -0.25,-0.1 -0.38,-0.15c-0.11,-0.04 -0.23,-0.08 -0.34,-0.11c-0.02,-0.01 -0.03,-0.02 -0.05,-0.02c-1.36,-0.46 -2.81,-0.7 -4.28,-0.7c-0.79,0 -1.58,0.07 -2.37,0.21c-0.02,0 -0.05,0.01 -0.07,0.01c-0.68,0.1 -1.36,-0.31 -1.56,-0.99L19.5,2.12c-0.19,-0.74 0.25,-1.5 1,-1.71c0.03,0 0.06,-0.01 0.09,-0.02c0.03,0 0.06,-0.01 0.08,-0.01c2.77,-0.49 5.53,-0.24 8.06,0.61c0.03,0 0.05,0.01 0.07,0.02c0.13,0.04 0.26,0.09 0.38,0.13c0.13,0.05 0.27,0.1 0.4,0.16c0.03,0.01 0.07,0.02 0.1,0.04c1.98,0.8 3.79,1.98 5.32,3.47l0.37,0.37c0.11,0.11 0.21,0.22 0.32,0.34c0.01,0.01 0.03,0.03 0.04,0.05c0.07,0.07 0.14,0.15 0.2,0.22c0.05,0.06 0.1,0.11 0.15,0.17c0.07,0.09 0.15,0.18 0.22,0.27c0.07,0.09 0.15,0.18 0.22,0.27c0.07,0.09 0.14,0.19 0.21,0.28c0.13,0.17 0.26,0.35 0.38,0.53c0.02,0.02 0.03,0.05 0.05,0.07c0.05,0.09 0.11,0.17 0.16,0.26c0.13,0.19 0.25,0.39 0.37,0.59c0.06,0.1 0.12,0.2 0.18,0.3c0.08,0.15 0.16,0.3 0.24,0.45c0.05,0.09 0.1,0.19 0.15,0.29c0.01,0.01 0.01,0.02 0.02,0.03c0.07,0.15 0.14,0.3 0.21,0.45c0,0.01 0.01,0.02 0.01,0.03c0.14,0.3 0.27,0.62 0.39,0.93c0.01,0.01 0.01,0.02 0.01,0.03c0.01,0.01 0.01,0.01 0.01,0.02c0.01,0.02 0.02,0.05 0.03,0.07C39.87,13.31 40.22,16.04 39.84,18.81z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
<path
android:fillColor="#FF000000"
android:pathData="M34.11,17.63v0.07c-0.01,0.06 -0.02,0.13 -0.03,0.19c-0.18,0.77 -0.95,1.24 -1.71,1.06c-0.06,-0.02 -0.13,-0.04 -0.19,-0.06c-0.56,-0.22 -0.91,-0.76 -0.9,-1.35c0.01,-0.06 0.01,-0.12 0.02,-0.18c0.12,-1.83 -0.39,-3.6 -1.38,-5.05c-0.23,-0.35 -0.49,-0.67 -0.77,-0.97l-0.18,-0.18c-0.09,-0.1 -0.19,-0.18 -0.28,-0.27c-0.07,-0.07 -0.15,-0.13 -0.22,-0.19c-0.06,-0.06 -0.13,-0.11 -0.2,-0.16c-0.04,-0.03 -0.08,-0.07 -0.12,-0.1c-0.11,-0.09 -0.23,-0.17 -0.35,-0.25c-0.06,-0.04 -0.12,-0.08 -0.18,-0.12c-0.01,0 -0.01,0 -0.01,0c-0.19,-0.12 -0.38,-0.24 -0.58,-0.34c-0.06,-0.03 -0.13,-0.07 -0.19,-0.1c-0.01,0 -0.02,-0.01 -0.03,-0.01c-0.13,-0.06 -0.25,-0.12 -0.38,-0.17c-0.01,-0.01 -0.02,-0.01 -0.02,-0.01c-1.25,-0.53 -2.63,-0.74 -4.03,-0.58c-0.69,0.1 -1.35,-0.33 -1.54,-1.02c-0.21,-0.76 0.23,-1.54 0.99,-1.75c0.03,-0.01 0.07,-0.01 0.1,-0.02c0.08,-0.01 0.15,-0.02 0.22,-0.02c1.77,-0.2 3.51,0.05 5.1,0.68c0.01,0 0.01,0 0.03,0.01c0.18,0.07 0.36,0.14 0.54,0.22c1.02,0.47 1.97,1.1 2.81,1.87c0.15,0.14 0.3,0.28 0.44,0.43c0.07,0.07 0.14,0.14 0.2,0.21c0.24,0.26 0.46,0.53 0.68,0.81c0.08,0.11 0.16,0.22 0.24,0.34c0.04,0.05 0.08,0.11 0.12,0.17s0.08,0.11 0.12,0.17c0.03,0.06 0.07,0.12 0.11,0.18c0.03,0.05 0.06,0.11 0.09,0.16c0.1,0.16 0.19,0.33 0.28,0.5c0.05,0.09 0.09,0.17 0.13,0.26c0,0 0,0 0.01,0.01c0.04,0.09 0.09,0.19 0.13,0.29c0.01,0.02 0.02,0.04 0.03,0.06c0.01,0.03 0.02,0.06 0.04,0.09C33.93,14.09 34.25,15.84 34.11,17.63z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:fillColor="#FF000000"
android:pathData="M14.29,11.41C1,11.41 0,12.41 0,25.71C0,39 1,40 14.29,40c13.3,0 14.3,-1 14.3,-14.29C28.59,12.41 27.59,11.41 14.29,11.41zM18.79,27.7c0,0.21 -0.02,0.4 -0.04,0.59l1.27,0.99c0.11,0.09 0.14,0.25 0.07,0.38l-1.2,2.08c-0.07,0.13 -0.23,0.19 -0.37,0.13l-1.49,-0.6c-0.31,0.24 -0.65,0.44 -1.02,0.59l-0.22,1.59c-0.02,0.14 -0.15,0.25 -0.3,0.25h-2.4c-0.15,0 -0.27,-0.11 -0.29,-0.25l-0.23,-1.59c-0.37,-0.15 -0.7,-0.35 -1.01,-0.59l-1.5,0.6c-0.13,0.05 -0.29,0 -0.36,-0.13l-1.2,-2.08c-0.08,-0.13 -0.05,-0.29 0.07,-0.38l1.26,-0.99C9.81,28.1 9.79,27.9 9.79,27.7c0,-0.2 0.02,-0.39 0.04,-0.59l-1.26,-0.99c-0.12,-0.09 -0.15,-0.25 -0.07,-0.38l1.2,-2.08c0.07,-0.13 0.22,-0.18 0.36,-0.13l1.5,0.6c0.31,-0.23 0.64,-0.44 1.01,-0.59l0.23,-1.59c0.02,-0.14 0.14,-0.25 0.29,-0.25h2.4c0.15,0 0.28,0.11 0.3,0.25l0.22,1.59c0.37,0.15 0.71,0.35 1.02,0.59l1.49,-0.6c0.13,-0.05 0.3,0 0.37,0.13l1.2,2.08c0.07,0.13 0.04,0.29 -0.07,0.38l-1.27,0.99C18.77,27.31 18.79,27.5 18.79,27.7zM24.56,18.57c-0.78,0.79 -2.07,0.79 -2.86,0c-0.79,-0.79 -0.79,-2.07 0,-2.86c0.79,-0.79 2.08,-0.79 2.86,0C25.35,16.5 25.35,17.78 24.56,18.57zM14.29,25.6c-1.16,0 -2.1,0.94 -2.1,2.1c0,1.16 0.94,2.1 2.1,2.1s2.1,-0.94 2.1,-2.1C16.39,26.54 15.45,25.6 14.29,25.6z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
</vector>

View File

@@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40"
tools:ignore="UnusedResources">
<path
android:fillColor="#FF000000"
android:pathData="M14.3,11.31c-13.26,0 -14.25,1 -14.25,14.26c0,13.26 1,14.25 14.25,14.25c13.27,0 14.26,-1 14.26,-14.25C28.57,12.3 27.57,11.31 14.3,11.31zM22.17,29.73c-0.35,1.15 -1.37,1.95 -2.48,2.06c-1.02,0.1 -1.78,-0.41 -1.99,-0.56c-0.2,0.18 -0.49,0.41 -0.85,0.65c-0.09,0.06 -0.33,0.2 -0.61,0.33c-0.87,0.39 -1.69,0.44 -1.91,0.45c-0.5,0.02 -1.22,-0.03 -2.03,-0.34c-0.68,-0.26 -1.17,-0.62 -1.48,-0.88c-0.17,0.11 -0.43,0.25 -0.78,0.35c-0.31,0.1 -0.95,0.29 -1.69,0.08c-0.6,-0.17 -0.98,-0.53 -1.14,-0.68c-0.17,-0.16 -0.82,-0.81 -0.89,-1.84c-0.04,-0.67 0.2,-1.17 0.28,-1.36c0.23,-0.46 0.53,-0.77 0.72,-0.94c0.18,-0.14 0.43,-0.34 0.82,-0.46c0.41,-0.11 0.76,-0.07 0.94,-0.03c0.1,-0.45 0.32,-1.22 0.89,-2c0.67,-0.93 1.47,-1.39 1.72,-1.53c1.88,-1.02 3.74,-0.45 3.97,-0.38c0.02,0.01 0.05,0.02 0.08,0.03c0.36,0.12 1.58,0.57 2.47,1.78c0.08,0.12 0.22,0.31 0.36,0.57c-0.22,-0.01 -0.76,0.02 -1.3,0.37c-0.4,0.26 -0.62,0.58 -0.73,0.77c-0.32,-0.17 -1.21,-0.59 -2.26,-0.37c-0.31,0.07 -0.87,0.19 -1.33,0.67c-0.43,0.44 -0.55,0.94 -0.57,1.06c-0.13,0.61 0.05,1.1 0.13,1.28c0.01,0.02 0.02,0.04 0.02,0.05c-0.02,-0.14 -0.03,-0.37 0.03,-0.63c0.01,-0.1 0.08,-0.38 0.29,-0.68c0.41,-0.58 1.06,-0.75 1.27,-0.8c0.14,-0.03 0.58,-0.14 1.13,-0.02c0.87,0.17 1.41,0.72 1.6,0.94c0.09,-0.22 0.32,-0.68 0.81,-1.08c0.14,-0.11 0.59,-0.45 1.24,-0.57c1.23,-0.21 2.16,0.49 2.25,0.56C22.04,27.27 22.53,28.52 22.17,29.73zM24.55,18.45c-0.78,0.79 -2.06,0.79 -2.85,0c-0.79,-0.79 -0.79,-2.06 0,-2.85c0.79,-0.79 2.07,-0.79 2.85,0C25.34,16.38 25.34,17.66 24.55,18.45z"
android:strokeAlpha="0.54"
android:fillAlpha="0.54"/>
<path
android:fillColor="#FF000000"
android:pathData="M39.8,18.81v0.05c0,0.03 -0.01,0.05 -0.01,0.08c0,0.02 -0.01,0.04 -0.02,0.07c-0.17,0.76 -0.94,1.23 -1.7,1.05c-0.07,-0.01 -0.13,-0.04 -0.19,-0.06c-0.56,-0.22 -0.92,-0.76 -0.9,-1.35v-0.08c0,-0.01 0,-0.01 0,-0.01c0.01,-0.05 0.02,-0.1 0.02,-0.15c0.31,-2.25 0.03,-4.49 -0.73,-6.52c-0.12,-0.32 -0.25,-0.64 -0.4,-0.96c-0.06,-0.13 -0.12,-0.26 -0.19,-0.38c-0.04,-0.1 -0.09,-0.19 -0.14,-0.28c-0.05,-0.1 -0.1,-0.2 -0.16,-0.29c-0.01,-0.03 -0.02,-0.05 -0.03,-0.07c-0.05,-0.07 -0.09,-0.15 -0.14,-0.23c-0.04,-0.08 -0.1,-0.17 -0.15,-0.25s-0.1,-0.16 -0.15,-0.24c-0.05,-0.08 -0.11,-0.16 -0.16,-0.24c-0.33,-0.48 -0.68,-0.94 -1.07,-1.37c-0.06,-0.07 -0.12,-0.14 -0.19,-0.21c-0.14,-0.15 -0.29,-0.3 -0.44,-0.45c-0.05,-0.06 -0.11,-0.12 -0.17,-0.17c-0.07,-0.07 -0.14,-0.13 -0.21,-0.2c-0.1,-0.09 -0.2,-0.17 -0.29,-0.26c-0.11,-0.09 -0.21,-0.17 -0.32,-0.26c-0.11,-0.09 -0.21,-0.17 -0.32,-0.25c-0.86,-0.66 -1.81,-1.22 -2.82,-1.66c-0.12,-0.06 -0.25,-0.11 -0.38,-0.16c-0.12,-0.05 -0.25,-0.1 -0.38,-0.15c-0.11,-0.04 -0.23,-0.08 -0.34,-0.11c-0.02,-0.01 -0.03,-0.02 -0.05,-0.02C26.43,3.24 24.99,3 23.52,3c-0.79,0 -1.58,0.07 -2.36,0.21c-0.02,0 -0.05,0.01 -0.07,0.01c-0.68,0.1 -1.36,-0.31 -1.56,-0.99l-0.02,-0.07c-0.19,-0.74 0.25,-1.5 1,-1.71c0.03,0 0.06,-0.01 0.09,-0.02c0.03,0 0.06,-0.01 0.08,-0.01c2.76,-0.49 5.52,-0.24 8.04,0.61c0.03,0 0.05,0.01 0.07,0.02c0.13,0.04 0.26,0.09 0.38,0.13c0.13,0.05 0.27,0.1 0.4,0.16c0.03,0.01 0.07,0.02 0.1,0.04c1.98,0.8 3.78,1.98 5.31,3.46l0.37,0.37c0.11,0.11 0.21,0.22 0.32,0.34c0.01,0.01 0.03,0.03 0.04,0.05c0.07,0.07 0.14,0.15 0.2,0.22c0.05,0.06 0.1,0.11 0.15,0.17c0.07,0.09 0.15,0.18 0.22,0.27c0.07,0.09 0.15,0.18 0.22,0.27c0.07,0.09 0.14,0.19 0.21,0.28c0.13,0.17 0.26,0.35 0.38,0.53c0.02,0.02 0.03,0.05 0.05,0.07c0.05,0.09 0.11,0.17 0.16,0.26c0.13,0.19 0.25,0.39 0.37,0.59c0.06,0.1 0.12,0.2 0.18,0.3c0.08,0.15 0.16,0.3 0.24,0.45c0.05,0.09 0.1,0.19 0.15,0.29c0.01,0.01 0.01,0.02 0.02,0.03c0.07,0.15 0.14,0.3 0.21,0.45c0,0.01 0.01,0.02 0.01,0.03c0.14,0.3 0.27,0.62 0.39,0.93c0.01,0.01 0.01,0.02 0.01,0.03c0.01,0.01 0.01,0.01 0.01,0.02c0.01,0.02 0.02,0.05 0.03,0.07C39.83,13.32 40.18,16.04 39.8,18.81z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
<path
android:fillColor="#FF000000"
android:pathData="M34.08,17.63v0.07c-0.01,0.06 -0.02,0.13 -0.03,0.19c-0.18,0.77 -0.95,1.24 -1.71,1.06c-0.06,-0.02 -0.13,-0.04 -0.19,-0.06c-0.56,-0.22 -0.91,-0.76 -0.9,-1.35c0.01,-0.06 0.01,-0.12 0.02,-0.18c0.12,-1.83 -0.39,-3.59 -1.38,-5.04c-0.23,-0.35 -0.49,-0.67 -0.77,-0.97l-0.18,-0.18c-0.09,-0.1 -0.19,-0.18 -0.28,-0.27c-0.07,-0.07 -0.15,-0.13 -0.22,-0.19c-0.06,-0.06 -0.13,-0.11 -0.2,-0.16c-0.04,-0.03 -0.08,-0.07 -0.12,-0.1c-0.11,-0.09 -0.23,-0.17 -0.35,-0.25c-0.06,-0.04 -0.12,-0.08 -0.18,-0.12c-0.01,0 -0.01,0 -0.01,0c-0.19,-0.12 -0.38,-0.24 -0.58,-0.34c-0.06,-0.03 -0.13,-0.07 -0.19,-0.1c-0.01,0 -0.02,-0.01 -0.03,-0.01c-0.13,-0.06 -0.25,-0.12 -0.38,-0.17c-0.01,-0.01 -0.02,-0.01 -0.02,-0.01c-1.25,-0.53 -2.62,-0.74 -4.02,-0.58c-0.69,0.1 -1.35,-0.33 -1.54,-1.02c-0.21,-0.76 0.23,-1.54 0.99,-1.75c0.03,-0.01 0.07,-0.01 0.1,-0.02c0.08,-0.01 0.15,-0.02 0.22,-0.02c1.77,-0.2 3.5,0.05 5.09,0.68c0.01,0 0.01,0 0.03,0.01c0.18,0.07 0.36,0.14 0.54,0.22c1.02,0.47 1.97,1.1 2.8,1.87c0.15,0.14 0.3,0.28 0.44,0.43c0.07,0.07 0.14,0.14 0.2,0.21c0.24,0.26 0.46,0.53 0.68,0.81c0.08,0.11 0.16,0.22 0.24,0.34c0.04,0.05 0.08,0.11 0.12,0.17c0.04,0.06 0.08,0.11 0.12,0.17c0.03,0.06 0.07,0.12 0.11,0.18c0.03,0.05 0.06,0.11 0.09,0.16c0.1,0.16 0.19,0.33 0.28,0.5c0.05,0.09 0.09,0.17 0.13,0.26c0,0 0,0 0.01,0.01c0.04,0.09 0.09,0.19 0.13,0.29c0.01,0.02 0.02,0.04 0.03,0.06c0.01,0.03 0.02,0.06 0.04,0.09C33.9,14.1 34.22,15.84 34.08,17.63z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40"
tools:ignore="UnusedResources">
<path
android:fillColor="#FF000000"
android:pathData="M14.29,11.41C1,11.41 0,12.41 0,25.71C0,39 1,40 14.29,40c13.3,0 14.3,-1 14.3,-14.29C28.59,12.41 27.59,11.41 14.29,11.41zM22.18,29.88c-0.35,1.15 -1.37,1.95 -2.49,2.07c-1.02,0.1 -1.78,-0.41 -2,-0.56c-0.2,0.18 -0.49,0.41 -0.85,0.65c-0.09,0.06 -0.33,0.2 -0.61,0.33c-0.87,0.39 -1.69,0.44 -1.91,0.45c-0.5,0.02 -1.22,-0.03 -2.04,-0.34c-0.68,-0.26 -1.17,-0.62 -1.48,-0.88c-0.17,0.11 -0.43,0.25 -0.78,0.35c-0.31,0.1 -0.95,0.29 -1.69,0.08c-0.6,-0.17 -0.98,-0.53 -1.14,-0.68c-0.17,-0.16 -0.82,-0.81 -0.89,-1.84c-0.04,-0.67 0.2,-1.17 0.28,-1.36c0.23,-0.46 0.53,-0.77 0.72,-0.94c0.18,-0.14 0.43,-0.34 0.82,-0.46c0.41,-0.11 0.76,-0.07 0.94,-0.03c0.1,-0.45 0.32,-1.22 0.89,-2.01c0.67,-0.93 1.47,-1.39 1.72,-1.53c1.88,-1.02 3.75,-0.45 3.98,-0.38c0.02,0.01 0.05,0.02 0.08,0.03c0.36,0.12 1.58,0.57 2.48,1.78c0.08,0.12 0.22,0.31 0.36,0.57c-0.22,-0.01 -0.76,0.02 -1.3,0.37c-0.4,0.26 -0.62,0.58 -0.73,0.77c-0.32,-0.17 -1.21,-0.59 -2.27,-0.37c-0.31,0.07 -0.87,0.19 -1.33,0.67c-0.43,0.44 -0.55,0.94 -0.57,1.06c-0.13,0.61 0.05,1.1 0.13,1.28c0.01,0.02 0.02,0.04 0.02,0.05c-0.02,-0.14 -0.03,-0.37 0.03,-0.63c0.01,-0.1 0.08,-0.38 0.29,-0.68c0.41,-0.58 1.06,-0.75 1.27,-0.8c0.14,-0.03 0.58,-0.14 1.13,-0.02c0.87,0.17 1.41,0.72 1.6,0.94c0.09,-0.22 0.32,-0.68 0.81,-1.08c0.14,-0.11 0.59,-0.45 1.24,-0.57c1.23,-0.21 2.17,0.49 2.26,0.56C22.05,27.41 22.54,28.67 22.18,29.88zM24.56,18.57c-0.78,0.79 -2.07,0.79 -2.86,0c-0.79,-0.79 -0.79,-2.07 0,-2.86c0.79,-0.79 2.08,-0.79 2.86,0C25.35,16.5 25.35,17.78 24.56,18.57z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
</vector>

View File

@@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:fillColor="#FF000000"
android:pathData="M14.29,11.28C1,11.28 0,12.28 0,25.58c0,13.29 1,14.29 14.29,14.29c13.3,0 14.3,-1 14.3,-14.29C28.59,12.28 27.59,11.28 14.29,11.28zM22.3,28.53c0,0.82 -0.66,1.49 -1.49,1.49h-0.75v0.3c0,0.82 -0.65,1.49 -1.49,1.49H7.79c-0.82,0 -1.49,-0.67 -1.49,-1.49v-5.48c0,-0.83 0.67,-1.49 1.49,-1.49h10.78c0.84,0 1.49,0.66 1.49,1.49v0.46h0.75c0.83,0 1.49,0.67 1.49,1.49V28.53zM24.56,18.44c-0.78,0.79 -2.07,0.79 -2.86,0c-0.79,-0.79 -0.79,-2.07 0,-2.86s2.08,-0.79 2.86,0C25.35,16.37 25.35,17.65 24.56,18.44zM17.99,24.84H13.1c-0.33,0 -0.6,0.27 -0.6,0.6v4.28c0,0.33 0.27,0.6 0.6,0.6h4.89c0.23,0 0.41,-0.12 0.5,-0.3c0.06,-0.09 0.08,-0.19 0.08,-0.3v-4.28c0,-0.05 0,-0.09 -0.02,-0.14C18.5,25.03 18.28,24.84 17.99,24.84z"
android:strokeAlpha="0.54"
android:fillAlpha="0.54"/>
<path
android:fillColor="#FF000000"
android:pathData="M39.85,18.81v0.05c0,0.03 -0.01,0.05 -0.01,0.08c0,0.02 -0.01,0.04 -0.02,0.07c-0.17,0.76 -0.94,1.23 -1.7,1.05c-0.07,-0.01 -0.13,-0.04 -0.19,-0.06c-0.56,-0.22 -0.92,-0.76 -0.9,-1.35v-0.08c0,-0.01 0,-0.01 0,-0.01c0.01,-0.05 0.02,-0.1 0.02,-0.15c0.31,-2.26 0.03,-4.5 -0.73,-6.54c-0.12,-0.32 -0.25,-0.64 -0.4,-0.96c-0.06,-0.13 -0.12,-0.26 -0.19,-0.38c-0.04,-0.1 -0.09,-0.19 -0.14,-0.28c-0.05,-0.1 -0.1,-0.2 -0.16,-0.29c-0.01,-0.03 -0.02,-0.05 -0.03,-0.07c-0.05,-0.07 -0.09,-0.15 -0.14,-0.23c-0.04,-0.08 -0.1,-0.17 -0.15,-0.25s-0.1,-0.16 -0.15,-0.24c-0.05,-0.08 -0.11,-0.16 -0.16,-0.24c-0.33,-0.48 -0.68,-0.94 -1.07,-1.37c-0.06,-0.07 -0.12,-0.14 -0.19,-0.21C33.4,7.2 33.25,7.05 33.1,6.9c-0.05,-0.06 -0.11,-0.12 -0.17,-0.17c-0.07,-0.07 -0.14,-0.13 -0.21,-0.2c-0.1,-0.09 -0.2,-0.17 -0.29,-0.26c-0.11,-0.09 -0.21,-0.17 -0.32,-0.26C32,5.92 31.9,5.84 31.79,5.76c-0.86,-0.66 -1.81,-1.22 -2.83,-1.66c-0.12,-0.06 -0.25,-0.11 -0.38,-0.16c-0.12,-0.05 -0.25,-0.1 -0.38,-0.15c-0.11,-0.04 -0.23,-0.08 -0.34,-0.11c-0.02,-0.01 -0.03,-0.02 -0.05,-0.02c-1.36,-0.46 -2.81,-0.7 -4.28,-0.7c-0.79,0 -1.58,0.07 -2.37,0.21c-0.02,0 -0.05,0.01 -0.07,0.01c-0.68,0.1 -1.36,-0.31 -1.56,-0.99l-0.02,-0.07c-0.19,-0.74 0.25,-1.5 1,-1.71c0.03,0 0.06,-0.01 0.09,-0.02c0.03,0 0.06,-0.01 0.08,-0.01c2.77,-0.49 5.53,-0.24 8.06,0.61c0.03,0 0.05,0.01 0.07,0.02c0.13,0.04 0.26,0.09 0.38,0.13c0.13,0.05 0.27,0.1 0.4,0.16c0.03,0.01 0.07,0.02 0.1,0.04c1.98,0.8 3.79,1.98 5.32,3.47l0.37,0.37c0.11,0.11 0.21,0.22 0.32,0.34c0.01,0.01 0.03,0.03 0.04,0.05c0.07,0.07 0.14,0.15 0.2,0.22c0.05,0.06 0.1,0.11 0.15,0.17c0.07,0.09 0.15,0.18 0.22,0.27c0.07,0.09 0.15,0.18 0.22,0.27c0.07,0.09 0.14,0.19 0.21,0.28c0.13,0.17 0.26,0.35 0.38,0.53c0.02,0.02 0.03,0.05 0.05,0.07c0.05,0.09 0.11,0.17 0.16,0.26c0.13,0.19 0.25,0.39 0.37,0.59c0.06,0.1 0.12,0.2 0.18,0.3c0.08,0.15 0.16,0.3 0.24,0.45c0.05,0.09 0.1,0.19 0.15,0.29c0.01,0.01 0.01,0.02 0.02,0.03c0.07,0.15 0.14,0.3 0.21,0.45c0,0.01 0.01,0.02 0.01,0.03c0.14,0.3 0.27,0.62 0.39,0.93c0.01,0.01 0.01,0.02 0.01,0.03c0.01,0.01 0.01,0.01 0.01,0.02c0.01,0.02 0.02,0.05 0.03,0.07C39.88,13.31 40.23,16.04 39.85,18.81z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
<path
android:fillColor="#FF000000"
android:pathData="M34.12,17.63v0.07c-0.01,0.06 -0.02,0.13 -0.03,0.19c-0.18,0.77 -0.95,1.24 -1.71,1.06c-0.06,-0.02 -0.13,-0.04 -0.19,-0.06c-0.56,-0.22 -0.91,-0.76 -0.9,-1.35c0.01,-0.06 0.01,-0.12 0.02,-0.18c0.12,-1.83 -0.39,-3.6 -1.38,-5.05c-0.23,-0.35 -0.49,-0.67 -0.77,-0.97l-0.18,-0.18c-0.09,-0.1 -0.19,-0.18 -0.28,-0.27c-0.07,-0.07 -0.15,-0.13 -0.22,-0.19c-0.06,-0.06 -0.13,-0.11 -0.2,-0.16c-0.04,-0.03 -0.08,-0.07 -0.12,-0.1c-0.11,-0.09 -0.23,-0.17 -0.35,-0.25c-0.06,-0.04 -0.12,-0.08 -0.18,-0.12c-0.01,0 -0.01,0 -0.01,0c-0.19,-0.12 -0.38,-0.24 -0.58,-0.34c-0.06,-0.03 -0.13,-0.07 -0.19,-0.1c-0.01,0 -0.02,-0.01 -0.03,-0.01c-0.13,-0.06 -0.25,-0.12 -0.38,-0.17c-0.01,-0.01 -0.02,-0.01 -0.02,-0.01c-1.25,-0.53 -2.63,-0.74 -4.03,-0.58c-0.69,0.1 -1.35,-0.33 -1.54,-1.02c-0.21,-0.76 0.23,-1.54 0.99,-1.75c0.03,-0.01 0.07,-0.01 0.1,-0.02c0.08,-0.01 0.15,-0.02 0.22,-0.02c1.77,-0.2 3.51,0.05 5.1,0.68c0.01,0 0.01,0 0.03,0.01c0.18,0.07 0.36,0.14 0.54,0.22c1.02,0.47 1.97,1.1 2.81,1.87c0.15,0.14 0.3,0.28 0.44,0.43c0.07,0.07 0.14,0.14 0.2,0.21c0.24,0.26 0.46,0.53 0.68,0.81c0.08,0.11 0.16,0.22 0.24,0.34c0.04,0.05 0.08,0.11 0.12,0.17s0.08,0.11 0.12,0.17c0.03,0.06 0.07,0.12 0.11,0.18c0.03,0.05 0.06,0.11 0.09,0.16c0.1,0.16 0.19,0.33 0.28,0.5c0.05,0.09 0.09,0.17 0.13,0.26c0,0 0,0 0.01,0.01c0.04,0.09 0.09,0.19 0.13,0.29c0.01,0.02 0.02,0.04 0.03,0.06c0.01,0.03 0.02,0.06 0.04,0.09C33.94,14.09 34.26,15.84 34.12,17.63z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:fillColor="#FF000000"
android:pathData="M14.29,11.41C1,11.41 0,12.41 0,25.71C0,39 1,40 14.29,40c13.3,0 14.3,-1 14.3,-14.29C28.59,12.41 27.59,11.41 14.29,11.41zM22.3,28.66c0,0.82 -0.66,1.49 -1.49,1.49h-0.75v0.3c0,0.82 -0.65,1.49 -1.49,1.49H7.79c-0.82,0 -1.49,-0.67 -1.49,-1.49v-5.48c0,-0.83 0.67,-1.49 1.49,-1.49h10.78c0.84,0 1.49,0.66 1.49,1.49v0.46h0.75c0.83,0 1.49,0.67 1.49,1.49V28.66zM24.56,18.57c-0.78,0.79 -2.07,0.79 -2.86,0c-0.79,-0.79 -0.79,-2.07 0,-2.86s2.08,-0.79 2.86,0C25.35,16.5 25.35,17.78 24.56,18.57zM17.99,24.97H13.1c-0.33,0 -0.6,0.27 -0.6,0.6v4.28c0,0.33 0.27,0.6 0.6,0.6h4.89c0.23,0 0.41,-0.12 0.5,-0.3c0.06,-0.09 0.08,-0.19 0.08,-0.3v-4.28c0,-0.05 0,-0.09 -0.02,-0.14C18.5,25.16 18.28,24.97 17.99,24.97z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
</vector>

View File

@@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:fillColor="#FF000000"
android:pathData="M14.29,11.28C1,11.28 0,12.28 0,25.58c0,13.29 1,14.29 14.29,14.29c13.3,0 14.3,-1 14.3,-14.29C28.59,12.28 27.59,11.28 14.29,11.28zM13.55,25.71c0.22,-0.14 0.48,-0.21 0.75,-0.21s0.53,0.07 0.74,0.21c0.46,0.25 0.76,0.74 0.76,1.3s-0.3,1.04 -0.76,1.29c-0.21,0.14 -0.47,0.21 -0.74,0.21s-0.53,-0.07 -0.75,-0.21c-0.45,-0.25 -0.76,-0.73 -0.76,-1.29S13.1,25.96 13.55,25.71zM7.3,26.49c0.16,-2.2 1.33,-4.11 3.05,-5.28c0.23,-0.16 0.55,-0.07 0.69,0.17l1.99,3.44c-0.75,0.44 -1.26,1.26 -1.26,2.19H7.79C7.51,27.01 7.28,26.77 7.3,26.49zM17.35,33.32c-0.92,0.45 -1.96,0.7 -3.05,0.7c-1.1,0 -2.14,-0.25 -3.06,-0.7c-0.26,-0.12 -0.34,-0.44 -0.2,-0.69l1.99,-3.44c0.37,0.22 0.81,0.34 1.27,0.34c0.46,0 0.89,-0.12 1.26,-0.34l1.99,3.44C17.69,32.88 17.61,33.2 17.35,33.32zM20.8,27.01h-3.98c0,-0.93 -0.51,-1.76 -1.26,-2.19l1.99,-3.44c0.14,-0.24 0.46,-0.33 0.69,-0.17c1.72,1.17 2.89,3.08 3.05,5.28C21.31,26.77 21.08,27.01 20.8,27.01zM24.56,18.44c-0.78,0.79 -2.07,0.79 -2.86,0c-0.79,-0.79 -0.79,-2.07 0,-2.86c0.79,-0.79 2.08,-0.79 2.86,0C25.35,16.37 25.35,17.65 24.56,18.44z"
android:strokeAlpha="0.54"
android:fillAlpha="0.54"/>
<path
android:fillColor="#FF000000"
android:pathData="M39.85,18.81v0.05c0,0.03 -0.01,0.05 -0.01,0.08c0,0.02 -0.01,0.04 -0.02,0.07c-0.17,0.76 -0.94,1.23 -1.7,1.05c-0.07,-0.01 -0.13,-0.04 -0.19,-0.06c-0.56,-0.22 -0.92,-0.76 -0.9,-1.35v-0.08c0,-0.01 0,-0.01 0,-0.01c0.01,-0.05 0.02,-0.1 0.02,-0.15c0.31,-2.26 0.03,-4.5 -0.73,-6.54c-0.12,-0.32 -0.25,-0.64 -0.4,-0.96c-0.06,-0.13 -0.12,-0.26 -0.19,-0.38c-0.04,-0.1 -0.09,-0.19 -0.14,-0.28c-0.05,-0.1 -0.1,-0.2 -0.16,-0.29c-0.01,-0.03 -0.02,-0.05 -0.03,-0.07c-0.05,-0.07 -0.09,-0.15 -0.14,-0.23c-0.04,-0.08 -0.1,-0.17 -0.15,-0.25c-0.05,-0.08 -0.1,-0.16 -0.15,-0.24c-0.05,-0.08 -0.11,-0.16 -0.16,-0.24c-0.33,-0.48 -0.68,-0.94 -1.07,-1.37c-0.06,-0.07 -0.12,-0.14 -0.19,-0.21C33.4,7.2 33.25,7.05 33.1,6.9c-0.05,-0.06 -0.11,-0.12 -0.17,-0.17c-0.07,-0.07 -0.14,-0.13 -0.21,-0.2c-0.1,-0.09 -0.2,-0.17 -0.29,-0.26c-0.11,-0.09 -0.21,-0.17 -0.32,-0.26C32,5.92 31.9,5.84 31.79,5.76c-0.86,-0.66 -1.81,-1.22 -2.83,-1.66c-0.12,-0.06 -0.25,-0.11 -0.38,-0.16c-0.12,-0.05 -0.25,-0.1 -0.38,-0.15c-0.11,-0.04 -0.23,-0.08 -0.34,-0.11c-0.02,-0.01 -0.03,-0.02 -0.05,-0.02c-1.36,-0.46 -2.81,-0.7 -4.28,-0.7c-0.79,0 -1.58,0.07 -2.37,0.21c-0.02,0 -0.05,0.01 -0.07,0.01c-0.68,0.1 -1.36,-0.31 -1.56,-0.99l-0.02,-0.07c-0.19,-0.74 0.25,-1.5 1,-1.71c0.03,0 0.06,-0.01 0.09,-0.02c0.03,0 0.06,-0.01 0.08,-0.01c2.77,-0.49 5.53,-0.24 8.06,0.61c0.03,0 0.05,0.01 0.07,0.02c0.13,0.04 0.26,0.09 0.38,0.13c0.13,0.05 0.27,0.1 0.4,0.16c0.03,0.01 0.07,0.02 0.1,0.04c1.98,0.8 3.79,1.98 5.32,3.47l0.37,0.37c0.11,0.11 0.21,0.22 0.32,0.34c0.01,0.01 0.03,0.03 0.04,0.05c0.07,0.07 0.14,0.15 0.2,0.22c0.05,0.06 0.1,0.11 0.15,0.17c0.07,0.09 0.15,0.18 0.22,0.27c0.07,0.09 0.15,0.18 0.22,0.27c0.07,0.09 0.14,0.19 0.21,0.28c0.13,0.17 0.26,0.35 0.38,0.53c0.02,0.02 0.03,0.05 0.05,0.07c0.05,0.09 0.11,0.17 0.16,0.26c0.13,0.19 0.25,0.39 0.37,0.59c0.06,0.1 0.12,0.2 0.18,0.3c0.08,0.15 0.16,0.3 0.24,0.45c0.05,0.09 0.1,0.19 0.15,0.29c0.01,0.01 0.01,0.02 0.02,0.03c0.07,0.15 0.14,0.3 0.21,0.45c0,0.01 0.01,0.02 0.01,0.03c0.14,0.3 0.27,0.62 0.39,0.93c0.01,0.01 0.01,0.02 0.01,0.03c0.01,0.01 0.01,0.01 0.01,0.02c0.01,0.02 0.02,0.05 0.03,0.07C39.88,13.31 40.23,16.04 39.85,18.81z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"
tools:ignore="VectorPath" />
<path
android:fillColor="#FF000000"
android:pathData="M34.12,17.63v0.07c-0.01,0.06 -0.02,0.13 -0.03,0.19c-0.18,0.77 -0.95,1.24 -1.71,1.06c-0.06,-0.02 -0.13,-0.04 -0.19,-0.06c-0.56,-0.22 -0.91,-0.76 -0.9,-1.35c0.01,-0.06 0.01,-0.12 0.02,-0.18c0.12,-1.83 -0.39,-3.6 -1.38,-5.05c-0.23,-0.35 -0.49,-0.67 -0.77,-0.97l-0.18,-0.18c-0.09,-0.1 -0.19,-0.18 -0.28,-0.27c-0.07,-0.07 -0.15,-0.13 -0.22,-0.19c-0.06,-0.06 -0.13,-0.11 -0.2,-0.16c-0.04,-0.03 -0.08,-0.07 -0.12,-0.1c-0.11,-0.09 -0.23,-0.17 -0.35,-0.25c-0.06,-0.04 -0.12,-0.08 -0.18,-0.12c-0.01,0 -0.01,0 -0.01,0c-0.19,-0.12 -0.38,-0.24 -0.58,-0.34c-0.06,-0.03 -0.13,-0.07 -0.19,-0.1c-0.01,0 -0.02,-0.01 -0.03,-0.01c-0.13,-0.06 -0.25,-0.12 -0.38,-0.17c-0.01,-0.01 -0.02,-0.01 -0.02,-0.01c-1.25,-0.53 -2.63,-0.74 -4.03,-0.58c-0.69,0.1 -1.35,-0.33 -1.54,-1.02c-0.21,-0.76 0.23,-1.54 0.99,-1.75c0.03,-0.01 0.07,-0.01 0.1,-0.02c0.08,-0.01 0.15,-0.02 0.22,-0.02c1.77,-0.2 3.51,0.05 5.1,0.68c0.01,0 0.01,0 0.03,0.01c0.18,0.07 0.36,0.14 0.54,0.22c1.02,0.47 1.97,1.1 2.81,1.87c0.15,0.14 0.3,0.28 0.44,0.43c0.07,0.07 0.14,0.14 0.2,0.21c0.24,0.26 0.46,0.53 0.68,0.81c0.08,0.11 0.16,0.22 0.24,0.34c0.04,0.05 0.08,0.11 0.12,0.17s0.08,0.11 0.12,0.17c0.03,0.06 0.07,0.12 0.11,0.18c0.03,0.05 0.06,0.11 0.09,0.16c0.1,0.16 0.19,0.33 0.28,0.5c0.05,0.09 0.09,0.17 0.13,0.26c0,0 0,0 0.01,0.01c0.04,0.09 0.09,0.19 0.13,0.29c0.01,0.02 0.02,0.04 0.03,0.06c0.01,0.03 0.02,0.06 0.04,0.09C33.94,14.09 34.26,15.84 34.12,17.63z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:fillColor="#FF000000"
android:pathData="M14.29,11.28C1,11.28 0,12.28 0,25.58c0,13.29 1,14.29 14.29,14.29c13.3,0 14.3,-1 14.3,-14.29C28.59,12.28 27.59,11.28 14.29,11.28zM13.55,25.71c0.22,-0.14 0.48,-0.21 0.75,-0.21s0.53,0.07 0.74,0.21c0.46,0.25 0.76,0.74 0.76,1.3s-0.3,1.04 -0.76,1.29c-0.21,0.14 -0.47,0.21 -0.74,0.21s-0.53,-0.07 -0.75,-0.21c-0.45,-0.25 -0.76,-0.73 -0.76,-1.29S13.1,25.96 13.55,25.71zM7.3,26.49c0.16,-2.2 1.33,-4.11 3.05,-5.28c0.23,-0.16 0.55,-0.07 0.69,0.17l1.99,3.44c-0.75,0.44 -1.26,1.26 -1.26,2.19H7.79C7.51,27.01 7.28,26.77 7.3,26.49zM17.35,33.32c-0.92,0.45 -1.96,0.7 -3.05,0.7c-1.1,0 -2.14,-0.25 -3.06,-0.7c-0.26,-0.12 -0.34,-0.44 -0.2,-0.69l1.99,-3.44c0.37,0.22 0.81,0.34 1.27,0.34c0.46,0 0.89,-0.12 1.26,-0.34l1.99,3.44C17.69,32.88 17.61,33.2 17.35,33.32zM20.8,27.01h-3.98c0,-0.93 -0.51,-1.76 -1.26,-2.19l1.99,-3.44c0.14,-0.24 0.46,-0.33 0.69,-0.17c1.72,1.17 2.89,3.08 3.05,5.28C21.31,26.77 21.08,27.01 20.8,27.01zM24.56,18.44c-0.78,0.79 -2.07,0.79 -2.86,0c-0.79,-0.79 -0.79,-2.07 0,-2.86c0.79,-0.79 2.08,-0.79 2.86,0C25.35,16.37 25.35,17.65 24.56,18.44z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
</vector>

View File

@@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:fillColor="#FF000000"
android:pathData="M14.32,11.32C1.09,11.32 0.1,12.32 0.1,25.55c0,13.22 0.99,14.22 14.22,14.22c13.23,0 14.23,-0.99 14.23,-14.22C28.55,12.32 27.55,11.32 14.32,11.32zM13.65,20.79c0,-0.38 0.3,-0.68 0.68,-0.68h0.01c0.38,0 0.68,0.3 0.68,0.68v1.33c0,0.37 -0.3,0.67 -0.69,0.67c-0.38,0 -0.68,-0.3 -0.68,-0.68V20.79zM9.07,22.29L9.07,22.29c0.27,-0.27 0.7,-0.27 0.96,-0.01l0.95,0.95c0.13,0.13 0.2,0.3 0.2,0.48c0,0.17 -0.07,0.34 -0.21,0.48c-0.27,0.27 -0.69,0.27 -0.96,0l-0.94,-0.95C8.81,22.98 8.81,22.56 9.07,22.29zM6.87,27.54L6.87,27.54c0,-0.38 0.3,-0.68 0.67,-0.68h1.37c0.38,0 0.68,0.3 0.68,0.68v0.01c0,0.37 -0.3,0.67 -0.68,0.66H7.53C7.16,28.21 6.87,27.91 6.87,27.54zM10.97,31.85L10,32.82c-0.26,0.27 -0.69,0.27 -0.95,0H9.04c-0.26,-0.27 -0.26,-0.69 0,-0.96l0.98,-0.97c0.26,-0.27 0.69,-0.27 0.96,0v0.01C11.24,31.16 11.24,31.59 10.97,31.85zM15,34.3c0,0.38 -0.3,0.68 -0.68,0.68c-0.38,0 -0.68,-0.3 -0.68,-0.68v-1.32c0,-0.38 0.3,-0.68 0.68,-0.68c0.38,0 0.68,0.3 0.68,0.68V34.3zM14.33,30.37c-1.56,0 -2.83,-1.26 -2.83,-2.83c0,-1.56 1.26,-2.84 2.83,-2.84s2.84,1.27 2.84,2.84C17.16,29.1 15.89,30.37 14.33,30.37zM17.68,23.22l0.97,-0.97c0.27,-0.26 0.69,-0.26 0.96,0v0.01c0.27,0.26 0.27,0.69 0,0.95l-0.97,0.97c-0.26,0.27 -0.69,0.27 -0.95,0h-0.01C17.42,23.91 17.42,23.49 17.68,23.22zM19.58,32.8c-0.27,0.27 -0.69,0.27 -0.96,0l-0.94,-0.94c-0.14,-0.14 -0.2,-0.31 -0.2,-0.48c0,-0.17 0.06,-0.34 0.2,-0.48c0.27,-0.27 0.69,-0.27 0.96,0l0.94,0.94C19.85,32.11 19.85,32.53 19.58,32.8zM21.79,27.54c0,0.37 -0.3,0.67 -0.68,0.67h-1.36c-0.37,0 -0.67,-0.3 -0.67,-0.67v-0.01c0,-0.37 0.3,-0.67 0.67,-0.67h1.36C21.49,26.87 21.79,27.16 21.79,27.54L21.79,27.54zM24.54,18.45c-0.78,0.79 -2.06,0.79 -2.85,0c-0.79,-0.79 -0.79,-2.06 0,-2.85c0.79,-0.79 2.07,-0.79 2.85,0C25.32,16.39 25.32,17.66 24.54,18.45z"
android:strokeAlpha="0.54"
android:fillAlpha="0.54"/>
<path
android:fillColor="#FF000000"
android:pathData="M39.75,18.81v0.05c0,0.03 -0.01,0.05 -0.01,0.08c0,0.02 -0.01,0.04 -0.02,0.07c-0.17,0.76 -0.94,1.22 -1.69,1.04c-0.07,-0.01 -0.13,-0.04 -0.19,-0.06c-0.56,-0.22 -0.92,-0.76 -0.9,-1.34v-0.08c0,-0.01 0,-0.01 0,-0.01c0.01,-0.05 0.02,-0.1 0.02,-0.15c0.31,-2.25 0.03,-4.48 -0.73,-6.51c-0.12,-0.32 -0.25,-0.64 -0.4,-0.96c-0.06,-0.13 -0.12,-0.26 -0.19,-0.38c-0.04,-0.1 -0.09,-0.19 -0.14,-0.28c-0.05,-0.1 -0.1,-0.2 -0.16,-0.29c-0.01,-0.03 -0.02,-0.05 -0.03,-0.07c-0.05,-0.07 -0.09,-0.15 -0.14,-0.23c-0.04,-0.08 -0.1,-0.17 -0.15,-0.25c-0.05,-0.08 -0.1,-0.16 -0.15,-0.24c-0.05,-0.08 -0.11,-0.16 -0.16,-0.24c-0.33,-0.48 -0.68,-0.94 -1.06,-1.36c-0.06,-0.07 -0.12,-0.14 -0.19,-0.21c-0.14,-0.15 -0.29,-0.3 -0.44,-0.45c-0.05,-0.06 -0.11,-0.12 -0.17,-0.17c-0.07,-0.07 -0.14,-0.13 -0.21,-0.2c-0.1,-0.09 -0.2,-0.17 -0.29,-0.26c-0.11,-0.09 -0.21,-0.17 -0.32,-0.26c-0.11,-0.09 -0.21,-0.17 -0.32,-0.25c-0.86,-0.66 -1.8,-1.21 -2.82,-1.65c-0.12,-0.06 -0.25,-0.11 -0.38,-0.16c-0.12,-0.05 -0.25,-0.1 -0.38,-0.15c-0.11,-0.04 -0.23,-0.08 -0.34,-0.11c-0.02,-0.01 -0.03,-0.02 -0.05,-0.02c-1.35,-0.46 -2.8,-0.7 -4.26,-0.7c-0.79,0 -1.57,0.07 -2.36,0.21c-0.02,0 -0.05,0.01 -0.07,0.01c-0.68,0.1 -1.35,-0.31 -1.55,-0.99l-0.02,-0.07c-0.19,-0.74 0.25,-1.49 0.99,-1.7c0.03,0 0.06,-0.01 0.09,-0.02c0.03,0 0.06,-0.01 0.08,-0.01c2.76,-0.49 5.5,-0.24 8.02,0.61c0.03,0 0.05,0.01 0.07,0.02c0.13,0.04 0.26,0.09 0.38,0.13c0.13,0.05 0.27,0.1 0.4,0.16c0.03,0.01 0.07,0.02 0.1,0.04c1.97,0.8 3.77,1.97 5.29,3.45l0.37,0.37c0.11,0.11 0.21,0.22 0.32,0.34c0.01,0.01 0.03,0.03 0.04,0.05c0.07,0.07 0.14,0.15 0.2,0.22c0.05,0.06 0.1,0.11 0.15,0.17c0.07,0.09 0.15,0.18 0.22,0.27c0.07,0.09 0.15,0.18 0.22,0.27c0.07,0.09 0.14,0.19 0.21,0.28c0.13,0.17 0.26,0.35 0.38,0.53c0.02,0.02 0.03,0.05 0.05,0.07c0.05,0.09 0.11,0.17 0.16,0.26c0.13,0.19 0.25,0.39 0.37,0.59c0.06,0.1 0.12,0.2 0.18,0.3c0.08,0.15 0.16,0.3 0.24,0.45c0.05,0.09 0.1,0.19 0.15,0.29c0.01,0.01 0.01,0.02 0.02,0.03c0.07,0.15 0.14,0.3 0.21,0.45c0,0.01 0.01,0.02 0.01,0.03c0.14,0.3 0.27,0.62 0.39,0.93c0.01,0.01 0.01,0.02 0.01,0.03c0.01,0.01 0.01,0.01 0.01,0.02c0.01,0.02 0.02,0.05 0.03,0.07C39.78,13.34 40.13,16.06 39.75,18.81z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
<path
android:fillColor="#FF000000"
android:pathData="M34.05,17.64v0.07c-0.01,0.06 -0.02,0.13 -0.03,0.19c-0.18,0.77 -0.95,1.23 -1.7,1.05c-0.06,-0.02 -0.13,-0.04 -0.19,-0.06c-0.56,-0.22 -0.91,-0.76 -0.9,-1.34c0.01,-0.06 0.01,-0.12 0.02,-0.18c0.12,-1.82 -0.39,-3.58 -1.37,-5.02c-0.23,-0.35 -0.49,-0.67 -0.77,-0.97l-0.18,-0.18c-0.09,-0.1 -0.19,-0.18 -0.28,-0.27c-0.07,-0.07 -0.15,-0.13 -0.22,-0.19c-0.06,-0.06 -0.13,-0.11 -0.2,-0.16c-0.04,-0.03 -0.08,-0.07 -0.12,-0.1c-0.11,-0.09 -0.23,-0.17 -0.35,-0.25c-0.06,-0.04 -0.12,-0.08 -0.18,-0.12c-0.01,0 -0.01,0 -0.01,0C27.39,10 27.2,9.88 27,9.78c-0.06,-0.03 -0.13,-0.07 -0.19,-0.1c-0.01,0 -0.02,-0.01 -0.03,-0.01c-0.13,-0.06 -0.25,-0.12 -0.38,-0.17c-0.01,-0.01 -0.02,-0.01 -0.02,-0.01c-1.24,-0.53 -2.62,-0.74 -4.01,-0.58c-0.69,0.1 -1.34,-0.33 -1.53,-1.01c-0.21,-0.76 0.23,-1.53 0.99,-1.74c0.03,-0.01 0.07,-0.01 0.1,-0.02c0.08,-0.01 0.15,-0.02 0.22,-0.02c1.76,-0.2 3.49,0.05 5.07,0.68c0.01,0 0.01,0 0.03,0.01c0.18,0.07 0.36,0.14 0.54,0.22c1.01,0.47 1.96,1.09 2.8,1.86c0.15,0.14 0.3,0.28 0.44,0.43c0.07,0.07 0.14,0.14 0.2,0.21c0.24,0.26 0.46,0.53 0.68,0.81c0.08,0.11 0.16,0.22 0.24,0.34c0.04,0.05 0.08,0.11 0.12,0.17c0.04,0.06 0.08,0.11 0.12,0.17c0.03,0.06 0.07,0.12 0.11,0.18c0.03,0.05 0.06,0.11 0.09,0.16c0.1,0.16 0.19,0.33 0.28,0.5c0.05,0.09 0.09,0.17 0.13,0.26c0,0 0,0 0.01,0.01c0.04,0.09 0.09,0.19 0.13,0.29c0.01,0.02 0.02,0.04 0.03,0.06c0.01,0.03 0.02,0.06 0.04,0.09C33.87,14.12 34.19,15.86 34.05,17.64z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:fillColor="#FF000000"
android:pathData="M14.29,11.41C1,11.41 0,12.41 0,25.71C0,39 1,40 14.29,40c13.3,0 14.3,-1 14.3,-14.29C28.59,12.41 27.59,11.41 14.29,11.41zM13.62,20.92c0,-0.38 0.3,-0.68 0.68,-0.68h0.01c0.38,0 0.68,0.3 0.68,0.68v1.34c0,0.37 -0.3,0.67 -0.69,0.67c-0.38,0 -0.68,-0.3 -0.68,-0.68V20.92zM9.02,22.43l0.01,-0.01c0.26,-0.26 0.69,-0.26 0.95,0l0.95,0.95c0.13,0.13 0.2,0.3 0.2,0.48c0,0.17 -0.07,0.34 -0.21,0.48c-0.27,0.27 -0.69,0.27 -0.96,0l-0.94,-0.95C8.75,23.12 8.75,22.7 9.02,22.43zM6.8,27.71V27.7c0,-0.37 0.3,-0.67 0.67,-0.67h1.38c0.38,0 0.68,0.3 0.68,0.68v0.01c0,0.37 -0.3,0.67 -0.68,0.66H7.47C7.1,28.38 6.8,28.08 6.8,27.71zM10.92,32.04l-0.97,0.97c-0.26,0.27 -0.69,0.27 -0.95,0H8.99c-0.26,-0.27 -0.26,-0.69 0,-0.96l0.98,-0.97c0.26,-0.27 0.69,-0.27 0.96,0v0.01C11.2,31.35 11.2,31.78 10.92,32.04zM14.98,34.5c0,0.38 -0.3,0.68 -0.68,0.68s-0.68,-0.3 -0.68,-0.68v-1.33c0,-0.38 0.3,-0.68 0.68,-0.68s0.68,0.3 0.68,0.68V34.5zM14.3,30.55c-1.57,0 -2.84,-1.27 -2.84,-2.84s1.27,-2.85 2.84,-2.85s2.85,1.28 2.85,2.85S15.87,30.55 14.3,30.55zM17.67,23.37l0.97,-0.97c0.27,-0.26 0.69,-0.26 0.96,0v0.01c0.27,0.26 0.27,0.69 0,0.95l-0.97,0.97c-0.26,0.27 -0.69,0.27 -0.95,0h-0.01C17.41,24.06 17.41,23.64 17.67,23.37zM19.58,32.99c-0.27,0.27 -0.69,0.27 -0.96,0l-0.94,-0.94c-0.14,-0.14 -0.2,-0.31 -0.2,-0.48c0,-0.17 0.06,-0.34 0.2,-0.48c0.27,-0.27 0.69,-0.27 0.96,0l0.94,0.94C19.85,32.3 19.85,32.72 19.58,32.99zM21.8,27.71c0,0.37 -0.3,0.67 -0.68,0.67h-1.37c-0.37,0 -0.67,-0.3 -0.67,-0.67V27.7c0,-0.37 0.3,-0.67 0.67,-0.67h1.37c0.38,0 0.68,0.3 0.68,0.67V27.71zM24.56,18.57c-0.78,0.79 -2.07,0.79 -2.86,0c-0.79,-0.79 -0.79,-2.07 0,-2.86c0.79,-0.79 2.08,-0.79 2.86,0C25.35,16.5 25.35,17.78 24.56,18.57z"
android:strokeAlpha="0.22"
android:fillAlpha="0.22"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M3,17.46v3.04c0,0.28 0.22,0.5 0.5,0.5h3.04c0.13,0 0.26,-0.05 0.35,-0.15L17.81,9.94l-3.75,-3.75L3.15,17.1c-0.1,0.1 -0.15,0.22 -0.15,0.36zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M6.84,14.04L6.84,14.04c0.47,0.47 1.21,0.47 1.68,0l2.28,-2.28l0,11.04c0,0.66 0.54,1.2 1.2,1.2l0,0c0.66,0 1.2,-0.54 1.2,-1.2l0,-11.04l2.28,2.28c0.47,0.47 1.21,0.47 1.68,0l0,0c0.47,-0.47 0.47,-1.21 0,-1.68l-4.31,-4.31c-0.47,-0.47 -1.22,-0.47 -1.69,0l-4.32,4.31C6.37,12.83 6.37,13.57 6.84,14.04zM20.4,2.4l0,4.8c0,0.66 0.54,1.2 1.2,1.2l0,0c0.66,0 1.2,-0.54 1.2,-1.2l0,-4.8c0,-1.32 -1.08,-2.4 -2.4,-2.4L3.6,0C2.28,0 1.2,1.08 1.2,2.4l0,4.8c0,0.66 0.54,1.2 1.2,1.2l0,0c0.66,0 1.2,-0.54 1.2,-1.2l0,-4.8L20.4,2.4z"/>
</vector>

View File

@@ -101,6 +101,7 @@
<string name="text_device_firmware">Версія прашыўкі</string> <string name="text_device_firmware">Версія прашыўкі</string>
<string name="text_device_location">Месцазнаходжанне</string> <string name="text_device_location">Месцазнаходжанне</string>
<string name="text_device_location_unset">Прылада не зарэгістравана</string> <string name="text_device_location_unset">Прылада не зарэгістравана</string>
<string name="text_device_connected">падлучана</string>
<string name="text_device_visibility">Бачнасць</string> <string name="text_device_visibility">Бачнасць</string>
<string name="text_device_visibility_visible">Апублікавана</string> <string name="text_device_visibility_visible">Апублікавана</string>
<string name="text_device_visibility_private">Схавана</string> <string name="text_device_visibility_private">Схавана</string>

View File

@@ -101,6 +101,7 @@
<string name="text_device_firmware">Версия прошивки</string> <string name="text_device_firmware">Версия прошивки</string>
<string name="text_device_location">Местоположение</string> <string name="text_device_location">Местоположение</string>
<string name="text_device_location_unset">Устройство не зарегистрировано</string> <string name="text_device_location_unset">Устройство не зарегистрировано</string>
<string name="text_device_connected">подключено</string>
<string name="text_device_visibility">Видимость</string> <string name="text_device_visibility">Видимость</string>
<string name="text_device_visibility_visible">Опубликовано</string> <string name="text_device_visibility_visible">Опубликовано</string>
<string name="text_device_visibility_private">Скрыто</string> <string name="text_device_visibility_private">Скрыто</string>

View File

@@ -11,9 +11,13 @@
<color name="sensorGreen">#FF00C853</color> <color name="sensorGreen">#FF00C853</color>
<color name="sensorYellow">#FFFFD54F</color> <color name="sensorYellow">#FFFFD54F</color>
<color name="sensorOrange">#FFFF9800</color> <color name="sensorOrange">#FFFF9800</color>
<color name="colorGreen">#FF00C853</color>
<color name="colorOrange">#FFFF6F00</color>
<color name="sensorRed">#FFF44336</color> <color name="sensorRed">#FFF44336</color>
<color name="sensorPink">#FFEC407A</color> <color name="sensorPink">#FFEC407A</color>
<color name="sensorPurple">#FF8E24AA</color> <color name="sensorPurple">#FF8E24AA</color>
<color name="black38">#61000000</color> <color name="black38">#61000000</color>
<color name="black54">#8A000000</color>
<color name="headerColor">#1F5DA5</color>
<color name="windowBackground">#FAFAFA</color> <color name="windowBackground">#FAFAFA</color>
</resources> </resources>

View File

@@ -158,6 +158,7 @@
<string name="text_device_location">Location</string> <string name="text_device_location">Location</string>
<string name="text_device_location_unset">Device is not registered</string> <string name="text_device_location_unset">Device is not registered</string>
<string name="text_device_location_set" translatable="false">%s, %s</string> <string name="text_device_location_set" translatable="false">%s, %s</string>
<string name="text_device_connected">connected</string>
<string name="text_device_visibility">Visibility</string> <string name="text_device_visibility">Visibility</string>
<string name="text_device_visibility_visible">Public</string> <string name="text_device_visibility_visible">Public</string>
<string name="text_device_visibility_private">Private</string> <string name="text_device_visibility_private">Private</string>

View File

@@ -112,6 +112,87 @@ class DeviceRepositoryImpl @Inject constructor(
return Result.success(Unit) return Result.success(Unit)
} }
override suspend fun setNarodmon(deviceId: String, enabled: Boolean): Result<Unit> {
val current = localDataSource.getDevice(deviceId) ?: return Result.failure(NoSuchElementException("Device not found: $deviceId"))
val previous = current.isNarodmonOn
localDataSource.updateNarodmon(deviceId, enabled)
val mutationId = UUID.randomUUID().toString()
localDataSource.enqueuePendingMutation(
PendingMutation(
id = mutationId,
type = PendingMutationType.NARODMON,
deviceId = deviceId,
payload = """{"enabled":$enabled}""",
createdAt = System.currentTimeMillis()
)
)
val result = remoteDataSource.setNarodmon(deviceId, enabled)
if (result.isSuccess) {
localDataSource.removePendingMutationsForDevice(deviceId, PendingMutationType.NARODMON)
} else {
localDataSource.updateNarodmon(deviceId, previous)
localDataSource.removePendingMutationsForDevice(deviceId, PendingMutationType.NARODMON)
return result
}
return Result.success(Unit)
}
override suspend fun setLuftdata(deviceId: String, enabled: Boolean): Result<Unit> {
val current = localDataSource.getDevice(deviceId) ?: return Result.failure(NoSuchElementException("Device not found: $deviceId"))
val previous = current.isLuftdataOn
localDataSource.updateLuftdata(deviceId, enabled)
val mutationId = UUID.randomUUID().toString()
localDataSource.enqueuePendingMutation(
PendingMutation(
id = mutationId,
type = PendingMutationType.LUFTDATA,
deviceId = deviceId,
payload = """{"enabled":$enabled}""",
createdAt = System.currentTimeMillis()
)
)
val result = remoteDataSource.setLuftdata(deviceId, enabled)
if (result.isSuccess) {
localDataSource.removePendingMutationsForDevice(deviceId, PendingMutationType.LUFTDATA)
} else {
localDataSource.updateLuftdata(deviceId, previous)
localDataSource.removePendingMutationsForDevice(deviceId, PendingMutationType.LUFTDATA)
return result
}
return Result.success(Unit)
}
override suspend fun setVisibility(deviceId: String, isPublic: Boolean): Result<Unit> {
val current = localDataSource.getDevice(deviceId) ?: return Result.failure(NoSuchElementException("Device not found: $deviceId"))
val previous = current.isPublic
localDataSource.updateIsPublic(deviceId, isPublic)
val mutationId = UUID.randomUUID().toString()
localDataSource.enqueuePendingMutation(
PendingMutation(
id = mutationId,
type = PendingMutationType.VISIBILITY,
deviceId = deviceId,
payload = """{"isPublic":$isPublic}""",
createdAt = System.currentTimeMillis()
)
)
val result = remoteDataSource.setVisibility(deviceId, isPublic)
if (result.isSuccess) {
localDataSource.removePendingMutationsForDevice(deviceId, PendingMutationType.VISIBILITY)
} else {
localDataSource.updateIsPublic(deviceId, previous)
localDataSource.removePendingMutationsForDevice(deviceId, PendingMutationType.VISIBILITY)
return result
}
return Result.success(Unit)
}
override suspend fun triggerFirmwareUpdate(deviceId: String): Result<Unit> = override suspend fun triggerFirmwareUpdate(deviceId: String): Result<Unit> =
remoteDataSource.triggerFirmwareUpdate(deviceId) remoteDataSource.triggerFirmwareUpdate(deviceId)

View File

@@ -30,6 +30,15 @@ interface DeviceDao {
@Query("UPDATE device SET dataSharingEnabled = :enabled WHERE id = :deviceId") @Query("UPDATE device SET dataSharingEnabled = :enabled WHERE id = :deviceId")
suspend fun updateDataSharing(deviceId: String, enabled: Boolean) suspend fun updateDataSharing(deviceId: String, enabled: Boolean)
@Query("UPDATE device SET isNarodmonOn = :enabled WHERE id = :deviceId")
suspend fun updateNarodmon(deviceId: String, enabled: Boolean)
@Query("UPDATE device SET isLuftdataOn = :enabled WHERE id = :deviceId")
suspend fun updateLuftdata(deviceId: String, enabled: Boolean)
@Query("UPDATE device SET isPublic = :isPublic WHERE id = :deviceId")
suspend fun updateIsPublic(deviceId: String, isPublic: Boolean)
@Query("UPDATE device SET isOnline = :isOnline, isOnlineUpdatedAt = :updatedAt WHERE id = :deviceId") @Query("UPDATE device SET isOnline = :isOnline, isOnlineUpdatedAt = :updatedAt WHERE id = :deviceId")
suspend fun updateOnlineStatus(deviceId: String, isOnline: Boolean, updatedAt: Long) suspend fun updateOnlineStatus(deviceId: String, isOnline: Boolean, updatedAt: Long)

View File

@@ -2,10 +2,27 @@ package org.db3.airmq.sdk.device.data.local
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val DEVICE_DB_MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE device ADD COLUMN deviceAddress TEXT NOT NULL DEFAULT ''")
db.execSQL("ALTER TABLE device ADD COLUMN configVersion TEXT NOT NULL DEFAULT ''")
db.execSQL("ALTER TABLE device ADD COLUMN isNarodmonOn INTEGER NOT NULL DEFAULT 0")
db.execSQL("ALTER TABLE device ADD COLUMN isLuftdataOn INTEGER NOT NULL DEFAULT 0")
}
}
val DEVICE_DB_MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE device ADD COLUMN isPublic INTEGER NOT NULL DEFAULT 0")
}
}
@Database( @Database(
entities = [DeviceEntity::class, PendingMutationEntity::class], entities = [DeviceEntity::class, PendingMutationEntity::class],
version = 1, version = 3,
exportSchema = false exportSchema = false
) )
abstract class DeviceDatabase : RoomDatabase() { abstract class DeviceDatabase : RoomDatabase() {

View File

@@ -18,9 +18,14 @@ data class DeviceEntity(
val name: String, val name: String,
val model: String, val model: String,
val firmwareVersion: String, val firmwareVersion: String,
val deviceAddress: String,
val configVersion: String,
val isNarodmonOn: Boolean,
val isLuftdataOn: Boolean,
val locationId: String? = null, val locationId: String? = null,
val latitude: Double? = null, val latitude: Double? = null,
val longitude: Double? = null, val longitude: Double? = null,
val isPublic: Boolean = false,
val dataSharingEnabled: Boolean = false, val dataSharingEnabled: Boolean = false,
val isOnline: Boolean = false, val isOnline: Boolean = false,
val isOnlineUpdatedAt: Long? = null, val isOnlineUpdatedAt: Long? = null,

View File

@@ -3,6 +3,7 @@ package org.db3.airmq.sdk.device.data.local
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import org.db3.airmq.sdk.device.domain.Device import org.db3.airmq.sdk.device.domain.Device
import org.db3.airmq.sdk.device.domain.DeviceModel
import org.db3.airmq.sdk.device.domain.OnlineFreshness import org.db3.airmq.sdk.device.domain.OnlineFreshness
import org.db3.airmq.sdk.device.domain.PendingMutation import org.db3.airmq.sdk.device.domain.PendingMutation
import org.db3.airmq.sdk.device.domain.PendingMutationType import org.db3.airmq.sdk.device.domain.PendingMutationType
@@ -47,6 +48,18 @@ class DeviceLocalDataSource @Inject constructor(
deviceDao.updateDataSharing(deviceId, enabled) deviceDao.updateDataSharing(deviceId, enabled)
} }
suspend fun updateNarodmon(deviceId: String, enabled: Boolean) {
deviceDao.updateNarodmon(deviceId, enabled)
}
suspend fun updateLuftdata(deviceId: String, enabled: Boolean) {
deviceDao.updateLuftdata(deviceId, enabled)
}
suspend fun updateIsPublic(deviceId: String, isPublic: Boolean) {
deviceDao.updateIsPublic(deviceId, isPublic)
}
suspend fun updateOnlineStatus(deviceId: String, isOnline: Boolean, updatedAt: Long) { suspend fun updateOnlineStatus(deviceId: String, isOnline: Boolean, updatedAt: Long) {
deviceDao.updateOnlineStatus(deviceId, isOnline, updatedAt) deviceDao.updateOnlineStatus(deviceId, isOnline, updatedAt)
} }
@@ -91,11 +104,16 @@ class DeviceLocalDataSource @Inject constructor(
return Device( return Device(
id = id, id = id,
name = name, name = name,
model = model, model = DeviceModel.fromString(model),
firmwareVersion = firmwareVersion, firmwareVersion = firmwareVersion,
deviceAddress = deviceAddress,
configVersion = configVersion,
isNarodmonOn = isNarodmonOn,
isLuftdataOn = isLuftdataOn,
locationId = locationId, locationId = locationId,
latitude = latitude, latitude = latitude,
longitude = longitude, longitude = longitude,
isPublic = isPublic,
city = city, city = city,
dataSharingEnabled = dataSharingEnabled, dataSharingEnabled = dataSharingEnabled,
isOnline = isOnline, isOnline = isOnline,
@@ -107,11 +125,16 @@ class DeviceLocalDataSource @Inject constructor(
private fun Device.toEntity(): DeviceEntity = DeviceEntity( private fun Device.toEntity(): DeviceEntity = DeviceEntity(
id = id, id = id,
name = name, name = name,
model = model, model = model.toStorageString(),
firmwareVersion = firmwareVersion, firmwareVersion = firmwareVersion,
deviceAddress = deviceAddress,
configVersion = configVersion,
isNarodmonOn = isNarodmonOn,
isLuftdataOn = isLuftdataOn,
locationId = locationId, locationId = locationId,
latitude = latitude, latitude = latitude,
longitude = longitude, longitude = longitude,
isPublic = isPublic,
dataSharingEnabled = dataSharingEnabled, dataSharingEnabled = dataSharingEnabled,
isOnline = isOnline, isOnline = isOnline,
isOnlineUpdatedAt = if (onlineFreshness == OnlineFreshness.Fresh) System.currentTimeMillis() else null, isOnlineUpdatedAt = if (onlineFreshness == OnlineFreshness.Fresh) System.currentTimeMillis() else null,

View File

@@ -4,6 +4,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import org.db3.airmq.sdk.device.domain.Device import org.db3.airmq.sdk.device.domain.Device
import org.db3.airmq.sdk.device.domain.DeviceModel
import org.db3.airmq.sdk.device.domain.OnlineFreshness import org.db3.airmq.sdk.device.domain.OnlineFreshness
import javax.inject.Inject import javax.inject.Inject
@@ -39,6 +40,21 @@ interface DeviceRemoteDataSource {
*/ */
suspend fun setDataSharing(deviceId: String, enabled: Boolean): Result<Unit> suspend fun setDataSharing(deviceId: String, enabled: Boolean): Result<Unit>
/**
* Execute Narodmon.ru toggle. Phase 1: No-op. Phase 2: Apollo mutation.
*/
suspend fun setNarodmon(deviceId: String, enabled: Boolean): Result<Unit>
/**
* Execute Sensor.community (Luftdata) toggle. Phase 1: No-op. Phase 2: Apollo mutation.
*/
suspend fun setLuftdata(deviceId: String, enabled: Boolean): Result<Unit>
/**
* Execute set visibility (publish/hide) mutation. Phase 1: No-op. Phase 2: Apollo mutation.
*/
suspend fun setVisibility(deviceId: String, isPublic: Boolean): Result<Unit>
/** /**
* Execute firmware update. Phase 1: No-op. Phase 2: Apollo mutation. * Execute firmware update. Phase 1: No-op. Phase 2: Apollo mutation.
*/ */
@@ -56,11 +72,16 @@ class MockDeviceRemoteDataSource @Inject constructor() : DeviceRemoteDataSource
Device( Device(
id = "device-1", id = "device-1",
name = "AirMQ #42", name = "AirMQ #42",
model = "mobile", model = DeviceModel.Mobile,
firmwareVersion = "1.0", firmwareVersion = "1.0",
deviceAddress = "192.168.1.100",
configVersion = "42",
isNarodmonOn = true,
isLuftdataOn = false,
locationId = "loc-1", locationId = "loc-1",
latitude = 53.9, latitude = 53.9,
longitude = 27.5, longitude = 27.5,
isPublic = false,
city = "Minsk", city = "Minsk",
dataSharingEnabled = true, dataSharingEnabled = true,
isOnline = true, isOnline = true,
@@ -70,11 +91,16 @@ class MockDeviceRemoteDataSource @Inject constructor() : DeviceRemoteDataSource
Device( Device(
id = "device-2", id = "device-2",
name = "AirMQ #17", name = "AirMQ #17",
model = "mobile", model = DeviceModel.Mobile,
firmwareVersion = "1.0", firmwareVersion = "1.0",
deviceAddress = "192.168.1.101",
configVersion = "41",
isNarodmonOn = false,
isLuftdataOn = false,
locationId = null, locationId = null,
latitude = null, latitude = null,
longitude = null, longitude = null,
isPublic = false,
city = null, city = null,
dataSharingEnabled = false, dataSharingEnabled = false,
isOnline = false, isOnline = false,
@@ -102,6 +128,15 @@ class MockDeviceRemoteDataSource @Inject constructor() : DeviceRemoteDataSource
override suspend fun setDataSharing(deviceId: String, enabled: Boolean): Result<Unit> = override suspend fun setDataSharing(deviceId: String, enabled: Boolean): Result<Unit> =
Result.success(Unit) Result.success(Unit)
override suspend fun setNarodmon(deviceId: String, enabled: Boolean): Result<Unit> =
Result.success(Unit)
override suspend fun setLuftdata(deviceId: String, enabled: Boolean): Result<Unit> =
Result.success(Unit)
override suspend fun setVisibility(deviceId: String, isPublic: Boolean): Result<Unit> =
Result.success(Unit)
override suspend fun triggerFirmwareUpdate(deviceId: String): Result<Unit> = override suspend fun triggerFirmwareUpdate(deviceId: String): Result<Unit> =
Result.success(Unit) Result.success(Unit)
} }

View File

@@ -10,6 +10,8 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton import javax.inject.Singleton
import org.db3.airmq.sdk.device.data.DeviceRepositoryImpl import org.db3.airmq.sdk.device.data.DeviceRepositoryImpl
import org.db3.airmq.sdk.device.data.local.DEVICE_DB_MIGRATION_1_2
import org.db3.airmq.sdk.device.data.local.DEVICE_DB_MIGRATION_2_3
import org.db3.airmq.sdk.device.data.local.DeviceDao import org.db3.airmq.sdk.device.data.local.DeviceDao
import org.db3.airmq.sdk.device.data.local.DeviceDatabase import org.db3.airmq.sdk.device.data.local.DeviceDatabase
import org.db3.airmq.sdk.device.data.local.PendingMutationDao import org.db3.airmq.sdk.device.data.local.PendingMutationDao
@@ -28,7 +30,7 @@ object DeviceDatabaseModule {
context, context,
DeviceDatabase::class.java, DeviceDatabase::class.java,
"airmq_device_db" "airmq_device_db"
).build() ).addMigrations(DEVICE_DB_MIGRATION_1_2, DEVICE_DB_MIGRATION_2_3).build()
@Provides @Provides
@Singleton @Singleton

View File

@@ -26,11 +26,16 @@ enum class OnlineFreshness {
* *
* @param id Unique device identifier * @param id Unique device identifier
* @param name Display name * @param name Display name
* @param model Device model identifier * @param model Device model type
* @param firmwareVersion Firmware version string * @param firmwareVersion Firmware version string
* @param deviceAddress Device IP address
* @param configVersion Config version string
* @param isNarodmonOn Whether Narodmon.ru data sharing is enabled
* @param isLuftdataOn Whether Sensor.community data sharing is enabled
* @param locationId Optional location identifier * @param locationId Optional location identifier
* @param latitude Optional latitude * @param latitude Optional latitude
* @param longitude Optional longitude * @param longitude Optional longitude
* @param isPublic Whether device location is published (visible to others)
* @param city Optional city name for the location * @param city Optional city name for the location
* @param dataSharingEnabled Whether data sharing is enabled * @param dataSharingEnabled Whether data sharing is enabled
* @param isOnline Whether the device is currently online * @param isOnline Whether the device is currently online
@@ -40,11 +45,16 @@ enum class OnlineFreshness {
data class Device( data class Device(
val id: String, val id: String,
val name: String, val name: String,
val model: String, val model: DeviceModel,
val firmwareVersion: String, val firmwareVersion: String,
val deviceAddress: String,
val configVersion: String,
val isNarodmonOn: Boolean,
val isLuftdataOn: Boolean,
val locationId: String? = null, val locationId: String? = null,
val latitude: Double? = null, val latitude: Double? = null,
val longitude: Double? = null, val longitude: Double? = null,
val isPublic: Boolean = false,
val city: String? = null, val city: String? = null,
val dataSharingEnabled: Boolean = false, val dataSharingEnabled: Boolean = false,
val isOnline: Boolean = false, val isOnline: Boolean = false,

View File

@@ -0,0 +1,24 @@
package org.db3.airmq.sdk.device.domain
/**
* Device model type. Maps to legacy numeric codes and string identifiers from API/DB.
*/
enum class DeviceModel(val displayName: String, private val storageValue: String) {
Basic("Basic", "0"),
Mobile("Mobile", "1"),
Solar("Solar", "2"),
Radiation("Radiation", "4"),
Custom("Custom", "-1");
fun toStorageString(): String = storageValue
companion object {
fun fromString(value: String?): DeviceModel = when (value?.lowercase()) {
"0", "basic" -> Basic
"1", "mobile" -> Mobile
"2", "solar" -> Solar
"4", "radiation" -> Radiation
else -> Custom
}
}
}

View File

@@ -47,6 +47,22 @@ interface DeviceRepository {
*/ */
suspend fun setDataSharing(deviceId: String, enabled: Boolean): Result<Unit> suspend fun setDataSharing(deviceId: String, enabled: Boolean): Result<Unit>
/**
* Enable or disable Narodmon.ru data sharing.
*/
suspend fun setNarodmon(deviceId: String, enabled: Boolean): Result<Unit>
/**
* Enable or disable Sensor.community (Luftdata) data sharing.
*/
suspend fun setLuftdata(deviceId: String, enabled: Boolean): Result<Unit>
/**
* Set device visibility (publish/hide location on map).
* Only effective when device has location.
*/
suspend fun setVisibility(deviceId: String, isPublic: Boolean): Result<Unit>
/** /**
* Trigger firmware update. Requires connectivity. * Trigger firmware update. Requires connectivity.
* *

View File

@@ -6,7 +6,10 @@ package org.db3.airmq.sdk.device.domain
enum class PendingMutationType { enum class PendingMutationType {
RENAME, RENAME,
LOCATION, LOCATION,
DATA_SHARING DATA_SHARING,
NARODMON,
LUFTDATA,
VISIBILITY
} }
/** /**

5
temp.md Normal file
View File

@@ -0,0 +1,5 @@
# Temporary & Implicit Implementations
Items implemented implicitly or as obvious placeholders are listed here with important details for later refinement or replacement.
---