android: restructure chat top app bar with header metadata
Some checks are pending
CI / test (push) Has started running

This commit is contained in:
Codex
2026-03-09 14:03:48 +03:00
parent 651d53f3df
commit db048b9f12
5 changed files with 65 additions and 7 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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,

View File

@@ -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<MessageItem> = emptyList(),
val pinnedMessageId: Long? = null,
val pinnedMessage: MessageItem? = null,

View File

@@ -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 контейнер.