android: polish chat info tabs and media grid layout
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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/"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user