From 1d4b3be493feaf56a9879e950c4949ecc22273ef Mon Sep 17 00:00:00 2001 From: James Rich Date: Thu, 21 May 2026 20:29:43 -0500 Subject: [PATCH] style(car): align visual patterns with main app design system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename SignalQuality enum to match core: POOR→BAD, UNKNOWN→NONE - Add last heard time to node list subtitles (was only in detail view) - Add message timestamps in conversation view - Align string resources with core terminology (bad/none vs poor/unknown) - Use DateFormatter.formatRelativeTime() consistently across all screens Ensures car experience uses consistent terminology and information density with the main Meshtastic app. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../org/meshtastic/feature/car/model/CarUiModels.kt | 4 ++-- .../feature/car/screens/ConversationScreen.kt | 9 ++++++++- .../meshtastic/feature/car/screens/HomeScreen.kt | 13 ++++++++++--- .../feature/car/screens/NodeDashboardScreen.kt | 13 ++++++++++--- .../feature/car/screens/NodeDetailScreen.kt | 4 ++-- .../feature/car/service/CarStateCoordinator.kt | 4 ++-- feature/car/src/main/res/values/strings.xml | 4 ++-- 7 files changed, 36 insertions(+), 15 deletions(-) diff --git a/feature/car/src/main/kotlin/org/meshtastic/feature/car/model/CarUiModels.kt b/feature/car/src/main/kotlin/org/meshtastic/feature/car/model/CarUiModels.kt index 8b4e6d601..01721991b 100644 --- a/feature/car/src/main/kotlin/org/meshtastic/feature/car/model/CarUiModels.kt +++ b/feature/car/src/main/kotlin/org/meshtastic/feature/car/model/CarUiModels.kt @@ -61,8 +61,8 @@ enum class SignalQuality { EXCELLENT, GOOD, FAIR, - POOR, - UNKNOWN, + BAD, + NONE, } data class TopologyHeader(val totalNodes: Int, val onlineNodes: Int, val meshName: String?) diff --git a/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/ConversationScreen.kt b/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/ConversationScreen.kt index cc5a9fc8f..a9c155755 100644 --- a/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/ConversationScreen.kt +++ b/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/ConversationScreen.kt @@ -25,6 +25,7 @@ import androidx.car.app.model.ItemList import androidx.car.app.model.ListTemplate import androidx.car.app.model.Row import androidx.car.app.model.Template +import org.meshtastic.core.common.util.DateFormatter import org.meshtastic.feature.car.R import org.meshtastic.feature.car.service.MessageSnapshot @@ -42,7 +43,13 @@ class ConversationScreen( val listBuilder = ItemList.Builder() messages.forEach { msg -> - listBuilder.addItem(Row.Builder().setTitle(msg.senderName).addText(msg.text).build()) + val timeText = + if (msg.timestamp != 0L) { + " • ${DateFormatter.formatRelativeTime(msg.timestamp)}" + } else { + "" + } + listBuilder.addItem(Row.Builder().setTitle(msg.senderName).addText("${msg.text}$timeText").build()) } val actionStrip = diff --git a/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/HomeScreen.kt b/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/HomeScreen.kt index 9f049a966..441de9042 100644 --- a/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/HomeScreen.kt +++ b/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/HomeScreen.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch +import org.meshtastic.core.common.util.DateFormatter import org.meshtastic.core.model.ConnectionState import org.meshtastic.feature.car.R import org.meshtastic.feature.car.model.NodeUi @@ -197,12 +198,18 @@ class HomeScreen(carContext: CarContext, private val stateCoordinator: CarStateC SignalQuality.EXCELLENT -> carContext.getString(R.string.car_signal_excellent) SignalQuality.GOOD -> carContext.getString(R.string.car_signal_good) SignalQuality.FAIR -> carContext.getString(R.string.car_signal_fair) - SignalQuality.POOR -> carContext.getString(R.string.car_signal_poor) - SignalQuality.UNKNOWN -> carContext.getString(R.string.car_signal_unknown) + SignalQuality.BAD -> carContext.getString(R.string.car_signal_bad) + SignalQuality.NONE -> carContext.getString(R.string.car_signal_none) } val battery = node.batteryPercent?.let { " • $it%" } ?: "" + val lastHeard = + if (node.lastHeard != 0L) { + " • ${DateFormatter.formatRelativeTime(node.lastHeard)}" + } else { + "" + } val status = if (!node.isOnline) " • ${carContext.getString(R.string.car_status_offline)}" else "" - return "$signal$battery$status" + return "$signal$battery$lastHeard$status" } private fun buildDisconnectedTemplate(): Template = PaneTemplate.Builder( diff --git a/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/NodeDashboardScreen.kt b/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/NodeDashboardScreen.kt index ca96abe4b..757ae63a5 100644 --- a/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/NodeDashboardScreen.kt +++ b/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/NodeDashboardScreen.kt @@ -26,6 +26,7 @@ import androidx.car.app.model.ListTemplate import androidx.car.app.model.Row import androidx.car.app.model.Template import androidx.core.graphics.drawable.IconCompat +import org.meshtastic.core.common.util.DateFormatter import org.meshtastic.feature.car.R import org.meshtastic.feature.car.model.NodeDashboardUiState import org.meshtastic.feature.car.model.NodeUi @@ -86,11 +87,17 @@ class NodeDashboardScreen( SignalQuality.EXCELLENT -> carContext.getString(R.string.car_signal_excellent) SignalQuality.GOOD -> carContext.getString(R.string.car_signal_good) SignalQuality.FAIR -> carContext.getString(R.string.car_signal_fair) - SignalQuality.POOR -> carContext.getString(R.string.car_signal_poor) - SignalQuality.UNKNOWN -> carContext.getString(R.string.car_signal_unknown) + SignalQuality.BAD -> carContext.getString(R.string.car_signal_bad) + SignalQuality.NONE -> carContext.getString(R.string.car_signal_none) } val battery = node.batteryPercent?.let { " • $it%" } ?: "" + val lastHeard = + if (node.lastHeard != 0L) { + " • ${DateFormatter.formatRelativeTime(node.lastHeard)}" + } else { + "" + } val status = if (!node.isOnline) " • ${carContext.getString(R.string.car_status_offline)}" else "" - return "$signal$battery$status" + return "$signal$battery$lastHeard$status" } } diff --git a/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/NodeDetailScreen.kt b/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/NodeDetailScreen.kt index 64b2379c2..0b2f13846 100644 --- a/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/NodeDetailScreen.kt +++ b/feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/NodeDetailScreen.kt @@ -102,8 +102,8 @@ class NodeDetailScreen( SignalQuality.EXCELLENT -> carContext.getString(R.string.car_signal_excellent) SignalQuality.GOOD -> carContext.getString(R.string.car_signal_good) SignalQuality.FAIR -> carContext.getString(R.string.car_signal_fair) - SignalQuality.POOR -> carContext.getString(R.string.car_signal_poor) - SignalQuality.UNKNOWN -> carContext.getString(R.string.car_signal_unknown) + SignalQuality.BAD -> carContext.getString(R.string.car_signal_bad) + SignalQuality.NONE -> carContext.getString(R.string.car_signal_none) } private fun formatLastHeard(epochMillis: Long): String { diff --git a/feature/car/src/main/kotlin/org/meshtastic/feature/car/service/CarStateCoordinator.kt b/feature/car/src/main/kotlin/org/meshtastic/feature/car/service/CarStateCoordinator.kt index 090a72cbf..5bc178e09 100644 --- a/feature/car/src/main/kotlin/org/meshtastic/feature/car/service/CarStateCoordinator.kt +++ b/feature/car/src/main/kotlin/org/meshtastic/feature/car/service/CarStateCoordinator.kt @@ -288,12 +288,12 @@ class CarStateCoordinator( @Suppress("MagicNumber") private fun determineSignalQuality(snr: Float, rssi: Int): SignalQuality = when { - snr == Float.MAX_VALUE || rssi == Int.MAX_VALUE -> SignalQuality.UNKNOWN + snr == Float.MAX_VALUE || rssi == Int.MAX_VALUE -> SignalQuality.NONE snr > SNR_GOOD_THRESHOLD && rssi > RSSI_GOOD_THRESHOLD -> SignalQuality.EXCELLENT snr > SNR_GOOD_THRESHOLD && rssi > RSSI_FAIR_THRESHOLD -> SignalQuality.GOOD snr > SNR_FAIR_THRESHOLD && rssi > RSSI_GOOD_THRESHOLD -> SignalQuality.GOOD snr > SNR_FAIR_THRESHOLD -> SignalQuality.FAIR - else -> SignalQuality.POOR + else -> SignalQuality.BAD } } } diff --git a/feature/car/src/main/res/values/strings.xml b/feature/car/src/main/res/values/strings.xml index 55c1e0f59..5268b26e2 100644 --- a/feature/car/src/main/res/values/strings.xml +++ b/feature/car/src/main/res/values/strings.xml @@ -23,11 +23,11 @@ Radio connection lost. Will reconnect automatically. Reconnecting… The app will automatically reconnect when the radio is available. + Bad Excellent Fair Good - Poor - Unknown + None Battery Last Heard Offline