Merge pull request #2642 from mstrofnone/fix/last-seen-formatting
Fix Last Seen formatting for older timestamps ("Last seen Jan 14 ago" → "Last seen on Jan 14, 2026 (3 months ago)")
This commit is contained in:
@@ -241,6 +241,92 @@ fun dateFormatter(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the full "Last seen ..." sentence shown on profiles and similar UI.
|
||||
*
|
||||
* Unlike [timeAgo], which can return a bare absolute date string (e.g. "Jan 14")
|
||||
* for older timestamps, this function always returns a self-contained, grammatical
|
||||
* description such as:
|
||||
*
|
||||
* - "Last seen 5 minutes ago"
|
||||
* - "Last seen 2 hours ago"
|
||||
* - "Last seen 3 days ago"
|
||||
* - "Last seen 2 weeks ago"
|
||||
* - "Last seen on Jul 27, 2024 (9 months ago)"
|
||||
* - "Last seen on Jan 14, 2024 (1 year ago)"
|
||||
*
|
||||
* The duration component uses sensible units (seconds/minutes/hours/days/weeks/months/years)
|
||||
* and pluralizes via Android plural resources. For anything older than a week we also
|
||||
* include the absolute date so users see exactly when the activity happened.
|
||||
*/
|
||||
fun lastSeenSentence(
|
||||
time: Long?,
|
||||
context: Context,
|
||||
): String {
|
||||
if (time == null) return ""
|
||||
if (time == 0L) return stringRes(context, R.string.last_seen_never)
|
||||
|
||||
val nowSec = TimeUtils.now()
|
||||
val diff = nowSec - time
|
||||
|
||||
// Negative drift (clock skew, future timestamp) — treat as "just now".
|
||||
if (diff < TimeUtils.ONE_MINUTE) {
|
||||
return stringRes(context, R.string.last_seen_just_now)
|
||||
}
|
||||
|
||||
val resources = context.resources
|
||||
|
||||
// Recent: render purely as a relative duration.
|
||||
if (diff < TimeUtils.ONE_WEEK) {
|
||||
val durationText =
|
||||
when {
|
||||
diff < TimeUtils.ONE_HOUR -> {
|
||||
val n = (diff / TimeUtils.ONE_MINUTE).toInt()
|
||||
resources.getQuantityString(R.plurals.duration_minutes, n, n)
|
||||
}
|
||||
|
||||
diff < TimeUtils.ONE_DAY -> {
|
||||
val n = (diff / TimeUtils.ONE_HOUR).toInt()
|
||||
resources.getQuantityString(R.plurals.duration_hours, n, n)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val n = (diff / TimeUtils.ONE_DAY).toInt()
|
||||
resources.getQuantityString(R.plurals.duration_days, n, n)
|
||||
}
|
||||
}
|
||||
return stringRes(context, R.string.last_seen, durationText)
|
||||
}
|
||||
|
||||
// Older than a week: include absolute date plus a coarse relative duration.
|
||||
val durationText =
|
||||
when {
|
||||
diff < TimeUtils.ONE_MONTH -> {
|
||||
val n = (diff / TimeUtils.ONE_WEEK).toInt()
|
||||
resources.getQuantityString(R.plurals.duration_weeks, n, n)
|
||||
}
|
||||
|
||||
diff < TimeUtils.ONE_YEAR -> {
|
||||
val n = (diff / TimeUtils.ONE_MONTH).toInt().coerceAtLeast(1)
|
||||
resources.getQuantityString(R.plurals.duration_months, n, n)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val n = (diff / TimeUtils.ONE_YEAR).toInt().coerceAtLeast(1)
|
||||
resources.getQuantityString(R.plurals.duration_years, n, n)
|
||||
}
|
||||
}
|
||||
|
||||
if (locale != Locale.getDefault()) {
|
||||
locale = Locale.getDefault()
|
||||
yearFormatter = SimpleDateFormat(YEAR_DATE_FORMAT, locale)
|
||||
monthFormatter = SimpleDateFormat(MONTH_DATE_FORMAT, locale)
|
||||
}
|
||||
val dateText = yearFormatter.format(time * 1000)
|
||||
|
||||
return stringRes(context, R.string.last_seen_on_date, dateText, durationText)
|
||||
}
|
||||
|
||||
fun timeAgoShort(
|
||||
mills: Long?,
|
||||
stringForNow: String,
|
||||
|
||||
+2
-2
@@ -66,7 +66,7 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.note.DrawPlayName
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.note.ObserveAndRenderNIP05VerifiedSymbol
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
||||
import com.vitorpamplona.amethyst.ui.note.lastSeenSentence
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.apps.DisplayAppRecommendations
|
||||
@@ -312,7 +312,7 @@ fun DisplayLastSeen(
|
||||
lastSeen?.let { timestamp ->
|
||||
val context = LocalContext.current
|
||||
Text(
|
||||
text = stringRes(R.string.last_seen, timeAgo(timestamp, context, prefix = "", seconds = R.string.seconds)),
|
||||
text = lastSeenSentence(timestamp, context),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
||||
@@ -2379,6 +2379,34 @@
|
||||
<string name="relay_settings_lower2">outbox lists</string>
|
||||
<string name="relay_settings_lower">relay settings</string>
|
||||
<string name="last_seen">Last seen %1$s ago</string>
|
||||
<string name="last_seen_on_date">Last seen on %1$s (%2$s ago)</string>
|
||||
<string name="last_seen_just_now">Last seen just now</string>
|
||||
<string name="last_seen_never">Never seen</string>
|
||||
|
||||
<plurals name="duration_minutes">
|
||||
<item quantity="one">%1$d minute</item>
|
||||
<item quantity="other">%1$d minutes</item>
|
||||
</plurals>
|
||||
<plurals name="duration_hours">
|
||||
<item quantity="one">%1$d hour</item>
|
||||
<item quantity="other">%1$d hours</item>
|
||||
</plurals>
|
||||
<plurals name="duration_days">
|
||||
<item quantity="one">%1$d day</item>
|
||||
<item quantity="other">%1$d days</item>
|
||||
</plurals>
|
||||
<plurals name="duration_weeks">
|
||||
<item quantity="one">%1$d week</item>
|
||||
<item quantity="other">%1$d weeks</item>
|
||||
</plurals>
|
||||
<plurals name="duration_months">
|
||||
<item quantity="one">%1$d month</item>
|
||||
<item quantity="other">%1$d months</item>
|
||||
</plurals>
|
||||
<plurals name="duration_years">
|
||||
<item quantity="one">%1$d year</item>
|
||||
<item quantity="other">%1$d years</item>
|
||||
</plurals>
|
||||
|
||||
<string name="event_sync_less_than_until"><%1$s</string>
|
||||
<string name="event_sync_status_connecting">Connecting</string>
|
||||
|
||||
Reference in New Issue
Block a user