mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor(ui): Icon audit and node list item refactor (#4313)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
5db2c9d69c
commit
a28aa4d52e
91 changed files with 2178 additions and 702 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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) }
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.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) }
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.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<LegendData>, 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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, ImageVector> = 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") }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.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),
|
||||
|
|
|
|||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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) }),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue