diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 42b4f5d..57d72b0 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -206,3 +206,9 @@ - Extended `ChatViewModel` state with pinned message id + resolved pinned message object. - Rendered pinned message bar under chat app bar with hide action. - Updated Telegram UI batch-2 checklist item for pinned message block. + +### Step 33 - Chat UI / top app bar restructuring +- Extended chat UI state with resolved chat header fields (`chatTitle`, `chatSubtitle`, `chatAvatarUrl`). +- Updated chat top app bar layout to Telegram-like structure: back, avatar, title, status, call action, menu action. +- Kept load-more behavior accessible via menu placeholder action button. +- Updated Telegram UI batch-2 checklist item for chat top app bar. 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 9bdca7a..1c64386 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 @@ -24,6 +24,8 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Button @@ -43,6 +45,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.safeDrawing @@ -125,15 +128,50 @@ fun ChatScreen( .background(MaterialTheme.colorScheme.surfaceVariant) .padding(horizontal = 12.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.spacedBy(10.dp), ) { Button(onClick = onBack) { Text("Back") } - Text( - text = "Chat #${state.chatId}", - style = MaterialTheme.typography.titleMedium, - ) + if (!state.chatAvatarUrl.isNullOrBlank()) { + AsyncImage( + model = state.chatAvatarUrl, + contentDescription = "Chat avatar", + modifier = Modifier + .size(40.dp) + .clip(CircleShape), + contentScale = ContentScale.Crop, + ) + } else { + Box( + modifier = Modifier + .size(40.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primaryContainer), + contentAlignment = Alignment.Center, + ) { + Text( + text = state.chatTitle.firstOrNull()?.uppercase() ?: "?", + style = MaterialTheme.typography.titleSmall, + ) + } + } + Column(modifier = Modifier.weight(1f)) { + Text( + text = state.chatTitle.ifBlank { "Chat #${state.chatId}" }, + style = MaterialTheme.typography.titleMedium, + maxLines = 1, + ) + if (state.chatSubtitle.isNotBlank()) { + Text( + text = state.chatSubtitle, + style = MaterialTheme.typography.labelSmall, + ) + } + } + Button(onClick = { /* call action placeholder */ }) { + Text("Call") + } Button(onClick = onLoadMore, enabled = !state.isLoadingMore) { - Text(if (state.isLoadingMore) "..." else "Load older") + Text("⋮") } } val pinnedMessage = state.pinnedMessage diff --git a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatViewModel.kt b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatViewModel.kt index ac3f01a..a236487 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatViewModel.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatViewModel.kt @@ -455,11 +455,22 @@ class ChatViewModel @Inject constructor( } else { "Only channel owner/admin can send messages." } + val chatTitle = chat.displayTitle.ifBlank { "Chat #$chatId" } + val chatSubtitle = when { + chat.type.equals("private", ignoreCase = true) && chat.counterpartIsOnline == true -> "online" + chat.type.equals("private", ignoreCase = true) && !chat.counterpartLastSeenAt.isNullOrBlank() -> "last seen recently" + chat.type.equals("group", ignoreCase = true) -> "group" + chat.type.equals("channel", ignoreCase = true) -> "channel" + else -> "" + } _uiState.update { val pinnedMessage = chat.pinnedMessageId?.let { pinnedId -> it.messages.firstOrNull { message -> message.id == pinnedId } } it.copy( + chatTitle = chatTitle, + chatSubtitle = chatSubtitle, + chatAvatarUrl = chat.avatarUrl ?: chat.counterpartAvatarUrl, canSendMessages = canSend, sendRestrictionText = restriction, pinnedMessageId = chat.pinnedMessageId, diff --git a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/MessageUiState.kt b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/MessageUiState.kt index 2f36bf7..d9fc0ff 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/MessageUiState.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/MessageUiState.kt @@ -10,6 +10,9 @@ data class MessageUiState( val isSending: Boolean = false, val isUploadingMedia: Boolean = false, val errorMessage: String? = null, + val chatTitle: String = "", + val chatSubtitle: String = "", + val chatAvatarUrl: String? = null, val messages: List = emptyList(), val pinnedMessageId: Long? = null, val pinnedMessage: MessageItem? = null, diff --git a/docs/android-ui-batch-2-checklist.md b/docs/android-ui-batch-2-checklist.md index 562ca17..8e6980a 100644 --- a/docs/android-ui-batch-2-checklist.md +++ b/docs/android-ui-batch-2-checklist.md @@ -4,7 +4,7 @@ Источник: 10 скринов Telegram Android (чат, контекстные действия, медиа, поиск/чаты) ## P0 — Chat Screen Parity (must-have) -- [ ] Top app bar чата: back + avatar + name + status + call + menu, полупрозрачная подложка на фоне обоев. +- [x] Top app bar чата: back + avatar + name + status + call + menu, полупрозрачная подложка на фоне обоев. - [x] Закреплённое сообщение блоком под app bar (2 строки, иконки pin/close, tap для перехода). - [ ] Message composer Telegram-стиля: - [ ] Полупрозрачный rounded input контейнер.