From d09300311f1aedcacbf29364430c2795e0fa27d8 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 9 Mar 2026 14:38:27 +0300 Subject: [PATCH] android: upgrade fullscreen media viewer header and gallery --- android/CHANGELOG.md | 6 ++ .../messenger/ui/chat/ChatScreen.kt | 82 ++++++++++++++++--- docs/android-ui-batch-2-checklist.md | 2 +- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index cd53ece..bb3a154 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -275,3 +275,9 @@ - Added file-list style attachment rows (icon + filename + type/size metadata). - Upgraded non-voice audio attachment player with play/pause, progress bar, and current/total duration labels. - Updated Telegram UI batch-2 checklist media-bubble items. + +### Step 46 - Media viewer / header and gallery navigation +- Upgraded chat image viewer to use global image gallery state (`index / total`) instead of a single URL. +- Added fullscreen viewer header with close, index, share placeholder, and delete placeholder actions. +- Added image navigation controls (`Prev`/`Next`) for gallery traversal. +- Updated Telegram UI batch-2 checklist for fullscreen media header support. 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 0f02c17..ba0460b 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 @@ -123,7 +123,15 @@ fun ChatScreen( onLoadMore: () -> Unit, onPickMedia: () -> Unit, ) { - var viewerImageUrl by remember { mutableStateOf(null) } + val allImageUrls = remember(state.messages) { + state.messages + .flatMap { message -> message.attachments } + .map { it.fileUrl to it.fileType.lowercase() } + .filter { (_, type) -> type.startsWith("image/") } + .map { (url, _) -> url } + .distinct() + } + var viewerImageIndex by remember { mutableStateOf(null) } var dismissedPinnedMessageId by remember { mutableStateOf(null) } var actionMenuMessage by remember { mutableStateOf(null) } Column( @@ -241,7 +249,10 @@ fun ChatScreen( message = message, isSelected = isSelected, reactions = state.reactionByMessageId[message.id].orEmpty(), - onAttachmentImageClick = { imageUrl -> viewerImageUrl = imageUrl }, + onAttachmentImageClick = { imageUrl -> + val idx = allImageUrls.indexOf(imageUrl) + viewerImageIndex = if (idx >= 0) idx else null + }, onClick = { actionMenuMessage = null if (state.actionState.mode == MessageSelectionMode.MULTI) { @@ -495,22 +506,71 @@ fun ChatScreen( ) } - if (viewerImageUrl != null) { + if (viewerImageIndex != null) { + val currentIndex = viewerImageIndex ?: 0 + val currentUrl = allImageUrls.getOrNull(currentIndex) Surface( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.7f)) - .clickable { viewerImageUrl = null }, + .clickable { }, ) { - Box(contentAlignment = Alignment.Center) { - AsyncImage( - model = viewerImageUrl, - contentDescription = "Attachment", + Column(modifier = Modifier.fillMaxSize()) { + Row( modifier = Modifier .fillMaxWidth() - .padding(16.dp), - contentScale = ContentScale.Fit, - ) + .padding(horizontal = 12.dp, vertical = 10.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Button(onClick = { viewerImageIndex = null }) { Text("✕") } + Text( + text = "${currentIndex + 1}/${allImageUrls.size.coerceAtLeast(1)}", + style = MaterialTheme.typography.titleSmall, + ) + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + Button(onClick = {}, enabled = false) { Text("↗") } + Button(onClick = {}, enabled = false) { Text("🗑") } + } + } + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.Center, + ) { + if (currentUrl != null) { + AsyncImage( + model = currentUrl, + contentDescription = "Attachment", + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + contentScale = ContentScale.Fit, + ) + } + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 10.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Button( + onClick = { + viewerImageIndex = if (allImageUrls.isEmpty()) null else { + (currentIndex - 1).coerceAtLeast(0) + } + }, + enabled = currentIndex > 0, + ) { Text("Prev") } + Button( + onClick = { + viewerImageIndex = if (allImageUrls.isEmpty()) null else { + (currentIndex + 1).coerceAtMost(allImageUrls.lastIndex) + } + }, + enabled = currentIndex < allImageUrls.lastIndex, + ) { Text("Next") } + } } } } diff --git a/docs/android-ui-batch-2-checklist.md b/docs/android-ui-batch-2-checklist.md index d3c6ac5..75f30c3 100644 --- a/docs/android-ui-batch-2-checklist.md +++ b/docs/android-ui-batch-2-checklist.md @@ -35,7 +35,7 @@ - [ ] Метаданные поста/канала (просмотры/время/reaction strip) у медиа-сообщений. ## P1 — Fullscreen Media Viewer -- [ ] Fullscreen header: close + index ("1") + share + delete. +- [x] Fullscreen header: close + index ("1") + share + delete. - [ ] Поддержка reaction overlay в viewer (как на скрине). - [ ] Свайп между медиа (gallery mode), если в сообщении несколько вложений.