diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 5d807cb..df065a8 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -1015,3 +1015,11 @@ - Message actions are now driven by Telegram-like context UI: - tap -> context sheet actions, - long-press -> selection mode flow. + +### Step 138 - Multi-select UX closer to Telegram +- Refined selection top bar: + - removed extra overflow/load action from selection mode, + - kept focused actions only: close, selected count, forward, delete. +- In `MULTI` selection mode, composer is now replaced with a compact bottom action row: + - `Reply` (enabled for single selected message), + - `Forward`. 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 5bb3b19..afaa878 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 @@ -419,9 +419,6 @@ fun ChatScreen( ) { Icon(imageVector = Icons.Filled.DeleteOutline, contentDescription = "Delete selected") } - IconButton(onClick = onLoadMore, enabled = !state.isLoadingMore) { - Icon(imageVector = Icons.Filled.MoreVert, contentDescription = "Selection menu") - } } } else { Row( @@ -1045,7 +1042,14 @@ fun ChatScreen( } } - if (isChannelChat && !state.canSendMessages) { + if (state.actionState.mode == MessageSelectionMode.MULTI && state.actionState.hasSelection) { + MultiSelectActionBar( + selectedCount = state.actionState.selectedCount, + canReply = state.actionState.selectedCount == 1 && state.selectedMessage != null, + onReply = { state.selectedMessage?.let(onReplySelected) }, + onForward = onForwardSelected, + ) + } else if (isChannelChat && !state.canSendMessages) { ChannelReadOnlyBar( modifier = Modifier .fillMaxWidth() @@ -2161,6 +2165,50 @@ private fun ChannelReadOnlyBar( } } +@Composable +private fun MultiSelectActionBar( + selectedCount: Int, + canReply: Boolean, + onReply: () -> Unit, + onForward: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .padding(horizontal = 10.dp, vertical = 6.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Surface( + shape = RoundedCornerShape(999.dp), + color = MaterialTheme.colorScheme.surface.copy(alpha = 0.95f), + modifier = Modifier.weight(1f), + ) { + TextButton( + onClick = onReply, + enabled = canReply, + modifier = Modifier.fillMaxWidth(), + ) { + Text("Reply") + } + } + Surface( + shape = RoundedCornerShape(999.dp), + color = MaterialTheme.colorScheme.surface.copy(alpha = 0.95f), + modifier = Modifier.weight(1f), + ) { + TextButton( + onClick = onForward, + enabled = selectedCount > 0, + modifier = Modifier.fillMaxWidth(), + ) { + Text("Forward") + } + } + } +} + private fun parseMessageLocalDate(createdAt: String): LocalDate? { return runCatching { Instant.parse(createdAt).atZone(ZoneId.systemDefault()).toLocalDate()