refactor(ui): Icon audit and node list item refactor (#4313)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-01-25 16:43:23 -06:00 committed by GitHub
parent 5db2c9d69c
commit a28aa4d52e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
91 changed files with 2178 additions and 702 deletions

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.component
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.PreviewLightDark
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.channel
import org.meshtastic.core.ui.icon.Channel
import org.meshtastic.core.ui.icon.Counter0
import org.meshtastic.core.ui.icon.Counter1
import org.meshtastic.core.ui.icon.Counter2
import org.meshtastic.core.ui.icon.Counter3
import org.meshtastic.core.ui.icon.Counter4
import org.meshtastic.core.ui.icon.Counter5
import org.meshtastic.core.ui.icon.Counter6
import org.meshtastic.core.ui.icon.Counter7
import org.meshtastic.core.ui.icon.Counter8
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.theme.AppTheme
@Composable
@Suppress("MagicNumber")
fun ChannelInfo(
channel: Int,
modifier: Modifier = Modifier,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
val icon =
when (channel) {
0 -> MeshtasticIcons.Counter0
1 -> MeshtasticIcons.Counter1
2 -> MeshtasticIcons.Counter2
3 -> MeshtasticIcons.Counter3
4 -> MeshtasticIcons.Counter4
5 -> MeshtasticIcons.Counter5
6 -> MeshtasticIcons.Counter6
7 -> MeshtasticIcons.Counter7
8 -> MeshtasticIcons.Counter8
else -> MeshtasticIcons.Channel
}
IconInfo(
modifier = modifier,
icon = icon,
contentDescription = stringResource(Res.string.channel),
text = stringResource(Res.string.channel),
contentColor = contentColor,
)
}
@PreviewLightDark
@Composable
private fun ChannelInfoPreview() {
AppTheme { ChannelInfo(channel = 2) }
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.component
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.PreviewLightDark
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.distance
import org.meshtastic.core.ui.icon.Distance
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.theme.AppTheme
@Composable
fun DistanceInfo(
distance: String,
modifier: Modifier = Modifier,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.Distance,
contentDescription = stringResource(Res.string.distance),
text = distance,
contentColor = contentColor,
)
}
@PreviewLightDark
@Composable
private fun DistanceInfoPreview() {
AppTheme { DistanceInfo(distance = "423 mi.") }
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.component
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.model.util.metersIn
import org.meshtastic.core.model.util.toString
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.altitude
import org.meshtastic.core.strings.elevation_suffix
import org.meshtastic.core.ui.icon.Elevation
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.proto.ConfigProtos.Config.DisplayConfig.DisplayUnits
@Composable
fun ElevationInfo(
modifier: Modifier = Modifier,
altitude: Int,
system: DisplayUnits,
suffix: String = stringResource(Res.string.elevation_suffix),
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.Elevation,
contentDescription = stringResource(Res.string.altitude),
text = altitude.metersIn(system).toString(system) + " " + suffix,
contentColor = contentColor,
)
}
@Composable
@Preview
private fun ElevationInfoPreview() {
MaterialTheme { ElevationInfo(altitude = 100, system = DisplayUnits.METRIC, suffix = "ASL") }
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.component
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.PreviewLightDark
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.hops_away
import org.meshtastic.core.ui.icon.Hops
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.theme.AppTheme
@Composable
fun HopsInfo(hops: Int, modifier: Modifier = Modifier, contentColor: Color = MaterialTheme.colorScheme.onSurface) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.Hops,
contentDescription = stringResource(Res.string.hops_away),
text = hops.toString(),
contentColor = contentColor,
)
}
@PreviewLightDark
@Composable
private fun HopsInfoPreview() {
AppTheme { HopsInfo(hops = 3) }
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.meshtastic.core.ui.icon.Elevation
import org.meshtastic.core.ui.icon.MeshtasticIcons
private const val SIZE_ICON = 20
@Composable
fun IconInfo(
icon: ImageVector,
contentDescription: String,
modifier: Modifier = Modifier,
text: String? = null,
style: TextStyle = MaterialTheme.typography.labelMedium,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
content: @Composable () -> Unit = {},
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
Icon(
modifier = Modifier.size(SIZE_ICON.dp),
imageVector = icon,
contentDescription = contentDescription,
tint = contentColor,
)
text?.let { Text(text = it, style = style, color = contentColor) }
content()
}
}
@Composable
@Preview
private fun IconInfoPreview() {
MaterialTheme {
IconInfo(icon = MeshtasticIcons.Elevation, contentDescription = "Elevation", content = { Text(text = "100") })
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.component
import androidx.compose.foundation.background
@ -31,9 +30,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ThumbUp
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
@ -61,6 +57,9 @@ import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.air_quality_icon
import org.meshtastic.core.strings.close
import org.meshtastic.core.strings.indoor_air_quality_iaq
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.ThumbUp
import org.meshtastic.core.ui.icon.Warning
import org.meshtastic.core.ui.theme.IAQColors.IAQDangerouslyPolluted
import org.meshtastic.core.ui.theme.IAQColors.IAQExcellent
import org.meshtastic.core.ui.theme.IAQColors.IAQExtremelyPolluted
@ -137,7 +136,7 @@ fun IndoorAirQuality(iaq: Int?, displayMode: IaqDisplayMode = IaqDisplayMode.Pil
Text(text = "IAQ $iaq", color = Color.White, fontWeight = FontWeight.Bold)
Icon(
imageVector =
if (iaqEnum.range.first < 100) Icons.Default.ThumbUp else Icons.Filled.Warning,
if (iaqEnum.range.first < 100) MeshtasticIcons.ThumbUp else MeshtasticIcons.Warning,
contentDescription = stringResource(Res.string.air_quality_icon),
tint = Color.White,
)

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.component
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.PreviewLightDark
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.node_sort_last_heard
import org.meshtastic.core.ui.R
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.core.ui.util.formatAgo
@Composable
fun LastHeardInfo(
modifier: Modifier = Modifier,
lastHeard: Int,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
IconInfo(
modifier = modifier,
icon = ImageVector.vectorResource(id = R.drawable.ic_antenna_24),
contentDescription = stringResource(Res.string.node_sort_last_heard),
text = formatAgo(lastHeard),
contentColor = contentColor,
)
}
@PreviewLightDark
@Composable
private fun LastHeardInfoPreview() {
AppTheme { LastHeardInfo(lastHeard = (System.currentTimeMillis() / 1000).toInt() - 8600) }
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@file:Suppress("MagicNumber")
package org.meshtastic.core.ui.component
@ -29,10 +28,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.SignalCellular4Bar
import androidx.compose.material.icons.filled.SignalCellularAlt
import androidx.compose.material.icons.filled.SignalCellularAlt1Bar
import androidx.compose.material.icons.filled.SignalCellularAlt2Bar
import androidx.compose.material.icons.rounded.SignalCellular4Bar
import androidx.compose.material.icons.rounded.SignalCellularAlt
import androidx.compose.material.icons.rounded.SignalCellularAlt1Bar
import androidx.compose.material.icons.rounded.SignalCellularAlt2Bar
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.colorScheme
@ -72,10 +71,10 @@ enum class Quality(
@Stable val imageVector: ImageVector,
@Stable val color: @Composable () -> Color,
) {
NONE(Res.string.none_quality, Icons.Default.SignalCellularAlt1Bar, { colorScheme.StatusRed }),
BAD(Res.string.bad, Icons.Default.SignalCellularAlt2Bar, { colorScheme.StatusOrange }),
FAIR(Res.string.fair, Icons.Default.SignalCellularAlt, { colorScheme.StatusYellow }),
GOOD(Res.string.good, Icons.Default.SignalCellular4Bar, { colorScheme.StatusGreen }),
NONE(Res.string.none_quality, Icons.Rounded.SignalCellularAlt1Bar, { colorScheme.StatusRed }),
BAD(Res.string.bad, Icons.Rounded.SignalCellularAlt2Bar, { colorScheme.StatusOrange }),
FAIR(Res.string.fair, Icons.Rounded.SignalCellularAlt, { colorScheme.StatusYellow }),
GOOD(Res.string.good, Icons.Rounded.SignalCellular4Bar, { colorScheme.StatusGreen }),
}
/**

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.component
import android.util.Base64
@ -28,10 +27,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyOff
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.LockOpen
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
@ -52,7 +47,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewLightDark
@ -61,7 +55,6 @@ import com.google.protobuf.ByteString
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.model.Channel
import org.meshtastic.core.strings.R
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.config_security_public_key
import org.meshtastic.core.strings.encryption_error
@ -74,6 +67,10 @@ import org.meshtastic.core.strings.security_icon_help_dismiss
import org.meshtastic.core.strings.security_icon_help_show_all
import org.meshtastic.core.strings.security_icon_help_show_less
import org.meshtastic.core.strings.show_all_key_title
import org.meshtastic.core.ui.icon.KeyOff
import org.meshtastic.core.ui.icon.Lock
import org.meshtastic.core.ui.icon.LockOpen
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
@ -106,11 +103,9 @@ fun NodeKeyStatusIcon(
val (icon, tint) =
when {
mismatchKey -> Icons.Default.KeyOff to colorScheme.StatusRed
hasPKC -> Icons.Default.Lock to colorScheme.StatusGreen
else ->
ImageVector.vectorResource(org.meshtastic.core.ui.R.drawable.ic_lock_open_right_24) to
colorScheme.StatusYellow
mismatchKey -> MeshtasticIcons.KeyOff to colorScheme.StatusRed
hasPKC -> MeshtasticIcons.Lock to colorScheme.StatusGreen
else -> MeshtasticIcons.LockOpen to colorScheme.StatusYellow
}
IconButton(onClick = { showEncryptionDialog = true }, modifier = modifier) {
@ -149,7 +144,7 @@ enum class NodeKeySecurityState(
) {
// State for public key mismatch
PKM(
icon = Icons.Default.KeyOff,
icon = MeshtasticIcons.KeyOff,
color = { colorScheme.StatusRed },
descriptionResId = Res.string.encryption_error,
helpTextResId = Res.string.encryption_error_text,
@ -158,7 +153,7 @@ enum class NodeKeySecurityState(
// State for public key encryption
PKC(
icon = Icons.Default.Lock,
icon = MeshtasticIcons.Lock,
color = { colorScheme.StatusGreen },
title = Res.string.encryption_pkc,
helpTextResId = Res.string.encryption_pkc_text,
@ -167,7 +162,7 @@ enum class NodeKeySecurityState(
// State for shared key encryption
PSK(
icon = Icons.Default.LockOpen,
icon = MeshtasticIcons.LockOpen,
color = { colorScheme.StatusYellow },
title = Res.string.encryption_psk,
helpTextResId = Res.string.encryption_psk_text,

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.component
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.PreviewLightDark
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.sats
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.Satellites
import org.meshtastic.core.ui.theme.AppTheme
@Composable
fun SatelliteCountInfo(
modifier: Modifier = Modifier,
satCount: Int,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.Satellites,
contentDescription = stringResource(Res.string.sats),
text = "$satCount",
contentColor = contentColor,
)
}
@PreviewLightDark
@Composable
private fun SatelliteCountInfoPreview() {
AppTheme { SatelliteCountInfo(satCount = 5) }
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@file:Suppress("TooManyFunctions")
package org.meshtastic.core.ui.component
@ -30,10 +29,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.LockOpen
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox
@ -78,6 +73,10 @@ import org.meshtastic.core.strings.security_icon_insecure_no_precise
import org.meshtastic.core.strings.security_icon_insecure_precise_only
import org.meshtastic.core.strings.security_icon_secure
import org.meshtastic.core.strings.security_icon_warning_precise_mqtt
import org.meshtastic.core.ui.icon.Lock
import org.meshtastic.core.ui.icon.LockOpen
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.Warning
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
import org.meshtastic.core.ui.theme.StatusColors.StatusYellow
@ -109,7 +108,7 @@ enum class SecurityState(
) {
/** State for a secure channel (green lock). */
SECURE(
icon = Icons.Filled.Lock,
icon = MeshtasticIcons.Lock,
color = { colorScheme.StatusGreen },
descriptionResId = Res.string.security_icon_secure,
helpTextResId = Res.string.security_icon_help_green_lock,
@ -120,7 +119,7 @@ enum class SecurityState(
* warning. (yellow open lock)
*/
INSECURE_NO_PRECISE(
icon = Icons.Filled.LockOpen,
icon = MeshtasticIcons.LockOpen,
color = { colorScheme.StatusYellow },
descriptionResId = Res.string.security_icon_insecure_no_precise,
helpTextResId = Res.string.security_icon_help_yellow_open_lock,
@ -131,7 +130,7 @@ enum class SecurityState(
* lock)
*/
INSECURE_PRECISE_ONLY(
icon = Icons.Filled.LockOpen,
icon = MeshtasticIcons.LockOpen,
color = { colorScheme.StatusRed },
descriptionResId = Res.string.security_icon_insecure_precise_only,
helpTextResId = Res.string.security_icon_help_red_open_lock,
@ -142,11 +141,11 @@ enum class SecurityState(
* badge).
*/
INSECURE_PRECISE_MQTT_WARNING(
icon = Icons.Filled.LockOpen,
icon = MeshtasticIcons.LockOpen,
color = { colorScheme.StatusRed },
descriptionResId = Res.string.security_icon_warning_precise_mqtt,
helpTextResId = Res.string.security_icon_help_warning_precise_mqtt,
badgeIcon = Icons.Filled.Warning,
badgeIcon = MeshtasticIcons.Warning,
badgeIconColor = { colorScheme.StatusYellow },
),
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,16 +14,12 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -34,12 +30,16 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.util.formatUptime
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.channel_air_util
import org.meshtastic.core.strings.hops_away
import org.meshtastic.core.strings.air_utilization
import org.meshtastic.core.strings.channel_utilization
import org.meshtastic.core.strings.signal
import org.meshtastic.core.strings.signal_quality
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
import org.meshtastic.core.ui.icon.AirUtilization
import org.meshtastic.core.ui.icon.ChannelUtilization
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.theme.AppTheme
const val MAX_VALID_SNR = 100F
@ -53,67 +53,53 @@ fun SignalInfo(
isThisNode: Boolean,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
val text =
if (isThisNode) {
stringResource(Res.string.channel_air_util)
.format(node.deviceMetrics.channelUtilization, node.deviceMetrics.airUtilTx)
} else {
buildList {
val hopsString =
"%s: %s"
.format(
stringResource(Res.string.hops_away),
if (node.hopsAway == -1) {
"?"
} else {
node.hopsAway.toString()
},
)
if (node.channel > 0) {
add("ch:${node.channel}")
}
if (node.hopsAway != 0) add(hopsString)
}
.joinToString(" ")
}
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
if (text.isNotEmpty()) {
Text(text = text, color = contentColor, style = MaterialTheme.typography.labelSmall)
}
/* We only know the Signal Quality from direct nodes aka 0 hop. */
if (node.hopsAway <= 0) {
if (node.snr < MAX_VALID_SNR && node.rssi < MAX_VALID_RSSI) {
val quality = determineSignalQuality(node.snr, node.rssi)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Snr(node.snr)
Rssi(node.rssi)
if (isThisNode) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
IconInfo(
icon = MeshtasticIcons.ChannelUtilization,
contentDescription = stringResource(Res.string.channel_utilization),
text = "%.1f%%".format(node.deviceMetrics.channelUtilization),
contentColor = contentColor,
)
IconInfo(
icon = MeshtasticIcons.AirUtilization,
contentDescription = stringResource(Res.string.air_utilization),
text = "%.1f%%".format(node.deviceMetrics.airUtilTx),
contentColor = contentColor,
)
}
} else {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
if (node.channel > 0) {
ChannelInfo(channel = node.channel, contentColor = contentColor)
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Icon(
modifier = Modifier.size(20.dp),
imageVector = quality.imageVector,
contentDescription = stringResource(Res.string.signal_quality),
tint = quality.color.invoke(),
)
Text(
text = "${stringResource(Res.string.signal)} ${stringResource(quality.nameRes)}",
style = MaterialTheme.typography.labelSmall,
color = contentColor,
maxLines = 1,
)
if (node.hopsAway > 0) {
HopsInfo(hops = node.hopsAway, contentColor = contentColor)
} else {
Row(verticalAlignment = Alignment.CenterVertically) {
if (node.snr < MAX_VALID_SNR && node.rssi < MAX_VALID_RSSI) {
val quality = determineSignalQuality(node.snr, node.rssi)
Snr(node.snr)
Rssi(node.rssi)
IconInfo(
icon = quality.imageVector,
contentDescription = stringResource(Res.string.signal_quality),
contentColor = quality.color.invoke(),
text = "${stringResource(Res.string.signal)} ${stringResource(quality.nameRes)}",
)
}
}
}
}
}
if (node.deviceMetrics.uptimeSeconds > 0) {
UptimeInfo(uptime = formatUptime(node.deviceMetrics.uptimeSeconds), contentColor = contentColor)
}
}
}

View file

@ -0,0 +1,265 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@file:Suppress("TooManyFunctions")
package org.meshtastic.core.ui.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.env_metrics_log
import org.meshtastic.core.strings.node_id
import org.meshtastic.core.strings.pax_metrics_log
import org.meshtastic.core.strings.role
import org.meshtastic.core.strings.uptime
import org.meshtastic.core.ui.icon.AirQuality
import org.meshtastic.core.ui.icon.ArrowCircleUp
import org.meshtastic.core.ui.icon.HardwareModel
import org.meshtastic.core.ui.icon.Humidity
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.NodeId
import org.meshtastic.core.ui.icon.Paxcount
import org.meshtastic.core.ui.icon.Power
import org.meshtastic.core.ui.icon.Pressure
import org.meshtastic.core.ui.icon.Role
import org.meshtastic.core.ui.icon.Soil
import org.meshtastic.core.ui.icon.Temperature
private const val SIZE_ICON = 20
@Composable
fun TemperatureInfo(
temp: String,
modifier: Modifier = Modifier,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.Temperature,
contentDescription = stringResource(Res.string.env_metrics_log),
text = temp,
contentColor = contentColor,
)
}
@Composable
fun HumidityInfo(
humidity: String,
modifier: Modifier = Modifier,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.Humidity,
contentDescription = stringResource(Res.string.env_metrics_log),
text = humidity,
contentColor = contentColor,
)
}
@Composable
fun PressureInfo(
pressure: String,
modifier: Modifier = Modifier,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.Pressure,
contentDescription = stringResource(Res.string.env_metrics_log),
text = pressure,
contentColor = contentColor,
)
}
@Composable
fun SoilTemperatureInfo(
temp: String,
modifier: Modifier = Modifier,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
OverlayIconInfo(
modifier = modifier,
icon = MeshtasticIcons.Soil,
overlayIcon = MeshtasticIcons.Temperature,
contentDescription = stringResource(Res.string.env_metrics_log),
text = temp,
contentColor = contentColor,
)
}
@Composable
fun SoilMoistureInfo(
moisture: String,
modifier: Modifier = Modifier,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
OverlayIconInfo(
modifier = modifier,
icon = MeshtasticIcons.Soil,
overlayIcon = MeshtasticIcons.Humidity,
contentDescription = stringResource(Res.string.env_metrics_log),
text = moisture,
contentColor = contentColor,
)
}
@Composable
fun PaxcountInfo(
pax: String,
modifier: Modifier = Modifier,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.Paxcount,
contentDescription = stringResource(Res.string.pax_metrics_log),
text = pax,
contentColor = contentColor,
)
}
@Composable
fun AirQualityInfo(
iaq: String,
modifier: Modifier = Modifier,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.AirQuality,
contentDescription = stringResource(Res.string.env_metrics_log),
text = iaq,
contentColor = contentColor,
)
}
@Composable
fun PowerInfo(value: String, modifier: Modifier = Modifier, contentColor: Color = MaterialTheme.colorScheme.onSurface) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.Power,
contentDescription = stringResource(Res.string.env_metrics_log),
text = value,
contentColor = contentColor,
)
}
@Composable
fun UptimeInfo(
uptime: String,
modifier: Modifier = Modifier,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.ArrowCircleUp,
contentDescription = stringResource(Res.string.uptime),
text = uptime,
contentColor = contentColor,
)
}
@Composable
fun HardwareInfo(
hwModel: String,
modifier: Modifier = Modifier,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.HardwareModel,
contentDescription = "Hardware Model",
text = hwModel,
style = MaterialTheme.typography.labelSmall,
contentColor = contentColor,
)
}
@Composable
fun RoleInfo(role: String, modifier: Modifier = Modifier, contentColor: Color = MaterialTheme.colorScheme.onSurface) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.Role,
contentDescription = stringResource(Res.string.role),
text = role,
style = MaterialTheme.typography.labelSmall,
contentColor = contentColor,
)
}
@Composable
fun NodeIdInfo(id: String, modifier: Modifier = Modifier, contentColor: Color = MaterialTheme.colorScheme.onSurface) {
IconInfo(
modifier = modifier,
icon = MeshtasticIcons.NodeId,
contentDescription = stringResource(Res.string.node_id),
text = id,
style = MaterialTheme.typography.labelSmall,
contentColor = contentColor,
)
}
@Composable
@Suppress("MagicNumber")
fun OverlayIconInfo(
icon: ImageVector,
overlayIcon: ImageVector,
contentDescription: String,
modifier: Modifier = Modifier,
text: String? = null,
style: TextStyle = MaterialTheme.typography.labelMedium,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
val foregroundPainter = rememberVectorPainter(overlayIcon)
Icon(
imageVector = icon,
contentDescription = contentDescription,
tint = contentColor,
modifier =
Modifier.size(SIZE_ICON.dp).drawWithContent {
drawContent()
val badgeSize = size.width * .5f
with(foregroundPainter) {
draw(size = Size(badgeSize, badgeSize), colorFilter = ColorFilter.tint(contentColor))
}
},
)
text?.let { Text(text = it, style = style, color = contentColor) }
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.component.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -120,6 +119,9 @@ class NodePreviewParameterProvider : PreviewParameterProvider<Node> {
voltage = 3.7F
current = 0.0F
iaq = 100
barometricPressure = 1013.25F
soilTemperature = 28.0F
soilMoisture = 50
},
paxcounter =
paxcount {

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Reply
import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.icons.automirrored.filled.Sort
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.AddReaction
import androidx.compose.material.icons.rounded.Clear
import androidx.compose.material.icons.rounded.Close
import androidx.compose.material.icons.rounded.CloudDownload
import androidx.compose.material.icons.rounded.ContentCopy
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Folder
import androidx.compose.material.icons.rounded.MoreVert
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material.icons.rounded.Save
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material.icons.rounded.SelectAll
import androidx.compose.material.icons.rounded.Share
import androidx.compose.material.icons.rounded.SystemUpdate
import androidx.compose.material.icons.rounded.ThumbUp
import androidx.compose.ui.graphics.vector.ImageVector
val MeshtasticIcons.Add: ImageVector
get() = Icons.Rounded.Add
val MeshtasticIcons.AddReaction: ImageVector
get() = Icons.Rounded.AddReaction
val MeshtasticIcons.Clear: ImageVector
get() = Icons.Rounded.Clear
val MeshtasticIcons.Close: ImageVector
get() = Icons.Rounded.Close
val MeshtasticIcons.Copy: ImageVector
get() = Icons.Rounded.ContentCopy
val MeshtasticIcons.Delete: ImageVector
get() = Icons.Rounded.Delete
val MeshtasticIcons.Edit: ImageVector
get() = Icons.Rounded.Edit
val MeshtasticIcons.More: ImageVector
get() = Icons.Rounded.MoreVert
val MeshtasticIcons.Refresh: ImageVector
get() = Icons.Rounded.Refresh
val MeshtasticIcons.Reply: ImageVector
get() = Icons.AutoMirrored.Filled.Reply
val MeshtasticIcons.Save: ImageVector
get() = Icons.Rounded.Save
val MeshtasticIcons.Search: ImageVector
get() = Icons.Rounded.Search
val MeshtasticIcons.Send: ImageVector
get() = Icons.AutoMirrored.Filled.Send
val MeshtasticIcons.Share: ImageVector
get() = Icons.Rounded.Share
val MeshtasticIcons.Sort: ImageVector
get() = Icons.AutoMirrored.Filled.Sort
val MeshtasticIcons.CloudDownload: ImageVector
get() = Icons.Rounded.CloudDownload
val MeshtasticIcons.Folder: ImageVector
get() = Icons.Rounded.Folder
val MeshtasticIcons.SystemUpdate: ImageVector
get() = Icons.Rounded.SystemUpdate
val MeshtasticIcons.SelectAll: ImageVector
get() = Icons.Rounded.SelectAll
val MeshtasticIcons.ThumbUp: ImageVector
get() = Icons.Rounded.ThumbUp

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import org.meshtastic.core.ui.R
/** These are from Material Symbols drawables. */
val MeshtasticIcons.Counter0: ImageVector
@Composable get() = ImageVector.vectorResource(R.drawable.counter_0_24px)
val MeshtasticIcons.Counter1: ImageVector
@Composable get() = ImageVector.vectorResource(R.drawable.counter_1_24px)
val MeshtasticIcons.Counter2: ImageVector
@Composable get() = ImageVector.vectorResource(R.drawable.counter_2_24px)
val MeshtasticIcons.Counter3: ImageVector
@Composable get() = ImageVector.vectorResource(R.drawable.counter_3_24px)
val MeshtasticIcons.Counter4: ImageVector
@Composable get() = ImageVector.vectorResource(R.drawable.counter_4_24px)
val MeshtasticIcons.Counter5: ImageVector
@Composable get() = ImageVector.vectorResource(R.drawable.counter_5_24px)
val MeshtasticIcons.Counter6: ImageVector
@Composable get() = ImageVector.vectorResource(R.drawable.counter_6_24px)
val MeshtasticIcons.Counter7: ImageVector
@Composable get() = ImageVector.vectorResource(R.drawable.counter_7_24px)
val MeshtasticIcons.Counter8: ImageVector
@Composable get() = ImageVector.vectorResource(R.drawable.counter_8_24px)

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,15 +14,25 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Fingerprint
import androidx.compose.material.icons.rounded.Router
import androidx.compose.material.icons.rounded.Work
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val MeshtasticIcons.HardwareModel: ImageVector
get() = Icons.Rounded.Router
val MeshtasticIcons.Role: ImageVector
get() = Icons.Rounded.Work
val MeshtasticIcons.NodeId: ImageVector
get() = Icons.Rounded.Fingerprint
/**
* This is from Material Symbols.
*

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Bluetooth
import androidx.compose.material.icons.rounded.Usb
import androidx.compose.material.icons.rounded.Wifi
import androidx.compose.ui.graphics.vector.ImageVector
val MeshtasticIcons.Bluetooth: ImageVector
get() = Icons.Rounded.Bluetooth
val MeshtasticIcons.Usb: ImageVector
get() = Icons.Rounded.Usb
val MeshtasticIcons.Wifi: ImageVector
get() = Icons.Rounded.Wifi

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.AccountCircle
import androidx.compose.material.icons.rounded.Group
import androidx.compose.material.icons.rounded.Groups
import androidx.compose.material.icons.rounded.Person
import androidx.compose.material.icons.rounded.PersonOff
import androidx.compose.material.icons.rounded.PersonSearch
import androidx.compose.ui.graphics.vector.ImageVector
val MeshtasticIcons.Person: ImageVector
get() = Icons.Rounded.Person
val MeshtasticIcons.PersonOff: ImageVector
get() = Icons.Rounded.PersonOff
val MeshtasticIcons.Groups: ImageVector
get() = Icons.Rounded.Groups
val MeshtasticIcons.Group: ImageVector
get() = Icons.Rounded.Group
val MeshtasticIcons.AccountCircle: ImageVector
get() = Icons.Rounded.AccountCircle
val MeshtasticIcons.PersonSearch: ImageVector
get() = Icons.Rounded.PersonSearch

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Fingerprint
import androidx.compose.material.icons.rounded.KeyOff
import androidx.compose.material.icons.rounded.Lock
import androidx.compose.material.icons.rounded.LockOpen
import androidx.compose.material.icons.rounded.Verified
import androidx.compose.material.icons.rounded.Warning
import androidx.compose.ui.graphics.vector.ImageVector
val MeshtasticIcons.Lock: ImageVector
get() = Icons.Rounded.Lock
val MeshtasticIcons.LockOpen: ImageVector
get() = Icons.Rounded.LockOpen
val MeshtasticIcons.Warning: ImageVector
get() = Icons.Rounded.Warning
val MeshtasticIcons.KeyOff: ImageVector
get() = Icons.Rounded.KeyOff
val MeshtasticIcons.Verified: ImageVector
get() = Icons.Rounded.Verified
val MeshtasticIcons.Fingerprint: ImageVector
get() = Icons.Rounded.Fingerprint

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,15 +14,31 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.CrueltyFree
import androidx.compose.material.icons.rounded.Route
import androidx.compose.material.icons.rounded.SignalCellularAlt
import androidx.compose.material.icons.rounded.SsidChart
import androidx.compose.material.icons.rounded.WifiChannel
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val MeshtasticIcons.Hops: ImageVector
get() = Icons.Rounded.CrueltyFree
val MeshtasticIcons.Route: ImageVector
get() = Icons.Rounded.Route
val MeshtasticIcons.Channel: ImageVector
get() = Icons.Rounded.WifiChannel
val MeshtasticIcons.ChannelUtilization: ImageVector
get() = Icons.Rounded.SignalCellularAlt
val MeshtasticIcons.AirUtilization: ImageVector
get() = Icons.Rounded.SsidChart
val MeshtasticIcons.SignalCellular0Bar: ImageVector
get() {
if (signalCellular0Bar != null) {

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.SpeakerNotes
import androidx.compose.material.icons.automirrored.filled.VolumeOff
import androidx.compose.material.icons.automirrored.filled.VolumeUp
import androidx.compose.material.icons.automirrored.twotone.VolumeMute
import androidx.compose.material.icons.automirrored.twotone.VolumeUp
import androidx.compose.material.icons.rounded.ArrowCircleUp
import androidx.compose.material.icons.rounded.CheckCircleOutline
import androidx.compose.material.icons.rounded.Cloud
import androidx.compose.material.icons.rounded.CloudOff
import androidx.compose.material.icons.rounded.Dangerous
import androidx.compose.material.icons.rounded.History
import androidx.compose.material.icons.rounded.NoCell
import androidx.compose.material.icons.rounded.SpeakerNotesOff
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material.icons.rounded.StarBorder
import androidx.compose.material.icons.twotone.Cloud
import androidx.compose.material.icons.twotone.CloudDone
import androidx.compose.material.icons.twotone.CloudOff
import androidx.compose.material.icons.twotone.CloudSync
import androidx.compose.ui.graphics.vector.ImageVector
val MeshtasticIcons.Favorite: ImageVector
get() = Icons.Rounded.Star
val MeshtasticIcons.NotFavorite: ImageVector
get() = Icons.Rounded.StarBorder
val MeshtasticIcons.Muted: ImageVector
get() = Icons.Rounded.SpeakerNotesOff
val MeshtasticIcons.Unmuted: ImageVector
get() = Icons.AutoMirrored.Filled.SpeakerNotes
val MeshtasticIcons.VolumeOff: ImageVector
get() = Icons.AutoMirrored.Filled.VolumeOff
val MeshtasticIcons.VolumeUp: ImageVector
get() = Icons.AutoMirrored.Filled.VolumeUp
val MeshtasticIcons.History: ImageVector
get() = Icons.Rounded.History
val MeshtasticIcons.Cloud: ImageVector
get() = Icons.Rounded.Cloud
val MeshtasticIcons.CloudOff: ImageVector
get() = Icons.Rounded.CloudOff
val MeshtasticIcons.Unmessageable: ImageVector
get() = Icons.Rounded.NoCell
val MeshtasticIcons.CloudDone: ImageVector
get() = Icons.TwoTone.CloudDone
val MeshtasticIcons.CloudSync: ImageVector
get() = Icons.TwoTone.CloudSync
val MeshtasticIcons.CloudOffTwoTone: ImageVector
get() = Icons.TwoTone.CloudOff
val MeshtasticIcons.CloudTwoTone: ImageVector
get() = Icons.TwoTone.Cloud
val MeshtasticIcons.ArrowCircleUp: ImageVector
get() = Icons.Rounded.ArrowCircleUp
val MeshtasticIcons.Dangerous: ImageVector
get() = Icons.Rounded.Dangerous
val MeshtasticIcons.VolumeUpTwoTone: ImageVector
get() = Icons.AutoMirrored.TwoTone.VolumeUp
val MeshtasticIcons.VolumeMuteTwoTone: ImageVector
get() = Icons.AutoMirrored.TwoTone.VolumeMute
val MeshtasticIcons.CheckCircle: ImageVector
get() = Icons.Rounded.CheckCircleOutline

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Air
import androidx.compose.material.icons.rounded.DataArray
import androidx.compose.material.icons.rounded.ElectricBolt
import androidx.compose.material.icons.rounded.Grass
import androidx.compose.material.icons.rounded.People
import androidx.compose.material.icons.rounded.SocialDistance
import androidx.compose.material.icons.rounded.Speed
import androidx.compose.material.icons.rounded.StackedLineChart
import androidx.compose.material.icons.rounded.Thermostat
import androidx.compose.material.icons.rounded.WaterDrop
import androidx.compose.material.icons.twotone.SatelliteAlt
import androidx.compose.ui.graphics.vector.ImageVector
val MeshtasticIcons.Temperature: ImageVector
get() = Icons.Rounded.Thermostat
val MeshtasticIcons.Humidity: ImageVector
get() = Icons.Rounded.WaterDrop
val MeshtasticIcons.Pressure: ImageVector
get() = Icons.Rounded.Speed
val MeshtasticIcons.Soil: ImageVector
get() = Icons.Rounded.Grass
val MeshtasticIcons.Paxcount: ImageVector
get() = Icons.Rounded.People
val MeshtasticIcons.AirQuality: ImageVector
get() = Icons.Rounded.Air
val MeshtasticIcons.Power: ImageVector
get() = Icons.Rounded.ElectricBolt
val MeshtasticIcons.Distance: ImageVector
get() = Icons.Rounded.SocialDistance
val MeshtasticIcons.Satellites: ImageVector
get() = Icons.TwoTone.SatelliteAlt
val MeshtasticIcons.DataArray: ImageVector
get() = Icons.Rounded.DataArray
val MeshtasticIcons.Speed: ImageVector
get() = Icons.Rounded.Speed
val MeshtasticIcons.Chart: ImageVector
get() = Icons.Rounded.StackedLineChart

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM440,680L520,680Q553,680 576.5,656.5Q600,633 600,600L600,360Q600,327 576.5,303.5Q553,280 520,280L440,280Q407,280 383.5,303.5Q360,327 360,360L360,600Q360,633 383.5,656.5Q407,680 440,680ZM440,360L520,360Q520,360 520,360Q520,360 520,360L520,600Q520,600 520,600Q520,600 520,600L440,600Q440,600 440,600Q440,600 440,600L440,360Q440,360 440,360Q440,360 440,360Z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM460,360L460,640Q460,657 471.5,668.5Q483,680 500,680Q517,680 528.5,668.5Q540,657 540,640L540,320Q540,303 528.5,291.5Q517,280 500,280L420,280Q403,280 391.5,291.5Q380,303 380,320Q380,337 391.5,348.5Q403,360 420,360L460,360Z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM560,680Q577,680 588.5,668.5Q600,657 600,640Q600,623 588.5,611.5Q577,600 560,600L440,600L440,520Q440,520 440,520Q440,520 440,520L520,520Q553,520 576.5,496.5Q600,473 600,440L600,360Q600,327 576.5,303.5Q553,280 520,280L400,280Q383,280 371.5,291.5Q360,303 360,320Q360,337 371.5,348.5Q383,360 400,360L520,360Q520,360 520,360Q520,360 520,360L520,440Q520,440 520,440Q520,440 520,440L440,440Q407,440 383.5,463.5Q360,487 360,520L360,640Q360,657 371.5,668.5Q383,680 400,680L560,680Z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM400,680L520,680Q553,680 576.5,656.5Q600,633 600,600L600,540Q600,514 583,497Q566,480 540,480Q566,480 583,463Q600,446 600,420L600,360Q600,327 576.5,303.5Q553,280 520,280L400,280Q383,280 371.5,291.5Q360,303 360,320Q360,337 371.5,348.5Q383,360 400,360L520,360Q520,360 520,360Q520,360 520,360L520,440Q520,440 520,440Q520,440 520,440L480,440Q463,440 451.5,451.5Q440,463 440,480Q440,497 451.5,508.5Q463,520 480,520L520,520Q520,520 520,520Q520,520 520,520L520,600Q520,600 520,600Q520,600 520,600L400,600Q383,600 371.5,611.5Q360,623 360,640Q360,657 371.5,668.5Q383,680 400,680Z"/>
</vector>

View file

@ -0,0 +1,22 @@
<!--
~ Copyright (c) 2025 Meshtastic LLC
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM520,680L600,680L600,280L520,280L520,440L440,440L440,280L360,280L360,520L520,520L520,680Z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM400,680L520,680Q553,680 576.5,656.5Q600,633 600,600L600,520Q600,487 576.5,463.5Q553,440 520,440L440,440L440,360L560,360Q577,360 588.5,348.5Q600,337 600,320Q600,303 588.5,291.5Q577,280 560,280L400,280Q383,280 371.5,291.5Q360,303 360,320L360,480Q360,497 371.5,508.5Q383,520 400,520L520,520Q520,520 520,520Q520,520 520,520L520,600Q520,600 520,600Q520,600 520,600L400,600Q383,600 371.5,611.5Q360,623 360,640Q360,657 371.5,668.5Q383,680 400,680Z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM440,680L520,680Q553,680 576.5,656.5Q600,633 600,600L600,520Q600,487 576.5,463.5Q553,440 520,440L440,440L440,360Q440,360 440,360Q440,360 440,360L520,360Q537,360 548.5,348.5Q560,337 560,320Q560,303 548.5,291.5Q537,280 520,280L440,280Q407,280 383.5,303.5Q360,327 360,360L360,600Q360,633 383.5,656.5Q407,680 440,680ZM440,520L520,520Q520,520 520,520Q520,520 520,520L520,600Q520,600 520,600Q520,600 520,600L440,600Q440,600 440,600Q440,600 440,600L440,520Z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M520,360L452,632Q447,650 459,665Q471,680 490,680Q504,680 514,672Q524,664 527,651L598,370Q600,365 600,361Q600,357 600,352Q600,323 579.5,301.5Q559,280 530,280L400,280Q383,280 371.5,291.5Q360,303 360,320Q360,337 371.5,348.5Q383,360 400,360L520,360ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM440,680L520,680Q553,680 576.5,656.5Q600,633 600,600L600,540Q600,515 582.5,497.5Q565,480 540,480Q565,480 582.5,462.5Q600,445 600,420L600,360Q600,327 576.5,303.5Q553,280 520,280L440,280Q407,280 383.5,303.5Q360,327 360,360L360,420Q360,445 377.5,462.5Q395,480 420,480Q395,480 377.5,497.5Q360,515 360,540L360,600Q360,633 383.5,656.5Q407,680 440,680ZM440,360L520,360Q520,360 520,360Q520,360 520,360L520,440Q520,440 520,440Q520,440 520,440L440,440Q440,440 440,440Q440,440 440,440L440,360Q440,360 440,360Q440,360 440,360ZM440,600Q440,600 440,600Q440,600 440,600L440,520Q440,520 440,520Q440,520 440,520L520,520Q520,520 520,520Q520,520 520,520L520,600Q520,600 520,600Q520,600 520,600L440,600Z"/>
</vector>