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, )