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(
|
fun timeAgoShort(
|
||||||
mills: Long?,
|
mills: Long?,
|
||||||
stringForNow: String,
|
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.DrawPlayName
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||||
import com.vitorpamplona.amethyst.ui.note.ObserveAndRenderNIP05VerifiedSymbol
|
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.painterRes
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.apps.DisplayAppRecommendations
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.apps.DisplayAppRecommendations
|
||||||
@@ -312,7 +312,7 @@ fun DisplayLastSeen(
|
|||||||
lastSeen?.let { timestamp ->
|
lastSeen?.let { timestamp ->
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
Text(
|
Text(
|
||||||
text = stringRes(R.string.last_seen, timeAgo(timestamp, context, prefix = "", seconds = R.string.seconds)),
|
text = lastSeenSentence(timestamp, context),
|
||||||
color = MaterialTheme.colorScheme.placeholderText,
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
|||||||
@@ -2379,6 +2379,34 @@
|
|||||||
<string name="relay_settings_lower2">outbox lists</string>
|
<string name="relay_settings_lower2">outbox lists</string>
|
||||||
<string name="relay_settings_lower">relay settings</string>
|
<string name="relay_settings_lower">relay settings</string>
|
||||||
<string name="last_seen">Last seen %1$s ago</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_less_than_until"><%1$s</string>
|
||||||
<string name="event_sync_status_connecting">Connecting</string>
|
<string name="event_sync_status_connecting">Connecting</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user