From a28aa4d52ecaf6141cfd0721f7a93e5ab5955b96 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Sun, 25 Jan 2026 16:43:23 -0600 Subject: [PATCH] refactor(ui): Icon audit and node list item refactor (#4313) Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com> --- .../mesh/navigation/NodesNavigation.kt | 35 +- .../main/java/com/geeksville/mesh/ui/Main.kt | 5 +- .../geeksville/mesh/ui/contact/Contacts.kt | 29 +- .../com/geeksville/mesh/ui/sharing/Channel.kt | 7 +- .../composeResources/values/strings.xml | 3 +- .../core/ui/component/ChannelInfo.kt | 74 +++++ .../core/ui/component/DistanceInfo.kt | 50 +++ .../core/ui/component/ElevationInfo.kt | 55 ++++ .../meshtastic/core/ui/component/HopsInfo.kt | 46 +++ .../meshtastic/core/ui/component/IconInfo.kt | 70 ++++ .../core/ui/component/IndoorAirQuality.kt | 11 +- .../core/ui/component/LastHeardInfo.kt | 52 +++ .../core/ui/component/LoraSignalIndicator.kt | 19 +- .../core/ui/component/NodeKeyStatusIcon.kt | 27 +- .../core/ui/component/SatelliteCountInfo.kt | 50 +++ .../core/ui/component/SecurityIcon.kt | 21 +- .../core/ui/component/SignalInfo.kt | 104 +++--- .../core/ui/component/TelemetryInfo.kt | 265 ++++++++++++++++ .../preview/NodePreviewParameterProvider.kt | 6 +- .../org/meshtastic/core/ui/icon/Actions.kt | 81 +++++ .../org/meshtastic/core/ui/icon/Counter.kt | 50 +++ .../org/meshtastic/core/ui/icon/Device.kt | 14 +- .../org/meshtastic/core/ui/icon/Hardware.kt | 30 ++ .../org/meshtastic/core/ui/icon/Person.kt | 39 +++ .../org/meshtastic/core/ui/icon/Security.kt | 39 +++ .../org/meshtastic/core/ui/icon/Signal.kt | 20 +- .../org/meshtastic/core/ui/icon/Status.kt | 82 +++++ .../org/meshtastic/core/ui/icon/Telemetry.kt | 56 ++++ .../src/main/res/drawable/counter_0_24px.xml | 5 + .../src/main/res/drawable/counter_1_24px.xml | 5 + .../src/main/res/drawable/counter_2_24px.xml | 5 + .../src/main/res/drawable/counter_3_24px.xml | 5 + .../src/main/res/drawable/counter_4_24px.xml | 22 ++ .../src/main/res/drawable/counter_5_24px.xml | 5 + .../src/main/res/drawable/counter_6_24px.xml | 5 + .../src/main/res/drawable/counter_7_24px.xml | 5 + .../src/main/res/drawable/counter_8_24px.xml | 5 + .../feature/firmware/FirmwareUpdateScreen.kt | 47 +-- .../org/meshtastic/feature/map/MapView.kt | 22 +- .../feature/map/component/DownloadButton.kt | 7 +- .../map/component/EditWaypointDialog.kt | 10 +- .../org/meshtastic/feature/map/MapView.kt | 4 +- .../map/component/EditWaypointDialog.kt | 8 +- .../map/component/MapControlsOverlay.kt | 7 +- .../meshtastic/feature/messaging/Message.kt | 36 +-- .../meshtastic/feature/messaging/QuickChat.kt | 23 +- .../messaging/component/MessageActions.kt | 4 +- .../component/MessageActionsBottomSheet.kt | 20 +- .../messaging/component/MessageItem.kt | 75 +++-- .../feature/messaging/component/Reaction.kt | 26 +- feature/node/component/DeviceActions.kt | 2 +- .../node/component/AdministrationSection.kt | 22 +- .../feature/node/component/ChannelInfo.kt | 47 +++ .../node/component/CompassBottomSheet.kt | 15 +- .../component/CooldownOutlinedIconButton.kt | 6 +- .../feature/node/component/DeviceActions.kt | 6 +- .../node/component/DeviceDetailsSection.kt | 4 +- .../node/component/EnvironmentMetrics.kt | 42 +-- .../component/FirmwareReleaseSheetContent.kt | 11 +- .../feature/node/component/HopsInfo.kt | 46 +++ .../feature/node/component/IconInfo.kt | 7 +- .../node/component/LinkedCoordinatesItem.kt | 4 +- .../node/component/NodeDetailsSection.kt | 65 ++-- .../node/component/NodeFilterTextField.kt | 11 +- .../feature/node/component/NodeItem.kt | 299 +++++++++++------- .../feature/node/component/NodeStatusIcons.kt | 54 ++-- .../feature/node/component/NotesSection.kt | 7 +- .../feature/node/component/PositionSection.kt | 12 +- .../feature/node/component/PowerMetrics.kt | 16 +- .../component/TelemetricActionsSection.kt | 29 +- .../feature/node/component/TelemetryInfo.kt | 179 +++++++++++ .../feature/node/metrics/CommonCharts.kt | 7 +- .../feature/node/metrics/DeviceMetrics.kt | 10 +- .../node/metrics/EnvironmentMetrics.kt | 27 +- .../feature/node/metrics/HostMetricsLog.kt | 13 +- .../feature/node/metrics/PaxMetrics.kt | 24 +- .../feature/node/metrics/PositionLog.kt | 14 +- .../feature/node/metrics/PowerMetrics.kt | 4 +- .../feature/node/metrics/SignalMetrics.kt | 6 +- .../feature/node/metrics/TracerouteLog.kt | 26 +- .../node/metrics/TracerouteMapScreen.kt | 9 +- .../meshtastic/feature/node/model/LogsType.kt | 40 +-- .../feature/settings/SettingsScreen.kt | 3 +- .../feature/settings/debugging/Debug.kt | 6 +- .../settings/debugging/DebugFilters.kt | 7 +- .../feature/settings/debugging/DebugSearch.kt | 15 +- .../settings/filter/FilterSettingsScreen.kt | 8 +- .../settings/navigation/ConfigRoute.kt | 39 ++- .../settings/navigation/ModuleRoute.kt | 30 +- .../feature/settings/radio/RadioConfig.kt | 6 +- .../ExternalNotificationConfigItemList.kt | 11 +- 91 files changed, 2178 insertions(+), 702 deletions(-) create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ChannelInfo.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/component/DistanceInfo.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ElevationInfo.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/component/HopsInfo.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/component/IconInfo.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/component/LastHeardInfo.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/component/SatelliteCountInfo.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/component/TelemetryInfo.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Actions.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Counter.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Hardware.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Person.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Security.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Status.kt create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/icon/Telemetry.kt create mode 100644 core/ui/src/main/res/drawable/counter_0_24px.xml create mode 100644 core/ui/src/main/res/drawable/counter_1_24px.xml create mode 100644 core/ui/src/main/res/drawable/counter_2_24px.xml create mode 100644 core/ui/src/main/res/drawable/counter_3_24px.xml create mode 100644 core/ui/src/main/res/drawable/counter_4_24px.xml create mode 100644 core/ui/src/main/res/drawable/counter_5_24px.xml create mode 100644 core/ui/src/main/res/drawable/counter_6_24px.xml create mode 100644 core/ui/src/main/res/drawable/counter_7_24px.xml create mode 100644 core/ui/src/main/res/drawable/counter_8_24px.xml create mode 100644 feature/node/src/main/kotlin/org/meshtastic/feature/node/component/ChannelInfo.kt create mode 100644 feature/node/src/main/kotlin/org/meshtastic/feature/node/component/HopsInfo.kt create mode 100644 feature/node/src/main/kotlin/org/meshtastic/feature/node/component/TelemetryInfo.kt 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)) } } },