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