feat(ui): Redesign NodeItem for improved clarity and density (#4475)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-02-05 23:01:42 -06:00 committed by GitHub
parent 96551761c8
commit 10df4d47f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 438 additions and 231 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.feature.node.component
import androidx.compose.material.icons.Icons
@ -39,6 +38,7 @@ fun DistanceInfo(
modifier = modifier,
icon = Icons.Rounded.SocialDistance,
contentDescription = stringResource(Res.string.distance),
label = stringResource(Res.string.distance),
text = distance,
contentColor = contentColor,
)

View file

@ -43,6 +43,7 @@ fun ElevationInfo(
modifier = modifier,
icon = MeshtasticIcons.Elevation,
contentDescription = stringResource(Res.string.altitude),
label = stringResource(Res.string.altitude),
text = altitude.metersIn(system).toString(system) + " " + suffix,
contentColor = contentColor,
)

View file

@ -34,6 +34,7 @@ fun HopsInfo(hops: Int, modifier: Modifier = Modifier, contentColor: Color = Mat
modifier = modifier,
icon = Icons.Rounded.CrueltyFree,
contentDescription = stringResource(Res.string.hops_away),
label = stringResource(Res.string.hops_away),
text = hops.toString(),
contentColor = contentColor,
)

View file

@ -40,6 +40,7 @@ fun IconInfo(
icon: ImageVector,
contentDescription: String,
modifier: Modifier = Modifier,
label: String? = null,
text: String? = null,
style: TextStyle = MaterialTheme.typography.labelMedium,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
@ -56,6 +57,7 @@ fun IconInfo(
contentDescription = contentDescription,
tint = contentColor,
)
label?.let { Text(text = it, style = style, color = contentColor) }
text?.let { Text(text = it, style = style, color = contentColor) }
content()
}

View file

@ -40,6 +40,7 @@ fun LastHeardInfo(
modifier = modifier,
icon = ImageVector.vectorResource(id = R.drawable.ic_antenna_24),
contentDescription = stringResource(Res.string.node_sort_last_heard),
label = stringResource(Res.string.node_sort_last_heard),
text = formatAgo(lastHeard),
contentColor = contentColor,
)

View file

@ -14,11 +14,14 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@file:Suppress("MagicNumber")
package org.meshtastic.feature.node.component
import android.content.res.Configuration
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
@ -40,6 +43,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
@ -50,13 +54,21 @@ 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.air_utilization
import org.meshtastic.core.strings.channel_utilization
import org.meshtastic.core.strings.current
import org.meshtastic.core.strings.elevation_suffix
import org.meshtastic.core.strings.signal_quality
import org.meshtastic.core.strings.unknown_username
import org.meshtastic.core.strings.voltage
import org.meshtastic.core.ui.component.AirQualityInfo
import org.meshtastic.core.ui.component.ChannelInfo
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.HopsInfo
import org.meshtastic.core.ui.component.HumidityInfo
import org.meshtastic.core.ui.component.IconInfo
import org.meshtastic.core.ui.component.LastHeardInfo
import org.meshtastic.core.ui.component.MaterialBatteryInfo
import org.meshtastic.core.ui.component.NodeChip
@ -66,21 +78,27 @@ 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.Rssi
import org.meshtastic.core.ui.component.SatelliteCountInfo
import org.meshtastic.core.ui.component.SignalInfo
import org.meshtastic.core.ui.component.Snr
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.determineSignalQuality
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
import org.meshtastic.core.ui.icon.AirUtilization
import org.meshtastic.core.ui.icon.ChannelUtilization
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.proto.Config
private const val ACTIVE_ALPHA = 0.5f
private const val INACTIVE_ALPHA = 0.2f
private const val GRID_COLUMNS = 3
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
@Suppress("LongMethod")
fun NodeItem(
thisNode: Node?,
thatNode: Node,
@ -97,15 +115,6 @@ fun NodeItem(
val isIgnored = thatNode.isIgnored
val originalLongName = (thatNode.user.long_name ?: "").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) {
@ -146,13 +155,13 @@ fun NodeItem(
Card(modifier = modifier.fillMaxWidth(), colors = cardColors) {
Column(
modifier =
Modifier.combinedClickable(onClick = onClick, onLongClick = onLongClick).fillMaxWidth().padding(8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
Modifier.combinedClickable(onClick = onClick, onLongClick = onLongClick).fillMaxWidth().padding(12.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
NodeItemHeader(
thatNode = thatNode,
isThisNode = isThisNode,
longName = longName,
longName = originalLongName,
style = style,
isIgnored = isIgnored,
isFavorite = isFavorite,
@ -162,17 +171,201 @@ fun NodeItem(
contentColor = contentColor,
)
NodeItemMetrics(thatNode = thatNode, distance = distance, system = system, contentColor = contentColor)
NodeBatteryPositionRow(
thatNode = thatNode,
distance = distance,
system = system,
contentColor = contentColor,
)
SignalInfo(node = thatNode, isThisNode = isThisNode, contentColor = contentColor)
NodeSignalRow(thatNode = thatNode, isThisNode = isThisNode, contentColor = contentColor)
NodeItemEnvironment(thatNode = thatNode, tempInFahrenheit = tempInFahrenheit, contentColor = contentColor)
val sensorItems = gatherSensors(thatNode, tempInFahrenheit, contentColor)
if (sensorItems.isNotEmpty()) {
MetricsGrid(sensorItems)
}
NodeItemFooter(thatNode = thatNode, contentColor = contentColor)
}
}
}
@Composable
private fun NodeBatteryPositionRow(
thatNode: Node,
distance: String?,
system: Config.DisplayConfig.DisplayUnits,
contentColor: Color,
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
MaterialBatteryInfo(
level = thatNode.batteryLevel ?: 0,
voltage = thatNode.voltage ?: 0f,
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 ?: 0,
system = system,
suffix = stringResource(Res.string.elevation_suffix),
contentColor = contentColor,
)
}
}
}
}
@Composable
private fun NodeSignalRow(thatNode: Node, isThisNode: Boolean, contentColor: Color) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
if (isThisNode) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
IconInfo(
icon = MeshtasticIcons.ChannelUtilization,
contentDescription = stringResource(Res.string.channel_utilization),
label = stringResource(Res.string.channel_utilization),
text = "%.1f%%".format(thatNode.deviceMetrics.channel_utilization),
contentColor = contentColor,
)
IconInfo(
icon = MeshtasticIcons.AirUtilization,
contentDescription = stringResource(Res.string.air_utilization),
label = stringResource(Res.string.air_utilization),
text = "%.1f%%".format(thatNode.deviceMetrics.air_util_tx),
contentColor = contentColor,
)
}
} else {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
if (thatNode.hopsAway > 0) {
HopsInfo(hops = thatNode.hopsAway, contentColor = contentColor)
} else {
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
if (thatNode.snr < 100f) Snr(thatNode.snr)
if (thatNode.rssi < 0) Rssi(thatNode.rssi)
if (thatNode.snr < 100f && thatNode.rssi < 0) {
val quality = determineSignalQuality(thatNode.snr, thatNode.rssi)
IconInfo(
icon = quality.imageVector,
contentDescription = stringResource(Res.string.signal_quality),
contentColor = quality.color.invoke(),
text = stringResource(quality.nameRes),
)
}
}
}
if (thatNode.channel > 0) {
ChannelInfo(channel = thatNode.channel, contentColor = contentColor)
}
}
}
val satCount = thatNode.validPosition?.sats_in_view ?: 0
if (satCount > 0) {
SatelliteCountInfo(satCount = satCount, contentColor = contentColor)
} else {
Spacer(Modifier)
}
}
}
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
private fun gatherSensors(node: Node, tempInFahrenheit: Boolean, contentColor: Color): List<@Composable () -> Unit> {
val items = mutableListOf<@Composable () -> Unit>()
val env = node.environmentMetrics
val pax = node.paxcounter
if ((pax.ble ?: 0) != 0 || (pax.wifi ?: 0) != 0) {
items.add { PaxcountInfo(pax = "B:${pax.ble ?: 0} W:${pax.wifi ?: 0}", contentColor = contentColor) }
}
if ((env.temperature ?: 0f) != 0f) {
val temp =
if (tempInFahrenheit) {
"%.1f°F".format(celsiusToFahrenheit(env.temperature ?: 0f))
} else {
"%.1f°C".format(env.temperature ?: 0f)
}
items.add { TemperatureInfo(temp = temp, contentColor = contentColor) }
}
if ((env.relative_humidity ?: 0f) != 0f) {
items.add { HumidityInfo(humidity = "%.0f%%".format(env.relative_humidity ?: 0f), contentColor = contentColor) }
}
if ((env.barometric_pressure ?: 0f) != 0f) {
items.add {
PressureInfo(pressure = "%.1fhPa".format(env.barometric_pressure ?: 0f), contentColor = contentColor)
}
}
if ((env.soil_temperature ?: 0f) != 0f) {
val temp =
if (tempInFahrenheit) {
"%.1f°F".format(celsiusToFahrenheit(env.soil_temperature ?: 0f))
} else {
"%.1f°C".format(env.soil_temperature ?: 0f)
}
items.add { SoilTemperatureInfo(temp = temp, contentColor = contentColor) }
}
if ((env.soil_moisture ?: 0) != 0 && (env.soil_temperature ?: 0f) != 0f) {
items.add { SoilMoistureInfo(moisture = "${env.soil_moisture}%", contentColor = contentColor) }
}
if ((env.voltage ?: 0f) != 0f) {
items.add {
PowerInfo(
value = "%.2fV".format(env.voltage ?: 0f),
label = stringResource(Res.string.voltage),
contentColor = contentColor,
)
}
}
if ((env.current ?: 0f) != 0f) {
items.add {
PowerInfo(
value = "%.1fmA".format(env.current ?: 0f),
label = stringResource(Res.string.current),
contentColor = contentColor,
)
}
}
if ((env.iaq ?: 0) != 0) {
items.add { AirQualityInfo(iaq = "${env.iaq}", contentColor = contentColor) }
}
return items
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun MetricsGrid(items: List<@Composable () -> Unit>) {
FlowRow(
modifier = Modifier.fillMaxWidth(),
maxItemsInEachRow = GRID_COLUMNS,
horizontalArrangement = Arrangement.SpaceBetween,
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
val remainder = items.size % GRID_COLUMNS
items.forEach { item -> Box(Modifier.weight(1f)) { item() } }
if (remainder != 0) {
repeat(GRID_COLUMNS - remainder) { Spacer(Modifier.weight(1f)) }
}
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun NodeItemHeader(
@ -187,23 +380,31 @@ private fun NodeItemHeader(
connectionState: ConnectionState,
contentColor: Color,
) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
NodeChip(node = thatNode)
NodeKeyStatusIcon(
hasPKC = thatNode.hasPKC,
mismatchKey = thatNode.mismatchKey,
publicKey = thatNode.user.public_key,
modifier = Modifier.size(32.dp),
modifier = Modifier.size(24.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)
Column(modifier = Modifier.weight(1f)) {
Text(
text = longName,
style = MaterialTheme.typography.titleMediumEmphasized.copy(fontStyle = style),
textDecoration = TextDecoration.LineThrough.takeIf { isIgnored },
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
LastHeardInfo(lastHeard = thatNode.lastHeard, showLabel = false, contentColor = contentColor)
}
NodeStatusIcons(
isThisNode = isThisNode,
isFavorite = isFavorite,
@ -215,102 +416,6 @@ private fun NodeItemHeader(
}
}
@Composable
private fun NodeItemMetrics(
thatNode: Node,
distance: String?,
system: Config.DisplayConfig.DisplayUnits,
contentColor: Color,
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
if ((thatNode.batteryLevel ?: 0) > 0 || (thatNode.voltage ?: 0f) > 0f) {
MaterialBatteryInfo(
level = thatNode.batteryLevel ?: 0,
voltage = thatNode.voltage ?: 0f,
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 ?: 0,
system = system,
suffix = stringResource(Res.string.elevation_suffix),
contentColor = contentColor,
)
}
val satCount = thatNode.validPosition?.sats_in_view ?: 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) != 0 || (pax.wifi ?: 0) != 0) {
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
if ((pax.ble ?: 0) != 0 || (pax.wifi ?: 0) != 0) {
PaxcountInfo(pax = "B:${pax.ble ?: 0} W:${pax.wifi ?: 0}", contentColor = contentColor)
}
if ((env.temperature ?: 0f) != 0f) {
val temp =
if (tempInFahrenheit) {
"%.1f°F".format(celsiusToFahrenheit(env.temperature ?: 0f))
} else {
"%.1f°C".format(env.temperature ?: 0f)
}
TemperatureInfo(temp = temp, contentColor = contentColor)
}
if ((env.relative_humidity ?: 0f) != 0f) {
HumidityInfo(humidity = "%.0f%%".format(env.relative_humidity ?: 0f), contentColor = contentColor)
}
if ((env.barometric_pressure ?: 0f) != 0f) {
PressureInfo(pressure = "%.1fhPa".format(env.barometric_pressure ?: 0f), contentColor = contentColor)
}
if ((env.soil_temperature ?: 0f) != 0f) {
val temp =
if (tempInFahrenheit) {
"%.1f°F".format(celsiusToFahrenheit(env.soil_temperature ?: 0f))
} else {
"%.1f°C".format(env.soil_temperature ?: 0f)
}
SoilTemperatureInfo(temp = temp, contentColor = contentColor)
}
if ((env.soil_moisture ?: 0) != 0 && (env.soil_temperature ?: 0f) != 0f) {
SoilMoistureInfo(moisture = "${env.soil_moisture}%", contentColor = contentColor)
}
if ((env.voltage ?: 0f) != 0f) {
PowerInfo(value = "%.2fV".format(env.voltage ?: 0f), contentColor = contentColor)
}
if ((env.current ?: 0f) != 0f) {
PowerInfo(value = "%.1fmA".format(env.current ?: 0f), contentColor = contentColor)
}
if ((env.iaq ?: 0) != 0) {
AirQualityInfo(iaq = "${env.iaq}", contentColor = contentColor)
}
}
}
}
@Composable
private fun NodeItemFooter(thatNode: Node, contentColor: Color) {
Row(
@ -318,9 +423,9 @@ private fun NodeItemFooter(thatNode: Node, contentColor: Color) {
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
HardwareInfo(hwModel = thatNode.user.hw_model?.name ?: "", contentColor = contentColor)
RoleInfo(role = thatNode.user.role?.name ?: "", contentColor = contentColor)
NodeIdInfo(id = (thatNode.user.id ?: "").ifEmpty { "???" }, contentColor = contentColor)
HardwareInfo(hwModel = thatNode.user.hw_model.name, contentColor = contentColor)
RoleInfo(role = thatNode.user.role.name, contentColor = contentColor)
NodeIdInfo(id = thatNode.user.id.ifEmpty { "???" }, contentColor = contentColor)
}
}
@ -329,7 +434,17 @@ private fun NodeItemFooter(thatNode: Node, contentColor: Color) {
fun NodeInfoSimplePreview() {
AppTheme {
val thisNode = NodePreviewParameterProvider().values.first()
val thatNode = NodePreviewParameterProvider().values.last()
val thatNode = NodePreviewParameterProvider().values.last().copy(lastHeard = 0)
NodeItem(thisNode = thisNode, thatNode = thatNode, 0, true, connectionState = ConnectionState.Connected)
}
}
@Composable
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
fun NodeInfoSignalPreview() {
AppTheme {
val thisNode = NodePreviewParameterProvider().values.first()
val thatNode = NodePreviewParameterProvider().values.last().copy(hopsAway = 0, snr = 5.5f, rssi = -100)
NodeItem(thisNode = thisNode, thatNode = thatNode, 0, true, connectionState = ConnectionState.Connected)
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.feature.node.component
import androidx.compose.material.icons.Icons
@ -39,6 +38,7 @@ fun SatelliteCountInfo(
modifier = modifier,
icon = Icons.TwoTone.SatelliteAlt,
contentDescription = stringResource(Res.string.sats),
label = stringResource(Res.string.sats),
text = "$satCount",
contentColor = contentColor,
)

View file

@ -56,7 +56,7 @@ 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.LineAxis
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.Person
import org.meshtastic.core.ui.icon.Refresh
@ -232,7 +232,7 @@ private fun FeatureRow(node: Node, feature: TelemetricFeature, hasLogs: Boolean,
},
) {
Icon(
MeshtasticIcons.Chart,
MeshtasticIcons.LineAxis,
contentDescription = logsDescription,
modifier = Modifier.size(IconButtonDefaults.mediumIconSize),
tint = MaterialTheme.colorScheme.primary,

View file

@ -14,6 +14,8 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@file:Suppress("TooManyFunctions")
package org.meshtastic.feature.node.component
import androidx.compose.material.icons.Icons
@ -33,9 +35,15 @@ 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.humidity
import org.meshtastic.core.strings.iaq
import org.meshtastic.core.strings.node_id
import org.meshtastic.core.strings.pax
import org.meshtastic.core.strings.pax_metrics_log
import org.meshtastic.core.strings.role
import org.meshtastic.core.strings.soil_moisture
import org.meshtastic.core.strings.soil_temperature
import org.meshtastic.core.strings.temperature
@Composable
fun TemperatureInfo(
@ -47,6 +55,7 @@ fun TemperatureInfo(
modifier = modifier,
icon = Icons.Rounded.Thermostat,
contentDescription = stringResource(Res.string.env_metrics_log),
label = stringResource(Res.string.temperature),
text = temp,
contentColor = contentColor,
)
@ -62,6 +71,7 @@ fun HumidityInfo(
modifier = modifier,
icon = Icons.Rounded.WaterDrop,
contentDescription = stringResource(Res.string.env_metrics_log),
label = stringResource(Res.string.humidity),
text = humidity,
contentColor = contentColor,
)
@ -77,6 +87,7 @@ fun SoilTemperatureInfo(
modifier = modifier,
icon = Icons.Rounded.Grass,
contentDescription = stringResource(Res.string.env_metrics_log),
label = stringResource(Res.string.soil_temperature),
text = temp,
contentColor = contentColor,
)
@ -92,6 +103,7 @@ fun SoilMoistureInfo(
modifier = modifier,
icon = Icons.Rounded.Grass,
contentDescription = stringResource(Res.string.env_metrics_log),
label = stringResource(Res.string.soil_moisture),
text = moisture,
contentColor = contentColor,
)
@ -107,6 +119,7 @@ fun PaxcountInfo(
modifier = modifier,
icon = Icons.Rounded.People,
contentDescription = stringResource(Res.string.pax_metrics_log),
label = stringResource(Res.string.pax),
text = pax,
contentColor = contentColor,
)
@ -122,17 +135,24 @@ fun AirQualityInfo(
modifier = modifier,
icon = Icons.Rounded.Air,
contentDescription = stringResource(Res.string.env_metrics_log),
label = stringResource(Res.string.iaq),
text = iaq,
contentColor = contentColor,
)
}
@Composable
fun PowerInfo(value: String, modifier: Modifier = Modifier, contentColor: Color = MaterialTheme.colorScheme.onSurface) {
fun PowerInfo(
value: String,
modifier: Modifier = Modifier,
label: String? = null,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
) {
IconInfo(
modifier = modifier,
icon = Icons.Rounded.ElectricBolt,
contentDescription = stringResource(Res.string.env_metrics_log),
label = label,
text = value,
contentColor = contentColor,
)