From a5a940b749611852452532583b2739a65ed7c4b1 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 9 Mar 2026 14:26:47 +0300 Subject: [PATCH] android: add long-press reaction and context action menu --- android/CHANGELOG.md | 6 ++ .../messenger/ui/chat/ChatScreen.kt | 76 ++++++++++++++++++- docs/android-ui-batch-2-checklist.md | 4 +- 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index f79a189..da32496 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -247,3 +247,9 @@ - Added invite token extraction from incoming intents (`query token` and `/join/{token}` path formats). - Wired deep link token into `MessengerNavHost -> ChatListRoute -> ChatListViewModel` auto-join flow. - Removed manual `Invite token` input row from chat list screen. + +### Step 41 - Chat UI / long-press action menu +- Added long-press message action card in `ChatScreen` with quick reactions. +- Added context actions from long-press: reply, edit, forward, delete, select, close. +- Added placeholder disabled pin action in the menu to keep action set consistent with Telegram-like flow. +- Updated Telegram UI batch-2 checklist items for long-press reactions and context menu. diff --git a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatScreen.kt b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatScreen.kt index dfeafed..ef93138 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatScreen.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatScreen.kt @@ -122,6 +122,7 @@ fun ChatScreen( ) { var viewerImageUrl by remember { mutableStateOf(null) } var dismissedPinnedMessageId by remember { mutableStateOf(null) } + var actionMenuMessage by remember { mutableStateOf(null) } Column( modifier = Modifier .fillMaxSize() @@ -239,6 +240,7 @@ fun ChatScreen( reactions = state.reactionByMessageId[message.id].orEmpty(), onAttachmentImageClick = { imageUrl -> viewerImageUrl = imageUrl }, onClick = { + actionMenuMessage = null if (state.actionState.mode == MessageSelectionMode.MULTI) { onToggleMessageMultiSelection(message) } @@ -247,7 +249,8 @@ fun ChatScreen( if (state.actionState.mode == MessageSelectionMode.MULTI) { onToggleMessageMultiSelection(message) } else { - onEnterMultiSelect(message) + onSelectMessage(message) + actionMenuMessage = message } }, ) @@ -256,6 +259,77 @@ fun ChatScreen( } } + if (actionMenuMessage != null && state.actionState.mode != MessageSelectionMode.MULTI) { + val selected = actionMenuMessage + if (selected != null) { + Column( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.96f)) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + listOf("👍", "❤️", "🔥", "😂").forEach { emoji -> + Button( + onClick = { + onSelectMessage(selected) + onToggleReaction(emoji) + actionMenuMessage = null + }, + ) { Text(emoji) } + } + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Button( + onClick = { + onReplySelected(selected) + actionMenuMessage = null + }, + ) { Text("Reply") } + Button( + onClick = { + onEditSelected(selected) + actionMenuMessage = null + }, + enabled = state.selectedCanEdit, + ) { Text("Edit") } + Button( + onClick = { + onSelectMessage(selected) + onForwardSelected() + actionMenuMessage = null + }, + ) { Text("Forward") } + Button( + onClick = { + onSelectMessage(selected) + onDeleteSelected(false) + actionMenuMessage = null + }, + ) { Text("Delete") } + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Button(onClick = { onEnterMultiSelect(selected); actionMenuMessage = null }) { + Text("Select") + } + Button(onClick = {}, enabled = false) { + Text("Pin") + } + Button(onClick = { actionMenuMessage = null }) { + Text("Close") + } + } + } + } + } + if (state.actionState.hasSelection) { Row( modifier = Modifier diff --git a/docs/android-ui-batch-2-checklist.md b/docs/android-ui-batch-2-checklist.md index 94303a2..c572f65 100644 --- a/docs/android-ui-batch-2-checklist.md +++ b/docs/android-ui-batch-2-checklist.md @@ -21,8 +21,8 @@ - [ ] Outgoing/incoming voice bubble отличаются по цвету/тональности как у text bubbles. ## P0 — Message Actions + Reactions -- [ ] Long press открывает reaction bar над сообщением (emoji + "expand"). -- [ ] Контекстное меню действий (reply/save/forward/pin/delete) в floating dark card. +- [x] Long press открывает reaction bar над сообщением (emoji + "expand"). +- [x] Контекстное меню действий (reply/save/forward/pin/delete) в floating dark card. - [x] Режим multi-select: - [x] Верхняя панель (close/count/actions). - [x] Нижняя action bar (reply/forward) в виде rounded pills.