diff --git a/app/src/main/java/com/geeksville/mesh/navigation/NodesNavigation.kt b/app/src/main/java/com/geeksville/mesh/navigation/NodesNavigation.kt
index df4b26822..f5cd4341b 100644
--- a/app/src/main/java/com/geeksville/mesh/navigation/NodesNavigation.kt
+++ b/app/src/main/java/com/geeksville/mesh/navigation/NodesNavigation.kt
@@ -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,18 +14,17 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package com.geeksville.mesh.navigation
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.CellTower
-import androidx.compose.material.icons.filled.LightMode
-import androidx.compose.material.icons.filled.LocationOn
-import androidx.compose.material.icons.filled.Memory
-import androidx.compose.material.icons.filled.People
-import androidx.compose.material.icons.filled.PermScanWifi
-import androidx.compose.material.icons.filled.Power
-import androidx.compose.material.icons.filled.Router
+import androidx.compose.material.icons.rounded.CellTower
+import androidx.compose.material.icons.rounded.LightMode
+import androidx.compose.material.icons.rounded.LocationOn
+import androidx.compose.material.icons.rounded.Memory
+import androidx.compose.material.icons.rounded.People
+import androidx.compose.material.icons.rounded.PermScanWifi
+import androidx.compose.material.icons.rounded.Power
+import androidx.compose.material.icons.rounded.Router
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.vector.ImageVector
@@ -286,49 +285,49 @@ enum class NodeDetailRoute(
DEVICE(
Res.string.device,
NodeDetailRoutes.DeviceMetrics::class,
- Icons.Default.Router,
+ Icons.Rounded.Router,
{ metricsVM, onNavigateUp -> DeviceMetricsScreen(metricsVM, onNavigateUp) },
),
POSITION_LOG(
Res.string.position_log,
NodeDetailRoutes.PositionLog::class,
- Icons.Default.LocationOn,
+ Icons.Rounded.LocationOn,
{ metricsVM, onNavigateUp -> PositionLogScreen(metricsVM, onNavigateUp) },
),
ENVIRONMENT(
Res.string.environment,
NodeDetailRoutes.EnvironmentMetrics::class,
- Icons.Default.LightMode,
+ Icons.Rounded.LightMode,
{ metricsVM, onNavigateUp -> EnvironmentMetricsScreen(metricsVM, onNavigateUp) },
),
SIGNAL(
Res.string.signal,
NodeDetailRoutes.SignalMetrics::class,
- Icons.Default.CellTower,
+ Icons.Rounded.CellTower,
{ metricsVM, onNavigateUp -> SignalMetricsScreen(metricsVM, onNavigateUp) },
),
TRACEROUTE(
Res.string.traceroute,
NodeDetailRoutes.TracerouteLog::class,
- Icons.Default.PermScanWifi,
+ Icons.Rounded.PermScanWifi,
{ metricsVM, onNavigateUp -> TracerouteLogScreen(viewModel = metricsVM, onNavigateUp = onNavigateUp) },
),
POWER(
Res.string.power,
NodeDetailRoutes.PowerMetrics::class,
- Icons.Default.Power,
+ Icons.Rounded.Power,
{ metricsVM, onNavigateUp -> PowerMetricsScreen(metricsVM, onNavigateUp) },
),
HOST(
Res.string.host,
NodeDetailRoutes.HostMetricsLog::class,
- Icons.Default.Memory,
+ Icons.Rounded.Memory,
{ metricsVM, onNavigateUp -> HostMetricsLogScreen(metricsVM, onNavigateUp) },
),
PAX(
Res.string.pax,
NodeDetailRoutes.PaxMetrics::class,
- Icons.Default.People,
+ Icons.Rounded.People,
{ metricsVM, onNavigateUp -> PaxMetricsScreen(metricsVM, onNavigateUp) },
),
}
diff --git a/app/src/main/java/com/geeksville/mesh/ui/Main.kt b/app/src/main/java/com/geeksville/mesh/ui/Main.kt
index 26e910ee8..842966f98 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt
@@ -41,8 +41,6 @@ import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.rounded.Wifi
import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -150,6 +148,7 @@ import org.meshtastic.core.ui.icon.Map
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.Nodes
import org.meshtastic.core.ui.icon.Settings
+import org.meshtastic.core.ui.icon.Wifi
import org.meshtastic.core.ui.qr.ScannedQrCodeDialog
import org.meshtastic.core.ui.share.SharedContactDialog
import org.meshtastic.core.ui.theme.StatusColors.StatusBlue
@@ -162,7 +161,7 @@ enum class TopLevelDestination(val label: StringResource, val icon: ImageVector,
Nodes(Res.string.nodes, MeshtasticIcons.Nodes, NodesRoutes.NodesGraph),
Map(Res.string.map, MeshtasticIcons.Map, MapRoutes.Map()),
Settings(Res.string.bottom_nav_settings, MeshtasticIcons.Settings, SettingsRoutes.SettingsGraph()),
- Connections(Res.string.connections, Icons.Rounded.Wifi, ConnectionsRoutes.ConnectionsGraph),
+ Connections(Res.string.connections, MeshtasticIcons.Wifi, ConnectionsRoutes.ConnectionsGraph),
;
companion object {
diff --git a/app/src/main/java/com/geeksville/mesh/ui/contact/Contacts.kt b/app/src/main/java/com/geeksville/mesh/ui/contact/Contacts.kt
index 5a07b721f..6c3f6295e 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/contact/Contacts.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/contact/Contacts.kt
@@ -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 .
*/
-
package com.geeksville.mesh.ui.contact
import androidx.compose.foundation.layout.Box
@@ -28,13 +27,6 @@ import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.selection.selectable
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.twotone.VolumeMute
-import androidx.compose.material.icons.automirrored.twotone.VolumeUp
-import androidx.compose.material.icons.filled.Close
-import androidx.compose.material.icons.filled.Delete
-import androidx.compose.material.icons.filled.SelectAll
-import androidx.compose.material.icons.rounded.QrCode2
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
@@ -100,6 +92,13 @@ import org.meshtastic.core.strings.unmute
import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.core.ui.component.ScrollToTopEvent
import org.meshtastic.core.ui.component.smartScrollToTop
+import org.meshtastic.core.ui.icon.Close
+import org.meshtastic.core.ui.icon.Delete
+import org.meshtastic.core.ui.icon.HardwareModel
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.SelectAll
+import org.meshtastic.core.ui.icon.VolumeMuteTwoTone
+import org.meshtastic.core.ui.icon.VolumeUpTwoTone
import org.meshtastic.proto.AppOnlyProtos
import java.util.concurrent.TimeUnit
@@ -232,7 +231,7 @@ fun ContactsScreen(
),
onClick = onNavigateToShare,
) {
- Icon(Icons.Rounded.QrCode2, contentDescription = stringResource(Res.string.share_contact))
+ Icon(MeshtasticIcons.HardwareModel, contentDescription = stringResource(Res.string.share_contact))
}
},
) { paddingValues ->
@@ -445,7 +444,7 @@ private fun SelectionToolbar(
title = { Text(text = "$selectedCount") },
navigationIcon = {
IconButton(onClick = onCloseSelection) {
- Icon(Icons.Default.Close, contentDescription = stringResource(Res.string.close_selection))
+ Icon(MeshtasticIcons.Close, contentDescription = stringResource(Res.string.close_selection))
}
},
actions = {
@@ -453,9 +452,9 @@ private fun SelectionToolbar(
Icon(
imageVector =
if (isAllMuted) {
- Icons.AutoMirrored.TwoTone.VolumeUp
+ MeshtasticIcons.VolumeUpTwoTone
} else {
- Icons.AutoMirrored.TwoTone.VolumeMute
+ MeshtasticIcons.VolumeMuteTwoTone
},
contentDescription =
if (isAllMuted) {
@@ -466,10 +465,10 @@ private fun SelectionToolbar(
)
}
IconButton(onClick = onDeleteSelected) {
- Icon(Icons.Default.Delete, contentDescription = stringResource(Res.string.delete_selection))
+ Icon(MeshtasticIcons.Delete, contentDescription = stringResource(Res.string.delete_selection))
}
IconButton(onClick = onSelectAll) {
- Icon(Icons.Default.SelectAll, contentDescription = stringResource(Res.string.select_all))
+ Icon(MeshtasticIcons.SelectAll, contentDescription = stringResource(Res.string.select_all))
}
},
)
diff --git a/app/src/main/java/com/geeksville/mesh/ui/sharing/Channel.kt b/app/src/main/java/com/geeksville/mesh/ui/sharing/Channel.kt
index a36f8fc2e..ca06abfed 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/sharing/Channel.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/sharing/Channel.kt
@@ -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 .
*/
-
package com.geeksville.mesh.ui.sharing
import android.Manifest
@@ -38,7 +37,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ChevronRight
+import androidx.compose.material.icons.rounded.ChevronRight
import androidx.compose.material.icons.twotone.Check
import androidx.compose.material.icons.twotone.Close
import androidx.compose.material.icons.twotone.ContentCopy
@@ -566,7 +565,7 @@ private fun ModemPresetInfo(modemPresetName: String, onClick: () -> Unit) {
}
Spacer(modifier = Modifier.width(16.dp))
Icon(
- imageVector = Icons.Default.ChevronRight,
+ imageVector = Icons.Rounded.ChevronRight,
contentDescription = stringResource(Res.string.navigate_into_label),
modifier = Modifier.padding(end = 16.dp),
)
diff --git a/core/strings/src/commonMain/composeResources/values/strings.xml b/core/strings/src/commonMain/composeResources/values/strings.xml
index 6f634dfbe..ffaf934e7 100644
--- a/core/strings/src/commonMain/composeResources/values/strings.xml
+++ b/core/strings/src/commonMain/composeResources/values/strings.xml
@@ -190,6 +190,7 @@
MSL
ChUtil %.1f%% AirUtilTX %.1f%%
+ Channel
Channel Name
QR code
Unknown Username
@@ -410,7 +411,7 @@
Public Key Encryption
Direct messages are using the new public key infrastructure for encryption.
Public key mismatch
- The public key does not match the recorded key. You may remove the node and let it exchange keys again, but this may indicate a more serious security problem. Contact the user through another trusted channel, to determine if the key change was due to a factory reset or other intentional action.
+ The public key does not match the recorded key. You may remove the node and let it exchange keys again, but this may indicate a more security problem. Contact the user through another trusted channel, to determine if the key change was due to a factory reset or other intentional action.
User Info
New node notifications
More details
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ChannelInfo.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ChannelInfo.kt
new file mode 100644
index 000000000..3e01ec558
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ChannelInfo.kt
@@ -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 .
+ */
+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) }
+}
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/DistanceInfo.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/DistanceInfo.kt
new file mode 100644
index 000000000..45cb45c9f
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/DistanceInfo.kt
@@ -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 .
+ */
+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.") }
+}
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ElevationInfo.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ElevationInfo.kt
new file mode 100644
index 000000000..cc6d7337c
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ElevationInfo.kt
@@ -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 .
+ */
+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") }
+}
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/HopsInfo.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/HopsInfo.kt
new file mode 100644
index 000000000..92def6bb6
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/HopsInfo.kt
@@ -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 .
+ */
+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) }
+}
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/IconInfo.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/IconInfo.kt
new file mode 100644
index 000000000..82887b8f6
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/IconInfo.kt
@@ -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 .
+ */
+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") })
+ }
+}
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/IndoorAirQuality.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/IndoorAirQuality.kt
index 6b25b5969..60b59b495 100644
--- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/IndoorAirQuality.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/IndoorAirQuality.kt
@@ -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 .
*/
-
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,
)
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/LastHeardInfo.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/LastHeardInfo.kt
new file mode 100644
index 000000000..b6e0a036d
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/LastHeardInfo.kt
@@ -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 .
+ */
+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) }
+}
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicator.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicator.kt
index a10910121..13bd0ac90 100644
--- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicator.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/LoraSignalIndicator.kt
@@ -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 .
*/
-
@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 }),
}
/**
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/NodeKeyStatusIcon.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/NodeKeyStatusIcon.kt
index 37c925a20..6328b27a7 100644
--- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/NodeKeyStatusIcon.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/NodeKeyStatusIcon.kt
@@ -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 .
*/
-
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,
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/SatelliteCountInfo.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/SatelliteCountInfo.kt
new file mode 100644
index 000000000..635d7524a
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/SatelliteCountInfo.kt
@@ -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 .
+ */
+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) }
+}
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/SecurityIcon.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/SecurityIcon.kt
index cd8bf3b7d..716d2d8e2 100644
--- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/SecurityIcon.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/SecurityIcon.kt
@@ -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 .
*/
-
@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 },
),
}
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/SignalInfo.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/SignalInfo.kt
index 651302a56..e8a93482a 100644
--- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/SignalInfo.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/SignalInfo.kt
@@ -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 .
*/
-
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)
+ }
}
}
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/TelemetryInfo.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/TelemetryInfo.kt
new file mode 100644
index 000000000..32d4e3afd
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/TelemetryInfo.kt
@@ -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 .
+ */
+@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) }
+ }
+}
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/preview/NodePreviewParameterProvider.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/preview/NodePreviewParameterProvider.kt
index 0879f92b4..782fd7f60 100644
--- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/preview/NodePreviewParameterProvider.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/preview/NodePreviewParameterProvider.kt
@@ -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 .
*/
-
package org.meshtastic.core.ui.component.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@@ -120,6 +119,9 @@ class NodePreviewParameterProvider : PreviewParameterProvider {
voltage = 3.7F
current = 0.0F
iaq = 100
+ barometricPressure = 1013.25F
+ soilTemperature = 28.0F
+ soilMoisture = 50
},
paxcounter =
paxcount {
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Actions.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Actions.kt
new file mode 100644
index 000000000..db959e523
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Actions.kt
@@ -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 .
+ */
+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
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Counter.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Counter.kt
new file mode 100644
index 000000000..688a80d79
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Counter.kt
@@ -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 .
+ */
+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)
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Device.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Device.kt
index 3e6b0aebf..079a920f2 100644
--- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Device.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Device.kt
@@ -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 .
*/
-
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.
*
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Hardware.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Hardware.kt
new file mode 100644
index 000000000..ad1c1dfb4
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Hardware.kt
@@ -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 .
+ */
+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
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Person.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Person.kt
new file mode 100644
index 000000000..016eab9d0
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Person.kt
@@ -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 .
+ */
+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
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Security.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Security.kt
new file mode 100644
index 000000000..136b58e5e
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Security.kt
@@ -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 .
+ */
+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
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Signal.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Signal.kt
index 326d566ed..bd77cf8db 100644
--- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Signal.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Signal.kt
@@ -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 .
*/
-
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) {
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Status.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Status.kt
new file mode 100644
index 000000000..525bb2ef7
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Status.kt
@@ -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 .
+ */
+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
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Telemetry.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Telemetry.kt
new file mode 100644
index 000000000..ce52f0085
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Telemetry.kt
@@ -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 .
+ */
+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
diff --git a/core/ui/src/main/res/drawable/counter_0_24px.xml b/core/ui/src/main/res/drawable/counter_0_24px.xml
new file mode 100644
index 000000000..06e907d37
--- /dev/null
+++ b/core/ui/src/main/res/drawable/counter_0_24px.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/counter_1_24px.xml b/core/ui/src/main/res/drawable/counter_1_24px.xml
new file mode 100644
index 000000000..34289e1da
--- /dev/null
+++ b/core/ui/src/main/res/drawable/counter_1_24px.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/counter_2_24px.xml b/core/ui/src/main/res/drawable/counter_2_24px.xml
new file mode 100644
index 000000000..8029b8e29
--- /dev/null
+++ b/core/ui/src/main/res/drawable/counter_2_24px.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/counter_3_24px.xml b/core/ui/src/main/res/drawable/counter_3_24px.xml
new file mode 100644
index 000000000..47ccd7c63
--- /dev/null
+++ b/core/ui/src/main/res/drawable/counter_3_24px.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/counter_4_24px.xml b/core/ui/src/main/res/drawable/counter_4_24px.xml
new file mode 100644
index 000000000..30e820929
--- /dev/null
+++ b/core/ui/src/main/res/drawable/counter_4_24px.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/counter_5_24px.xml b/core/ui/src/main/res/drawable/counter_5_24px.xml
new file mode 100644
index 000000000..8a5b92179
--- /dev/null
+++ b/core/ui/src/main/res/drawable/counter_5_24px.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/counter_6_24px.xml b/core/ui/src/main/res/drawable/counter_6_24px.xml
new file mode 100644
index 000000000..1a41b158d
--- /dev/null
+++ b/core/ui/src/main/res/drawable/counter_6_24px.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/counter_7_24px.xml b/core/ui/src/main/res/drawable/counter_7_24px.xml
new file mode 100644
index 000000000..874589a96
--- /dev/null
+++ b/core/ui/src/main/res/drawable/counter_7_24px.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/counter_8_24px.xml b/core/ui/src/main/res/drawable/counter_8_24px.xml
new file mode 100644
index 000000000..6e88d7fa5
--- /dev/null
+++ b/core/ui/src/main/res/drawable/counter_8_24px.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/FirmwareUpdateScreen.kt b/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/FirmwareUpdateScreen.kt
index 49c7fe66c..41bf5d124 100644
--- a/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/FirmwareUpdateScreen.kt
+++ b/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/FirmwareUpdateScreen.kt
@@ -42,16 +42,6 @@ import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
-import androidx.compose.material.icons.filled.CheckCircle
-import androidx.compose.material.icons.filled.CloudDownload
-import androidx.compose.material.icons.filled.Dangerous
-import androidx.compose.material.icons.filled.Folder
-import androidx.compose.material.icons.filled.Refresh
-import androidx.compose.material.icons.filled.SystemUpdate
-import androidx.compose.material.icons.filled.Warning
-import androidx.compose.material.icons.rounded.Bluetooth
-import androidx.compose.material.icons.rounded.Usb
-import androidx.compose.material.icons.rounded.Wifi
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
@@ -146,6 +136,17 @@ import org.meshtastic.core.strings.i_know_what_i_m_doing
import org.meshtastic.core.strings.learn_more
import org.meshtastic.core.strings.okay
import org.meshtastic.core.strings.save
+import org.meshtastic.core.ui.icon.Bluetooth
+import org.meshtastic.core.ui.icon.CheckCircle
+import org.meshtastic.core.ui.icon.CloudDownload
+import org.meshtastic.core.ui.icon.Dangerous
+import org.meshtastic.core.ui.icon.Folder
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Refresh
+import org.meshtastic.core.ui.icon.SystemUpdate
+import org.meshtastic.core.ui.icon.Usb
+import org.meshtastic.core.ui.icon.Warning
+import org.meshtastic.core.ui.icon.Wifi
private const val CYCLE_DELAY_MS = 4500L
@@ -413,7 +414,7 @@ private fun ReadyState(
},
modifier = Modifier.fillMaxWidth().height(56.dp),
) {
- Icon(Icons.Default.Folder, contentDescription = null)
+ Icon(MeshtasticIcons.Folder, contentDescription = null)
Spacer(Modifier.width(8.dp))
Text(stringResource(Res.string.firmware_update_select_file))
}
@@ -428,10 +429,10 @@ private fun ReadyState(
Icon(
imageVector =
when (state.updateMethod) {
- FirmwareUpdateMethod.Ble -> Icons.Rounded.Bluetooth
- FirmwareUpdateMethod.Usb -> Icons.Rounded.Usb
- FirmwareUpdateMethod.Wifi -> Icons.Rounded.Wifi
- else -> Icons.Default.SystemUpdate
+ FirmwareUpdateMethod.Ble -> MeshtasticIcons.Bluetooth
+ FirmwareUpdateMethod.Usb -> MeshtasticIcons.Usb
+ FirmwareUpdateMethod.Wifi -> MeshtasticIcons.Wifi
+ else -> MeshtasticIcons.SystemUpdate
},
contentDescription = null,
)
@@ -459,7 +460,7 @@ private fun DisclaimerDialog(updateMethod: FirmwareUpdateMethod, onDismissReques
Spacer(modifier = Modifier.height(8.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
- Icons.Default.Warning,
+ MeshtasticIcons.Warning,
contentDescription = null,
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(16.dp),
@@ -616,7 +617,7 @@ private fun BootloaderWarningCard(deviceHardware: DeviceHardware, onDismissForDe
Column(modifier = Modifier.padding(16.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
- imageVector = Icons.Default.Warning,
+ imageVector = MeshtasticIcons.Warning,
contentDescription = null,
tint = MaterialTheme.colorScheme.onErrorContainer,
)
@@ -708,7 +709,7 @@ private fun ProgressContent(
) {
if (isDownloading) {
Icon(
- Icons.Default.CloudDownload,
+ MeshtasticIcons.CloudDownload,
contentDescription = null,
modifier = Modifier.size(48.dp),
tint = MaterialTheme.colorScheme.primary,
@@ -818,7 +819,7 @@ private fun CyclingMessages() {
@Composable
private fun VerificationFailedState(onRetry: () -> Unit, onIgnore: () -> Unit) {
Icon(
- Icons.Default.Warning,
+ MeshtasticIcons.Warning,
contentDescription = null,
modifier = Modifier.size(64.dp),
tint = MaterialTheme.colorScheme.error,
@@ -833,7 +834,7 @@ private fun VerificationFailedState(onRetry: () -> Unit, onIgnore: () -> Unit) {
Spacer(Modifier.height(32.dp))
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
OutlinedButton(onClick = onRetry) {
- Icon(Icons.Default.Refresh, contentDescription = null)
+ Icon(MeshtasticIcons.Refresh, contentDescription = null)
Spacer(Modifier.width(8.dp))
Text(stringResource(Res.string.firmware_update_retry))
}
@@ -844,7 +845,7 @@ private fun VerificationFailedState(onRetry: () -> Unit, onIgnore: () -> Unit) {
@Composable
private fun ErrorState(error: String, onRetry: () -> Unit) {
Icon(
- Icons.Default.Dangerous,
+ MeshtasticIcons.Dangerous,
contentDescription = null,
modifier = Modifier.size(64.dp),
tint = MaterialTheme.colorScheme.error,
@@ -858,7 +859,7 @@ private fun ErrorState(error: String, onRetry: () -> Unit) {
)
Spacer(Modifier.height(32.dp))
OutlinedButton(onClick = onRetry) {
- Icon(Icons.Default.Refresh, contentDescription = null)
+ Icon(MeshtasticIcons.Refresh, contentDescription = null)
Spacer(Modifier.width(8.dp))
Text(stringResource(Res.string.firmware_update_retry))
}
@@ -871,7 +872,7 @@ private fun SuccessState(onDone: () -> Unit) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
- Icons.Default.CheckCircle,
+ MeshtasticIcons.CheckCircle,
contentDescription = null,
modifier = Modifier.size(100.dp),
tint = MaterialTheme.colorScheme.primary,
diff --git a/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/MapView.kt b/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/MapView.kt
index 0aa68cbe6..4b4c4f809 100644
--- a/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/MapView.kt
+++ b/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/MapView.kt
@@ -16,7 +16,7 @@
*/
package org.meshtastic.feature.map
-import android.Manifest // Added for Accompanist
+import android.Manifest
import android.graphics.Paint
import android.text.format.DateUtils
import androidx.appcompat.content.res.AppCompatResources
@@ -34,14 +34,14 @@ import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Lens
-import androidx.compose.material.icons.filled.LocationDisabled
-import androidx.compose.material.icons.filled.PinDrop
-import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.outlined.Layers
import androidx.compose.material.icons.outlined.MyLocation
import androidx.compose.material.icons.outlined.Tune
import androidx.compose.material.icons.rounded.Check
+import androidx.compose.material.icons.rounded.Lens
+import androidx.compose.material.icons.rounded.LocationDisabled
+import androidx.compose.material.icons.rounded.PinDrop
+import androidx.compose.material.icons.rounded.Star
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Checkbox
@@ -77,8 +77,8 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.touchlab.kermit.Logger
-import com.google.accompanist.permissions.ExperimentalPermissionsApi // Added for Accompanist
-import com.google.accompanist.permissions.rememberMultiplePermissionsState // Added for Accompanist
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.StringResource
@@ -758,7 +758,7 @@ fun MapView(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
- imageVector = Icons.Default.Star,
+ imageVector = Icons.Rounded.Star,
contentDescription = null,
modifier = Modifier.padding(end = 8.dp),
tint = MaterialTheme.colorScheme.onSurface,
@@ -783,7 +783,7 @@ fun MapView(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
- imageVector = Icons.Default.PinDrop,
+ imageVector = Icons.Rounded.PinDrop,
contentDescription = null,
modifier = Modifier.padding(end = 8.dp),
tint = MaterialTheme.colorScheme.onSurface,
@@ -808,7 +808,7 @@ fun MapView(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
- imageVector = Icons.Default.Lens,
+ imageVector = Icons.Rounded.Lens,
contentDescription = null,
modifier = Modifier.padding(end = 8.dp),
tint = MaterialTheme.colorScheme.onSurface,
@@ -834,7 +834,7 @@ fun MapView(
if (myLocationOverlay == null) {
Icons.Outlined.MyLocation
} else {
- Icons.Default.LocationDisabled
+ Icons.Rounded.LocationDisabled
},
contentDescription = stringResource(Res.string.toggle_my_position),
) {
diff --git a/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/component/DownloadButton.kt b/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/component/DownloadButton.kt
index 42cadeb73..ea7ae2102 100644
--- a/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/component/DownloadButton.kt
+++ b/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/component/DownloadButton.kt
@@ -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 .
*/
-
package org.meshtastic.feature.map.component
import androidx.compose.animation.AnimatedVisibility
@@ -23,7 +22,7 @@ import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Download
+import androidx.compose.material.icons.rounded.Download
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -51,7 +50,7 @@ fun DownloadButton(enabled: Boolean, onClick: () -> Unit) {
) {
FloatingActionButton(onClick = onClick, contentColor = MaterialTheme.colorScheme.primary) {
Icon(
- imageVector = Icons.Default.Download,
+ imageVector = Icons.Rounded.Download,
contentDescription = stringResource(Res.string.map_download_region),
modifier = Modifier.scale(1.25f),
)
diff --git a/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt b/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt
index 63c9d4c23..a8dc3091f 100644
--- a/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt
+++ b/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt
@@ -17,8 +17,6 @@
package org.meshtastic.feature.map.component
import android.app.DatePickerDialog
-import android.app.TimePickerDialog
-import android.text.format.DateFormat
import android.widget.DatePicker
import android.widget.TimePicker
import androidx.compose.foundation.Image
@@ -37,8 +35,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.CalendarMonth
-import androidx.compose.material.icons.filled.Lock
+import androidx.compose.material.icons.rounded.CalendarMonth
+import androidx.compose.material.icons.rounded.Lock
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.IconButton
@@ -179,7 +177,7 @@ fun EditWaypointDialog(
modifier = Modifier.fillMaxWidth().size(48.dp),
verticalAlignment = Alignment.CenterVertically,
) {
- Image(imageVector = Icons.Default.Lock, contentDescription = stringResource(Res.string.locked))
+ Image(imageVector = Icons.Rounded.Lock, contentDescription = stringResource(Res.string.locked))
Text(stringResource(Res.string.locked))
Switch(
modifier = Modifier.fillMaxWidth().wrapContentWidth(Alignment.End),
@@ -221,7 +219,7 @@ fun EditWaypointDialog(
verticalAlignment = Alignment.CenterVertically,
) {
Image(
- imageVector = Icons.Default.CalendarMonth,
+ imageVector = Icons.Rounded.CalendarMonth,
contentDescription = stringResource(Res.string.expires),
)
Text(stringResource(Res.string.expires))
diff --git a/feature/map/src/google/kotlin/org/meshtastic/feature/map/MapView.kt b/feature/map/src/google/kotlin/org/meshtastic/feature/map/MapView.kt
index 2c301302e..e2fe84d33 100644
--- a/feature/map/src/google/kotlin/org/meshtastic/feature/map/MapView.kt
+++ b/feature/map/src/google/kotlin/org/meshtastic/feature/map/MapView.kt
@@ -35,7 +35,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
-import androidx.compose.material.icons.filled.TripOrigin
+import androidx.compose.material.icons.rounded.TripOrigin
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
@@ -501,7 +501,7 @@ fun MapView(
},
) {
Icon(
- imageVector = androidx.compose.material.icons.Icons.Default.TripOrigin,
+ imageVector = androidx.compose.material.icons.Icons.Rounded.TripOrigin,
contentDescription = stringResource(Res.string.track_point),
tint = color,
)
diff --git a/feature/map/src/google/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt b/feature/map/src/google/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt
index cde9825ab..bdecbd2f5 100644
--- a/feature/map/src/google/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt
+++ b/feature/map/src/google/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt
@@ -34,8 +34,8 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.CalendarMonth
-import androidx.compose.material.icons.filled.Lock
+import androidx.compose.material.icons.rounded.CalendarMonth
+import androidx.compose.material.icons.rounded.Lock
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -180,7 +180,7 @@ fun EditWaypointDialog(
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
- imageVector = Icons.Default.Lock,
+ imageVector = Icons.Rounded.Lock,
contentDescription = stringResource(Res.string.locked),
)
Spacer(modifier = Modifier.width(8.dp))
@@ -199,7 +199,7 @@ fun EditWaypointDialog(
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
- imageVector = Icons.Default.CalendarMonth,
+ imageVector = Icons.Rounded.CalendarMonth,
contentDescription = stringResource(Res.string.expires),
)
Spacer(modifier = Modifier.width(8.dp))
diff --git a/feature/map/src/google/kotlin/org/meshtastic/feature/map/component/MapControlsOverlay.kt b/feature/map/src/google/kotlin/org/meshtastic/feature/map/component/MapControlsOverlay.kt
index 72cc94268..cd6ed7ca8 100644
--- a/feature/map/src/google/kotlin/org/meshtastic/feature/map/component/MapControlsOverlay.kt
+++ b/feature/map/src/google/kotlin/org/meshtastic/feature/map/component/MapControlsOverlay.kt
@@ -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,18 +14,17 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package org.meshtastic.feature.map.component
import androidx.compose.foundation.layout.Box
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.LocationDisabled
import androidx.compose.material.icons.filled.Navigation
import androidx.compose.material.icons.outlined.Layers
import androidx.compose.material.icons.outlined.Map
import androidx.compose.material.icons.outlined.MyLocation
import androidx.compose.material.icons.outlined.Navigation
import androidx.compose.material.icons.outlined.Tune
+import androidx.compose.material.icons.rounded.LocationDisabled
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.HorizontalFloatingToolbar
import androidx.compose.material3.MaterialTheme
@@ -122,7 +121,7 @@ fun MapControlsOverlay(
MapButton(
icon =
if (isLocationTrackingEnabled) {
- Icons.Default.LocationDisabled
+ Icons.Rounded.LocationDisabled
} else {
Icons.Outlined.MyLocation
},
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/Message.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/Message.kt
index 42fff3573..f56cdf275 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/Message.kt
+++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/Message.kt
@@ -48,19 +48,19 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Reply
import androidx.compose.material.icons.automirrored.filled.Send
-import androidx.compose.material.icons.filled.ArrowDownward
-import androidx.compose.material.icons.filled.ChatBubbleOutline
import androidx.compose.material.icons.filled.Close
-import androidx.compose.material.icons.filled.ContentCopy
-import androidx.compose.material.icons.filled.Delete
-import androidx.compose.material.icons.filled.MoreVert
-import androidx.compose.material.icons.filled.SelectAll
-import androidx.compose.material.icons.filled.SpeakerNotes
-import androidx.compose.material.icons.filled.SpeakerNotesOff
-import androidx.compose.material.icons.filled.Visibility
-import androidx.compose.material.icons.filled.VisibilityOff
+import androidx.compose.material.icons.rounded.ArrowDownward
+import androidx.compose.material.icons.rounded.ChatBubbleOutline
+import androidx.compose.material.icons.rounded.ContentCopy
+import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.FilterList
import androidx.compose.material.icons.rounded.FilterListOff
+import androidx.compose.material.icons.rounded.MoreVert
+import androidx.compose.material.icons.rounded.SelectAll
+import androidx.compose.material.icons.rounded.SpeakerNotes
+import androidx.compose.material.icons.rounded.SpeakerNotesOff
+import androidx.compose.material.icons.rounded.Visibility
+import androidx.compose.material.icons.rounded.VisibilityOff
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu
@@ -499,7 +499,7 @@ private fun BoxScope.ScrollToBottomFab(coroutineScope: CoroutineScope, listState
},
) {
Icon(
- imageVector = Icons.Default.ArrowDownward,
+ imageVector = Icons.Rounded.ArrowDownward,
contentDescription = stringResource(Res.string.scroll_to_bottom),
)
}
@@ -683,13 +683,13 @@ private fun ActionModeTopBar(selectedCount: Int, onAction: (MessageMenuAction) -
},
actions = {
IconButton(onClick = { onAction(MessageMenuAction.ClipboardCopy) }) {
- Icon(imageVector = Icons.Default.ContentCopy, contentDescription = stringResource(Res.string.copy))
+ Icon(imageVector = Icons.Rounded.ContentCopy, contentDescription = stringResource(Res.string.copy))
}
IconButton(onClick = { onAction(MessageMenuAction.Delete) }) {
- Icon(imageVector = Icons.Default.Delete, contentDescription = stringResource(Res.string.delete))
+ Icon(imageVector = Icons.Rounded.Delete, contentDescription = stringResource(Res.string.delete))
}
IconButton(onClick = { onAction(MessageMenuAction.SelectAll) }) {
- Icon(imageVector = Icons.Default.SelectAll, contentDescription = stringResource(Res.string.select_all))
+ Icon(imageVector = Icons.Rounded.SelectAll, contentDescription = stringResource(Res.string.select_all))
}
},
)
@@ -775,7 +775,7 @@ private fun MessageTopBarActions(
var expanded by remember { mutableStateOf(false) }
Box {
IconButton(onClick = { expanded = true }, enabled = true) {
- Icon(imageVector = Icons.Default.MoreVert, contentDescription = stringResource(Res.string.overflow_menu))
+ Icon(imageVector = Icons.Rounded.MoreVert, contentDescription = stringResource(Res.string.overflow_menu))
}
OverFlowMenu(
expanded = expanded,
@@ -828,7 +828,7 @@ private fun QuickChatToggleMenuItem(showQuickChat: Boolean, onDismiss: () -> Uni
},
leadingIcon = {
Icon(
- imageVector = if (showQuickChat) Icons.Default.SpeakerNotesOff else Icons.Default.SpeakerNotes,
+ imageVector = if (showQuickChat) Icons.Rounded.SpeakerNotesOff else Icons.Rounded.SpeakerNotes,
contentDescription = title,
)
},
@@ -844,7 +844,7 @@ private fun QuickChatOptionsMenuItem(onDismiss: () -> Unit, onNavigate: () -> Un
onDismiss()
onNavigate()
},
- leadingIcon = { Icon(imageVector = Icons.Default.ChatBubbleOutline, contentDescription = title) },
+ leadingIcon = { Icon(imageVector = Icons.Rounded.ChatBubbleOutline, contentDescription = title) },
)
}
@@ -859,7 +859,7 @@ private fun FilteredMessagesMenuItem(showFiltered: Boolean, count: Int, onDismis
},
leadingIcon = {
Icon(
- imageVector = if (showFiltered) Icons.Default.VisibilityOff else Icons.Default.Visibility,
+ imageVector = if (showFiltered) Icons.Rounded.VisibilityOff else Icons.Rounded.Visibility,
contentDescription = title,
)
},
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/QuickChat.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/QuickChat.kt
index 6a7c1baa4..2b9aa4a11 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/QuickChat.kt
+++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/QuickChat.kt
@@ -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 .
*/
-
package org.meshtastic.feature.messaging
import androidx.compose.foundation.layout.Arrangement
@@ -35,10 +34,10 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.DragHandle
-import androidx.compose.material.icons.filled.Edit
-import androidx.compose.material.icons.filled.FastForward
+import androidx.compose.material.icons.rounded.Add
+import androidx.compose.material.icons.rounded.DragHandle
+import androidx.compose.material.icons.rounded.Edit
+import androidx.compose.material.icons.rounded.FastForward
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
@@ -148,7 +147,7 @@ fun QuickChatScreen(
onClick = { showActionDialog = QuickChatAction(position = actions.size) },
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp),
) {
- Icon(imageVector = Icons.Default.Add, contentDescription = stringResource(Res.string.add))
+ Icon(imageVector = Icons.Rounded.Add, contentDescription = stringResource(Res.string.add))
}
}
}
@@ -231,9 +230,9 @@ private fun EditQuickChatDialog(
val (text, icon) =
if (isInstant) {
- Res.string.quick_chat_instant to Icons.Default.FastForward
+ Res.string.quick_chat_instant to Icons.Rounded.FastForward
} else {
- Res.string.quick_chat_append to Icons.Default.Add
+ Res.string.quick_chat_append to Icons.Rounded.Add
}
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -338,7 +337,7 @@ private fun QuickChatItem(
leadingContent = {
if (action.mode == QuickChatAction.Mode.Instant) {
Icon(
- imageVector = Icons.Default.FastForward,
+ imageVector = Icons.Rounded.FastForward,
contentDescription = stringResource(Res.string.quick_chat_instant),
)
}
@@ -349,12 +348,12 @@ private fun QuickChatItem(
Row(verticalAlignment = Alignment.CenterVertically) {
IconButton(onClick = { onEdit(action) }, modifier = Modifier.size(48.dp)) {
Icon(
- imageVector = Icons.Default.Edit,
+ imageVector = Icons.Rounded.Edit,
contentDescription = stringResource(Res.string.quick_chat_edit),
)
}
Icon(
- imageVector = Icons.Default.DragHandle,
+ imageVector = Icons.Rounded.DragHandle,
contentDescription = stringResource(Res.string.quick_chat),
)
}
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActions.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActions.kt
index d789d830c..1d68d69f3 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActions.kt
+++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActions.kt
@@ -22,7 +22,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Reply
-import androidx.compose.material.icons.filled.AddReaction
+import androidx.compose.material.icons.rounded.AddReaction
import androidx.compose.material.icons.twotone.AddLink
import androidx.compose.material.icons.twotone.Cloud
import androidx.compose.material.icons.twotone.CloudDone
@@ -61,7 +61,7 @@ internal fun ReactionButton(onSendReaction: (String) -> Unit = {}) {
)
}
IconButton(onClick = { showEmojiPickerDialog = true }) {
- Icon(imageVector = Icons.Default.AddReaction, contentDescription = stringResource(Res.string.react))
+ Icon(imageVector = Icons.Rounded.AddReaction, contentDescription = stringResource(Res.string.react))
}
}
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActionsBottomSheet.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActionsBottomSheet.kt
index 02d06a936..e60e773cc 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActionsBottomSheet.kt
+++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActionsBottomSheet.kt
@@ -27,11 +27,11 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.AddReaction
-import androidx.compose.material.icons.filled.ContentCopy
-import androidx.compose.material.icons.filled.Delete
-import androidx.compose.material.icons.filled.Reply
-import androidx.compose.material.icons.filled.SelectAll
+import androidx.compose.material.icons.rounded.AddReaction
+import androidx.compose.material.icons.rounded.ContentCopy
+import androidx.compose.material.icons.rounded.Delete
+import androidx.compose.material.icons.rounded.Reply
+import androidx.compose.material.icons.rounded.SelectAll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
@@ -95,25 +95,25 @@ fun MessageActionsContent(
ListItem(
headlineContent = { Text(stringResource(Res.string.reply)) },
- leadingContent = { Icon(Icons.Default.Reply, contentDescription = stringResource(Res.string.reply)) },
+ leadingContent = { Icon(Icons.Rounded.Reply, contentDescription = stringResource(Res.string.reply)) },
modifier = Modifier.clickable(onClick = onReply),
)
ListItem(
headlineContent = { Text(stringResource(Res.string.copy)) },
- leadingContent = { Icon(Icons.Default.ContentCopy, contentDescription = stringResource(Res.string.copy)) },
+ leadingContent = { Icon(Icons.Rounded.ContentCopy, contentDescription = stringResource(Res.string.copy)) },
modifier = Modifier.clickable(onClick = onCopy),
)
ListItem(
headlineContent = { Text(stringResource(Res.string.select)) },
- leadingContent = { Icon(Icons.Default.SelectAll, contentDescription = stringResource(Res.string.select)) },
+ leadingContent = { Icon(Icons.Rounded.SelectAll, contentDescription = stringResource(Res.string.select)) },
modifier = Modifier.clickable(onClick = onSelect),
)
ListItem(
headlineContent = { Text(stringResource(Res.string.delete)) },
- leadingContent = { Icon(Icons.Default.Delete, contentDescription = stringResource(Res.string.delete)) },
+ leadingContent = { Icon(Icons.Rounded.Delete, contentDescription = stringResource(Res.string.delete)) },
modifier = Modifier.clickable(onClick = onDelete),
)
}
@@ -146,7 +146,7 @@ private fun QuickEmojiRow(quickEmojis: List, onReact: (String) -> Unit,
modifier = Modifier.size(40.dp).background(MaterialTheme.colorScheme.surfaceVariant, CircleShape),
) {
Icon(
- Icons.Default.AddReaction,
+ Icons.Rounded.AddReaction,
contentDescription = "More reactions",
modifier = Modifier.size(20.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageItem.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageItem.kt
index 0c12a7b19..450595705 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageItem.kt
+++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageItem.kt
@@ -16,6 +16,7 @@
*/
package org.meshtastic.feature.messaging.component
+import android.content.ClipData
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -29,16 +30,7 @@ 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.Cloud
-import androidx.compose.material.icons.filled.FormatQuote
-import androidx.compose.material.icons.twotone.AddLink
-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.CloudUpload
-import androidx.compose.material.icons.twotone.HowToReg
-import androidx.compose.material.icons.twotone.Link
-import androidx.compose.material.icons.twotone.Warning
+import androidx.compose.material.icons.rounded.FormatQuote
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
@@ -52,18 +44,20 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.ClipEntry
+import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.database.entity.Reaction
import org.meshtastic.core.database.model.Message
@@ -71,7 +65,6 @@ import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.filter_message_label
-import org.meshtastic.core.strings.hops_away_template
import org.meshtastic.core.strings.message_delivery_status
import org.meshtastic.core.strings.reply
import org.meshtastic.core.strings.sample_message
@@ -82,6 +75,14 @@ import org.meshtastic.core.ui.component.Rssi
import org.meshtastic.core.ui.component.Snr
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
import org.meshtastic.core.ui.emoji.EmojiPicker
+import org.meshtastic.core.ui.icon.Cloud
+import org.meshtastic.core.ui.icon.CloudDone
+import org.meshtastic.core.ui.icon.CloudOffTwoTone
+import org.meshtastic.core.ui.icon.CloudSync
+import org.meshtastic.core.ui.icon.CloudTwoTone
+import org.meshtastic.core.ui.icon.Hops
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Warning
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.core.ui.theme.MessageItemColors
@@ -123,7 +124,8 @@ internal fun MessageItem(
),
) {
var activeSheet by remember { mutableStateOf(null) }
- val clipboardManager = LocalClipboardManager.current
+ val clipboardManager = LocalClipboard.current
+ val coroutineScope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
if (activeSheet != null) {
@@ -143,7 +145,11 @@ internal fun MessageItem(
onMoreReactions = { activeSheet = ActiveSheet.Emoji },
onCopy = {
activeSheet = null
- clipboardManager.setText(AnnotatedString(message.text))
+ coroutineScope.launch {
+ clipboardManager.setClipEntry(
+ ClipEntry(ClipData.newPlainText("message", message.text)),
+ )
+ }
},
onSelect = {
activeSheet = null
@@ -222,7 +228,7 @@ internal fun MessageItem(
)
if (message.viaMqtt) {
Icon(
- Icons.Default.Cloud,
+ MeshtasticIcons.Cloud,
contentDescription = stringResource(Res.string.via_mqtt),
modifier = Modifier.size(16.dp),
)
@@ -278,10 +284,21 @@ internal fun MessageItem(
Rssi(message.rssi)
}
} else {
- Text(
- text = stringResource(Res.string.hops_away_template, message.hopsAway),
- style = MaterialTheme.typography.labelSmall,
- )
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ Icon(
+ imageVector = MeshtasticIcons.Hops,
+ contentDescription = null,
+ modifier = Modifier.size(14.dp),
+ tint = cardColors.contentColor.copy(alpha = 0.7f),
+ )
+ Text(
+ text = message.hopsAway.toString(),
+ style = MaterialTheme.typography.labelSmall,
+ )
+ }
}
}
if (containsBel) {
@@ -341,14 +358,14 @@ private enum class ActiveSheet {
private fun MessageStatusIcon(status: MessageStatus, onClick: () -> Unit, modifier: Modifier = Modifier) {
val icon =
when (status) {
- MessageStatus.RECEIVED -> Icons.TwoTone.HowToReg
- MessageStatus.QUEUED -> Icons.TwoTone.CloudUpload
- MessageStatus.DELIVERED -> Icons.TwoTone.CloudDone
- MessageStatus.SFPP_ROUTING -> Icons.TwoTone.AddLink
- MessageStatus.SFPP_CONFIRMED -> Icons.TwoTone.Link
- MessageStatus.ENROUTE -> Icons.TwoTone.Cloud
- MessageStatus.ERROR -> Icons.TwoTone.CloudOff
- else -> Icons.TwoTone.Warning
+ MessageStatus.RECEIVED -> MeshtasticIcons.CloudDone
+ MessageStatus.QUEUED -> MeshtasticIcons.CloudSync
+ MessageStatus.DELIVERED -> MeshtasticIcons.CloudDone
+ MessageStatus.SFPP_ROUTING -> MeshtasticIcons.CloudSync
+ MessageStatus.SFPP_CONFIRMED -> MeshtasticIcons.CloudDone
+ MessageStatus.ENROUTE -> MeshtasticIcons.CloudTwoTone
+ MessageStatus.ERROR -> MeshtasticIcons.CloudOffTwoTone
+ else -> MeshtasticIcons.Warning
}
Icon(
imageVector = icon,
@@ -392,7 +409,7 @@ private fun OriginalMessageSnippet(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Icon(
- Icons.Default.FormatQuote,
+ Icons.Rounded.FormatQuote,
contentDescription = stringResource(Res.string.reply),
modifier = Modifier.size(16.dp),
)
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/Reaction.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/Reaction.kt
index 2591819f5..1390ea3ac 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/Reaction.kt
+++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/Reaction.kt
@@ -35,7 +35,7 @@ import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.AddReaction
+import androidx.compose.material.icons.rounded.AddReaction
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -67,7 +67,6 @@ import org.meshtastic.core.model.util.getShortDateTime
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.delivery_confirmed
import org.meshtastic.core.strings.error
-import org.meshtastic.core.strings.hops_away_template
import org.meshtastic.core.strings.message_delivery_status
import org.meshtastic.core.strings.message_status_enroute
import org.meshtastic.core.strings.message_status_queued
@@ -77,6 +76,8 @@ import org.meshtastic.core.ui.component.BottomSheetDialog
import org.meshtastic.core.ui.component.Rssi
import org.meshtastic.core.ui.component.Snr
import org.meshtastic.core.ui.emoji.EmojiPickerDialog
+import org.meshtastic.core.ui.icon.Hops
+import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.feature.messaging.DeliveryInfo
import org.meshtastic.proto.MeshProtos
@@ -186,7 +187,7 @@ private fun AddReactionButton(modifier: Modifier = Modifier, onSendReaction: (St
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.2f)),
) {
Icon(
- imageVector = Icons.Default.AddReaction,
+ imageVector = Icons.Rounded.AddReaction,
contentDescription = stringResource(Res.string.react),
modifier = Modifier.padding(6.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -300,10 +301,21 @@ internal fun ReactionDialog(
Rssi(reaction.rssi)
}
} else {
- Text(
- text = stringResource(Res.string.hops_away_template, reaction.hopsAway),
- style = MaterialTheme.typography.labelSmall,
- )
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ Icon(
+ imageVector = MeshtasticIcons.Hops,
+ contentDescription = null,
+ modifier = Modifier.size(14.dp),
+ tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
+ )
+ Text(
+ text = reaction.hopsAway.toString(),
+ style = MaterialTheme.typography.labelSmall,
+ )
+ }
}
}
Spacer(modifier = Modifier.weight(1f))
diff --git a/feature/node/component/DeviceActions.kt b/feature/node/component/DeviceActions.kt
index d509c118e..4607cd4aa 100644
--- a/feature/node/component/DeviceActions.kt
+++ b/feature/node/component/DeviceActions.kt
@@ -210,7 +210,7 @@ private fun PrimaryActionsRow(
IconToggleButton(checked = node.isFavorite, onCheckedChange = { onFavoriteClick() }) {
Icon(
- imageVector = if (node.isFavorite) Icons.Default.Star else Icons.Default.StarBorder,
+ imageVector = if (node.isFavorite) Icons.Rounded.Star else Icons.Rounded.StarBorder,
contentDescription = stringResource(Res.string.favorite),
tint = if (node.isFavorite) Color.Yellow else LocalContentColor.current,
)
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/AdministrationSection.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/AdministrationSection.kt
index b9f38fbf6..8737957e2 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/AdministrationSection.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/AdministrationSection.kt
@@ -18,10 +18,10 @@ package org.meshtastic.feature.node.component
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ForkLeft
-import androidx.compose.material.icons.filled.Icecream
-import androidx.compose.material.icons.filled.Memory
-import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material.icons.rounded.ForkLeft
+import androidx.compose.material.icons.rounded.Icecream
+import androidx.compose.material.icons.rounded.Memory
+import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -63,7 +63,7 @@ fun AdministrationSection(
Column {
ListItem(
text = stringResource(Res.string.request_metadata),
- leadingIcon = Icons.Default.Memory,
+ leadingIcon = Icons.Rounded.Memory,
trailingIcon = null,
onClick = {
onAction(NodeDetailAction.TriggerServiceAction(ServiceAction.GetDeviceMetadata(node.num)))
@@ -74,7 +74,7 @@ fun AdministrationSection(
ListItem(
text = stringResource(Res.string.remote_admin),
- leadingIcon = Icons.Default.Settings,
+ leadingIcon = Icons.Rounded.Settings,
enabled = metricsState.isLocal || node.metadata != null,
) {
onAction(NodeDetailAction.Navigate(SettingsRoutes.Settings(node.num)))
@@ -101,8 +101,8 @@ private fun FirmwareSection(
firmwareEdition?.let { edition ->
val icon =
when (edition) {
- MeshProtos.FirmwareEdition.VANILLA -> Icons.Default.Icecream
- else -> Icons.Default.ForkLeft
+ MeshProtos.FirmwareEdition.VANILLA -> Icons.Rounded.Icecream
+ else -> Icons.Rounded.ForkLeft
}
ListItem(
@@ -138,7 +138,7 @@ private fun FirmwareVersionItems(
ListItem(
text = stringResource(Res.string.installed_firmware_version),
- leadingIcon = Icons.Default.Memory,
+ leadingIcon = Icons.Rounded.Memory,
supportingText = version.substringBeforeLast("."),
copyable = true,
leadingIconTint = statusColor,
@@ -149,7 +149,7 @@ private fun FirmwareVersionItems(
ListItem(
text = stringResource(Res.string.latest_stable_firmware),
- leadingIcon = Icons.Default.Memory,
+ leadingIcon = Icons.Rounded.Memory,
supportingText = latestStable.id.substringBeforeLast(".").replace("v", ""),
copyable = true,
leadingIconTint = MaterialTheme.colorScheme.StatusGreen,
@@ -161,7 +161,7 @@ private fun FirmwareVersionItems(
ListItem(
text = stringResource(Res.string.latest_alpha_firmware),
- leadingIcon = Icons.Default.Memory,
+ leadingIcon = Icons.Rounded.Memory,
supportingText = latestAlpha.id.substringBeforeLast(".").replace("v", ""),
copyable = true,
leadingIconTint = MaterialTheme.colorScheme.StatusYellow,
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/ChannelInfo.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/ChannelInfo.kt
new file mode 100644
index 000000000..040856fc8
--- /dev/null
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/ChannelInfo.kt
@@ -0,0 +1,47 @@
+/*
+ * 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 .
+ */
+package org.meshtastic.feature.node.component
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Tsunami
+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.meshtastic.core.ui.theme.AppTheme
+
+@Composable
+fun ChannelInfo(
+ channel: Int,
+ modifier: Modifier = Modifier,
+ contentColor: Color = MaterialTheme.colorScheme.onSurface,
+) {
+ IconInfo(
+ modifier = modifier,
+ icon = Icons.Rounded.Tsunami,
+ contentDescription = "Channel",
+ text = channel.toString(),
+ contentColor = contentColor,
+ )
+}
+
+@PreviewLightDark
+@Composable
+private fun ChannelInfoPreview() {
+ AppTheme { ChannelInfo(channel = 2) }
+}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/CompassBottomSheet.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/CompassBottomSheet.kt
index ecf650789..407e02830 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/CompassBottomSheet.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/CompassBottomSheet.kt
@@ -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 .
*/
-
package org.meshtastic.feature.node.component
import androidx.compose.foundation.Canvas
@@ -27,8 +26,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ErrorOutline
-import androidx.compose.material.icons.filled.GpsFixed
+import androidx.compose.material.icons.rounded.ErrorOutline
+import androidx.compose.material.icons.rounded.GpsFixed
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -153,7 +152,7 @@ fun CompassSheetContent(
)
// Quick way to re-request a fresh fix without leaving the compass sheet
Button(onClick = onRequestPosition, modifier = Modifier.fillMaxWidth()) {
- Icon(imageVector = Icons.Default.GpsFixed, contentDescription = null)
+ Icon(imageVector = Icons.Rounded.GpsFixed, contentDescription = null)
Spacer(modifier = Modifier.width(8.dp))
Text(text = stringResource(Res.string.exchange_position))
}
@@ -190,7 +189,7 @@ private fun WarningList(
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Icon(
- imageVector = Icons.Default.ErrorOutline,
+ imageVector = Icons.Rounded.ErrorOutline,
contentDescription = null,
tint = MaterialTheme.colorScheme.onErrorContainer,
)
@@ -205,13 +204,13 @@ private fun WarningList(
if (warnings.contains(CompassWarning.NO_LOCATION_PERMISSION)) {
Button(onClick = onRequestPermission, modifier = Modifier.fillMaxWidth()) {
- Icon(imageVector = Icons.Default.GpsFixed, contentDescription = null)
+ Icon(imageVector = Icons.Rounded.GpsFixed, contentDescription = null)
Spacer(modifier = Modifier.width(8.dp))
Text(text = stringResource(Res.string.compass_no_location_permission))
}
} else if (warnings.contains(CompassWarning.LOCATION_DISABLED)) {
Button(onClick = onOpenLocationSettings, modifier = Modifier.fillMaxWidth()) {
- Icon(imageVector = Icons.Default.GpsFixed, contentDescription = null)
+ Icon(imageVector = Icons.Rounded.GpsFixed, contentDescription = null)
Spacer(modifier = Modifier.width(8.dp))
Text(text = stringResource(Res.string.compass_location_disabled))
}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/CooldownOutlinedIconButton.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/CooldownOutlinedIconButton.kt
index 9d562299d..a94af644b 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/CooldownOutlinedIconButton.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/CooldownOutlinedIconButton.kt
@@ -19,8 +19,6 @@ package org.meshtastic.feature.node.component
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.size
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.CircularWavyProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
@@ -36,6 +34,8 @@ import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Refresh
import org.meshtastic.core.ui.theme.AppTheme
internal const val COOL_DOWN_TIME_MS = 30000L
@@ -147,7 +147,7 @@ fun CooldownOutlinedIconButton(
private fun CooldownOutlinedIconButtonPreview() {
AppTheme {
CooldownOutlinedIconButton(onClick = {}, cooldownTimestamp = System.currentTimeMillis() - 15000L) {
- Icon(imageVector = Icons.Default.Refresh, contentDescription = null)
+ Icon(imageVector = MeshtasticIcons.Refresh, contentDescription = null)
}
}
}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/DeviceActions.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/DeviceActions.kt
index 7db6bd588..e98b41176 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/DeviceActions.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/DeviceActions.kt
@@ -28,10 +28,10 @@ import androidx.compose.material.icons.automirrored.filled.Message
import androidx.compose.material.icons.automirrored.filled.VolumeOff
import androidx.compose.material.icons.automirrored.filled.VolumeUp
import androidx.compose.material.icons.automirrored.outlined.VolumeMute
-import androidx.compose.material.icons.filled.Star
-import androidx.compose.material.icons.filled.StarBorder
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.QrCode2
+import androidx.compose.material.icons.rounded.Star
+import androidx.compose.material.icons.rounded.StarBorder
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
@@ -170,7 +170,7 @@ private fun PrimaryActionsRow(
IconToggleButton(checked = node.isFavorite, onCheckedChange = { onFavoriteClick() }) {
Icon(
- imageVector = if (node.isFavorite) Icons.Default.Star else Icons.Default.StarBorder,
+ imageVector = if (node.isFavorite) Icons.Rounded.Star else Icons.Rounded.StarBorder,
contentDescription = stringResource(Res.string.favorite),
tint = if (node.isFavorite) Color.Yellow else LocalContentColor.current,
)
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/DeviceDetailsSection.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/DeviceDetailsSection.kt
index 61a64bdeb..2d0b974d0 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/DeviceDetailsSection.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/DeviceDetailsSection.kt
@@ -28,7 +28,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Router
+import androidx.compose.material.icons.rounded.Router
import androidx.compose.material.icons.twotone.Verified
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.runtime.Composable
@@ -78,7 +78,7 @@ fun DeviceDetailsSection(state: MetricsState, modifier: Modifier = Modifier) {
?: deviceHardware.displayName
ListItem(
text = stringResource(Res.string.hardware),
- leadingIcon = Icons.Default.Router,
+ leadingIcon = Icons.Rounded.Router,
supportingText = deviceText,
copyable = true,
trailingIcon = null,
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/EnvironmentMetrics.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/EnvironmentMetrics.kt
index fe95adce1..5a9c01966 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/EnvironmentMetrics.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/EnvironmentMetrics.kt
@@ -20,17 +20,17 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Air
-import androidx.compose.material.icons.filled.BlurOn
-import androidx.compose.material.icons.filled.Bolt
-import androidx.compose.material.icons.filled.Height
-import androidx.compose.material.icons.filled.LightMode
-import androidx.compose.material.icons.filled.Power
-import androidx.compose.material.icons.filled.Scale
-import androidx.compose.material.icons.filled.Speed
-import androidx.compose.material.icons.filled.Thermostat
-import androidx.compose.material.icons.filled.WaterDrop
import androidx.compose.material.icons.outlined.Navigation
+import androidx.compose.material.icons.rounded.Air
+import androidx.compose.material.icons.rounded.BlurOn
+import androidx.compose.material.icons.rounded.Bolt
+import androidx.compose.material.icons.rounded.Height
+import androidx.compose.material.icons.rounded.LightMode
+import androidx.compose.material.icons.rounded.Power
+import androidx.compose.material.icons.rounded.Scale
+import androidx.compose.material.icons.rounded.Speed
+import androidx.compose.material.icons.rounded.Thermostat
+import androidx.compose.material.icons.rounded.WaterDrop
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@@ -77,7 +77,7 @@ internal fun EnvironmentMetrics(
VectorMetricInfo(
Res.string.temperature,
temperature.toTempString(isFahrenheit),
- Icons.Default.Thermostat,
+ Icons.Rounded.Thermostat,
),
)
}
@@ -86,7 +86,7 @@ internal fun EnvironmentMetrics(
VectorMetricInfo(
Res.string.humidity,
"%.0f%%".format(relativeHumidity),
- Icons.Default.WaterDrop,
+ Icons.Rounded.WaterDrop,
),
)
}
@@ -95,7 +95,7 @@ internal fun EnvironmentMetrics(
VectorMetricInfo(
Res.string.pressure,
"%.0f hPa".format(barometricPressure),
- Icons.Default.Speed,
+ Icons.Rounded.Speed,
),
)
}
@@ -104,29 +104,29 @@ internal fun EnvironmentMetrics(
VectorMetricInfo(
Res.string.gas_resistance,
"%.0f MΩ".format(gasResistance),
- Icons.Default.BlurOn,
+ Icons.Rounded.BlurOn,
),
)
}
if (hasVoltage()) {
- add(VectorMetricInfo(Res.string.voltage, "%.2fV".format(voltage), Icons.Default.Bolt))
+ add(VectorMetricInfo(Res.string.voltage, "%.2fV".format(voltage), Icons.Rounded.Bolt))
}
if (hasCurrent()) {
- add(VectorMetricInfo(Res.string.current, "%.1fmA".format(current), Icons.Default.Power))
+ add(VectorMetricInfo(Res.string.current, "%.1fmA".format(current), Icons.Rounded.Power))
}
- if (hasIaq()) add(VectorMetricInfo(Res.string.iaq, iaq.toString(), Icons.Default.Air))
+ if (hasIaq()) add(VectorMetricInfo(Res.string.iaq, iaq.toString(), Icons.Rounded.Air))
if (hasDistance()) {
add(
VectorMetricInfo(
Res.string.distance,
distance.toSmallDistanceString(displayUnits),
- Icons.Default.Height,
+ Icons.Rounded.Height,
),
)
}
- if (hasLux()) add(VectorMetricInfo(Res.string.lux, "%.0f lx".format(lux), Icons.Default.LightMode))
+ if (hasLux()) add(VectorMetricInfo(Res.string.lux, "%.0f lx".format(lux), Icons.Rounded.LightMode))
if (hasUvLux()) {
- add(VectorMetricInfo(Res.string.uv_lux, "%.0f lx".format(uvLux), Icons.Default.LightMode))
+ add(VectorMetricInfo(Res.string.uv_lux, "%.0f lx".format(uvLux), Icons.Rounded.LightMode))
}
if (hasWindSpeed()) {
@Suppress("MagicNumber")
@@ -141,7 +141,7 @@ internal fun EnvironmentMetrics(
)
}
if (hasWeight()) {
- add(VectorMetricInfo(Res.string.weight, "%.2f kg".format(weight), Icons.Default.Scale))
+ add(VectorMetricInfo(Res.string.weight, "%.2f kg".format(weight), Icons.Rounded.Scale))
}
if (hasTemperature() && hasRelativeHumidity()) {
val dewPoint = UnitConversions.calculateDewPoint(temperature, relativeHumidity)
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/FirmwareReleaseSheetContent.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/FirmwareReleaseSheetContent.kt
index a909bd4bc..d5d55abf3 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/FirmwareReleaseSheetContent.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/FirmwareReleaseSheetContent.kt
@@ -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 .
*/
-
package org.meshtastic.feature.node.component
import android.content.ActivityNotFoundException
@@ -29,8 +28,8 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Download
-import androidx.compose.material.icons.filled.Link
+import androidx.compose.material.icons.rounded.Download
+import androidx.compose.material.icons.rounded.Link
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -77,7 +76,7 @@ fun FirmwareReleaseSheetContent(firmwareRelease: FirmwareRelease, modifier: Modi
},
modifier = Modifier.weight(1f),
) {
- Icon(imageVector = Icons.Default.Link, contentDescription = stringResource(Res.string.view_release))
+ Icon(imageVector = Icons.Rounded.Link, contentDescription = stringResource(Res.string.view_release))
Spacer(modifier = Modifier.width(8.dp))
Text(text = stringResource(Res.string.view_release))
}
@@ -93,7 +92,7 @@ fun FirmwareReleaseSheetContent(firmwareRelease: FirmwareRelease, modifier: Modi
},
modifier = Modifier.weight(1f),
) {
- Icon(imageVector = Icons.Default.Download, contentDescription = stringResource(Res.string.download))
+ Icon(imageVector = Icons.Rounded.Download, contentDescription = stringResource(Res.string.download))
Spacer(modifier = Modifier.width(8.dp))
Text(text = stringResource(Res.string.download))
}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/HopsInfo.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/HopsInfo.kt
new file mode 100644
index 000000000..3a339f51b
--- /dev/null
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/HopsInfo.kt
@@ -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 .
+ */
+package org.meshtastic.feature.node.component
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.CrueltyFree
+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.theme.AppTheme
+
+@Composable
+fun HopsInfo(hops: Int, modifier: Modifier = Modifier, contentColor: Color = MaterialTheme.colorScheme.onSurface) {
+ IconInfo(
+ modifier = modifier,
+ icon = Icons.Rounded.CrueltyFree,
+ contentDescription = stringResource(Res.string.hops_away),
+ text = hops.toString(),
+ contentColor = contentColor,
+ )
+}
+
+@PreviewLightDark
+@Composable
+private fun HopsInfoPreview() {
+ AppTheme { HopsInfo(hops = 3) }
+}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/IconInfo.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/IconInfo.kt
index 95792d62d..60812deb8 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/IconInfo.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/IconInfo.kt
@@ -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 .
*/
-
package org.meshtastic.feature.node.component
import androidx.compose.foundation.layout.Arrangement
@@ -28,6 +27,7 @@ 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
@@ -41,6 +41,7 @@ fun IconInfo(
contentDescription: String,
modifier: Modifier = Modifier,
text: String? = null,
+ style: TextStyle = MaterialTheme.typography.labelMedium,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
content: @Composable () -> Unit = {},
) {
@@ -55,7 +56,7 @@ fun IconInfo(
contentDescription = contentDescription,
tint = contentColor,
)
- text?.let { Text(text = it, style = MaterialTheme.typography.labelMedium, color = contentColor) }
+ text?.let { Text(text = it, style = style, color = contentColor) }
content()
}
}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/LinkedCoordinatesItem.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/LinkedCoordinatesItem.kt
index 4fbc09255..c3b2f147a 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/LinkedCoordinatesItem.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/LinkedCoordinatesItem.kt
@@ -22,7 +22,7 @@ import android.content.Intent
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
-import androidx.compose.material.icons.filled.LocationOn
+import androidx.compose.material.icons.rounded.LocationOn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
@@ -87,7 +87,7 @@ fun LinkedCoordinatesItem(node: Node, displayUnits: DisplayUnits = DisplayUnits.
)
},
text = stringResource(Res.string.last_position_update),
- leadingIcon = Icons.Default.LocationOn,
+ leadingIcon = Icons.Rounded.LocationOn,
supportingText = "$ago • $coordinates$elevationText",
trailingContent = Icons.AutoMirrored.Rounded.KeyboardArrowRight.icon(),
onClick = {
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeDetailsSection.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeDetailsSection.kt
index cc976a8d5..47c0ad333 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeDetailsSection.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeDetailsSection.kt
@@ -30,17 +30,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.CheckCircle
-import androidx.compose.material.icons.filled.Cloud
-import androidx.compose.material.icons.filled.History
-import androidx.compose.material.icons.filled.KeyOff
-import androidx.compose.material.icons.filled.Lock
-import androidx.compose.material.icons.filled.Numbers
-import androidx.compose.material.icons.filled.Person
-import androidx.compose.material.icons.filled.SignalCellularAlt
-import androidx.compose.material.icons.filled.Verified
-import androidx.compose.material.icons.filled.Work
-import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material.icons.rounded.Numbers
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -82,6 +72,17 @@ import org.meshtastic.core.strings.supported
import org.meshtastic.core.strings.uptime
import org.meshtastic.core.strings.user_id
import org.meshtastic.core.strings.via_mqtt
+import org.meshtastic.core.ui.icon.ArrowCircleUp
+import org.meshtastic.core.ui.icon.ChannelUtilization
+import org.meshtastic.core.ui.icon.Cloud
+import org.meshtastic.core.ui.icon.History
+import org.meshtastic.core.ui.icon.Hops
+import org.meshtastic.core.ui.icon.KeyOff
+import org.meshtastic.core.ui.icon.Lock
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Person
+import org.meshtastic.core.ui.icon.Role
+import org.meshtastic.core.ui.icon.Verified
import org.meshtastic.core.ui.util.formatAgo
@Composable
@@ -107,7 +108,7 @@ private fun MismatchKeyWarning(modifier: Modifier = Modifier) {
Column(modifier = Modifier.padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
- imageVector = Icons.Default.KeyOff,
+ imageVector = MeshtasticIcons.KeyOff,
contentDescription = null,
tint = MaterialTheme.colorScheme.onErrorContainer,
)
@@ -129,7 +130,6 @@ private fun MismatchKeyWarning(modifier: Modifier = Modifier) {
}
}
-@Suppress("LongMethod")
@Composable
private fun MainNodeDetails(node: Node) {
Column {
@@ -151,19 +151,6 @@ private fun MainNodeDetails(node: Node) {
SectionDivider()
PublicKeyItem(publicKey.toByteArray())
}
-
- if (!node.nodeStatus.isNullOrEmpty()) {
- HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f))
-
- Row(modifier = Modifier.fillMaxWidth()) {
- InfoItem(
- label = "Status",
- value = node.nodeStatus!!,
- icon = Icons.Default.CheckCircle,
- modifier = Modifier.weight(1f),
- )
- }
- }
}
}
@@ -173,13 +160,13 @@ private fun NameAndRoleRow(node: Node) {
InfoItem(
label = stringResource(Res.string.short_name),
value = node.user.shortName.ifEmpty { "???" },
- icon = Icons.Default.Person,
+ icon = MeshtasticIcons.Person,
modifier = Modifier.weight(1f),
)
InfoItem(
label = stringResource(Res.string.role),
value = node.user.role.name,
- icon = Icons.Default.Work,
+ icon = MeshtasticIcons.Role,
modifier = Modifier.weight(1f),
)
}
@@ -191,13 +178,13 @@ private fun NodeIdentificationRow(node: Node) {
InfoItem(
label = stringResource(Res.string.node_id),
value = DataPacket.nodeNumToDefaultId(node.num),
- icon = Icons.Default.Numbers,
+ icon = Icons.Rounded.Numbers,
modifier = Modifier.weight(1f),
)
InfoItem(
label = stringResource(Res.string.node_number),
value = node.num.toUInt().toString(),
- icon = Icons.Default.Numbers,
+ icon = Icons.Rounded.Numbers,
modifier = Modifier.weight(1f),
)
}
@@ -209,14 +196,14 @@ private fun HearsAndHopsRow(node: Node) {
InfoItem(
label = stringResource(Res.string.node_sort_last_heard),
value = formatAgo(node.lastHeard),
- icon = Icons.Default.History,
+ icon = MeshtasticIcons.History,
modifier = Modifier.weight(1f),
)
if (node.hopsAway >= 0) {
InfoItem(
label = stringResource(Res.string.hops_away),
value = node.hopsAway.toString(),
- icon = Icons.Default.SignalCellularAlt,
+ icon = MeshtasticIcons.Hops,
modifier = Modifier.weight(1f),
)
} else {
@@ -231,14 +218,14 @@ private fun UserAndUptimeRow(node: Node) {
InfoItem(
label = stringResource(Res.string.user_id),
value = node.user.id,
- icon = Icons.Default.Person,
+ icon = MeshtasticIcons.Person,
modifier = Modifier.weight(1f),
)
if (node.deviceMetrics.uptimeSeconds > 0) {
InfoItem(
label = stringResource(Res.string.uptime),
value = formatUptime(node.deviceMetrics.uptimeSeconds),
- icon = Icons.Default.CheckCircle,
+ icon = MeshtasticIcons.ArrowCircleUp,
modifier = Modifier.weight(1f),
)
} else {
@@ -254,7 +241,7 @@ private fun SignalRow(node: Node) {
InfoItem(
label = stringResource(Res.string.snr),
value = "%.1f dB".format(node.snr),
- icon = Icons.Default.SignalCellularAlt,
+ icon = MeshtasticIcons.ChannelUtilization,
modifier = Modifier.weight(1f),
)
} else {
@@ -264,7 +251,7 @@ private fun SignalRow(node: Node) {
InfoItem(
label = stringResource(Res.string.rssi),
value = "%d dBm".format(node.rssi),
- icon = Icons.Default.SignalCellularAlt,
+ icon = MeshtasticIcons.ChannelUtilization,
modifier = Modifier.weight(1f),
)
} else {
@@ -280,7 +267,7 @@ private fun MqttAndVerificationRow(node: Node) {
InfoItem(
label = stringResource(Res.string.via_mqtt),
value = "Yes",
- icon = Icons.Default.Cloud,
+ icon = MeshtasticIcons.Cloud,
modifier = Modifier.weight(1f),
)
} else {
@@ -290,7 +277,7 @@ private fun MqttAndVerificationRow(node: Node) {
InfoItem(
label = stringResource(Res.string.supported),
value = "Verified",
- icon = Icons.Default.Verified,
+ icon = MeshtasticIcons.Verified,
modifier = Modifier.weight(1f),
)
} else {
@@ -327,7 +314,7 @@ private fun PublicKeyItem(publicKeyBytes: ByteArray) {
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
- imageVector = Icons.Default.Lock,
+ imageVector = MeshtasticIcons.Lock,
contentDescription = null,
modifier = Modifier.size(14.dp),
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.8f),
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeFilterTextField.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeFilterTextField.kt
index ec9e8fcef..1aa8a1bbb 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeFilterTextField.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeFilterTextField.kt
@@ -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 .
*/
-
package org.meshtastic.feature.node.component
import androidx.compose.foundation.background
@@ -32,8 +31,8 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Sort
-import androidx.compose.material.icons.filled.Clear
-import androidx.compose.material.icons.filled.Search
+import androidx.compose.material.icons.rounded.Clear
+import androidx.compose.material.icons.rounded.Search
import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
@@ -156,13 +155,13 @@ private fun NodeFilterTextField(filterText: String, onTextChange: (String) -> Un
)
},
leadingIcon = {
- Icon(Icons.Default.Search, contentDescription = stringResource(Res.string.node_filter_placeholder))
+ Icon(Icons.Rounded.Search, contentDescription = stringResource(Res.string.node_filter_placeholder))
},
onValueChange = onTextChange,
trailingIcon = {
if (filterText.isNotEmpty() || isFocused) {
Icon(
- Icons.Default.Clear,
+ Icons.Rounded.Clear,
contentDescription = stringResource(Res.string.desc_node_filter_clear),
modifier =
Modifier.clickable {
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeItem.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeItem.kt
index 394a36f7c..4e447b58a 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeItem.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeItem.kt
@@ -20,12 +20,11 @@ import android.content.res.Configuration
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.defaultMinSize
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.material3.Card
@@ -47,15 +46,31 @@ import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.database.model.isUnmessageableRole
+import org.meshtastic.core.model.util.UnitConversions.celsiusToFahrenheit
import org.meshtastic.core.model.util.toDistanceString
import org.meshtastic.core.service.ConnectionState
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.elevation_suffix
import org.meshtastic.core.strings.unknown_username
+import org.meshtastic.core.ui.component.AirQualityInfo
+import org.meshtastic.core.ui.component.DistanceInfo
+import org.meshtastic.core.ui.component.ElevationInfo
+import org.meshtastic.core.ui.component.HardwareInfo
+import org.meshtastic.core.ui.component.HumidityInfo
+import org.meshtastic.core.ui.component.LastHeardInfo
import org.meshtastic.core.ui.component.MaterialBatteryInfo
import org.meshtastic.core.ui.component.NodeChip
+import org.meshtastic.core.ui.component.NodeIdInfo
import org.meshtastic.core.ui.component.NodeKeyStatusIcon
+import org.meshtastic.core.ui.component.PaxcountInfo
+import org.meshtastic.core.ui.component.PowerInfo
+import org.meshtastic.core.ui.component.PressureInfo
+import org.meshtastic.core.ui.component.RoleInfo
+import org.meshtastic.core.ui.component.SatelliteCountInfo
import org.meshtastic.core.ui.component.SignalInfo
+import org.meshtastic.core.ui.component.SoilMoistureInfo
+import org.meshtastic.core.ui.component.SoilTemperatureInfo
+import org.meshtastic.core.ui.component.TemperatureInfo
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.proto.ConfigProtos.Config.DisplayConfig
@@ -80,7 +95,17 @@ fun NodeItem(
val isFavorite = remember(thatNode) { thatNode.isFavorite }
val isMuted = remember(thatNode) { thatNode.isMuted }
val isIgnored = thatNode.isIgnored
- val longName = thatNode.user.longName.ifEmpty { stringResource(Res.string.unknown_username) }
+ val originalLongName = thatNode.user.longName.ifEmpty { stringResource(Res.string.unknown_username) }
+
+ @Suppress("MagicNumber")
+ val longName =
+ remember(originalLongName) {
+ if (originalLongName.length > 20) {
+ "${originalLongName.take(20)}…"
+ } else {
+ originalLongName
+ }
+ }
val isThisNode = remember(thatNode) { thisNode?.num == thatNode.num }
val system = remember(distanceUnits) { DisplayConfig.DisplayUnits.forNumber(distanceUnits) }
val distance =
@@ -115,121 +140,183 @@ fun NodeItem(
}
}
- Card(modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 80.dp), colors = cardColors) {
+ Card(modifier = modifier.fillMaxWidth(), colors = cardColors) {
Column(
modifier =
Modifier.combinedClickable(onClick = onClick, onLongClick = onLongClick).fillMaxWidth().padding(8.dp),
+ verticalArrangement = Arrangement.spacedBy(4.dp),
) {
- Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
- NodeChip(node = thatNode)
+ NodeItemHeader(
+ thatNode = thatNode,
+ isThisNode = isThisNode,
+ longName = longName,
+ style = style,
+ isIgnored = isIgnored,
+ isFavorite = isFavorite,
+ isMuted = isMuted,
+ isUnmessageable = unmessageable,
+ connectionState = connectionState,
+ contentColor = contentColor,
+ )
- NodeKeyStatusIcon(
- hasPKC = thatNode.hasPKC,
- mismatchKey = thatNode.mismatchKey,
- publicKey = thatNode.user.publicKey,
- modifier = Modifier.size(32.dp),
- )
- Text(
- modifier = Modifier.weight(1f),
- text = longName,
- style = MaterialTheme.typography.titleMediumEmphasized.copy(fontStyle = style),
- textDecoration = TextDecoration.LineThrough.takeIf { isIgnored },
- softWrap = true,
- )
- LastHeardInfo(lastHeard = thatNode.lastHeard, contentColor = contentColor)
- NodeStatusIcons(
- isThisNode = isThisNode,
- isFavorite = isFavorite,
- isMuted = isMuted,
- isUnmessageable = unmessageable,
- connectionState = connectionState,
+ NodeItemMetrics(thatNode = thatNode, distance = distance, system = system, contentColor = contentColor)
+
+ SignalInfo(node = thatNode, isThisNode = isThisNode, contentColor = contentColor)
+
+ NodeItemEnvironment(thatNode = thatNode, tempInFahrenheit = tempInFahrenheit, contentColor = contentColor)
+
+ NodeItemFooter(thatNode = thatNode, contentColor = contentColor)
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+private fun NodeItemHeader(
+ thatNode: Node,
+ isThisNode: Boolean,
+ longName: String,
+ style: FontStyle,
+ isIgnored: Boolean,
+ isFavorite: Boolean,
+ isMuted: Boolean,
+ isUnmessageable: Boolean,
+ connectionState: ConnectionState,
+ contentColor: Color,
+) {
+ Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
+ NodeChip(node = thatNode)
+
+ NodeKeyStatusIcon(
+ hasPKC = thatNode.hasPKC,
+ mismatchKey = thatNode.mismatchKey,
+ publicKey = thatNode.user.publicKey,
+ modifier = Modifier.size(32.dp),
+ )
+ Text(
+ modifier = Modifier.weight(1f),
+ text = longName,
+ style = MaterialTheme.typography.titleMediumEmphasized.copy(fontStyle = style),
+ textDecoration = TextDecoration.LineThrough.takeIf { isIgnored },
+ softWrap = true,
+ )
+ LastHeardInfo(lastHeard = thatNode.lastHeard, contentColor = contentColor)
+ NodeStatusIcons(
+ isThisNode = isThisNode,
+ isFavorite = isFavorite,
+ isMuted = isMuted,
+ isUnmessageable = isUnmessageable,
+ connectionState = connectionState,
+ contentColor = contentColor,
+ )
+ }
+}
+
+@Composable
+private fun NodeItemMetrics(
+ thatNode: Node,
+ distance: String?,
+ system: DisplayConfig.DisplayUnits,
+ contentColor: Color,
+) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ if (thatNode.batteryLevel > 0 || thatNode.voltage > 0f) {
+ MaterialBatteryInfo(level = thatNode.batteryLevel, voltage = thatNode.voltage, contentColor = contentColor)
+ } else {
+ Spacer(modifier = Modifier.weight(1f))
+ }
+ Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+ if (distance != null) {
+ DistanceInfo(distance = distance, contentColor = contentColor)
+ }
+ thatNode.validPosition?.let { position ->
+ ElevationInfo(
+ altitude = position.altitude,
+ system = system,
+ suffix = stringResource(Res.string.elevation_suffix),
+ contentColor = contentColor,
)
}
-
- Spacer(modifier = Modifier.height(4.dp))
-
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically,
- ) {
- if (thatNode.batteryLevel > 0 || thatNode.voltage > 0f) {
- MaterialBatteryInfo(
- level = thatNode.batteryLevel,
- voltage = thatNode.voltage,
- contentColor = contentColor,
- )
- }
- Row(
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- if (distance != null) {
- DistanceInfo(distance = distance, contentColor = contentColor)
- }
- thatNode.validPosition?.let { position ->
- ElevationInfo(
- altitude = position.altitude,
- system = system,
- suffix = stringResource(Res.string.elevation_suffix),
- contentColor = contentColor,
- )
- val satCount = position.satsInView
- if (satCount > 0) {
- SatelliteCountInfo(satCount = satCount, contentColor = contentColor)
- }
- }
- }
- }
- Spacer(modifier = Modifier.height(4.dp))
- FlowRow(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- itemVerticalAlignment = Alignment.CenterVertically,
- ) {
- SignalInfo(node = thatNode, isThisNode = isThisNode, contentColor = contentColor)
- }
- val telemetryStrings = thatNode.getTelemetryStrings(tempInFahrenheit)
-
- if (telemetryStrings.isNotEmpty()) {
- Spacer(modifier = Modifier.height(2.dp))
- Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
- telemetryStrings.forEach { telemetryString ->
- Text(text = telemetryString, style = MaterialTheme.typography.bodySmall, color = contentColor)
- }
- }
- }
-
- if (!thatNode.nodeStatus.isNullOrEmpty()) {
- Spacer(modifier = Modifier.height(2.dp))
- Text(
- text = thatNode.nodeStatus!!,
- style = MaterialTheme.typography.bodySmall,
- color = contentColor,
- maxLines = 2,
- )
- }
-
- Spacer(modifier = Modifier.height(2.dp))
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically,
- ) {
- val labelStyle =
- if (thatNode.isUnknownUser) {
- MaterialTheme.typography.labelSmall.copy(fontStyle = FontStyle.Italic)
- } else {
- MaterialTheme.typography.labelSmall
- }
- Text(text = thatNode.user.hwModel.name, style = labelStyle)
- Text(text = thatNode.user.role.name, style = labelStyle)
- Text(text = thatNode.user.id.ifEmpty { "???" }, style = labelStyle)
+ val satCount = thatNode.validPosition?.satsInView ?: 0
+ if (satCount > 0) {
+ SatelliteCountInfo(satCount = satCount, contentColor = contentColor)
}
}
}
}
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+@Suppress("CyclomaticComplexMethod")
+private fun NodeItemEnvironment(thatNode: Node, tempInFahrenheit: Boolean, contentColor: Color) {
+ val env = thatNode.environmentMetrics
+ val pax = thatNode.paxcounter
+ if (thatNode.hasEnvironmentMetrics || pax.ble != 0 || pax.wifi != 0) {
+ FlowRow(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
+ if (pax.ble != 0 || pax.wifi != 0) {
+ PaxcountInfo(pax = "B:${pax.ble} W:${pax.wifi}", contentColor = contentColor)
+ }
+ if (env.temperature != 0f) {
+ val temp =
+ if (tempInFahrenheit) {
+ "%.1f°F".format(celsiusToFahrenheit(env.temperature))
+ } else {
+ "%.1f°C".format(env.temperature)
+ }
+ TemperatureInfo(temp = temp, contentColor = contentColor)
+ }
+ if (env.relativeHumidity != 0f) {
+ HumidityInfo(humidity = "%.0f%%".format(env.relativeHumidity), contentColor = contentColor)
+ }
+ if (env.barometricPressure != 0f) {
+ PressureInfo(pressure = "%.1fhPa".format(env.barometricPressure), contentColor = contentColor)
+ }
+ if (env.soilTemperature != 0f) {
+ val temp =
+ if (tempInFahrenheit) {
+ "%.1f°F".format(celsiusToFahrenheit(env.soilTemperature))
+ } else {
+ "%.1f°C".format(env.soilTemperature)
+ }
+ SoilTemperatureInfo(temp = temp, contentColor = contentColor)
+ }
+ if (env.soilMoisture != 0 && env.soilTemperature != 0f) {
+ SoilMoistureInfo(moisture = "${env.soilMoisture}%", contentColor = contentColor)
+ }
+ if (env.voltage != 0f) {
+ PowerInfo(value = "%.2fV".format(env.voltage), contentColor = contentColor)
+ }
+ if (env.current != 0f) {
+ PowerInfo(value = "%.1fmA".format(env.current), contentColor = contentColor)
+ }
+ if (env.iaq != 0) {
+ AirQualityInfo(iaq = "${env.iaq}", contentColor = contentColor)
+ }
+ }
+ }
+}
+
+@Composable
+private fun NodeItemFooter(thatNode: Node, contentColor: Color) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ HardwareInfo(hwModel = thatNode.user.hwModel.name, contentColor = contentColor)
+ RoleInfo(role = thatNode.user.role.name, contentColor = contentColor)
+ NodeIdInfo(id = thatNode.user.id.ifEmpty { "???" }, contentColor = contentColor)
+ }
+}
+
@Composable
@Preview(showBackground = false, uiMode = Configuration.UI_MODE_NIGHT_YES)
fun NodeInfoSimplePreview() {
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeStatusIcons.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeStatusIcons.kt
index da8666e31..cd5559656 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeStatusIcons.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeStatusIcons.kt
@@ -19,17 +19,9 @@ package org.meshtastic.feature.node.component
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.VolumeOff
-import androidx.compose.material.icons.rounded.NoCell
-import androidx.compose.material.icons.rounded.Star
-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.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
@@ -55,6 +47,14 @@ import org.meshtastic.core.strings.favorite
import org.meshtastic.core.strings.mute_always
import org.meshtastic.core.strings.unmessageable
import org.meshtastic.core.strings.unmonitored_or_infrastructure
+import org.meshtastic.core.ui.icon.CloudDone
+import org.meshtastic.core.ui.icon.CloudOffTwoTone
+import org.meshtastic.core.ui.icon.CloudSync
+import org.meshtastic.core.ui.icon.CloudTwoTone
+import org.meshtastic.core.ui.icon.Favorite
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Unmessageable
+import org.meshtastic.core.ui.icon.VolumeOff
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
import org.meshtastic.core.ui.theme.StatusColors.StatusOrange
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
@@ -68,29 +68,33 @@ fun NodeStatusIcons(
isFavorite: Boolean,
isMuted: Boolean,
connectionState: ConnectionState,
+ modifier: Modifier = Modifier,
+ contentColor: Color = LocalContentColor.current,
) {
- Row(modifier = Modifier.padding(4.dp)) {
+ Row(modifier = modifier.padding(4.dp)) {
if (isThisNode) {
ThisNodeStatusBadge(connectionState)
}
if (isUnmessageable) {
StatusBadge(
- imageVector = Icons.Rounded.NoCell,
+ imageVector = MeshtasticIcons.Unmessageable,
contentDescription = Res.string.unmessageable,
tooltipText = Res.string.unmonitored_or_infrastructure,
+ tint = contentColor,
)
}
if (isMuted && !isThisNode) {
StatusBadge(
- imageVector = Icons.AutoMirrored.Filled.VolumeOff,
+ imageVector = MeshtasticIcons.VolumeOff,
contentDescription = Res.string.mute_always,
tooltipText = Res.string.mute_always,
+ tint = contentColor,
)
}
if (isFavorite && !isThisNode) {
StatusBadge(
- imageVector = Icons.Rounded.Star,
+ imageVector = MeshtasticIcons.Favorite,
contentDescription = Res.string.favorite,
tooltipText = Res.string.favorite,
tint = MaterialTheme.colorScheme.StatusYellow,
@@ -132,7 +136,7 @@ private fun ThisNodeStatusBadge(connectionState: ConnectionState) {
@Composable
private fun ConnectedStatusIcon() {
Icon(
- imageVector = Icons.TwoTone.CloudDone,
+ imageVector = MeshtasticIcons.CloudDone,
contentDescription = stringResource(Res.string.connected),
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.StatusGreen,
@@ -142,7 +146,7 @@ private fun ConnectedStatusIcon() {
@Composable
private fun ConnectingStatusIcon() {
Icon(
- imageVector = Icons.TwoTone.CloudSync,
+ imageVector = MeshtasticIcons.CloudSync,
contentDescription = stringResource(Res.string.connecting),
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.StatusOrange,
@@ -152,7 +156,7 @@ private fun ConnectingStatusIcon() {
@Composable
private fun DisconnectedStatusIcon() {
Icon(
- imageVector = Icons.TwoTone.CloudOff,
+ imageVector = MeshtasticIcons.CloudOffTwoTone,
contentDescription = stringResource(Res.string.disconnected),
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.StatusRed,
@@ -162,7 +166,7 @@ private fun DisconnectedStatusIcon() {
@Composable
private fun DeviceSleepStatusIcon() {
Icon(
- imageVector = Icons.TwoTone.Cloud,
+ imageVector = MeshtasticIcons.CloudTwoTone,
contentDescription = stringResource(Res.string.device_sleeping),
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.StatusYellow,
@@ -175,21 +179,19 @@ private fun StatusBadge(
imageVector: ImageVector,
contentDescription: StringResource,
tooltipText: StringResource,
- tint: Color = Color.Unspecified,
+ tint: Color = LocalContentColor.current,
) {
TooltipBox(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = { PlainTooltip { Text(stringResource(tooltipText)) } },
state = rememberTooltipState(),
) {
- IconButton(onClick = {}, modifier = Modifier.size(24.dp)) {
- Icon(
- imageVector = imageVector,
- contentDescription = stringResource(contentDescription),
- modifier = Modifier.size(24.dp),
- tint = tint,
- )
- }
+ Icon(
+ imageVector = imageVector,
+ contentDescription = stringResource(contentDescription),
+ modifier = Modifier.size(24.dp),
+ tint = tint,
+ )
}
}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NotesSection.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NotesSection.kt
index 48cbae124..5c28218b1 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NotesSection.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NotesSection.kt
@@ -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 .
*/
-
package org.meshtastic.feature.node.component
import androidx.compose.foundation.layout.Column
@@ -25,7 +24,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Save
+import androidx.compose.material.icons.rounded.Save
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
@@ -87,7 +86,7 @@ fun NotesSection(node: Node, onSaveNotes: (Int, String) -> Unit, modifier: Modif
},
enabled = edited,
) {
- Icon(imageVector = Icons.Default.Save, contentDescription = stringResource(Res.string.save))
+ Icon(imageVector = Icons.Rounded.Save, contentDescription = stringResource(Res.string.save))
}
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/PositionSection.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/PositionSection.kt
index ebf0f567f..0925b8eb9 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/PositionSection.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/PositionSection.kt
@@ -29,9 +29,9 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Explore
-import androidx.compose.material.icons.filled.LocationOn
-import androidx.compose.material.icons.filled.SocialDistance
+import androidx.compose.material.icons.rounded.Explore
+import androidx.compose.material.icons.rounded.LocationOn
+import androidx.compose.material.icons.rounded.SocialDistance
import androidx.compose.material3.AssistChip
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
@@ -132,7 +132,7 @@ private fun PositionMap(node: Node, distance: String?) {
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
- Icon(Icons.Default.SocialDistance, null, Modifier.size(16.dp))
+ Icon(Icons.Rounded.SocialDistance, null, Modifier.size(16.dp))
Spacer(Modifier.width(6.dp))
Text(distance, style = MaterialTheme.typography.labelLarge)
}
@@ -163,7 +163,7 @@ private fun PositionActionButtons(
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
),
) {
- Icon(Icons.Default.LocationOn, null, Modifier.size(18.dp))
+ Icon(Icons.Rounded.LocationOn, null, Modifier.size(18.dp))
Spacer(Modifier.width(6.dp))
Text(
text = stringResource(Res.string.exchange_position),
@@ -179,7 +179,7 @@ private fun PositionActionButtons(
modifier = Modifier.weight(COMPASS_BUTTON_WEIGHT),
shape = MaterialTheme.shapes.large,
) {
- Icon(Icons.Default.Explore, null, Modifier.size(18.dp))
+ Icon(Icons.Rounded.Explore, null, Modifier.size(18.dp))
Spacer(Modifier.width(6.dp))
Text(
text = stringResource(Res.string.open_compass),
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/PowerMetrics.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/PowerMetrics.kt
index 01c032942..d44a0276b 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/PowerMetrics.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/PowerMetrics.kt
@@ -20,8 +20,8 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Bolt
-import androidx.compose.material.icons.filled.Power
+import androidx.compose.material.icons.rounded.Bolt
+import androidx.compose.material.icons.rounded.Power
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@@ -47,16 +47,16 @@ internal fun PowerMetrics(node: Node) {
buildList {
with(node.powerMetrics) {
if (ch1Voltage != 0f) {
- add(VectorMetricInfo(Res.string.channel_1, "%.2fV".format(ch1Voltage), Icons.Default.Bolt))
- add(VectorMetricInfo(Res.string.channel_1, "%.1fmA".format(ch1Current), Icons.Default.Power))
+ add(VectorMetricInfo(Res.string.channel_1, "%.2fV".format(ch1Voltage), Icons.Rounded.Bolt))
+ add(VectorMetricInfo(Res.string.channel_1, "%.1fmA".format(ch1Current), Icons.Rounded.Power))
}
if (ch2Voltage != 0f) {
- add(VectorMetricInfo(Res.string.channel_2, "%.2fV".format(ch2Voltage), Icons.Default.Bolt))
- add(VectorMetricInfo(Res.string.channel_2, "%.1fmA".format(ch2Current), Icons.Default.Power))
+ add(VectorMetricInfo(Res.string.channel_2, "%.2fV".format(ch2Voltage), Icons.Rounded.Bolt))
+ add(VectorMetricInfo(Res.string.channel_2, "%.1fmA".format(ch2Current), Icons.Rounded.Power))
}
if (ch3Voltage != 0f) {
- add(VectorMetricInfo(Res.string.channel_3, "%.2fV".format(ch3Voltage), Icons.Default.Bolt))
- add(VectorMetricInfo(Res.string.channel_3, "%.1fmA".format(ch3Current), Icons.Default.Power))
+ add(VectorMetricInfo(Res.string.channel_3, "%.2fV".format(ch3Voltage), Icons.Rounded.Bolt))
+ add(VectorMetricInfo(Res.string.channel_3, "%.1fmA".format(ch3Current), Icons.Rounded.Power))
}
}
}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/TelemetricActionsSection.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/TelemetricActionsSection.kt
index ed9c47825..9640a9a43 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/TelemetricActionsSection.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/TelemetricActionsSection.kt
@@ -23,13 +23,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Air
-import androidx.compose.material.icons.filled.Groups
-import androidx.compose.material.icons.filled.Person
-import androidx.compose.material.icons.filled.Refresh
-import androidx.compose.material.icons.filled.Speed
-import androidx.compose.material.icons.filled.StackedLineChart
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalIconButton
@@ -64,6 +57,14 @@ import org.meshtastic.core.strings.request_local_stats
import org.meshtastic.core.strings.request_telemetry
import org.meshtastic.core.strings.telemetry
import org.meshtastic.core.strings.userinfo
+import org.meshtastic.core.ui.icon.AirQuality
+import org.meshtastic.core.ui.icon.Chart
+import org.meshtastic.core.ui.icon.Groups
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Person
+import org.meshtastic.core.ui.icon.Refresh
+import org.meshtastic.core.ui.icon.Speed
+import org.meshtastic.core.ui.icon.Temperature
import org.meshtastic.feature.node.model.LogsType
import org.meshtastic.feature.node.model.MetricsState
import org.meshtastic.feature.node.model.NodeDetailAction
@@ -119,7 +120,7 @@ private fun rememberTelemetricFeatures(
listOf(
TelemetricFeature(
titleRes = Res.string.userinfo,
- icon = Icons.Default.Person,
+ icon = MeshtasticIcons.Person,
requestAction = { NodeMenuAction.RequestUserInfo(it) },
),
TelemetricFeature(
@@ -131,7 +132,7 @@ private fun rememberTelemetricFeatures(
),
TelemetricFeature(
titleRes = Res.string.neighbor_info,
- icon = Icons.Default.Groups,
+ icon = MeshtasticIcons.Groups,
requestAction = { NodeMenuAction.RequestNeighborInfo(it) },
isVisible = { it.capabilities.canRequestNeighborInfo },
cooldownTimestamp = lastRequestNeighborsTime,
@@ -145,7 +146,7 @@ private fun rememberTelemetricFeatures(
),
TelemetricFeature(
titleRes = LogsType.ENVIRONMENT.titleRes,
- icon = Icons.Default.Air,
+ icon = MeshtasticIcons.Temperature,
requestAction = { NodeMenuAction.RequestTelemetry(it, TelemetryType.ENVIRONMENT) },
logsType = LogsType.ENVIRONMENT,
content = { EnvironmentMetrics(it, metricsState.displayUnits, metricsState.isFahrenheit) },
@@ -153,7 +154,7 @@ private fun rememberTelemetricFeatures(
),
TelemetricFeature(
titleRes = Res.string.request_air_quality_metrics,
- icon = Icons.Default.Air,
+ icon = MeshtasticIcons.AirQuality,
requestAction = { NodeMenuAction.RequestTelemetry(it, TelemetryType.AIR_QUALITY) },
),
TelemetricFeature(
@@ -166,7 +167,7 @@ private fun rememberTelemetricFeatures(
),
TelemetricFeature(
titleRes = Res.string.request_local_stats,
- icon = Icons.Default.Speed,
+ icon = MeshtasticIcons.Speed,
requestAction = { NodeMenuAction.RequestTelemetry(it, TelemetryType.LOCAL_STATS) },
),
TelemetricFeature(
@@ -226,7 +227,7 @@ private fun FeatureRow(node: Node, feature: TelemetricFeature, hasLogs: Boolean,
},
) {
Icon(
- Icons.Default.StackedLineChart,
+ MeshtasticIcons.Chart,
contentDescription = logsDescription,
modifier = Modifier.size(IconButtonDefaults.mediumIconSize),
tint = MaterialTheme.colorScheme.primary,
@@ -252,7 +253,7 @@ private fun FeatureRow(node: Node, feature: TelemetricFeature, hasLogs: Boolean,
cooldownDuration = feature.cooldownDuration,
) {
Icon(
- imageVector = Icons.Default.Refresh,
+ imageVector = MeshtasticIcons.Refresh,
contentDescription = requestDescription,
tint = MaterialTheme.colorScheme.primary,
)
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/TelemetryInfo.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/TelemetryInfo.kt
new file mode 100644
index 000000000..5875bbaad
--- /dev/null
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/TelemetryInfo.kt
@@ -0,0 +1,179 @@
+/*
+ * 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 .
+ */
+package org.meshtastic.feature.node.component
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Air
+import androidx.compose.material.icons.rounded.ElectricBolt
+import androidx.compose.material.icons.rounded.Fingerprint
+import androidx.compose.material.icons.rounded.Grass
+import androidx.compose.material.icons.rounded.People
+import androidx.compose.material.icons.rounded.Router
+import androidx.compose.material.icons.rounded.Thermostat
+import androidx.compose.material.icons.rounded.WaterDrop
+import androidx.compose.material.icons.rounded.Work
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+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
+
+@Composable
+fun TemperatureInfo(
+ temp: String,
+ modifier: Modifier = Modifier,
+ contentColor: Color = MaterialTheme.colorScheme.onSurface,
+) {
+ IconInfo(
+ modifier = modifier,
+ icon = Icons.Rounded.Thermostat,
+ 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 = Icons.Rounded.WaterDrop,
+ contentDescription = stringResource(Res.string.env_metrics_log),
+ text = humidity,
+ contentColor = contentColor,
+ )
+}
+
+@Composable
+fun SoilTemperatureInfo(
+ temp: String,
+ modifier: Modifier = Modifier,
+ contentColor: Color = MaterialTheme.colorScheme.onSurface,
+) {
+ IconInfo(
+ modifier = modifier,
+ icon = Icons.Rounded.Grass,
+ contentDescription = stringResource(Res.string.env_metrics_log),
+ text = temp,
+ contentColor = contentColor,
+ )
+}
+
+@Composable
+fun SoilMoistureInfo(
+ moisture: String,
+ modifier: Modifier = Modifier,
+ contentColor: Color = MaterialTheme.colorScheme.onSurface,
+) {
+ IconInfo(
+ modifier = modifier,
+ icon = Icons.Rounded.Grass,
+ 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 = Icons.Rounded.People,
+ 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 = Icons.Rounded.Air,
+ 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 = Icons.Rounded.ElectricBolt,
+ contentDescription = stringResource(Res.string.env_metrics_log),
+ text = value,
+ contentColor = contentColor,
+ )
+}
+
+@Composable
+fun HardwareInfo(
+ hwModel: String,
+ modifier: Modifier = Modifier,
+ contentColor: Color = MaterialTheme.colorScheme.onSurface,
+) {
+ IconInfo(
+ modifier = modifier,
+ icon = Icons.Rounded.Router,
+ 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 = Icons.Rounded.Work,
+ 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 = Icons.Rounded.Fingerprint,
+ contentDescription = stringResource(Res.string.node_id),
+ text = id,
+ style = MaterialTheme.typography.labelSmall,
+ contentColor = contentColor,
+ )
+}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/CommonCharts.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/CommonCharts.kt
index 5a35f0085..cc1ac4850 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/CommonCharts.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/CommonCharts.kt
@@ -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 .
*/
-
package org.meshtastic.feature.node.metrics
import android.graphics.Paint
@@ -32,7 +31,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Info
+import androidx.compose.material.icons.rounded.Info
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -258,7 +257,7 @@ fun Legend(legendData: List, displayInfoIcon: Boolean = true, prompt
if (displayInfoIcon) {
Spacer(modifier = Modifier.width(4.dp))
Icon(
- imageVector = Icons.Default.Info,
+ imageVector = Icons.Rounded.Info,
modifier = Modifier.clickable { promptInfoDialog() },
contentDescription = stringResource(Res.string.info),
)
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/DeviceMetrics.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/DeviceMetrics.kt
index 913ded4c1..5d85d2d73 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/DeviceMetrics.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/DeviceMetrics.kt
@@ -33,9 +33,8 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Card
+import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
@@ -78,6 +77,8 @@ import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.core.ui.component.MaterialBatteryInfo
import org.meshtastic.core.ui.component.OptionLabel
import org.meshtastic.core.ui.component.SlidingSelector
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Refresh
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.core.ui.theme.GraphColors.Cyan
import org.meshtastic.core.ui.theme.GraphColors.Green
@@ -158,10 +159,7 @@ fun DeviceMetricsScreen(viewModel: MetricsViewModel = hiltViewModel(), onNavigat
actions = {
if (!state.isLocal) {
IconButton(onClick = { viewModel.requestTelemetry(TelemetryType.DEVICE) }) {
- androidx.compose.material3.Icon(
- imageVector = Icons.Default.Refresh,
- contentDescription = null,
- )
+ Icon(imageVector = MeshtasticIcons.Refresh, contentDescription = null)
}
}
},
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/EnvironmentMetrics.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/EnvironmentMetrics.kt
index 0799aee4f..2e81842a0 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/EnvironmentMetrics.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/EnvironmentMetrics.kt
@@ -28,8 +28,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.selection.SelectionContainer
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Card
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -75,6 +73,8 @@ import org.meshtastic.core.ui.component.IndoorAirQuality
import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.core.ui.component.OptionLabel
import org.meshtastic.core.ui.component.SlidingSelector
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Refresh
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.feature.node.metrics.CommonCharts.DATE_TIME_FORMAT
import org.meshtastic.feature.node.metrics.CommonCharts.MS_PER_SEC
@@ -134,7 +134,7 @@ fun EnvironmentMetricsScreen(viewModel: MetricsViewModel = hiltViewModel(), onNa
if (!state.isLocal) {
IconButton(onClick = { viewModel.requestTelemetry(TelemetryType.ENVIRONMENT) }) {
androidx.compose.material3.Icon(
- imageVector = Icons.Default.Refresh,
+ imageVector = MeshtasticIcons.Refresh,
contentDescription = null,
)
}
@@ -339,27 +339,6 @@ private fun GasCompositionDisplay(envMetrics: TelemetryProtos.EnvironmentMetrics
}
}
}
- // These are in a differnt proto ...
- // envMetrics.co2?.let { co2 ->
- // Spacer(modifier = Modifier.height(4.dp))
- // Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
- // Text(
- // text = "%s %.0f ppm".format(stringResource(Res.string.co2), co2),
- // color = MaterialTheme.colorScheme.onSurface,
- // fontSize = MaterialTheme.typography.labelLarge.fontSize,
- // )
- // }
- // }
- // envMetrics.tvoc?.let { tvoc ->
- // Spacer(modifier = Modifier.height(4.dp))
- // Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
- // Text(
- // text = "%s %.0f ppb".format(stringResource(Res.string.tvoc), tvoc),
- // color = MaterialTheme.colorScheme.onSurface,
- // fontSize = MaterialTheme.typography.labelLarge.fontSize,
- // )
- // }
- // }
}
@Composable
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/HostMetricsLog.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/HostMetricsLog.kt
index 6a01f1e0c..76ff6beb0 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/HostMetricsLog.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/HostMetricsLog.kt
@@ -30,9 +30,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.selection.SelectionContainer
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.DataArray
-import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
@@ -69,6 +66,9 @@ import org.meshtastic.core.strings.load_indexed
import org.meshtastic.core.strings.uptime
import org.meshtastic.core.strings.user_string
import org.meshtastic.core.ui.component.MainAppBar
+import org.meshtastic.core.ui.icon.DataArray
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Refresh
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.feature.node.metrics.CommonCharts.DATE_TIME_FORMAT
@@ -105,10 +105,7 @@ fun HostMetricsLogScreen(metricsViewModel: MetricsViewModel = hiltViewModel(), o
actions = {
if (!state.isLocal) {
IconButton(onClick = { metricsViewModel.requestTelemetry(TelemetryType.HOST) }) {
- Icon(
- imageVector = androidx.compose.material.icons.Icons.Default.Refresh,
- contentDescription = null,
- )
+ Icon(imageVector = MeshtasticIcons.Refresh, contentDescription = null)
}
}
},
@@ -136,7 +133,7 @@ fun HostMetricsItem(modifier: Modifier = Modifier, telemetry: TelemetryProtos.Te
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
) {
Row(modifier = Modifier.padding(16.dp)) {
- Icon(imageVector = Icons.Default.DataArray, contentDescription = null, modifier = Modifier.width(24.dp))
+ Icon(imageVector = MeshtasticIcons.DataArray, contentDescription = null, modifier = Modifier.width(24.dp))
Spacer(modifier = Modifier.width(16.dp))
SelectionContainer {
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PaxMetrics.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PaxMetrics.kt
index b34d67217..d0b501139 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PaxMetrics.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PaxMetrics.kt
@@ -33,8 +33,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -69,11 +67,16 @@ import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.ble_devices
import org.meshtastic.core.strings.no_pax_metrics_logs
import org.meshtastic.core.strings.pax
+import org.meshtastic.core.strings.pax_metrics_log
import org.meshtastic.core.strings.uptime
import org.meshtastic.core.strings.wifi_devices
+import org.meshtastic.core.ui.component.IconInfo
import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.core.ui.component.OptionLabel
import org.meshtastic.core.ui.component.SlidingSelector
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Paxcount
+import org.meshtastic.core.ui.icon.Refresh
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.feature.node.model.TimeFrame
import org.meshtastic.proto.PaxcountProtos
@@ -229,7 +232,7 @@ fun PaxMetricsScreen(metricsViewModel: MetricsViewModel = hiltViewModel(), onNav
actions = {
if (!state.isLocal) {
IconButton(onClick = { metricsViewModel.requestTelemetry(TelemetryType.PAX) }) {
- Icon(imageVector = Icons.Default.Refresh, contentDescription = null)
+ Icon(imageVector = MeshtasticIcons.Refresh, contentDescription = null)
}
}
},
@@ -331,6 +334,21 @@ fun unescapeProtoString(escaped: String): ByteArray {
return out.toByteArray()
}
+@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 PaxMetricsItem(log: MeshLog, pax: PaxcountProtos.Paxcount, dateFormat: DateFormat) {
Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp)) {
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PositionLog.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PositionLog.kt
index 775b22f0e..79e3af580 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PositionLog.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PositionLog.kt
@@ -34,10 +34,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Delete
-import androidx.compose.material.icons.filled.Refresh
-import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -80,6 +76,10 @@ import org.meshtastic.core.strings.save
import org.meshtastic.core.strings.speed
import org.meshtastic.core.strings.timestamp
import org.meshtastic.core.ui.component.MainAppBar
+import org.meshtastic.core.ui.icon.Delete
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Refresh
+import org.meshtastic.core.ui.icon.Save
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.core.ui.util.formatPositionTime
import org.meshtastic.feature.node.detail.NodeRequestEffect
@@ -157,13 +157,13 @@ private fun ActionButtons(
enabled = clearButtonEnabled,
colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.error),
) {
- Icon(imageVector = Icons.Default.Delete, contentDescription = stringResource(Res.string.clear))
+ Icon(imageVector = MeshtasticIcons.Delete, contentDescription = stringResource(Res.string.clear))
Spacer(Modifier.width(8.dp))
Text(text = stringResource(Res.string.clear))
}
OutlinedButton(modifier = Modifier.weight(1f), onClick = onSave, enabled = saveButtonEnabled) {
- Icon(imageVector = Icons.Default.Save, contentDescription = stringResource(Res.string.save))
+ Icon(imageVector = MeshtasticIcons.Save, contentDescription = stringResource(Res.string.save))
Spacer(Modifier.width(8.dp))
Text(text = stringResource(Res.string.save))
}
@@ -207,7 +207,7 @@ fun PositionLogScreen(viewModel: MetricsViewModel = hiltViewModel(), onNavigateU
actions = {
if (!state.isLocal) {
IconButton(onClick = { viewModel.requestPosition() }) {
- Icon(imageVector = Icons.Default.Refresh, contentDescription = null)
+ Icon(imageVector = MeshtasticIcons.Refresh, contentDescription = null)
}
}
},
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PowerMetrics.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PowerMetrics.kt
index f868a4fb3..1df1bba7b 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PowerMetrics.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PowerMetrics.kt
@@ -34,7 +34,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material3.Card
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -161,7 +161,7 @@ fun PowerMetricsScreen(viewModel: MetricsViewModel = hiltViewModel(), onNavigate
if (!state.isLocal) {
IconButton(onClick = { viewModel.requestTelemetry(TelemetryType.POWER) }) {
androidx.compose.material3.Icon(
- imageVector = Icons.Default.Refresh,
+ imageVector = Icons.Rounded.Refresh,
contentDescription = null,
)
}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/SignalMetrics.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/SignalMetrics.kt
index 32d834186..327620321 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/SignalMetrics.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/SignalMetrics.kt
@@ -34,8 +34,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Card
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -74,6 +72,8 @@ import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.core.ui.component.OptionLabel
import org.meshtastic.core.ui.component.SlidingSelector
import org.meshtastic.core.ui.component.SnrAndRssi
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Refresh
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.feature.node.metrics.CommonCharts.DATE_TIME_FORMAT
import org.meshtastic.feature.node.metrics.CommonCharts.MS_PER_SEC
@@ -133,7 +133,7 @@ fun SignalMetricsScreen(viewModel: MetricsViewModel = hiltViewModel(), onNavigat
if (!state.isLocal) {
IconButton(onClick = { viewModel.requestTelemetry(TelemetryType.LOCAL_STATS) }) {
androidx.compose.material3.Icon(
- imageVector = Icons.Default.Refresh,
+ imageVector = MeshtasticIcons.Refresh,
contentDescription = null,
)
}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/TracerouteLog.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/TracerouteLog.kt
index ffeaca21f..8bf789261 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/TracerouteLog.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/TracerouteLog.kt
@@ -32,12 +32,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.selection.SelectionContainer
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Delete
-import androidx.compose.material.icons.filled.Group
-import androidx.compose.material.icons.filled.Groups
-import androidx.compose.material.icons.filled.PersonOff
-import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Card
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
@@ -92,6 +86,12 @@ import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.core.ui.component.SNR_FAIR_THRESHOLD
import org.meshtastic.core.ui.component.SNR_GOOD_THRESHOLD
import org.meshtastic.core.ui.component.SimpleAlertDialog
+import org.meshtastic.core.ui.icon.Delete
+import org.meshtastic.core.ui.icon.Group
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.PersonOff
+import org.meshtastic.core.ui.icon.Refresh
+import org.meshtastic.core.ui.icon.Route
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
import org.meshtastic.core.ui.theme.StatusColors.StatusOrange
@@ -163,7 +163,7 @@ fun TracerouteLogScreen(
onClick = { viewModel.requestTraceroute() },
cooldownTimestamp = lastTracerouteTime,
) {
- Icon(imageVector = Icons.Default.Refresh, contentDescription = null)
+ Icon(imageVector = MeshtasticIcons.Refresh, contentDescription = null)
}
}
},
@@ -329,7 +329,7 @@ private fun DeleteItem(onClick: () -> Unit) {
text = {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
- imageVector = Icons.Default.Delete,
+ imageVector = MeshtasticIcons.Delete,
contentDescription = stringResource(Res.string.delete),
tint = MaterialTheme.colorScheme.error,
)
@@ -357,23 +357,23 @@ private fun TracerouteItem(icon: ImageVector, text: String, modifier: Modifier =
@Composable
private fun MeshProtos.RouteDiscovery?.getTextAndIcon(): Pair = when {
this == null -> {
- stringResource(Res.string.routing_error_no_response) to Icons.Default.PersonOff
+ stringResource(Res.string.routing_error_no_response) to MeshtasticIcons.PersonOff
}
// A direct route means the sender and receiver are the only two nodes in the route.
routeCount <= 2 && routeBackCount <= 2 -> { // also check routeBackCount for direct to be more robust
- stringResource(Res.string.traceroute_direct) to Icons.Default.Group
+ stringResource(Res.string.traceroute_direct) to MeshtasticIcons.Group
}
routeCount == routeBackCount -> {
val hops = routeCount - 2
- pluralStringResource(Res.plurals.traceroute_hops, hops, hops) to Icons.Default.Groups
+ pluralStringResource(Res.plurals.traceroute_hops, hops, hops) to MeshtasticIcons.Route
}
else -> {
// Asymmetric route
val towards = maxOf(0, routeCount - 2)
val back = maxOf(0, routeBackCount - 2)
- stringResource(Res.string.traceroute_diff, towards, back) to Icons.Default.Groups
+ stringResource(Res.string.traceroute_diff, towards, back) to MeshtasticIcons.Route
}
}
@@ -424,5 +424,5 @@ private fun TracerouteItemPreview() {
System.currentTimeMillis(),
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_ABBREV_ALL,
)
- AppTheme { TracerouteItem(icon = Icons.Default.Group, text = "$time - Direct") }
+ AppTheme { TracerouteItem(icon = MeshtasticIcons.Group, text = "$time - Direct") }
}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/TracerouteMapScreen.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/TracerouteMapScreen.kt
index 790f9afab..eb35e1a20 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/TracerouteMapScreen.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/TracerouteMapScreen.kt
@@ -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 .
*/
-
package org.meshtastic.feature.node.metrics
import androidx.compose.foundation.layout.Arrangement
@@ -24,8 +23,6 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Route
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -52,6 +49,8 @@ import org.meshtastic.core.strings.traceroute_outgoing_route
import org.meshtastic.core.strings.traceroute_return_route
import org.meshtastic.core.strings.traceroute_showing_nodes
import org.meshtastic.core.ui.component.MainAppBar
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Route
import org.meshtastic.core.ui.theme.TracerouteColors
import org.meshtastic.feature.map.MapView
import org.meshtastic.feature.map.model.TracerouteOverlay
@@ -172,7 +171,7 @@ private fun TracerouteNodeCount(modifier: Modifier = Modifier, shown: Int, total
private fun LegendRow(color: Color, label: String) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
- imageVector = Icons.Default.Route,
+ imageVector = MeshtasticIcons.Route,
contentDescription = null,
tint = color,
modifier = Modifier.padding(end = 8.dp).size(18.dp),
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/model/LogsType.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/model/LogsType.kt
index 0076b4405..38044a722 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/model/LogsType.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/model/LogsType.kt
@@ -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,19 +14,16 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package org.meshtastic.feature.node.model
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ChargingStation
-import androidx.compose.material.icons.filled.LocationOn
-import androidx.compose.material.icons.filled.Map
-import androidx.compose.material.icons.filled.Memory
-import androidx.compose.material.icons.filled.People
-import androidx.compose.material.icons.filled.Power
-import androidx.compose.material.icons.filled.Route
-import androidx.compose.material.icons.filled.SignalCellularAlt
-import androidx.compose.material.icons.filled.Thermostat
+import androidx.compose.material.icons.rounded.ChargingStation
+import androidx.compose.material.icons.rounded.LocationOn
+import androidx.compose.material.icons.rounded.Map
+import androidx.compose.material.icons.rounded.Memory
+import androidx.compose.material.icons.rounded.Power
+import androidx.compose.material.icons.rounded.SignalCellularAlt
+import androidx.compose.material.icons.rounded.Thermostat
import androidx.compose.ui.graphics.vector.ImageVector
import org.jetbrains.compose.resources.StringResource
import org.meshtastic.core.navigation.NodeDetailRoutes
@@ -41,15 +38,18 @@ import org.meshtastic.core.strings.position_log
import org.meshtastic.core.strings.power_metrics_log
import org.meshtastic.core.strings.sig_metrics_log
import org.meshtastic.core.strings.traceroute_log
+import org.meshtastic.core.ui.icon.MeshtasticIcons
+import org.meshtastic.core.ui.icon.Paxcount
+import org.meshtastic.core.ui.icon.Route
enum class LogsType(val titleRes: StringResource, val icon: ImageVector, val routeFactory: (Int) -> Route) {
- DEVICE(Res.string.device_metrics_log, Icons.Default.ChargingStation, { NodeDetailRoutes.DeviceMetrics(it) }),
- NODE_MAP(Res.string.node_map, Icons.Default.Map, { NodeDetailRoutes.NodeMap(it) }),
- POSITIONS(Res.string.position_log, Icons.Default.LocationOn, { NodeDetailRoutes.PositionLog(it) }),
- ENVIRONMENT(Res.string.env_metrics_log, Icons.Default.Thermostat, { NodeDetailRoutes.EnvironmentMetrics(it) }),
- SIGNAL(Res.string.sig_metrics_log, Icons.Default.SignalCellularAlt, { NodeDetailRoutes.SignalMetrics(it) }),
- POWER(Res.string.power_metrics_log, Icons.Default.Power, { NodeDetailRoutes.PowerMetrics(it) }),
- TRACEROUTE(Res.string.traceroute_log, Icons.Default.Route, { NodeDetailRoutes.TracerouteLog(it) }),
- HOST(Res.string.host_metrics_log, Icons.Default.Memory, { NodeDetailRoutes.HostMetricsLog(it) }),
- PAX(Res.string.pax_metrics_log, Icons.Default.People, { NodeDetailRoutes.PaxMetrics(it) }),
+ DEVICE(Res.string.device_metrics_log, Icons.Rounded.ChargingStation, { NodeDetailRoutes.DeviceMetrics(it) }),
+ NODE_MAP(Res.string.node_map, Icons.Rounded.Map, { NodeDetailRoutes.NodeMap(it) }),
+ POSITIONS(Res.string.position_log, Icons.Rounded.LocationOn, { NodeDetailRoutes.PositionLog(it) }),
+ ENVIRONMENT(Res.string.env_metrics_log, Icons.Rounded.Thermostat, { NodeDetailRoutes.EnvironmentMetrics(it) }),
+ SIGNAL(Res.string.sig_metrics_log, Icons.Rounded.SignalCellularAlt, { NodeDetailRoutes.SignalMetrics(it) }),
+ POWER(Res.string.power_metrics_log, Icons.Rounded.Power, { NodeDetailRoutes.PowerMetrics(it) }),
+ TRACEROUTE(Res.string.traceroute_log, MeshtasticIcons.Route, { NodeDetailRoutes.TracerouteLog(it) }),
+ HOST(Res.string.host_metrics_log, Icons.Rounded.Memory, { NodeDetailRoutes.HostMetricsLog(it) }),
+ PAX(Res.string.pax_metrics_log, MeshtasticIcons.Paxcount, { NodeDetailRoutes.PaxMetrics(it) }),
}
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt
index e9116e1af..256e37fbd 100644
--- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt
+++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt
@@ -38,6 +38,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
import androidx.compose.material.icons.filled.BugReport
import androidx.compose.material.icons.rounded.AppSettingsAlt
+import androidx.compose.material.icons.rounded.BugReport
import androidx.compose.material.icons.rounded.FilterList
import androidx.compose.material.icons.rounded.FormatPaint
import androidx.compose.material.icons.rounded.Info
@@ -283,7 +284,7 @@ fun SettingsScreen(
SwitchListItem(
text = stringResource(Res.string.analytics_okay),
checked = allowed,
- leadingIcon = Icons.Default.BugReport,
+ leadingIcon = Icons.Rounded.BugReport,
onClick = { viewModel.toggleAnalyticsAllowed() },
)
}
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/Debug.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/Debug.kt
index 327d888b7..a3b613f14 100644
--- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/Debug.kt
+++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/Debug.kt
@@ -36,8 +36,8 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.outlined.FileDownload
+import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material.icons.twotone.FilterAltOff
import androidx.compose.material3.Card
@@ -416,7 +416,7 @@ fun DebugMenuActions(deleteLogs: () -> Unit, modifier: Modifier = Modifier) {
var showDeleteLogsDialog by remember { mutableStateOf(false) }
IconButton(onClick = { showDeleteLogsDialog = true }, modifier = modifier.padding(4.dp)) {
- Icon(imageVector = Icons.Default.Delete, contentDescription = stringResource(Res.string.debug_clear))
+ Icon(imageVector = Icons.Rounded.Delete, contentDescription = stringResource(Res.string.debug_clear))
}
if (showDeleteLogsDialog) {
SimpleAlertDialog(
@@ -664,7 +664,7 @@ private fun DebugMenuActionsPreview() {
)
}
IconButton(onClick = { /* Preview only */ }, modifier = Modifier.padding(4.dp)) {
- Icon(imageVector = Icons.Default.Delete, contentDescription = stringResource(Res.string.debug_clear))
+ Icon(imageVector = Icons.Rounded.Delete, contentDescription = stringResource(Res.string.debug_clear))
}
}
}
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/DebugFilters.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/DebugFilters.kt
index 98d8cf757..4a205552b 100644
--- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/DebugFilters.kt
+++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/DebugFilters.kt
@@ -29,9 +29,10 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Done
+import androidx.compose.material.icons.rounded.Add
+import androidx.compose.material.icons.rounded.Clear
import androidx.compose.material.icons.twotone.FilterAlt
import androidx.compose.material.icons.twotone.FilterAltOff
import androidx.compose.material3.DropdownMenu
@@ -103,7 +104,7 @@ fun DebugCustomFilterInput(
},
enabled = customFilterText.isNotBlank(),
) {
- Icon(imageVector = Icons.Default.Add, contentDescription = stringResource(Res.string.debug_filter_add))
+ Icon(imageVector = Icons.Rounded.Add, contentDescription = stringResource(Res.string.debug_filter_add))
}
}
}
@@ -266,7 +267,7 @@ internal fun DebugActiveFilters(
}
IconButton(onClick = { onFilterTextsChange(emptyList()) }) {
Icon(
- imageVector = Icons.Default.Clear,
+ imageVector = Icons.Rounded.Clear,
contentDescription = stringResource(Res.string.debug_filter_clear),
)
}
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/DebugSearch.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/DebugSearch.kt
index 734ac5743..e0978815a 100644
--- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/DebugSearch.kt
+++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/DebugSearch.kt
@@ -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 .
*/
-
package org.meshtastic.feature.settings.debugging
import androidx.compose.foundation.background
@@ -29,10 +28,10 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Clear
-import androidx.compose.material.icons.filled.KeyboardArrowDown
-import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material.icons.outlined.FileDownload
+import androidx.compose.material.icons.rounded.Clear
+import androidx.compose.material.icons.rounded.KeyboardArrowDown
+import androidx.compose.material.icons.rounded.KeyboardArrowUp
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -83,14 +82,14 @@ internal fun DebugSearchNavigation(
)
IconButton(onClick = onPreviousMatch, enabled = searchState.hasMatches, modifier = Modifier.size(32.dp)) {
Icon(
- imageVector = Icons.Default.KeyboardArrowUp,
+ imageVector = Icons.Rounded.KeyboardArrowUp,
contentDescription = stringResource(Res.string.debug_search_prev),
modifier = Modifier.size(16.dp),
)
}
IconButton(onClick = onNextMatch, enabled = searchState.hasMatches, modifier = Modifier.size(32.dp)) {
Icon(
- imageVector = Icons.Default.KeyboardArrowDown,
+ imageVector = Icons.Rounded.KeyboardArrowDown,
contentDescription = stringResource(Res.string.debug_search_next),
modifier = Modifier.size(16.dp),
)
@@ -136,7 +135,7 @@ internal fun DebugSearchBar(
if (searchState.searchText.isNotEmpty()) {
IconButton(onClick = onClearSearch, modifier = Modifier.size(32.dp)) {
Icon(
- imageVector = Icons.Default.Clear,
+ imageVector = Icons.Rounded.Clear,
contentDescription = stringResource(Res.string.debug_search_clear),
modifier = Modifier.size(16.dp),
)
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/filter/FilterSettingsScreen.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/filter/FilterSettingsScreen.kt
index 09febc6c2..e8e953cf6 100644
--- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/filter/FilterSettingsScreen.kt
+++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/filter/FilterSettingsScreen.kt
@@ -26,8 +26,8 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.rounded.Add
+import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -156,7 +156,7 @@ private fun FilterWordsInputCard(newWord: String, onNewWordChange: (String) -> U
keyboardActions = KeyboardActions(onDone = { onAddWord() }),
)
IconButton(onClick = onAddWord) {
- Icon(Icons.Default.Add, contentDescription = stringResource(Res.string.add))
+ Icon(Icons.Rounded.Add, contentDescription = stringResource(Res.string.add))
}
}
}
@@ -184,7 +184,7 @@ private fun FilterWordItem(word: String, onRemove: () -> Unit) {
)
}
IconButton(onClick = onRemove) {
- Icon(Icons.Default.Delete, contentDescription = stringResource(Res.string.delete))
+ Icon(Icons.Rounded.Delete, contentDescription = stringResource(Res.string.delete))
}
}
}
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/ConfigRoute.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/ConfigRoute.kt
index 768fb4696..dcaa0055e 100644
--- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/ConfigRoute.kt
+++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/ConfigRoute.kt
@@ -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,20 +14,19 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package org.meshtastic.feature.settings.navigation
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.List
-import androidx.compose.material.icons.filled.Bluetooth
-import androidx.compose.material.icons.filled.CellTower
-import androidx.compose.material.icons.filled.DisplaySettings
-import androidx.compose.material.icons.filled.LocationOn
-import androidx.compose.material.icons.filled.Person
-import androidx.compose.material.icons.filled.Power
-import androidx.compose.material.icons.filled.Router
-import androidx.compose.material.icons.filled.Security
-import androidx.compose.material.icons.filled.Wifi
+import androidx.compose.material.icons.rounded.Bluetooth
+import androidx.compose.material.icons.rounded.CellTower
+import androidx.compose.material.icons.rounded.DisplaySettings
+import androidx.compose.material.icons.rounded.LocationOn
+import androidx.compose.material.icons.rounded.Person
+import androidx.compose.material.icons.rounded.Power
+import androidx.compose.material.icons.rounded.Router
+import androidx.compose.material.icons.rounded.Security
+import androidx.compose.material.icons.rounded.Wifi
import androidx.compose.ui.graphics.vector.ImageVector
import org.jetbrains.compose.resources.StringResource
import org.meshtastic.core.navigation.Route
@@ -47,54 +46,54 @@ import org.meshtastic.proto.AdminProtos
import org.meshtastic.proto.MeshProtos.DeviceMetadata
enum class ConfigRoute(val title: StringResource, val route: Route, val icon: ImageVector?, val type: Int = 0) {
- USER(Res.string.user, SettingsRoutes.User, Icons.Default.Person, 0),
+ USER(Res.string.user, SettingsRoutes.User, Icons.Rounded.Person, 0),
CHANNELS(Res.string.channels, SettingsRoutes.ChannelConfig, Icons.AutoMirrored.Default.List, 0),
DEVICE(
Res.string.device,
SettingsRoutes.Device,
- Icons.Default.Router,
+ Icons.Rounded.Router,
AdminProtos.AdminMessage.ConfigType.DEVICE_CONFIG_VALUE,
),
POSITION(
Res.string.position,
SettingsRoutes.Position,
- Icons.Default.LocationOn,
+ Icons.Rounded.LocationOn,
AdminProtos.AdminMessage.ConfigType.POSITION_CONFIG_VALUE,
),
POWER(
Res.string.power,
SettingsRoutes.Power,
- Icons.Default.Power,
+ Icons.Rounded.Power,
AdminProtos.AdminMessage.ConfigType.POWER_CONFIG_VALUE,
),
NETWORK(
Res.string.network,
SettingsRoutes.Network,
- Icons.Default.Wifi,
+ Icons.Rounded.Wifi,
AdminProtos.AdminMessage.ConfigType.NETWORK_CONFIG_VALUE,
),
DISPLAY(
Res.string.display,
SettingsRoutes.Display,
- Icons.Default.DisplaySettings,
+ Icons.Rounded.DisplaySettings,
AdminProtos.AdminMessage.ConfigType.DISPLAY_CONFIG_VALUE,
),
LORA(
Res.string.lora,
SettingsRoutes.LoRa,
- Icons.Default.CellTower,
+ Icons.Rounded.CellTower,
AdminProtos.AdminMessage.ConfigType.LORA_CONFIG_VALUE,
),
BLUETOOTH(
Res.string.bluetooth,
SettingsRoutes.Bluetooth,
- Icons.Default.Bluetooth,
+ Icons.Rounded.Bluetooth,
AdminProtos.AdminMessage.ConfigType.BLUETOOTH_CONFIG_VALUE,
),
SECURITY(
Res.string.security,
SettingsRoutes.Security,
- Icons.Default.Security,
+ Icons.Rounded.Security,
AdminProtos.AdminMessage.ConfigType.SECURITY_CONFIG_VALUE,
),
;
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/ModuleRoute.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/ModuleRoute.kt
index b65a4ca8d..8ad211c6f 100644
--- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/ModuleRoute.kt
+++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/ModuleRoute.kt
@@ -30,6 +30,16 @@ import androidx.compose.material.icons.filled.Sensors
import androidx.compose.material.icons.filled.SettingsRemote
import androidx.compose.material.icons.filled.Speed
import androidx.compose.material.icons.filled.Usb
+import androidx.compose.material.icons.rounded.Cloud
+import androidx.compose.material.icons.rounded.DataUsage
+import androidx.compose.material.icons.rounded.LightMode
+import androidx.compose.material.icons.rounded.Notifications
+import androidx.compose.material.icons.rounded.People
+import androidx.compose.material.icons.rounded.PermScanWifi
+import androidx.compose.material.icons.rounded.Sensors
+import androidx.compose.material.icons.rounded.SettingsRemote
+import androidx.compose.material.icons.rounded.Speed
+import androidx.compose.material.icons.rounded.Usb
import androidx.compose.ui.graphics.vector.ImageVector
import org.jetbrains.compose.resources.StringResource
import org.meshtastic.core.navigation.Route
@@ -56,19 +66,19 @@ enum class ModuleRoute(val title: StringResource, val route: Route, val icon: Im
MQTT(
Res.string.mqtt,
SettingsRoutes.MQTT,
- Icons.Default.Cloud,
+ Icons.Rounded.Cloud,
AdminProtos.AdminMessage.ModuleConfigType.MQTT_CONFIG_VALUE,
),
SERIAL(
Res.string.serial,
SettingsRoutes.Serial,
- Icons.Default.Usb,
+ Icons.Rounded.Usb,
AdminProtos.AdminMessage.ModuleConfigType.SERIAL_CONFIG_VALUE,
),
EXT_NOTIFICATION(
Res.string.external_notification,
SettingsRoutes.ExtNotification,
- Icons.Default.Notifications,
+ Icons.Rounded.Notifications,
AdminProtos.AdminMessage.ModuleConfigType.EXTNOTIF_CONFIG_VALUE,
),
STORE_FORWARD(
@@ -80,13 +90,13 @@ enum class ModuleRoute(val title: StringResource, val route: Route, val icon: Im
RANGE_TEST(
Res.string.range_test,
SettingsRoutes.RangeTest,
- Icons.Default.Speed,
+ Icons.Rounded.Speed,
AdminProtos.AdminMessage.ModuleConfigType.RANGETEST_CONFIG_VALUE,
),
TELEMETRY(
Res.string.telemetry,
SettingsRoutes.Telemetry,
- Icons.Default.DataUsage,
+ Icons.Rounded.DataUsage,
AdminProtos.AdminMessage.ModuleConfigType.TELEMETRY_CONFIG_VALUE,
),
CANNED_MESSAGE(
@@ -104,31 +114,31 @@ enum class ModuleRoute(val title: StringResource, val route: Route, val icon: Im
REMOTE_HARDWARE(
Res.string.remote_hardware,
SettingsRoutes.RemoteHardware,
- Icons.Default.SettingsRemote,
+ Icons.Rounded.SettingsRemote,
AdminProtos.AdminMessage.ModuleConfigType.REMOTEHARDWARE_CONFIG_VALUE,
),
NEIGHBOR_INFO(
Res.string.neighbor_info,
SettingsRoutes.NeighborInfo,
- Icons.Default.People,
+ Icons.Rounded.People,
AdminProtos.AdminMessage.ModuleConfigType.NEIGHBORINFO_CONFIG_VALUE,
),
AMBIENT_LIGHTING(
Res.string.ambient_lighting,
SettingsRoutes.AmbientLighting,
- Icons.Default.LightMode,
+ Icons.Rounded.LightMode,
AdminProtos.AdminMessage.ModuleConfigType.AMBIENTLIGHTING_CONFIG_VALUE,
),
DETECTION_SENSOR(
Res.string.detection_sensor,
SettingsRoutes.DetectionSensor,
- Icons.Default.Sensors,
+ Icons.Rounded.Sensors,
AdminProtos.AdminMessage.ModuleConfigType.DETECTIONSENSOR_CONFIG_VALUE,
),
PAXCOUNTER(
Res.string.paxcounter,
SettingsRoutes.Paxcounter,
- Icons.Default.PermScanWifi,
+ Icons.Rounded.PermScanWifi,
AdminProtos.AdminMessage.ModuleConfigType.PAXCOUNTER_CONFIG_VALUE,
),
STATUS_MESSAGE(
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfig.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfig.kt
index 3ead7a6a8..679abc664 100644
--- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfig.kt
+++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfig.kt
@@ -26,11 +26,13 @@ import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.Upload
import androidx.compose.material.icons.rounded.BugReport
import androidx.compose.material.icons.rounded.CleaningServices
+import androidx.compose.material.icons.rounded.Download
import androidx.compose.material.icons.rounded.PowerSettingsNew
import androidx.compose.material.icons.rounded.RestartAlt
import androidx.compose.material.icons.rounded.Restore
import androidx.compose.material.icons.rounded.Storage
import androidx.compose.material.icons.rounded.SystemUpdate
+import androidx.compose.material.icons.rounded.Upload
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
@@ -143,13 +145,13 @@ fun RadioConfigItemList(
ListItem(
text = stringResource(Res.string.import_configuration),
- leadingIcon = Icons.Default.Download,
+ leadingIcon = Icons.Rounded.Download,
enabled = enabled,
onClick = onImport,
)
ListItem(
text = stringResource(Res.string.export_configuration),
- leadingIcon = Icons.Default.Upload,
+ leadingIcon = Icons.Rounded.Upload,
enabled = enabled,
onClick = onExport,
)
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ExternalNotificationConfigItemList.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ExternalNotificationConfigItemList.kt
index 39b736aa0..a1ac41314 100644
--- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ExternalNotificationConfigItemList.kt
+++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ExternalNotificationConfigItemList.kt
@@ -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 .
*/
-
package org.meshtastic.feature.settings.radio.component
import android.media.MediaPlayer
@@ -25,8 +24,8 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.FolderOpen
-import androidx.compose.material.icons.filled.PlayArrow
+import androidx.compose.material.icons.rounded.FolderOpen
+import androidx.compose.material.icons.rounded.PlayArrow
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
@@ -289,7 +288,7 @@ fun ExternalNotificationConfigScreen(
Row {
IconButton(onClick = { launcher.launch("*/*") }, enabled = state.connected) {
Icon(
- Icons.Default.FolderOpen,
+ Icons.Rounded.FolderOpen,
contentDescription = stringResource(Res.string.import_label),
)
}
@@ -313,7 +312,7 @@ fun ExternalNotificationConfigScreen(
},
enabled = state.connected,
) {
- Icon(Icons.Default.PlayArrow, contentDescription = stringResource(Res.string.play))
+ Icon(Icons.Rounded.PlayArrow, contentDescription = stringResource(Res.string.play))
}
}
},