android: polish chat info tabs and media grid layout
Some checks failed
Android CI / android (push) Has started running
Android Release / release (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
Codex
2026-03-10 01:32:19 +03:00
parent 2ed0e1f041
commit 90c25c5eb8
2 changed files with 91 additions and 13 deletions

View File

@@ -876,3 +876,11 @@
- `Links` - `Links`
- `Voice` - `Voice`
- Implemented local tab content from current loaded chat messages/attachments to provide immediate media/files/links/voice overview. - Implemented local tab content from current loaded chat messages/attachments to provide immediate media/files/links/voice overview.
### Step 123 - Chat info visual pass (Telegram-like density)
- Updated `Chat info` tabs to pill-style horizontal chips with tighter Telegram-like spacing.
- Improved tab content rendering:
- `Media` now uses a 3-column thumbnail grid.
- `Files / Links / Voice` use denser card rows with icon+meta layout.
- `Voice` rows now show a dedicated play affordance.
- Refined menu order in chat `3-dot` popup and kept actions consistent with current no-calls scope.

View File

@@ -33,10 +33,16 @@ import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@@ -103,6 +109,7 @@ import androidx.compose.material.icons.filled.Wallpaper
import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Image import androidx.compose.material.icons.filled.Image
import androidx.compose.material.icons.filled.Link import androidx.compose.material.icons.filled.Link
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
import coil.compose.AsyncImage import coil.compose.AsyncImage
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@@ -444,13 +451,13 @@ fun ChatScreen(
}, },
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text("Notifications") }, text = { Text("Change wallpaper") },
leadingIcon = { Icon(Icons.Filled.Notifications, contentDescription = null) }, leadingIcon = { Icon(Icons.Filled.Wallpaper, contentDescription = null) },
onClick = { showChatMenu = false }, onClick = { showChatMenu = false },
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text("Change wallpaper") }, text = { Text("Notifications") },
leadingIcon = { Icon(Icons.Filled.Wallpaper, contentDescription = null) }, leadingIcon = { Icon(Icons.Filled.Notifications, contentDescription = null) },
onClick = { showChatMenu = false }, onClick = { showChatMenu = false },
) )
DropdownMenuItem( DropdownMenuItem(
@@ -795,7 +802,9 @@ fun ChatScreen(
} }
} }
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
ChatInfoTab.entries.forEach { tab -> ChatInfoTab.entries.forEach { tab ->
@@ -803,19 +812,16 @@ fun ChatScreen(
Surface( Surface(
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),
color = if (selected) { color = if (selected) {
MaterialTheme.colorScheme.primary.copy(alpha = 0.22f) MaterialTheme.colorScheme.primary.copy(alpha = 0.24f)
} else { } else {
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f) MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.48f)
}, },
modifier = Modifier modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(16.dp))
.clickable { chatInfoTab = tab }, .clickable { chatInfoTab = tab },
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier.padding(horizontal = 14.dp, vertical = 8.dp),
.fillMaxWidth()
.padding(vertical = 8.dp),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
Text( Text(
@@ -1743,6 +1749,7 @@ private data class ChatInfoEntry(
val title: String, val title: String,
val subtitle: String, val subtitle: String,
val previewImageUrl: String? = null, val previewImageUrl: String? = null,
val previewIsVideo: Boolean = false,
) )
@Composable @Composable
@@ -1775,6 +1782,61 @@ private fun ChatInfoTabContent(
} }
return return
} }
if (tab == ChatInfoTab.Media) {
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier
.fillMaxWidth()
.height(320.dp),
verticalArrangement = Arrangement.spacedBy(2.dp),
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
items(filtered.take(120)) { entry ->
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
.clip(RoundedCornerShape(4.dp))
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f)),
) {
if (!entry.previewImageUrl.isNullOrBlank()) {
AsyncImage(
model = entry.previewImageUrl,
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop,
)
} else {
Icon(
imageVector = Icons.Filled.Movie,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.align(Alignment.Center)
.size(20.dp),
)
}
if (entry.previewIsVideo) {
Surface(
shape = RoundedCornerShape(8.dp),
color = Color.Black.copy(alpha = 0.5f),
modifier = Modifier
.align(Alignment.BottomStart)
.padding(6.dp),
) {
Text(
text = "Video",
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
style = MaterialTheme.typography.labelSmall,
color = Color.White,
)
}
}
}
}
}
return
}
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1786,8 +1848,8 @@ private fun ChatInfoTabContent(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clip(RoundedCornerShape(12.dp)) .clip(RoundedCornerShape(12.dp))
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f)) .background(MaterialTheme.colorScheme.surface.copy(alpha = 0.55f))
.padding(10.dp), .padding(horizontal = 10.dp, vertical = 9.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp), horizontalArrangement = Arrangement.spacedBy(10.dp),
) { ) {
@@ -1835,6 +1897,13 @@ private fun ChatInfoTabContent(
maxLines = 2, maxLines = 2,
) )
} }
if (tab == ChatInfoTab.Voice) {
Icon(
imageVector = Icons.Filled.PlayArrow,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
}
} }
} }
} }
@@ -1857,6 +1926,7 @@ private fun buildChatInfoEntries(messages: List<MessageItem>): List<ChatInfoEntr
title = extractFileName(attachment.fileUrl), title = extractFileName(attachment.fileUrl),
subtitle = "$time${attachment.fileType}", subtitle = "$time${attachment.fileType}",
previewImageUrl = if (normalized.startsWith("image/")) attachment.fileUrl else null, previewImageUrl = if (normalized.startsWith("image/")) attachment.fileUrl else null,
previewIsVideo = normalized.startsWith("video/"),
) )
} }