From c740a346ae843c837bfa4ade95a0f5a89d41867c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 19:47:38 +0000 Subject: [PATCH] fix(chat): timestamp tap opens delivery (not format toggle); relays for Concord MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chat timestamp tap toggled relative/absolute time instead of opening the relay dialog, because ToggleableTimeAgoText has its own inner click. Give it a `toggleable` flag and turn it off for the chat time, so the enclosing tap target opens the delivery dialog; the absolute time now shows as a header inside that dialog so nothing is lost. Also: - Encrypted-channel messages (Concord/Armada) decrypt locally with no per-relay attribution, so the dialog showed "no relay information". Fall back to the channel's own relays as "where this message lives". - Move the "Message Delivery" sheet tile out of the always-visible row into the Show More section — it's a secondary action. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0129yvP2hmVeDFfuKKy94tqX --- .../amethyst/ui/note/elements/TimeAgo.kt | 15 ++++++++--- .../loggedIn/chats/feed/ChatDeliveryTicks.kt | 25 +++++++++++++++++++ .../chats/feed/ChatMessageActionSheet.kt | 21 ++++++++++------ .../screen/loggedIn/chats/feed/ChatTimeAgo.kt | 4 +++ 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/TimeAgo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/TimeAgo.kt index a552e99cfa..f5d919d234 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/TimeAgo.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/TimeAgo.kt @@ -89,6 +89,9 @@ fun ToggleableTimeAgoText( fontSize: TextUnit = TextUnit.Unspecified, maxLines: Int = 1, overflow: TextOverflow = TextOverflow.Clip, + // When false, the text is not itself clickable, so an enclosing tap target (e.g. the + // chat timestamp opening the relay/delivery dialog) receives the tap instead. + toggleable: Boolean = true, ) { val context = LocalContext.current val nowState = LocalNowSeconds.current @@ -125,10 +128,14 @@ fun ToggleableTimeAgoText( maxLines = maxLines, overflow = overflow, modifier = - modifier.clickable( - interactionSource = interactionSource, - indication = null, - ) { showAbsolute = !showAbsolute }, + if (toggleable) { + modifier.clickable( + interactionSource = interactionSource, + indication = null, + ) { showAbsolute = !showAbsolute } + } else { + modifier + }, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatDeliveryTicks.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatDeliveryTicks.kt index c804d13051..703a7c6b4e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatDeliveryTicks.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatDeliveryTicks.kt @@ -41,12 +41,14 @@ 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.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols +import com.vitorpamplona.amethyst.commons.model.Channel import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.relayClient.chatDelivery.ChatDelivery import com.vitorpamplona.amethyst.service.relayClient.chatDelivery.RecipientDelivery @@ -54,6 +56,7 @@ import com.vitorpamplona.amethyst.ui.components.ClickableBox import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.note.UserPicture import com.vitorpamplona.amethyst.ui.note.UsernameDisplay +import com.vitorpamplona.amethyst.ui.note.timeAbsolute import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser import com.vitorpamplona.amethyst.ui.stringRes @@ -175,6 +178,23 @@ private fun ChatDeliveryDetailDialog( modifier = Modifier.verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(8.dp), ) { + // Absolute timestamp header (the chat time no longer toggles to absolute + // on tap — it opens this dialog instead). + val context = LocalContext.current + Text( + text = timeAbsolute(baseNote.createdAt(), context, prefix = "").trim(), + color = MaterialTheme.colorScheme.placeholderText, + fontSize = Font12SP, + ) + + // Encrypted-channel messages (Concord/Armada) are decrypted locally with no + // per-relay attribution, so seen-on is empty — fall back to the channel's own + // relays as "where this message lives". + val channelRelays = + remember(baseNote) { + baseNote.inGatherers?.firstNotNullOfOrNull { (it as? Channel)?.relays()?.takeIf { r -> r.isNotEmpty() } } ?: emptySet() + } + val recipients = delivery?.recipients when { !recipients.isNullOrEmpty() -> @@ -196,6 +216,11 @@ private fun ChatDeliveryDetailDialog( RelayDeliveryRow(relay = relay, accepted = true) } + channelRelays.isNotEmpty() -> + channelRelays.sortedBy { it.url }.forEach { relay -> + RelayDeliveryRow(relay = relay, accepted = true) + } + else -> // Nothing to list: our own just-sent message no relay has acknowledged // yet (pending), or a received/old message with no recorded relays. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatMessageActionSheet.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatMessageActionSheet.kt index 179de3b8b4..b6f9e307ae 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatMessageActionSheet.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatMessageActionSheet.kt @@ -261,7 +261,7 @@ fun ChatMessageActionSheet( } // Stage one: the primary chat action (reply / edit draft) is always shown. - ChatOnlyRow(note, state, onWantsToReply, onWantsToEditDraft, { showDeliveryDialog = true }, onDismiss) + ChatOnlyRow(note, state, onWantsToReply, onWantsToEditDraft, onDismiss) val handlers = NoteActionHandlers( @@ -312,6 +312,17 @@ fun ChatMessageActionSheet( } } } + + // Chat-specific: which relays carry this message (and, for our own, + // their acceptance). A secondary action, so it lives under Show More. + if (!note.isDraft()) { + SectionDivider() + TileRow { + ActionTile(MaterialSymbols.DoneAll, stringRes(R.string.chat_delivery_details_title)) { + showDeliveryDialog = true + } + } + } } } @@ -357,7 +368,7 @@ private fun MoreActionsToggle( // ---------- Action tile sections ---------- -/** Chat-specific tiles (reply, message delivery, edit draft) that have no 3-dot menu equivalent. */ +/** Chat-specific tiles (reply, edit draft) that have no 3-dot menu equivalent. */ @OptIn(ExperimentalLayoutApi::class) @Composable private fun ChatOnlyRow( @@ -365,7 +376,6 @@ private fun ChatOnlyRow( state: DropDownParams, onWantsToReply: (Note) -> Unit, onWantsToEditDraft: (Note) -> Unit, - onShowDelivery: () -> Unit, onDismiss: () -> Unit, ) { if (note.isDraft() && !state.isLoggedUser) return @@ -384,11 +394,6 @@ private fun ChatOnlyRow( onWantsToReply(note) onDismiss() } - // Which relays carry this message (and, for our own, their acceptance) — the - // reliable way to reach the relay list now that the detail row is gone. - ActionTile(MaterialSymbols.DoneAll, stringRes(R.string.chat_delivery_details_title)) { - onShowDelivery() - } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatTimeAgo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatTimeAgo.kt index c7b05ebad9..cddb259cd9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatTimeAgo.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatTimeAgo.kt @@ -59,6 +59,10 @@ fun ChatTimeAgo(baseNote: Note) { style = TimeAgoStyle.Short, color = MaterialTheme.colorScheme.placeholderText, fontSize = Font12SP, + // The chat time is wrapped in a tap target that opens the relay/delivery dialog, + // so it must not steal the tap to toggle relative/absolute. The absolute time is + // shown in that dialog instead. + toggleable = false, ) }