From 0591e9186bf37217e226026f68949ab28ea25e67 Mon Sep 17 00:00:00 2001
From: James Rich <2199651+jamesarich@users.noreply.github.com>
Date: Sat, 10 Jan 2026 20:25:21 -0600
Subject: [PATCH] feat: Add "now" string and update formatAgo function (#4183)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
---
.../composeResources/values/strings.xml | 1 +
.../org/meshtastic/core/ui/util/FormatAgo.kt | 42 ++++++++++++++-----
.../feature/node/component/LastHeardInfo.kt | 10 +----
.../feature/node/component/NodeItem.kt | 17 +-------
.../feature/node/list/NodeListScreen.kt | 3 --
5 files changed, 37 insertions(+), 36 deletions(-)
diff --git a/core/strings/src/commonMain/composeResources/values/strings.xml b/core/strings/src/commonMain/composeResources/values/strings.xml
index 109f1a87a..0e2eb436d 100644
--- a/core/strings/src/commonMain/composeResources/values/strings.xml
+++ b/core/strings/src/commonMain/composeResources/values/strings.xml
@@ -1088,4 +1088,5 @@
Estimated area: \u00b1%1$s (\u00b1%2$s)
Estimated area: unknown accuracy
Mark as read
+ Now
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/util/FormatAgo.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/util/FormatAgo.kt
index 248e0a2e2..709a32590 100644
--- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/util/FormatAgo.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/util/FormatAgo.kt
@@ -17,16 +17,38 @@
package org.meshtastic.core.ui.util
import android.text.format.DateUtils
+import com.meshtastic.core.strings.getString
+import org.meshtastic.core.strings.Res
+import org.meshtastic.core.strings.now
+import java.lang.System.currentTimeMillis
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.minutes
+import kotlin.time.Duration.Companion.seconds
-@Suppress("MagicNumber")
-fun formatAgo(lastSeenUnix: Int, currentTimeMillis: Long = System.currentTimeMillis()): String {
- val timeInMillis = lastSeenUnix * 1000L
+/**
+ * Formats a given Unix timestamp (in seconds) into a relative "time ago" string.
+ *
+ * For durations less than a minute, it returns "now". For longer durations, it uses Android's
+ * `DateUtils.getRelativeTimeSpanString` to generate a concise, localized, and abbreviated representation (e.g., "5m
+ * ago", "2h ago").
+ *
+ * @param lastSeenUnixSeconds The Unix timestamp in seconds to be formatted.
+ * @return A [String] representing the relative time that has passed.
+ */
+fun formatAgo(lastSeenUnixSeconds: Int): String {
+ val lastSeenDuration = lastSeenUnixSeconds.seconds
+ val currentDuration = currentTimeMillis().milliseconds
+ val diff = (currentDuration - lastSeenDuration).absoluteValue
- return DateUtils.getRelativeTimeSpanString(
- timeInMillis,
- currentTimeMillis,
- DateUtils.SECOND_IN_MILLIS,
- DateUtils.FORMAT_ABBREV_RELATIVE,
- )
- .toString()
+ return if (diff < 1.minutes) {
+ getString(Res.string.now)
+ } else {
+ DateUtils.getRelativeTimeSpanString(
+ lastSeenDuration.inWholeMilliseconds,
+ currentDuration.inWholeMilliseconds,
+ DateUtils.MINUTE_IN_MILLIS,
+ DateUtils.FORMAT_ABBREV_RELATIVE,
+ )
+ .toString()
+ }
}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/LastHeardInfo.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/LastHeardInfo.kt
index 64c333cfd..c79a2ab79 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/LastHeardInfo.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/LastHeardInfo.kt
@@ -34,14 +34,13 @@ import org.meshtastic.core.ui.util.formatAgo
fun LastHeardInfo(
modifier: Modifier = Modifier,
lastHeard: Int,
- currentTimeMillis: Long,
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),
- text = formatAgo(lastHeard, currentTimeMillis),
+ text = formatAgo(lastHeard),
contentColor = contentColor,
)
}
@@ -49,10 +48,5 @@ fun LastHeardInfo(
@PreviewLightDark
@Composable
private fun LastHeardInfoPreview() {
- AppTheme {
- LastHeardInfo(
- lastHeard = (System.currentTimeMillis() / 1000).toInt() - 8600,
- currentTimeMillis = System.currentTimeMillis(),
- )
- }
+ AppTheme { LastHeardInfo(lastHeard = (System.currentTimeMillis() / 1000).toInt() - 8600) }
}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeItem.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeItem.kt
index 01a1cdcc2..551153aa4 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeItem.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeItem.kt
@@ -74,7 +74,6 @@ fun NodeItem(
modifier: Modifier = Modifier,
onClick: () -> Unit = {},
onLongClick: (() -> Unit)? = null,
- currentTimeMillis: Long,
connectionState: ConnectionState,
isActive: Boolean = false,
) {
@@ -137,11 +136,7 @@ fun NodeItem(
textDecoration = TextDecoration.LineThrough.takeIf { isIgnored },
softWrap = true,
)
- LastHeardInfo(
- lastHeard = thatNode.lastHeard,
- currentTimeMillis = currentTimeMillis,
- contentColor = contentColor,
- )
+ LastHeardInfo(lastHeard = thatNode.lastHeard, contentColor = contentColor)
NodeStatusIcons(
isThisNode = isThisNode,
isFavorite = isFavorite,
@@ -228,14 +223,7 @@ fun NodeInfoSimplePreview() {
AppTheme {
val thisNode = NodePreviewParameterProvider().values.first()
val thatNode = NodePreviewParameterProvider().values.last()
- NodeItem(
- thisNode = thisNode,
- thatNode = thatNode,
- 0,
- true,
- currentTimeMillis = System.currentTimeMillis(),
- connectionState = ConnectionState.Connected,
- )
+ NodeItem(thisNode = thisNode, thatNode = thatNode, 0, true, connectionState = ConnectionState.Connected)
}
}
@@ -249,7 +237,6 @@ fun NodeInfoPreview(@PreviewParameter(NodePreviewParameterProvider::class) thatN
thatNode = thatNode,
distanceUnits = 1,
tempInFahrenheit = true,
- currentTimeMillis = System.currentTimeMillis(),
connectionState = ConnectionState.Connected,
)
}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt
index 686114343..16dfedbd6 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt
@@ -78,7 +78,6 @@ import org.meshtastic.core.strings.unmute
import org.meshtastic.core.ui.component.AddContactFAB
import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.core.ui.component.ScrollToTopEvent
-import org.meshtastic.core.ui.component.rememberTimeTickWithLifecycle
import org.meshtastic.core.ui.component.smartScrollToTop
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
import org.meshtastic.feature.node.component.NodeActionDialogs
@@ -115,7 +114,6 @@ fun NodeListScreen(
}
}
- val currentTimeMillis = rememberTimeTickWithLifecycle()
val connectionState by viewModel.connectionState.collectAsStateWithLifecycle()
val isScrollInProgress by remember {
@@ -224,7 +222,6 @@ fun NodeListScreen(
tempInFahrenheit = state.tempInFahrenheit,
onClick = { navigateToNodeDetails(node.num) },
onLongClick = longClick,
- currentTimeMillis = currentTimeMillis,
connectionState = connectionState,
isActive = isActive,
)