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.
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
import android.R as AndroidR
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.text.BasicTextField
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.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Switch
@@ -27,24 +35,44 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.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.sp
import androidx.hilt.navigation.compose.hiltViewModel
import kotlinx.coroutines.flow.collectLatest
import org.db3.airmq.R
import org.db3.airmq.sdk.device.domain.DeviceModel
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.sdk.device.domain.OnlineStatus
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
fun DeviceSettingsScreen(
@@ -52,11 +80,15 @@ fun DeviceSettingsScreen(
onOpenLocation: () -> Unit,
onShowOnMap: () -> Unit,
onNavigateBack: () -> Unit,
viewModel: DeviceSettingsViewModel = hiltViewModel()
viewModel: DeviceSettingsViewModel = hiltViewModel(
creationCallback = { factory: DeviceSettingsViewModel.Factory ->
factory.create(deviceId)
}
)
) {
val uiState by viewModel.uiState.collectAsState()
val snackbarHostState = remember { SnackbarHostState() }
val toastDoneText = stringResource(R.string.toast_done)
val context = LocalContext.current
LaunchedEffect(viewModel) {
viewModel.actions.collectLatest { action ->
@@ -64,21 +96,23 @@ fun DeviceSettingsScreen(
is DeviceSettingsScreenContract.Action.ShowError -> {
snackbarHostState.showSnackbar(action.message)
}
is DeviceSettingsScreenContract.Action.ShowSuccess -> {
snackbarHostState.showSnackbar(toastDoneText)
}
is DeviceSettingsScreenContract.Action.ShowSuccess -> { /* no snackbar for success */ }
is DeviceSettingsScreenContract.Action.OpenLocation -> onOpenLocation()
is DeviceSettingsScreenContract.Action.ShowOnMap -> onShowOnMap()
is DeviceSettingsScreenContract.Action.OpenInfoUrl -> {
context.startActivity(Intent(Intent.ACTION_VIEW, INFO_URL.toUri()))
}
}
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
modifier = Modifier.fillMaxWidth(),
topBar = {
Row(
modifier = Modifier
.fillMaxWidth()
.statusBarsPadding()
.background(LegacyBackground)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
@@ -90,26 +124,42 @@ fun DeviceSettingsScreen(
)
}
Text(
text = stringResource(R.string.title_device),
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(start = 8.dp)
text = stringResource(R.string.title_device_config),
style = MaterialTheme.typography.titleLarge.copy(
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 ->
Box(
modifier = Modifier
.fillMaxSize()
.fillMaxWidth()
.padding(innerPadding)
) {
when {
uiState.isLoading -> {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
uiState.device == null && uiState.isLoading -> {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
uiState.device == null -> {
uiState.device == null && !uiState.isLoading -> {
Text(
text = stringResource(R.string.text_nothing_to_show),
modifier = Modifier
@@ -118,26 +168,48 @@ fun DeviceSettingsScreen(
)
}
else -> {
DeviceSettingsContent(
state = uiState,
onEvent = viewModel::onEvent,
deviceName = uiState.device?.name ?: ""
)
Box(modifier = Modifier.fillMaxWidth()) {
DeviceSettingsContent(
state = uiState,
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
private fun DeviceSettingsContent(
state: DeviceSettingsScreenContract.State,
onEvent: (DeviceSettingsScreenContract.Event) -> Unit,
deviceName: String
labelColorOverride: Color? = null,
greyColorOverride: Color? = null,
iconResOverride: Int? = null
) {
val device = state.device!!
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) {
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 statusIcon = when (device.toOnlineStatus()) {
OnlineStatus.Online -> R.drawable.device_chip_online
val statusIcon = when {
state.isConnected -> R.drawable.device_chip_online
else -> R.drawable.device_chip_offline
}
@@ -189,115 +275,458 @@ private fun DeviceSettingsContent(
.fillMaxWidth()
.verticalScroll(scrollState)
.background(LegacyBackground)
.padding(16.dp)
) {
HorizontalDivider(color = LegacyBlack12)
Row(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.height(72.dp)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(R.drawable.ic_chip),
painter = painterResource(
iconResOverride ?: device.model.iconRes()
),
contentDescription = null,
modifier = Modifier.size(48.dp)
modifier = Modifier.size(40.dp)
)
Spacer(modifier = Modifier.size(16.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = device.name,
style = MaterialTheme.typography.headlineSmall
style = MaterialTheme.typography.bodyLarge.copy(fontSize = 16.sp)
)
Text(
text = device.model,
style = MaterialTheme.typography.bodyMedium,
color = Color.Gray
text = device.model.displayName,
style = MaterialTheme.typography.bodyMedium.copy(fontSize = 14.sp),
color = greyColor
)
}
Icon(
painter = painterResource(statusIcon),
contentDescription = device.toOnlineStatus().toString(),
modifier = Modifier.size(24.dp)
)
}
if (state.isPendingSync) {
Text(
text = stringResource(R.string.text_pending_sync),
style = MaterialTheme.typography.bodySmall,
color = Color.Gray,
modifier = Modifier.padding(top = 8.dp)
text = if (state.isConnected) stringResource(R.string.text_device_connected) else "",
style = MaterialTheme.typography.labelSmall.copy(fontSize = 11.sp),
color = greyColor
)
if (!state.isConnected) {
Icon(
painter = painterResource(statusIcon),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
}
}
HorizontalDivider(color = LegacyBlack12)
Spacer(modifier = Modifier.height(24.dp))
AirMQButton(
text = stringResource(R.string.button_rename),
ConfigRow(
label = stringResource(R.string.text_device_name),
value = device.name,
onClick = { showRenameDialog = true },
style = AirMQButtonStyle.Outlined,
modifier = Modifier.fillMaxWidth()
trailingIcon = R.drawable.ic_edit,
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()) {
Text(
text = stringResource(
ConfigRowReadOnly(
label = stringResource(R.string.text_device_ip),
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,
device.latitude!!,
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)
roundLocation(device.latitude!!),
roundLocation(device.longitude!!)
)
AirMQButton(
text = stringResource(R.string.button_view_on_map),
onClick = { onEvent(DeviceSettingsScreenContract.Event.ShowOnMapClicked) },
style = AirMQButtonStyle.Outlined,
modifier = Modifier.weight(1f)
)
}
} else {
AirMQButton(
text = stringResource(R.string.button_register),
onClick = { onEvent(DeviceSettingsScreenContract.Event.OpenLocationClicked) },
style = AirMQButtonStyle.Outlined,
modifier = Modifier.fillMaxWidth()
)
}
} else {
stringResource(R.string.text_device_location_unset)
},
showIndicator = !device.hasLocation(),
indicatorColor = labelColorOverride?.let { PreviewOrange } ?: colorResource(R.color.colorOrange),
onClick = { onEvent(DeviceSettingsScreenContract.Event.OpenLocationClicked) },
trailingIcon = if (device.hasLocation()) null else R.drawable.ic_edit,
iconTint = labelColorOverride?.let { PreviewBlack54 } ?: colorResource(R.color.black54),
labelColor = headerColor,
indicatorDrawableRes = null
)
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(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = stringResource(R.string.text_data_sharing),
style = MaterialTheme.typography.bodyLarge
)
Switch(
checked = device.dataSharingEnabled,
onCheckedChange = { onEvent(DeviceSettingsScreenContract.Event.DataSharingToggled(it)) }
)
}
ToggleRow(
title = stringResource(R.string.text_device_luftdata_title),
subtitle = stringResource(R.string.text_device_luftdata),
checked = device.isLuftdataOn,
onCheckedChange = { onEvent(DeviceSettingsScreenContract.Event.LuftdataToggled(it)) },
onInfoClick = { onEvent(DeviceSettingsScreenContract.Event.LuftdataInfoClicked) },
labelColor = headerColor,
infoIconTint = labelColorOverride?.let { PreviewBlack54 } ?: colorResource(R.color.black54),
infoIconResOverride = null
)
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(
text = stringResource(R.string.button_firmware_update),
onClick = { onEvent(DeviceSettingsScreenContract.Event.FirmwareUpdateClicked) },
style = AirMQButtonStyle.Gradient,
modifier = Modifier.fillMaxWidth()
text = stringResource(R.string.button_view_on_map),
onClick = { onEvent(DeviceSettingsScreenContract.Event.ShowOnMapClicked) },
style = AirMQButtonStyle.Contained,
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
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 {
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(
val device: Device? = null,
val isLoading: Boolean = false,
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 {
@@ -16,15 +67,23 @@ object DeviceSettingsScreenContract {
data object ShowSuccess : Action
data object OpenLocation : Action
data object ShowOnMap : Action
data object OpenInfoUrl : Action
}
sealed interface 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 object LocationRemoved : Event
data class DataSharingToggled(val enabled: Boolean) : Event
data object FirmwareUpdateClicked : Event
data object OpenLocationClicked : 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
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
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.ObservePendingSyncUseCase
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.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 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
class DeviceSettingsViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
@HiltViewModel(assistedFactory = DeviceSettingsViewModel.Factory::class)
class DeviceSettingsViewModel @AssistedInject constructor(
@Assisted private val deviceId: String,
private val getDeviceUseCase: GetDeviceUseCase,
private val renameDeviceUseCase: RenameDeviceUseCase,
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 observePendingSyncUseCase: ObservePendingSyncUseCase
) : ViewModel() {
private val deviceId: String = checkNotNull(savedStateHandle.get<String>("deviceId"))
@AssistedFactory
interface Factory {
fun create(deviceId: String): DeviceSettingsViewModel
}
private val _uiState = MutableStateFlow(DeviceSettingsScreenContract.State())
val uiState: StateFlow<DeviceSettingsScreenContract.State> = _uiState.asStateFlow()
@@ -48,22 +60,60 @@ class DeviceSettingsViewModel @Inject constructor(
) { device, hasPending ->
device to hasPending
}.collect { (device, hasPending) ->
val displayDevice = device ?: stubDevice()
val configVersionNeedsUpdate = deriveConfigVersionNeedsUpdate(displayDevice)
val isConnected = true // Stub: always connected for UI testing
_uiState.update {
it.copy(
device = device,
isPendingSync = hasPending
device = displayDevice,
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) {
when (event) {
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.LocationRemoved -> removeLocation()
is DeviceSettingsScreenContract.Event.DataSharingToggled -> setDataSharing(event.enabled)
is DeviceSettingsScreenContract.Event.FirmwareUpdateClicked -> triggerFirmwareUpdate()
is DeviceSettingsScreenContract.Event.OpenLocationClicked -> _actions.tryEmit(
DeviceSettingsScreenContract.Action.OpenLocation
@@ -71,12 +121,24 @@ class DeviceSettingsViewModel @Inject constructor(
is DeviceSettingsScreenContract.Event.ShowOnMapClicked -> _actions.tryEmit(
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) {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
delay(500) // Stub simulation
val result = renameDeviceUseCase(deviceId, name)
_uiState.update { it.copy(isLoading = false) }
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) {
viewModelScope.launch {
_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 {
_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) }
if (result.isSuccess) {
_actions.tryEmit(DeviceSettingsScreenContract.Action.ShowSuccess)
} else {
if (result.isFailure) {
_actions.tryEmit(
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(
id = id,
name = name,
extra = model.ifEmpty { "mobile" },
extra = model.displayName,
status = statusText,
hasLocation = hasLocation()
)

View File

@@ -13,6 +13,8 @@ val LegacyOnBackground = Color(0xFF000000)
val LegacyOnSurface = Color(0xFF000000)
val LegacyOutline = Color(0x61000000)
val LegacyOutlineLight = Color(0x3B000000)
val LegacyBlack12 = Color(0x1F000000)
val LegacyBlack38 = Color(0x61000000)
val LegacyNavSelected = Color(0xFFFFFFFF)
val LegacyNavUnselected = Color(0x8AFFFFFF)
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_location">Месцазнаходжанне</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_visible">Апублікавана</string>
<string name="text_device_visibility_private">Схавана</string>

View File

@@ -101,6 +101,7 @@
<string name="text_device_firmware">Версия прошивки</string>
<string name="text_device_location">Местоположение</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_visible">Опубликовано</string>
<string name="text_device_visibility_private">Скрыто</string>

View File

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

View File

@@ -158,6 +158,7 @@
<string name="text_device_location">Location</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_connected">connected</string>
<string name="text_device_visibility">Visibility</string>
<string name="text_device_visibility_visible">Public</string>
<string name="text_device_visibility_private">Private</string>

View File

@@ -112,6 +112,87 @@ class DeviceRepositoryImpl @Inject constructor(
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> =
remoteDataSource.triggerFirmwareUpdate(deviceId)

View File

@@ -30,6 +30,15 @@ interface DeviceDao {
@Query("UPDATE device SET dataSharingEnabled = :enabled WHERE id = :deviceId")
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")
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.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(
entities = [DeviceEntity::class, PendingMutationEntity::class],
version = 1,
version = 3,
exportSchema = false
)
abstract class DeviceDatabase : RoomDatabase() {

View File

@@ -18,9 +18,14 @@ data class DeviceEntity(
val name: String,
val model: String,
val firmwareVersion: String,
val deviceAddress: String,
val configVersion: String,
val isNarodmonOn: Boolean,
val isLuftdataOn: Boolean,
val locationId: String? = null,
val latitude: Double? = null,
val longitude: Double? = null,
val isPublic: Boolean = false,
val dataSharingEnabled: Boolean = false,
val isOnline: Boolean = false,
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.map
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.PendingMutation
import org.db3.airmq.sdk.device.domain.PendingMutationType
@@ -47,6 +48,18 @@ class DeviceLocalDataSource @Inject constructor(
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) {
deviceDao.updateOnlineStatus(deviceId, isOnline, updatedAt)
}
@@ -91,11 +104,16 @@ class DeviceLocalDataSource @Inject constructor(
return Device(
id = id,
name = name,
model = model,
model = DeviceModel.fromString(model),
firmwareVersion = firmwareVersion,
deviceAddress = deviceAddress,
configVersion = configVersion,
isNarodmonOn = isNarodmonOn,
isLuftdataOn = isLuftdataOn,
locationId = locationId,
latitude = latitude,
longitude = longitude,
isPublic = isPublic,
city = city,
dataSharingEnabled = dataSharingEnabled,
isOnline = isOnline,
@@ -107,11 +125,16 @@ class DeviceLocalDataSource @Inject constructor(
private fun Device.toEntity(): DeviceEntity = DeviceEntity(
id = id,
name = name,
model = model,
model = model.toStorageString(),
firmwareVersion = firmwareVersion,
deviceAddress = deviceAddress,
configVersion = configVersion,
isNarodmonOn = isNarodmonOn,
isLuftdataOn = isLuftdataOn,
locationId = locationId,
latitude = latitude,
longitude = longitude,
isPublic = isPublic,
dataSharingEnabled = dataSharingEnabled,
isOnline = isOnline,
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 org.db3.airmq.sdk.device.domain.Device
import org.db3.airmq.sdk.device.domain.DeviceModel
import org.db3.airmq.sdk.device.domain.OnlineFreshness
import javax.inject.Inject
@@ -39,6 +40,21 @@ interface DeviceRemoteDataSource {
*/
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.
*/
@@ -56,11 +72,16 @@ class MockDeviceRemoteDataSource @Inject constructor() : DeviceRemoteDataSource
Device(
id = "device-1",
name = "AirMQ #42",
model = "mobile",
model = DeviceModel.Mobile,
firmwareVersion = "1.0",
deviceAddress = "192.168.1.100",
configVersion = "42",
isNarodmonOn = true,
isLuftdataOn = false,
locationId = "loc-1",
latitude = 53.9,
longitude = 27.5,
isPublic = false,
city = "Minsk",
dataSharingEnabled = true,
isOnline = true,
@@ -70,11 +91,16 @@ class MockDeviceRemoteDataSource @Inject constructor() : DeviceRemoteDataSource
Device(
id = "device-2",
name = "AirMQ #17",
model = "mobile",
model = DeviceModel.Mobile,
firmwareVersion = "1.0",
deviceAddress = "192.168.1.101",
configVersion = "41",
isNarodmonOn = false,
isLuftdataOn = false,
locationId = null,
latitude = null,
longitude = null,
isPublic = false,
city = null,
dataSharingEnabled = false,
isOnline = false,
@@ -102,6 +128,15 @@ class MockDeviceRemoteDataSource @Inject constructor() : DeviceRemoteDataSource
override suspend fun setDataSharing(deviceId: String, enabled: Boolean): Result<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> =
Result.success(Unit)
}

View File

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

View File

@@ -26,11 +26,16 @@ enum class OnlineFreshness {
*
* @param id Unique device identifier
* @param name Display name
* @param model Device model identifier
* @param model Device model type
* @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 latitude Optional latitude
* @param longitude Optional longitude
* @param isPublic Whether device location is published (visible to others)
* @param city Optional city name for the location
* @param dataSharingEnabled Whether data sharing is enabled
* @param isOnline Whether the device is currently online
@@ -40,11 +45,16 @@ enum class OnlineFreshness {
data class Device(
val id: String,
val name: String,
val model: String,
val model: DeviceModel,
val firmwareVersion: String,
val deviceAddress: String,
val configVersion: String,
val isNarodmonOn: Boolean,
val isLuftdataOn: Boolean,
val locationId: String? = null,
val latitude: Double? = null,
val longitude: Double? = null,
val isPublic: Boolean = false,
val city: String? = null,
val dataSharingEnabled: 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>
/**
* 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.
*

View File

@@ -6,7 +6,10 @@ package org.db3.airmq.sdk.device.domain
enum class PendingMutationType {
RENAME,
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.
---