chore(deps): bump deps to take advantage of new functionality (#4658)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-02-26 07:26:50 -06:00 committed by GitHub
parent 46b32f1cce
commit 145cde9393
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 230 additions and 237 deletions

View file

@ -67,13 +67,28 @@ import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.common.util.DateFormatter
import org.meshtastic.core.model.util.formatUptime
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.air_utilization
import org.meshtastic.core.resources.battery
import org.meshtastic.core.resources.channel_utilization
import org.meshtastic.core.resources.getStringSuspend
import org.meshtastic.core.resources.connecting
import org.meshtastic.core.resources.device_sleeping
import org.meshtastic.core.resources.disconnected
import org.meshtastic.core.resources.local_stats_bad
import org.meshtastic.core.resources.local_stats_diagnostics_prefix
import org.meshtastic.core.resources.local_stats_dropped
import org.meshtastic.core.resources.local_stats_heap
import org.meshtastic.core.resources.local_stats_heap_value
import org.meshtastic.core.resources.local_stats_noise
import org.meshtastic.core.resources.local_stats_relays
import org.meshtastic.core.resources.local_stats_traffic
import org.meshtastic.core.resources.local_stats_updated_at
import org.meshtastic.core.resources.meshtastic_app_name
import org.meshtastic.core.resources.nodes
import org.meshtastic.core.resources.powered
import org.meshtastic.core.resources.refresh
import org.meshtastic.core.resources.updated
import org.meshtastic.core.resources.uptime
@ -129,11 +144,11 @@ class LocalStatsWidget : GlanceAppWidget() {
titleBar = {
TitleBar(
startIcon = ImageProvider(com.geeksville.mesh.R.drawable.app_icon),
title = state.appName,
title = stringResource(Res.string.meshtastic_app_name),
actions = {
CircleIconButton(
imageProvider = ImageProvider(com.geeksville.mesh.R.drawable.ic_refresh),
contentDescription = state.refreshLabel,
contentDescription = stringResource(Res.string.refresh),
onClick = actionRunCallback<RefreshLocalStatsAction>(),
backgroundColor = null,
)
@ -154,6 +169,7 @@ class LocalStatsWidget : GlanceAppWidget() {
}
@Composable
@Suppress("LongMethod", "MagicNumber")
private fun FullStatsContent(state: LocalStatsWidgetUiState) {
val size = LocalSize.current
val isNarrow = size.width < 160.dp
@ -168,13 +184,20 @@ class LocalStatsWidget : GlanceAppWidget() {
state.nodeColors?.let { colors -> NodeChip(shortName = name, colors = colors) }
}
Spacer(GlanceModifier.width(8.dp))
StatRow(
label = state.batteryLabel,
value = state.batteryValue,
progress = state.batteryProgress,
isSmall = isSmall,
modifier = GlanceModifier.defaultWeight(),
)
if (state.hasBattery) {
val isPowered = state.batteryLevel > 100
val batteryValue =
if (isPowered) stringResource(Res.string.powered) else "${state.batteryLevel}%"
StatRow(
label = stringResource(Res.string.battery),
value = batteryValue,
progress = state.batteryProgress,
isSmall = isSmall,
modifier = GlanceModifier.defaultWeight(),
)
} else {
Spacer(GlanceModifier.defaultWeight())
}
}
Spacer(GlanceModifier.height(2.dp))
@ -183,15 +206,15 @@ class LocalStatsWidget : GlanceAppWidget() {
Row(modifier = GlanceModifier.fillMaxWidth()) {
StatRow(
label = state.channelUtilizationLabel,
value = state.channelUtilizationValue,
label = stringResource(Res.string.channel_utilization),
value = "%.1f%%".format(state.channelUtilization),
progress = state.channelUtilizationProgress,
isSmall = isSmall,
modifier = GlanceModifier.defaultWeight().padding(end = 4.dp),
)
StatRow(
label = state.airUtilizationLabel,
value = state.airUtilizationValue,
label = stringResource(Res.string.air_utilization),
value = "%.1f%%".format(state.airUtilization),
progress = state.airUtilizationProgress,
isSmall = isSmall,
modifier = GlanceModifier.defaultWeight().padding(start = 4.dp),
@ -201,17 +224,53 @@ class LocalStatsWidget : GlanceAppWidget() {
// Detailed Traffic/Relay Stats
Spacer(GlanceModifier.height(2.dp))
Column(modifier = GlanceModifier.fillMaxWidth()) {
state.trafficText?.let { StatText(it, isSmall) }
state.relayText?.let { StatText(it, isSmall) }
state.diagnosticsText?.let { StatText(it, isSmall) }
state.heapText?.let {
if (state.hasStats) {
StatText(
stringResource(
Res.string.local_stats_traffic,
state.numPacketsTx,
state.numPacketsRx,
state.numRxDupe,
),
isSmall,
)
if (state.numTxRelay > 0 || state.numTxRelayCanceled > 0) {
StatText(
stringResource(
Res.string.local_stats_relays,
state.numTxRelay,
state.numTxRelayCanceled,
),
isSmall,
)
}
val diag = mutableListOf<String>()
if (state.noiseFloor != 0) {
diag.add(stringResource(Res.string.local_stats_noise, state.noiseFloor))
}
if (state.numPacketsRxBad > 0) {
diag.add(stringResource(Res.string.local_stats_bad, state.numPacketsRxBad))
}
if (state.numTxDropped > 0) {
diag.add(stringResource(Res.string.local_stats_dropped, state.numTxDropped))
}
if (diag.isNotEmpty()) {
StatText(
stringResource(Res.string.local_stats_diagnostics_prefix, diag.joinToString(" | ")),
isSmall,
)
}
val heapProgress =
if (state.heapTotalBytes > 0) {
state.heapFreeBytes.toFloat() / state.heapTotalBytes
} else {
0f
}
StatRow(it, state.heapValue, heapProgress, isSmall)
val heapValue =
stringResource(Res.string.local_stats_heap_value, state.heapFreeBytes, state.heapTotalBytes)
StatRow(stringResource(Res.string.local_stats_heap), heapValue, heapProgress, isSmall)
}
}
}
@ -246,8 +305,15 @@ class LocalStatsWidget : GlanceAppWidget() {
modifier = GlanceModifier.size(32.dp),
)
}
val statusText =
when (state.connectionState) {
is ConnectionState.Disconnected -> stringResource(Res.string.disconnected)
is ConnectionState.Connecting -> stringResource(Res.string.connecting)
is ConnectionState.DeviceSleep -> stringResource(Res.string.device_sleeping)
is ConnectionState.Connected -> ""
}
Text(
text = state.statusText,
text = statusText,
style =
TextStyle(
color = GlanceTheme.colors.onSurfaceVariant,
@ -258,6 +324,7 @@ class LocalStatsWidget : GlanceAppWidget() {
}
}
@Suppress("LongMethod")
@Composable
private fun Footer(state: LocalStatsWidgetUiState) {
Column(modifier = GlanceModifier.fillMaxWidth()) {
@ -267,11 +334,11 @@ class LocalStatsWidget : GlanceAppWidget() {
) {
Column(modifier = GlanceModifier.defaultWeight(), horizontalAlignment = Alignment.Start) {
Text(
text = state.nodesLabel,
text = stringResource(Res.string.nodes),
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = 10.sp),
)
Text(
text = state.nodeCountText,
text = "${state.onlineNodes}/${state.totalNodes}",
maxLines = 1,
style =
TextStyle(
@ -283,11 +350,11 @@ class LocalStatsWidget : GlanceAppWidget() {
}
Column(modifier = GlanceModifier.defaultWeight(), horizontalAlignment = Alignment.End) {
Text(
text = state.uptimeLabel,
text = stringResource(Res.string.uptime),
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = 10.sp),
)
Text(
text = state.uptimeText,
text = formatUptime(state.uptimeSecs.toInt()),
maxLines = 1,
style =
TextStyle(
@ -299,11 +366,17 @@ class LocalStatsWidget : GlanceAppWidget() {
}
}
Row(modifier = GlanceModifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
val updatedLabel = stringResource(Res.string.updated)
val updatedText =
stringResource(
Res.string.local_stats_updated_at,
DateFormatter.formatShortDate(state.updateTimeMillis),
)
val footerText =
if (state.updatedLabel.isNotEmpty()) {
"${state.updatedLabel} ${state.updatedText}"
if (updatedLabel.isNotEmpty()) {
"$updatedLabel $updatedText"
} else {
state.updatedText
updatedText
}
Text(
text = footerText,
@ -386,27 +459,24 @@ class LocalStatsWidget : GlanceAppWidget() {
}
}
internal suspend fun createMockWidgetState() = LocalStatsWidgetUiState(
internal fun createMockWidgetState() = LocalStatsWidgetUiState(
connectionState = ConnectionState.Connected,
showContent = true,
appName = getStringSuspend(Res.string.meshtastic_app_name),
nodesLabel = getStringSuspend(Res.string.nodes),
uptimeLabel = getStringSuspend(Res.string.uptime),
updatedLabel = getStringSuspend(Res.string.updated),
refreshLabel = getStringSuspend(Res.string.refresh),
nodeShortName = "ME",
nodeColors = 0xFFFFFFFF.toInt() to 0xFF000000.toInt(),
batteryLabel = getStringSuspend(Res.string.battery),
batteryValue = "85%",
batteryLevel = 85,
hasBattery = true,
batteryProgress = 0.85f,
channelUtilizationLabel = getStringSuspend(Res.string.channel_utilization),
channelUtilizationValue = "18.5%",
channelUtilization = 18.5f,
channelUtilizationProgress = 0.185f,
airUtilizationLabel = getStringSuspend(Res.string.air_utilization),
airUtilizationValue = "3.2%",
airUtilization = 3.2f,
airUtilizationProgress = 0.032f,
trafficText = "TX: 145 | RX: 892 | D: 42",
nodeCountText = "2/3",
uptimeText = "2d 0h",
updatedText = "5m ago",
hasStats = true,
numPacketsTx = 145,
numPacketsRx = 892,
numRxDupe = 42,
totalNodes = 3,
onlineNodes = 2,
uptimeSecs = 172800L,
updateTimeMillis = System.currentTimeMillis() - 300000L,
)

View file

@ -27,35 +27,10 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.meshtastic.core.common.util.DateFormatter
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.util.formatUptime
import org.meshtastic.core.model.util.onlineTimeThreshold
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.air_utilization
import org.meshtastic.core.resources.battery
import org.meshtastic.core.resources.channel_utilization
import org.meshtastic.core.resources.connecting
import org.meshtastic.core.resources.device_sleeping
import org.meshtastic.core.resources.disconnected
import org.meshtastic.core.resources.getStringSuspend
import org.meshtastic.core.resources.local_stats_bad
import org.meshtastic.core.resources.local_stats_diagnostics_prefix
import org.meshtastic.core.resources.local_stats_dropped
import org.meshtastic.core.resources.local_stats_heap
import org.meshtastic.core.resources.local_stats_heap_value
import org.meshtastic.core.resources.local_stats_noise
import org.meshtastic.core.resources.local_stats_relays
import org.meshtastic.core.resources.local_stats_traffic
import org.meshtastic.core.resources.local_stats_updated_at
import org.meshtastic.core.resources.meshtastic_app_name
import org.meshtastic.core.resources.nodes
import org.meshtastic.core.resources.powered
import org.meshtastic.core.resources.refresh
import org.meshtastic.core.resources.updated
import org.meshtastic.core.resources.uptime
import org.meshtastic.core.service.ConnectionState
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.proto.LocalStats
@ -64,48 +39,42 @@ import javax.inject.Singleton
data class LocalStatsWidgetUiState(
val connectionState: ConnectionState = ConnectionState.Disconnected,
// Rendering data
val statusText: String = "",
val isConnecting: Boolean = false,
val showContent: Boolean = false,
// Static Strings (Resolved in provider for Glance stability)
val appName: String = "",
val nodesLabel: String = "",
val uptimeLabel: String = "",
val updatedLabel: String = "",
val refreshLabel: String = "",
// Node Identity
val nodeShortName: String? = null,
val nodeColors: Pair<Int, Int>? = null,
// Battery
val batteryLabel: String = "",
val batteryValue: String = "",
val batteryLevel: Int = 0,
val hasBattery: Boolean = false,
val batteryProgress: Float = 0f,
// Utilization
val channelUtilizationLabel: String = "",
val channelUtilizationValue: String = "",
val channelUtilization: Float = 0f,
val channelUtilizationProgress: Float = 0f,
val airUtilizationLabel: String = "",
val airUtilizationValue: String = "",
val airUtilization: Float = 0f,
val airUtilizationProgress: Float = 0f,
// Packet Stats Lines
val trafficText: String? = null,
val relayText: String? = null,
val diagnosticsText: String? = null,
// Stats
val hasStats: Boolean = false,
val numPacketsTx: Int = 0,
val numPacketsRx: Int = 0,
val numRxDupe: Int = 0,
val numTxRelay: Int = 0,
val numTxRelayCanceled: Int = 0,
val noiseFloor: Int = 0,
val numPacketsRxBad: Int = 0,
val numTxDropped: Int = 0,
val heapFreeBytes: Int = 0,
val heapTotalBytes: Int = 0,
val heapValue: String? = null,
val heapText: String? = null,
// Footer
val nodeCountText: String = "",
val uptimeText: String = "",
val updatedText: String = "",
val totalNodes: Int = 0,
val onlineNodes: Int = 0,
val uptimeSecs: Long = 0,
val updateTimeMillis: Long = 0,
)
@Singleton
@ -151,101 +120,49 @@ constructor(
)
@Suppress("LongMethod", "CyclomaticComplexMethod", "MagicNumber")
private suspend fun mapToUiState(
private fun mapToUiState(
connectionState: ConnectionState,
totalNodes: Int,
onlineNodes: Int,
stats: LocalStats,
localNode: Node?,
): LocalStatsWidgetUiState {
val statusText =
when (connectionState) {
is ConnectionState.Disconnected -> getStringSuspend(Res.string.disconnected)
is ConnectionState.Connecting -> getStringSuspend(Res.string.connecting)
is ConnectionState.DeviceSleep -> getStringSuspend(Res.string.device_sleeping)
is ConnectionState.Connected -> ""
}
val metrics = localNode?.deviceMetrics
val batteryLevel = metrics?.battery_level ?: 0
val isPowered = batteryLevel > 100
val batteryValue = if (isPowered) getStringSuspend(Res.string.powered) else "$batteryLevel%"
val hasStats = stats.uptime_seconds != 0
val channelUtil = if (hasStats) stats.channel_utilization else metrics?.channel_utilization ?: 0f
val airUtilTx = if (hasStats) stats.air_util_tx else metrics?.air_util_tx ?: 0f
val diag = mutableListOf<String>()
if (hasStats) {
if (stats.noise_floor != 0) {
diag.add(getStringSuspend(Res.string.local_stats_noise, stats.noise_floor))
}
if (stats.num_packets_rx_bad > 0) {
diag.add(getStringSuspend(Res.string.local_stats_bad, stats.num_packets_rx_bad))
}
if (stats.num_tx_dropped > 0) {
diag.add(getStringSuspend(Res.string.local_stats_dropped, stats.num_tx_dropped))
}
}
val uptimeSecs = if (hasStats) stats.uptime_seconds.toLong() else metrics?.uptime_seconds?.toLong() ?: 0L
return LocalStatsWidgetUiState(
connectionState = connectionState,
statusText = statusText,
isConnecting = connectionState is ConnectionState.Connecting,
showContent = connectionState is ConnectionState.Connected,
appName = getStringSuspend(Res.string.meshtastic_app_name),
nodesLabel = getStringSuspend(Res.string.nodes),
uptimeLabel = getStringSuspend(Res.string.uptime),
updatedLabel = getStringSuspend(Res.string.updated),
refreshLabel = getStringSuspend(Res.string.refresh),
nodeShortName = localNode?.user?.short_name,
nodeColors = localNode?.colors,
batteryLabel = getStringSuspend(Res.string.battery),
batteryValue = batteryValue,
batteryLevel = batteryLevel,
hasBattery = metrics?.battery_level != null,
batteryProgress = (batteryLevel / 100f).coerceIn(0f, 1f),
channelUtilizationLabel = getStringSuspend(Res.string.channel_utilization),
channelUtilizationValue = "%.1f%%".format(channelUtil),
channelUtilization = channelUtil,
channelUtilizationProgress = (channelUtil / 100f).coerceIn(0f, 1f),
airUtilizationLabel = getStringSuspend(Res.string.air_utilization),
airUtilizationValue = "%.1f%%".format(airUtilTx),
airUtilization = airUtilTx,
airUtilizationProgress = (airUtilTx / 100f).coerceIn(0f, 1f),
trafficText =
if (hasStats) {
getStringSuspend(
Res.string.local_stats_traffic,
stats.num_packets_tx,
stats.num_packets_rx,
stats.num_rx_dupe,
)
} else {
null
},
relayText =
stats
.takeIf { hasStats && (it.num_tx_relay > 0 || it.num_tx_relay_canceled > 0) }
?.let {
getStringSuspend(Res.string.local_stats_relays, it.num_tx_relay, it.num_tx_relay_canceled)
},
diagnosticsText =
if (diag.isNotEmpty()) {
getStringSuspend(Res.string.local_stats_diagnostics_prefix, diag.joinToString(" | "))
} else {
null
},
heapFreeBytes = if (hasStats) stats.heap_free_bytes else 0,
heapTotalBytes = if (hasStats) stats.heap_total_bytes else 0,
heapValue =
if (hasStats) {
getStringSuspend(Res.string.local_stats_heap_value, stats.heap_free_bytes, stats.heap_total_bytes)
} else {
null
},
heapText = if (hasStats) getStringSuspend(Res.string.local_stats_heap) else null,
nodeCountText = "$onlineNodes/$totalNodes",
uptimeText = formatUptime(uptimeSecs.toInt()),
updatedText = getStringSuspend(Res.string.local_stats_updated_at, DateFormatter.formatShortDate(nowMillis)),
hasStats = hasStats,
numPacketsTx = stats.num_packets_tx,
numPacketsRx = stats.num_packets_rx,
numRxDupe = stats.num_rx_dupe,
numTxRelay = stats.num_tx_relay,
numTxRelayCanceled = stats.num_tx_relay_canceled,
noiseFloor = stats.noise_floor,
numPacketsRxBad = stats.num_packets_rx_bad,
numTxDropped = stats.num_tx_dropped,
heapFreeBytes = stats.heap_free_bytes,
heapTotalBytes = stats.heap_total_bytes,
totalNodes = totalNodes,
onlineNodes = onlineNodes,
uptimeSecs = uptimeSecs,
updateTimeMillis = nowMillis,
)
}
}