mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
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:
parent
96551761c8
commit
10df4d47f1
23 changed files with 438 additions and 231 deletions
|
|
@ -38,6 +38,7 @@ fun DistanceInfo(
|
|||
modifier = modifier,
|
||||
icon = MeshtasticIcons.Distance,
|
||||
contentDescription = stringResource(Res.string.distance),
|
||||
label = stringResource(Res.string.distance),
|
||||
text = distance,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ fun HopsInfo(hops: Int, modifier: Modifier = Modifier, contentColor: Color = Mat
|
|||
modifier = modifier,
|
||||
icon = MeshtasticIcons.Hops,
|
||||
contentDescription = stringResource(Res.string.hops_away),
|
||||
label = stringResource(Res.string.hops_away),
|
||||
text = hops.toString(),
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -28,18 +28,22 @@ 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.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import org.meshtastic.core.ui.icon.Elevation
|
||||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
|
||||
private const val SIZE_ICON = 20
|
||||
private const val SIZE_ICON = 14
|
||||
|
||||
@Composable
|
||||
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,
|
||||
|
|
@ -54,9 +58,31 @@ fun IconInfo(
|
|||
modifier = Modifier.size(SIZE_ICON.dp),
|
||||
imageVector = icon,
|
||||
contentDescription = contentDescription,
|
||||
tint = contentColor,
|
||||
tint = contentColor.copy(alpha = 0.65f),
|
||||
)
|
||||
text?.let { Text(text = it, style = style, color = contentColor) }
|
||||
if (label != null || text != null) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
label?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.labelSmall.copy(fontSize = 10.sp, letterSpacing = 0.sp),
|
||||
color = contentColor.copy(alpha = 0.55f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Clip,
|
||||
softWrap = false,
|
||||
)
|
||||
}
|
||||
text?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = style.copy(fontWeight = FontWeight.SemiBold, fontSize = 12.sp),
|
||||
color = contentColor.copy(alpha = 0.95f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +91,6 @@ fun IconInfo(
|
|||
@Preview
|
||||
private fun IconInfoPreview() {
|
||||
MaterialTheme {
|
||||
IconInfo(icon = MeshtasticIcons.Elevation, contentDescription = "Elevation", content = { Text(text = "100") })
|
||||
IconInfo(icon = MeshtasticIcons.Elevation, contentDescription = "Elevation", label = "Elevation", text = "100m")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,12 +34,14 @@ import org.meshtastic.core.ui.util.formatAgo
|
|||
fun LastHeardInfo(
|
||||
modifier: Modifier = Modifier,
|
||||
lastHeard: Int,
|
||||
showLabel: Boolean = true,
|
||||
contentColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
) {
|
||||
IconInfo(
|
||||
modifier = modifier,
|
||||
icon = ImageVector.vectorResource(id = R.drawable.ic_antenna_24),
|
||||
contentDescription = stringResource(Res.string.node_sort_last_heard),
|
||||
label = if (showLabel) stringResource(Res.string.node_sort_last_heard) else null,
|
||||
text = formatAgo(lastHeard),
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ fun NodeSignalQuality(snr: Float, rssi: Int, modifier: Modifier = Modifier) {
|
|||
maxLines = 1,
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
modifier = Modifier.size(SIZE_ICON_DP.dp),
|
||||
imageVector = quality.imageVector,
|
||||
contentDescription = stringResource(Res.string.signal_quality),
|
||||
tint = quality.color.invoke(),
|
||||
|
|
@ -106,6 +106,8 @@ fun NodeSignalQuality(snr: Float, rssi: Int, modifier: Modifier = Modifier) {
|
|||
}
|
||||
}
|
||||
|
||||
private const val SIZE_ICON_DP = 16
|
||||
|
||||
/** Displays the `snr` and `rssi` with color depending on the values respectively. */
|
||||
@Composable
|
||||
fun SnrAndRssi(snr: Float, rssi: Int) {
|
||||
|
|
@ -125,7 +127,7 @@ fun LoraSignalIndicator(snr: Float, rssi: Int, contentColor: Color = MaterialThe
|
|||
modifier = Modifier.fillMaxSize().padding(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
modifier = Modifier.size(SIZE_ICON_DP.dp),
|
||||
imageVector = quality.imageVector,
|
||||
contentDescription = stringResource(Res.string.signal_quality),
|
||||
tint = quality.color.invoke(),
|
||||
|
|
@ -139,7 +141,7 @@ fun LoraSignalIndicator(snr: Float, rssi: Int, contentColor: Color = MaterialThe
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun Snr(snr: Float) {
|
||||
fun Snr(snr: Float, modifier: Modifier = Modifier) {
|
||||
val color: Color =
|
||||
if (snr > SNR_GOOD_THRESHOLD) {
|
||||
Quality.GOOD.color.invoke()
|
||||
|
|
@ -150,6 +152,7 @@ fun Snr(snr: Float) {
|
|||
}
|
||||
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = "%s %.2fdB".format(stringResource(Res.string.snr), snr),
|
||||
color = color,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
|
|
@ -157,7 +160,7 @@ fun Snr(snr: Float) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun Rssi(rssi: Int) {
|
||||
fun Rssi(rssi: Int, modifier: Modifier = Modifier) {
|
||||
val color: Color =
|
||||
if (rssi > RSSI_GOOD_THRESHOLD) {
|
||||
Quality.GOOD.color.invoke()
|
||||
|
|
@ -167,6 +170,7 @@ fun Rssi(rssi: Int) {
|
|||
Quality.BAD.color.invoke()
|
||||
}
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = "%s %ddBm".format(stringResource(Res.string.rssi), rssi),
|
||||
color = color,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.core.ui.component
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -33,10 +32,12 @@ import androidx.compose.ui.draw.rotate
|
|||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.unknown
|
||||
|
|
@ -49,7 +50,7 @@ import org.meshtastic.core.ui.theme.StatusColors.StatusOrange
|
|||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
|
||||
private const val FORMAT = "%d%%"
|
||||
private const val SIZE_ICON = 20
|
||||
private const val SIZE_ICON = 16
|
||||
|
||||
@Suppress("MagicNumber", "LongMethod")
|
||||
@Composable
|
||||
|
|
@ -64,24 +65,28 @@ fun MaterialBatteryInfo(
|
|||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(1.dp),
|
||||
) {
|
||||
if (level == null || level < 0) {
|
||||
Icon(
|
||||
modifier = Modifier.size(SIZE_ICON.dp),
|
||||
imageVector = MeshtasticIcons.BatteryUnknown,
|
||||
tint = contentColor,
|
||||
tint = contentColor.copy(alpha = 0.65f),
|
||||
contentDescription = stringResource(Res.string.unknown),
|
||||
)
|
||||
} else if (level > 100) {
|
||||
Icon(
|
||||
modifier = Modifier.size(SIZE_ICON.dp).rotate(90f),
|
||||
imageVector = Icons.Rounded.Power,
|
||||
tint = contentColor,
|
||||
tint = contentColor.copy(alpha = 0.65f),
|
||||
contentDescription = levelString,
|
||||
)
|
||||
|
||||
Text(text = "PWD", color = contentColor, style = MaterialTheme.typography.labelMedium)
|
||||
Text(
|
||||
text = "PWR",
|
||||
color = contentColor.copy(alpha = 0.95f),
|
||||
style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.SemiBold, fontSize = 12.sp),
|
||||
)
|
||||
} else {
|
||||
// Map battery percentage to color
|
||||
val fillColor =
|
||||
|
|
@ -111,16 +116,24 @@ fun MaterialBatteryInfo(
|
|||
)
|
||||
},
|
||||
imageVector = MeshtasticIcons.BatteryEmpty,
|
||||
tint = contentColor,
|
||||
tint = contentColor.copy(alpha = 0.65f),
|
||||
contentDescription = levelString,
|
||||
)
|
||||
|
||||
Text(text = levelString, color = contentColor, style = MaterialTheme.typography.labelMedium)
|
||||
Text(
|
||||
text = levelString,
|
||||
color = contentColor.copy(alpha = 0.95f),
|
||||
style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.SemiBold, fontSize = 12.sp),
|
||||
)
|
||||
}
|
||||
voltage
|
||||
?.takeIf { it > 0 }
|
||||
?.let {
|
||||
Text(text = "%.2fV".format(it), color = contentColor, style = MaterialTheme.typography.labelMedium)
|
||||
Text(
|
||||
text = "%.2fV".format(it),
|
||||
color = contentColor.copy(alpha = 0.8f),
|
||||
style = MaterialTheme.typography.labelMedium.copy(fontSize = 12.sp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ fun NodeChip(modifier: Modifier = Modifier, node: Node, onClick: ((Node) -> Unit
|
|||
Box(
|
||||
modifier =
|
||||
Modifier.width(IntrinsicSize.Min)
|
||||
.defaultMinSize(minWidth = 72.dp, minHeight = 32.dp)
|
||||
.defaultMinSize(minWidth = 64.dp, minHeight = 28.dp)
|
||||
.padding(horizontal = 8.dp)
|
||||
.semantics { contentDescription = node.user.short_name.ifEmpty { "Node" } },
|
||||
contentAlignment = Alignment.Center,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ fun SatelliteCountInfo(
|
|||
modifier = modifier,
|
||||
icon = MeshtasticIcons.Satellites,
|
||||
contentDescription = stringResource(Res.string.sats),
|
||||
label = stringResource(Res.string.sats),
|
||||
text = "$satCount",
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,83 +18,62 @@ package org.meshtastic.core.ui.component
|
|||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
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.signal
|
||||
import org.meshtastic.core.strings.signal_quality
|
||||
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
|
||||
import org.meshtastic.core.ui.icon.AirUtilization
|
||||
import org.meshtastic.core.ui.icon.ChannelUtilization
|
||||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
const val MAX_VALID_SNR = 100F
|
||||
const val MAX_VALID_RSSI = 0
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun SignalInfo(
|
||||
modifier: Modifier = Modifier,
|
||||
node: Node,
|
||||
isThisNode: Boolean,
|
||||
contentColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
@Suppress("UNUSED_PARAMETER") contentColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (isThisNode) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
IconInfo(
|
||||
icon = MeshtasticIcons.ChannelUtilization,
|
||||
contentDescription = stringResource(Res.string.channel_utilization),
|
||||
text = "%.1f%%".format(node.deviceMetrics.channel_utilization),
|
||||
contentColor = contentColor,
|
||||
)
|
||||
IconInfo(
|
||||
icon = MeshtasticIcons.AirUtilization,
|
||||
contentDescription = stringResource(Res.string.air_utilization),
|
||||
text = "%.1f%%".format(node.deviceMetrics.air_util_tx),
|
||||
contentColor = contentColor,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
if (node.channel > 0) {
|
||||
ChannelInfo(channel = node.channel, contentColor = contentColor)
|
||||
}
|
||||
if (node.hopsAway > 0) {
|
||||
HopsInfo(hops = node.hopsAway, contentColor = contentColor)
|
||||
} else {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (node.snr < MAX_VALID_SNR && node.rssi < MAX_VALID_RSSI) {
|
||||
val quality = determineSignalQuality(node.snr, node.rssi)
|
||||
Snr(node.snr)
|
||||
Rssi(node.rssi)
|
||||
IconInfo(
|
||||
icon = quality.imageVector,
|
||||
contentDescription = stringResource(Res.string.signal_quality),
|
||||
contentColor = quality.color.invoke(),
|
||||
text = "${stringResource(Res.string.signal)} ${stringResource(quality.nameRes)}",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.snr < MAX_VALID_SNR && node.rssi < MAX_VALID_RSSI) {
|
||||
val quality = determineSignalQuality(node.snr, node.rssi)
|
||||
val signalColor = quality.color.invoke()
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = quality.imageVector,
|
||||
contentDescription = stringResource(Res.string.signal_quality),
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = signalColor,
|
||||
)
|
||||
Text(
|
||||
text = "%.1fdB · %ddBm · %s".format(node.snr, node.rssi, stringResource(quality.nameRes)),
|
||||
style =
|
||||
MaterialTheme.typography.labelSmall.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 10.sp,
|
||||
letterSpacing = 0.sp,
|
||||
),
|
||||
color = signalColor,
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -102,22 +81,11 @@ fun SignalInfo(
|
|||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
fun SignalInfoSimplePreview() {
|
||||
AppTheme {
|
||||
SignalInfo(
|
||||
node = Node(num = 1, lastHeard = 0, channel = 0, snr = 12.5F, rssi = -42, hopsAway = 0),
|
||||
isThisNode = false,
|
||||
)
|
||||
}
|
||||
AppTheme { SignalInfo(node = Node(num = 1, lastHeard = 0, channel = 0, snr = 12.5F, rssi = -42, hopsAway = 0)) }
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
fun SignalInfoPreview(@PreviewParameter(NodePreviewParameterProvider::class) node: Node) {
|
||||
AppTheme { SignalInfo(node = node, isThisNode = false) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
@PreviewLightDark
|
||||
fun SignalInfoSelfPreview(@PreviewParameter(NodePreviewParameterProvider::class) node: Node) {
|
||||
AppTheme { SignalInfo(node = node, isThisNode = true) }
|
||||
AppTheme { SignalInfo(node = node) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,13 +34,23 @@ import androidx.compose.ui.graphics.ColorFilter
|
|||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.baro_pressure
|
||||
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
|
||||
import org.meshtastic.core.strings.uptime
|
||||
import org.meshtastic.core.ui.icon.AirQuality
|
||||
import org.meshtastic.core.ui.icon.ArrowCircleUp
|
||||
|
|
@ -55,7 +65,7 @@ import org.meshtastic.core.ui.icon.Role
|
|||
import org.meshtastic.core.ui.icon.Soil
|
||||
import org.meshtastic.core.ui.icon.Temperature
|
||||
|
||||
private const val SIZE_ICON = 20
|
||||
private const val SIZE_ICON = 14
|
||||
|
||||
@Composable
|
||||
fun TemperatureInfo(
|
||||
|
|
@ -67,6 +77,7 @@ fun TemperatureInfo(
|
|||
modifier = modifier,
|
||||
icon = MeshtasticIcons.Temperature,
|
||||
contentDescription = stringResource(Res.string.env_metrics_log),
|
||||
label = stringResource(Res.string.temperature),
|
||||
text = temp,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
|
@ -82,6 +93,7 @@ fun HumidityInfo(
|
|||
modifier = modifier,
|
||||
icon = MeshtasticIcons.Humidity,
|
||||
contentDescription = stringResource(Res.string.env_metrics_log),
|
||||
label = stringResource(Res.string.humidity),
|
||||
text = humidity,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
|
@ -97,6 +109,7 @@ fun PressureInfo(
|
|||
modifier = modifier,
|
||||
icon = MeshtasticIcons.Pressure,
|
||||
contentDescription = stringResource(Res.string.env_metrics_log),
|
||||
label = stringResource(Res.string.baro_pressure),
|
||||
text = pressure,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
|
@ -113,6 +126,7 @@ fun SoilTemperatureInfo(
|
|||
icon = MeshtasticIcons.Soil,
|
||||
overlayIcon = MeshtasticIcons.Temperature,
|
||||
contentDescription = stringResource(Res.string.env_metrics_log),
|
||||
label = stringResource(Res.string.soil_temperature),
|
||||
text = temp,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
|
@ -129,6 +143,7 @@ fun SoilMoistureInfo(
|
|||
icon = MeshtasticIcons.Soil,
|
||||
overlayIcon = MeshtasticIcons.Humidity,
|
||||
contentDescription = stringResource(Res.string.env_metrics_log),
|
||||
label = stringResource(Res.string.soil_moisture),
|
||||
text = moisture,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
|
@ -144,6 +159,7 @@ fun PaxcountInfo(
|
|||
modifier = modifier,
|
||||
icon = MeshtasticIcons.Paxcount,
|
||||
contentDescription = stringResource(Res.string.pax_metrics_log),
|
||||
label = stringResource(Res.string.pax),
|
||||
text = pax,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
|
@ -159,17 +175,24 @@ fun AirQualityInfo(
|
|||
modifier = modifier,
|
||||
icon = MeshtasticIcons.AirQuality,
|
||||
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 = MeshtasticIcons.Power,
|
||||
contentDescription = stringResource(Res.string.env_metrics_log),
|
||||
label = label,
|
||||
text = value,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
|
@ -185,6 +208,7 @@ fun UptimeInfo(
|
|||
modifier = modifier,
|
||||
icon = MeshtasticIcons.ArrowCircleUp,
|
||||
contentDescription = stringResource(Res.string.uptime),
|
||||
label = stringResource(Res.string.uptime),
|
||||
text = uptime,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
|
@ -237,6 +261,7 @@ fun OverlayIconInfo(
|
|||
overlayIcon: ImageVector,
|
||||
contentDescription: String,
|
||||
modifier: Modifier = Modifier,
|
||||
label: String? = null,
|
||||
text: String? = null,
|
||||
style: TextStyle = MaterialTheme.typography.labelMedium,
|
||||
contentColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
|
|
@ -250,7 +275,7 @@ fun OverlayIconInfo(
|
|||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = contentDescription,
|
||||
tint = contentColor,
|
||||
tint = contentColor.copy(alpha = 0.65f),
|
||||
modifier =
|
||||
Modifier.size(SIZE_ICON.dp).drawWithContent {
|
||||
drawContent()
|
||||
|
|
@ -260,6 +285,24 @@ fun OverlayIconInfo(
|
|||
}
|
||||
},
|
||||
)
|
||||
text?.let { Text(text = it, style = style, color = contentColor) }
|
||||
label?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.labelSmall.copy(fontSize = 10.sp, letterSpacing = 0.sp),
|
||||
color = contentColor.copy(alpha = 0.55f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Clip,
|
||||
softWrap = false,
|
||||
)
|
||||
}
|
||||
text?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = style.copy(fontWeight = FontWeight.SemiBold, fontSize = 12.sp),
|
||||
color = contentColor.copy(alpha = 0.95f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.compose.material.icons.rounded.Air
|
|||
import androidx.compose.material.icons.rounded.DataArray
|
||||
import androidx.compose.material.icons.rounded.ElectricBolt
|
||||
import androidx.compose.material.icons.rounded.Grass
|
||||
import androidx.compose.material.icons.rounded.LineAxis
|
||||
import androidx.compose.material.icons.rounded.People
|
||||
import androidx.compose.material.icons.rounded.SocialDistance
|
||||
import androidx.compose.material.icons.rounded.Speed
|
||||
|
|
@ -54,3 +55,6 @@ val MeshtasticIcons.Speed: ImageVector
|
|||
get() = Icons.Rounded.Speed
|
||||
val MeshtasticIcons.Chart: ImageVector
|
||||
get() = Icons.Rounded.StackedLineChart
|
||||
|
||||
val MeshtasticIcons.LineAxis: ImageVector
|
||||
get() = Icons.Rounded.LineAxis
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import android.text.format.DateUtils
|
|||
import com.meshtastic.core.strings.getString
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.now
|
||||
import org.meshtastic.core.strings.unknown
|
||||
import java.lang.System.currentTimeMillis
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
|
@ -36,6 +37,8 @@ import kotlin.time.Duration.Companion.seconds
|
|||
* @return A [String] representing the relative time that has passed.
|
||||
*/
|
||||
fun formatAgo(lastSeenUnixSeconds: Int): String {
|
||||
if (lastSeenUnixSeconds <= 0) return getString(Res.string.unknown)
|
||||
|
||||
val lastSeenDuration = lastSeenUnixSeconds.seconds
|
||||
val currentDuration = currentTimeMillis().milliseconds
|
||||
val diff = (currentDuration - lastSeenDuration).absoluteValue
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue