From ace9d20690933ce18d2c35f01a1568060d30a3df Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 5 Jun 2026 16:58:34 +0000 Subject: [PATCH] refactor(dm): extract the history status card to commons (shared with desktop) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2 (UI), stage 2. Move DmHistoryLoadingCard + its tap-through per-relay dialog + the historySubtitle/incompleteSubtitle helpers out of amethyst into commons/commonMain (com.vitorpamplona.amethyst.commons.ui.feeds), so the same "older history / all caught up / some relays didn't respond" boundary card can back any per-relay BackwardRelayPager feed on Android and Desktop. The card's localized date formatting (SimpleDateFormat/Locale) can't live in commons commonMain (it targets iOS/linux/macOS, no java.*), so it's injected as a formatReachDate: (epochSeconds) -> String lambda — the platform that renders the card supplies its native formatter, no i18n regression. Android passes formatHistoryReachDate (new HistoryDateFormat.kt). The dialog's per-relay reach now uses that same month-precision formatter (was "MMM d, yyyy", now "MMM yyyy") — a negligible cosmetic change. Its ~11 strings move to commons composeResources, including the module's first (chats_history_relays). The three Android call sites (ChatroomView, ChatroomListFeedView, LoadingReplyNote) now import the shared card/helpers and pass the formatter; no behaviour change. The old amethyst string copies are left in place (harmless, separate resource namespace) for a later cleanup pass. --- .../loggedIn/chats/feed/HistoryDateFormat.kt | 32 +++++++ .../loggedIn/chats/feed/LoadingReplyNote.kt | 7 +- .../loggedIn/chats/privateDM/ChatroomView.kt | 7 +- .../chats/rooms/feed/ChatroomListFeedView.kt | 7 +- .../composeResources/values/strings.xml | 17 +++- .../commons/ui/feeds/DmHistoryLoadingCard.kt | 90 +++++++++++-------- 6 files changed, 113 insertions(+), 47 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/HistoryDateFormat.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/DmLoadMoreIndicator.kt => commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/feeds/DmHistoryLoadingCard.kt (77%) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/HistoryDateFormat.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/HistoryDateFormat.kt new file mode 100644 index 0000000000..7b6ab94391 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/HistoryDateFormat.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed + +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +/** + * The Android locale date formatter for the shared [com.vitorpamplona.amethyst.commons.ui.feeds.DmHistoryLoadingCard] + * — passed in so the shared (KMP) card carries no `java.time` dependency. Formats a paging reach point + * (epoch seconds) to a short month-year label, e.g. "Jun 2026". + */ +fun formatHistoryReachDate(epochSeconds: Long): String = SimpleDateFormat("MMM yyyy", Locale.getDefault()).format(Date(epochSeconds * 1000)) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/LoadingReplyNote.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/LoadingReplyNote.kt index d606811988..695a15fcdd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/LoadingReplyNote.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/LoadingReplyNote.kt @@ -49,6 +49,9 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.commons.ui.feeds.DmHistoryRelayDialog +import com.vitorpamplona.amethyst.commons.ui.feeds.historySubtitle +import com.vitorpamplona.amethyst.commons.ui.feeds.incompleteSubtitle import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.nip01Core.relay.client.paging.RelayPagingProgress @@ -156,7 +159,7 @@ fun LoadingReplyNote( // can see exactly which relays were reached and which stalled. Empty progress keeps it non-interactive. var showRelays by remember { mutableStateOf(false) } if (showRelays) { - DmHistoryRelayDialog(protocolTag, relayProgress) { showRelays = false } + DmHistoryRelayDialog(protocolTag, relayProgress, ::formatHistoryReachDate) { showRelays = false } } // Same chrome as DmHistoryLoadingCard (the older-history status card at the oldest end) so an @@ -224,7 +227,7 @@ fun LoadingReplyNote( when { stalledOut -> incompleteSubtitle(stalledCount) isExhausted -> stringRes(R.string.chats_reply_searched) - else -> historySubtitle(protocolTag, relayCount, stalledCount, reachedBack) + else -> historySubtitle(protocolTag, relayCount, stalledCount, reachedBack, ::formatHistoryReachDate) }, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/ChatroomView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/ChatroomView.kt index a856c1f8c2..decd0d2efe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/ChatroomView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/ChatroomView.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.commons.ui.feeds.DmHistoryLoadingCard import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState import com.vitorpamplona.amethyst.commons.ui.feeds.RelayReachState @@ -52,8 +53,8 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMsAndDisplayIfNotFound import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.DmHistoryLoadingCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.RefreshingChatroomFeedView +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.formatHistoryReachDate import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.dal.ChatroomFeedViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.datasource.ChatroomFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.ChatNewMessageViewModel @@ -273,8 +274,8 @@ fun ChatroomViewUI( // while it pages and crossfades to "All caught up" when that protocol runs dry. olderBoundary = { Column { - DmHistoryLoadingCard(nip17Name, "NIP-17", loadingGiftWraps, giftWrapsExhausted, giftWrapsRelays, giftWrapsStalled, giftWrapsReached, giftWrapsProgress) - DmHistoryLoadingCard(nip04Name, "NIP-04", loadingNip04, nip04Exhausted, nip04Relays, nip04Stalled, nip04Reached, nip04Progress) + DmHistoryLoadingCard(nip17Name, "NIP-17", loadingGiftWraps, giftWrapsExhausted, giftWrapsRelays, giftWrapsStalled, giftWrapsReached, giftWrapsProgress, ::formatHistoryReachDate) + DmHistoryLoadingCard(nip04Name, "NIP-04", loadingNip04, nip04Exhausted, nip04Relays, nip04Stalled, nip04Reached, nip04Progress, ::formatHistoryReachDate) } }, // Each relay's window-limit marker, placed at its reached cursor (pure UI). Hidden once diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListFeedView.kt index 68620f2cb2..923c20473e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListFeedView.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.model.marmotGroups.MarmotGroupChatroom +import com.vitorpamplona.amethyst.commons.ui.feeds.DmHistoryLoadingCard import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState import com.vitorpamplona.amethyst.commons.ui.feeds.RelayReachState @@ -53,7 +54,7 @@ import com.vitorpamplona.amethyst.ui.feeds.SaveableFeedContentState import com.vitorpamplona.amethyst.ui.layouts.rememberFeedContentPadding import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.DmHistoryLoadingCard +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.formatHistoryReachDate import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChatroomHeaderCompose import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.FeedPadding @@ -230,10 +231,10 @@ private fun FeedLoaded( // Rendered unconditionally at the protocol's oldest room so the card can run its own // "All caught up" crossfade-and-collapse when that protocol exhausts. if (index == oldestNip17Index) { - DmHistoryLoadingCard(nip17Name, "NIP-17", loadingGiftWraps, giftWrapsExhausted, giftWrapsRelays, giftWrapsStalled, giftWrapsReached, giftWrapsProgress) + DmHistoryLoadingCard(nip17Name, "NIP-17", loadingGiftWraps, giftWrapsExhausted, giftWrapsRelays, giftWrapsStalled, giftWrapsReached, giftWrapsProgress, ::formatHistoryReachDate) } if (index == oldestNip04Index) { - DmHistoryLoadingCard(nip04Name, "NIP-04", loadingNip04, nip04Exhausted, nip04Relays, nip04Stalled, nip04Reached, nip04Progress) + DmHistoryLoadingCard(nip04Name, "NIP-04", loadingNip04, nip04Exhausted, nip04Relays, nip04Stalled, nip04Reached, nip04Progress, ::formatHistoryReachDate) } // Per-relay window-limit markers/sentinels belonging in the gap toward the next-older room: diff --git a/commons/src/commonMain/composeResources/values/strings.xml b/commons/src/commonMain/composeResources/values/strings.xml index da98c3d19e..baae5cad1b 100644 --- a/commons/src/commonMain/composeResources/values/strings.xml +++ b/commons/src/commonMain/composeResources/values/strings.xml @@ -55,6 +55,21 @@ User avatar Navigate - + Relay sync: + Older %1$s messages + All caught up + Reached the start of your %1$s messages + %1$s · %2$s · back to %3$s + %1$s · %2$s + waiting on %1$s + Some relays didn\'t respond + %1$s unreachable · tap to see which + %1$s · history by relay + back to %1$s + Dismiss + + %1$d relay + %1$d relays + diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/DmLoadMoreIndicator.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/feeds/DmHistoryLoadingCard.kt similarity index 77% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/DmLoadMoreIndicator.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/feeds/DmHistoryLoadingCard.kt index be71ae466a..8388088669 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/DmLoadMoreIndicator.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/feeds/DmHistoryLoadingCard.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed +package com.vitorpamplona.amethyst.commons.ui.feeds import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade @@ -54,33 +54,48 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.pluralStringResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.action_dismiss +import com.vitorpamplona.amethyst.commons.resources.chats_history_all_caught_up +import com.vitorpamplona.amethyst.commons.resources.chats_history_incomplete +import com.vitorpamplona.amethyst.commons.resources.chats_history_incomplete_sub +import com.vitorpamplona.amethyst.commons.resources.chats_history_older +import com.vitorpamplona.amethyst.commons.resources.chats_history_reached_start +import com.vitorpamplona.amethyst.commons.resources.chats_history_relay_back +import com.vitorpamplona.amethyst.commons.resources.chats_history_relays +import com.vitorpamplona.amethyst.commons.resources.chats_history_relays_title +import com.vitorpamplona.amethyst.commons.resources.chats_history_subtitle +import com.vitorpamplona.amethyst.commons.resources.chats_history_subtitle_no_date +import com.vitorpamplona.amethyst.commons.resources.chats_history_waiting import com.vitorpamplona.quartz.nip01Core.relay.client.paging.RelayPagingProgress import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import kotlinx.coroutines.delay -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale +import org.jetbrains.compose.resources.pluralStringResource +import org.jetbrains.compose.resources.stringResource // How long the "All caught up" state lingers before the card collapses away. private const val ALL_DONE_VISIBLE_MS = 2200L /** - * The DM "older history" status card, shown at one protocol's oldest-loaded boundary (rooms list and - * conversation). It tells the user exactly what the app is reaching for: which protocol, how many - * relays it is asking, and how far back it has paged. When that protocol runs dry it does NOT just - * vanish — it crossfades to an "All caught up" state, holds for a beat, then collapses away. + * The "older history" status card for a per-relay [BackwardRelayPager]-backed feed, shown at one + * protocol's oldest-loaded boundary (rooms list and conversation). It tells the user exactly what the + * app is reaching for: which protocol, how many relays it is asking, and how far back it has paged. + * When that protocol runs dry it does NOT just vanish — it crossfades to an "All caught up" state, + * holds for a beat, then collapses away. When it stops short because relays stalled it says so and + * stays put (an *incomplete* window — messages may still be out there). + * + * Shared across front ends; pass the platform's locale date formatter as [formatReachDate] so the card + * carries no `java.time` / `NSDateFormatter` dependency. * * @param protocolName human label woven into sentences, e.g. "encrypted" / "legacy". * @param protocolTag short technical tag for the subtitle, e.g. "NIP-17" / "NIP-04". * @param reachedBack epoch seconds of the oldest point reached so far (the deepest `until` cursor). * @param relayProgress per-relay reach (where each relay's window is, done/stalled). Tapping the card * opens a popup listing them; pass empty to make the card non-interactive. + * @param formatReachDate formats an epoch-seconds reach point to a short label (e.g. "Jun 2026"). */ @Composable fun DmHistoryLoadingCard( @@ -92,6 +107,7 @@ fun DmHistoryLoadingCard( stalledCount: Int, reachedBack: Long?, relayProgress: Map = emptyMap(), + formatReachDate: (epochSeconds: Long) -> String, modifier: Modifier = Modifier, ) { // Exhausted ("nothing more reachable right now") splits two ways and must NOT read the same: @@ -115,7 +131,7 @@ fun DmHistoryLoadingCard( var showRelays by remember { mutableStateOf(false) } if (showRelays) { - DmHistoryRelayDialog(protocolTag, relayProgress) { showRelays = false } + DmHistoryRelayDialog(protocolTag, relayProgress, formatReachDate) { showRelays = false } } AnimatedVisibility( @@ -169,9 +185,9 @@ fun DmHistoryLoadingCard( if (loading) { CircularProgressIndicator(Modifier.size(18.dp), strokeWidth = 2.dp) } else { - // Paused: not caught up, but not actively loading (the rooms-list auto-fill - // stopped short of exhaustion, or we're between round-model pages). Show a - // static "more" glyph so the icon slot is never blank — resumes on scroll. + // Paused: not caught up, but not actively loading (the auto-fill stopped short + // of exhaustion, or we're between pages). Show a static "more" glyph so the + // icon slot is never blank — resumes on scroll. Text( "⋯", style = MaterialTheme.typography.titleMedium, @@ -186,9 +202,9 @@ fun DmHistoryLoadingCard( Text( text = when (state) { - HistoryPhase.CaughtUp -> stringResource(R.string.chats_history_all_caught_up) - HistoryPhase.Incomplete -> stringResource(R.string.chats_history_incomplete) - HistoryPhase.Loading -> stringResource(R.string.chats_history_older, protocolName) + HistoryPhase.CaughtUp -> stringResource(Res.string.chats_history_all_caught_up) + HistoryPhase.Incomplete -> stringResource(Res.string.chats_history_incomplete) + HistoryPhase.Loading -> stringResource(Res.string.chats_history_older, protocolName) }, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.SemiBold, @@ -197,9 +213,9 @@ fun DmHistoryLoadingCard( Text( text = when (state) { - HistoryPhase.CaughtUp -> stringResource(R.string.chats_history_reached_start, protocolName) + HistoryPhase.CaughtUp -> stringResource(Res.string.chats_history_reached_start, protocolName) HistoryPhase.Incomplete -> incompleteSubtitle(stalledCount) - HistoryPhase.Loading -> historySubtitle(protocolTag, relayCount, stalledCount, reachedBack) + HistoryPhase.Loading -> historySubtitle(protocolTag, relayCount, stalledCount, reachedBack, formatReachDate) }, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, @@ -218,40 +234,38 @@ private enum class HistoryPhase { Loading, CaughtUp, Incomplete } /** Subtitle for the "stopped early" state: how many relays we couldn't reach, with a hint to tap for the list. * Shared with the reply placeholder so both read identically. */ @Composable -internal fun incompleteSubtitle(stalledCount: Int): String = +fun incompleteSubtitle(stalledCount: Int): String = stringResource( - R.string.chats_history_incomplete_sub, - pluralStringResource(R.plurals.chats_history_relays, stalledCount, stalledCount), + Res.string.chats_history_incomplete_sub, + pluralStringResource(Res.plurals.chats_history_relays, stalledCount, stalledCount), ) @Composable -internal fun historySubtitle( +fun historySubtitle( protocolTag: String, relayCount: Int, stalledCount: Int, reachedBack: Long?, + formatReachDate: (epochSeconds: Long) -> String, ): String { - val backLabel = - remember(reachedBack) { - reachedBack?.let { SimpleDateFormat("MMM yyyy", Locale.getDefault()).format(Date(it * 1000)) } - } + val backLabel = remember(reachedBack) { reachedBack?.let(formatReachDate) } // Middle segment: the relays actively fetching ("N relays"), or — when none are in flight but some // can't be reached — what we're waiting on ("waiting on N relays"). With neither, just the tag, since // a paged-out-but-parked protocol isn't waiting on anything (it resumes on scroll). val middle = when { - relayCount > 0 -> pluralStringResource(R.plurals.chats_history_relays, relayCount, relayCount) + relayCount > 0 -> pluralStringResource(Res.plurals.chats_history_relays, relayCount, relayCount) stalledCount > 0 -> stringResource( - R.string.chats_history_waiting, - pluralStringResource(R.plurals.chats_history_relays, stalledCount, stalledCount), + Res.string.chats_history_waiting, + pluralStringResource(Res.plurals.chats_history_relays, stalledCount, stalledCount), ) else -> return protocolTag } return if (backLabel != null) { - stringResource(R.string.chats_history_subtitle, protocolTag, middle, backLabel) + stringResource(Res.string.chats_history_subtitle, protocolTag, middle, backLabel) } else { - stringResource(R.string.chats_history_subtitle_no_date, protocolTag, middle) + stringResource(Res.string.chats_history_subtitle_no_date, protocolTag, middle) } } @@ -260,19 +274,19 @@ internal fun historySubtitle( * ↓ still reaching) and how far back it has paged ("back to "), deepest-reaching first. */ @Composable -internal fun DmHistoryRelayDialog( +fun DmHistoryRelayDialog( protocolTag: String, relayProgress: Map, + formatReachDate: (epochSeconds: Long) -> String, onDismiss: () -> Unit, ) { - val df = remember { SimpleDateFormat("MMM d, yyyy", Locale.getDefault()) } val rows = remember(relayProgress) { relayProgress.entries.sortedBy { it.value.reachedUntil } } AlertDialog( onDismissRequest = onDismiss, confirmButton = { - TextButton(onClick = onDismiss) { Text(stringResource(R.string.dismiss)) } + TextButton(onClick = onDismiss) { Text(stringResource(Res.string.action_dismiss)) } }, - title = { Text(stringResource(R.string.chats_history_relays_title, protocolTag)) }, + title = { Text(stringResource(Res.string.chats_history_relays_title, protocolTag)) }, text = { Column( Modifier @@ -301,7 +315,7 @@ internal fun DmHistoryRelayDialog( ) Spacer(Modifier.width(8.dp)) Text( - text = stringResource(R.string.chats_history_relay_back, df.format(Date(p.reachedUntil * 1000))), + text = stringResource(Res.string.chats_history_relay_back, formatReachDate(p.reachedUntil)), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1,