android: tune chats list visuals closer to telegram
This commit is contained in:
@@ -530,3 +530,10 @@
|
||||
- Added `ChatDao.markChatRead(chatId)` to clear `unread_count` and `unread_mentions_count` in Room.
|
||||
- Applied optimistic local unread reset on `markMessageRead(...)` in message repository.
|
||||
- Fixed realtime unread logic: incoming messages in currently active chat no longer increment unread badge.
|
||||
|
||||
### Step 86 - Chats list visual pass toward Telegram reference
|
||||
- Updated chats list row density: tighter vertical rhythm, larger avatar, stronger title hierarchy, cleaner secondary text.
|
||||
- Restyled archive as dedicated list row with leading archive icon avatar, subtitle, and unread badge.
|
||||
- Kept search in top app bar action and changed search field default to collapsed (opens via search icon).
|
||||
- Returned message-type emoji markers in chat previews:
|
||||
- `🖼` photo, `🎤` voice, `🎵` audio, `🎥` video, `⭕` circle video, `🔗` links.
|
||||
|
||||
@@ -53,6 +53,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Inventory2
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@@ -134,7 +135,7 @@ fun ChatListScreen(
|
||||
onUnbanMember: (Long, Long) -> Unit,
|
||||
) {
|
||||
var managementExpanded by remember { mutableStateOf(false) }
|
||||
var searchExpanded by remember { mutableStateOf(true) }
|
||||
var searchExpanded by remember { mutableStateOf(false) }
|
||||
var createTitle by remember { mutableStateOf("") }
|
||||
var createMemberIds by remember { mutableStateOf("") }
|
||||
var createHandle by remember { mutableStateOf("") }
|
||||
@@ -539,49 +540,56 @@ private fun ChatRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
.padding(horizontal = 14.dp, vertical = 8.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
if (!chat.avatarUrl.isNullOrBlank()) {
|
||||
AsyncImage(
|
||||
model = chat.avatarUrl,
|
||||
contentDescription = "Avatar for ${chat.displayTitle}",
|
||||
modifier = Modifier
|
||||
.size(44.dp)
|
||||
.size(52.dp)
|
||||
.clip(CircleShape),
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(44.dp)
|
||||
.size(52.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(chat.displayTitle.firstOrNull()?.uppercase() ?: "?")
|
||||
Text(
|
||||
text = chat.displayTitle.firstOrNull()?.uppercase() ?: "?",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = chat.displayTitle,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = if (chat.unreadCount > 0) FontWeight.SemiBold else FontWeight.Normal,
|
||||
maxLines = 1,
|
||||
)
|
||||
if (chat.pinned) {
|
||||
Text(
|
||||
text = " [PIN]",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
text = "📌",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
}
|
||||
if (chat.muted) {
|
||||
Text(
|
||||
text = " [MUTE]",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
text = "🔕",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -589,9 +597,10 @@ private fun ChatRow(
|
||||
if (preview.isNotBlank()) {
|
||||
Text(
|
||||
text = preview,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 2.dp),
|
||||
maxLines = 1,
|
||||
modifier = Modifier.padding(top = 1.dp),
|
||||
)
|
||||
}
|
||||
if (chat.type == "private") {
|
||||
@@ -602,19 +611,19 @@ private fun ChatRow(
|
||||
}
|
||||
Text(
|
||||
text = presence,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(top = 2.dp),
|
||||
modifier = Modifier.padding(top = 1.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End,
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(5.dp),
|
||||
) {
|
||||
Text(
|
||||
text = formatChatTime(chat.lastMessageCreatedAt),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
if (chat.unreadMentionsCount > 0) {
|
||||
BadgeChip(label = "@")
|
||||
@@ -678,15 +687,35 @@ private fun ArchiveRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 10.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
.padding(horizontal = 14.dp, vertical = 10.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = "Archive ($count)",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(52.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Inventory2,
|
||||
contentDescription = "Archived chats",
|
||||
)
|
||||
}
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = "Archive",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Text(
|
||||
text = "$count chats",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
if (unreadCount > 0) {
|
||||
BadgeChip(label = unreadCount.toString())
|
||||
}
|
||||
@@ -716,22 +745,33 @@ private fun CenterState(
|
||||
private fun ChatItem.previewText(): String {
|
||||
val raw = lastMessageText.orEmpty().trim()
|
||||
val prefix = when (lastMessageType) {
|
||||
"image" -> "Photo"
|
||||
"video" -> "Video"
|
||||
"audio" -> "Audio"
|
||||
"voice" -> "Voice"
|
||||
"file" -> "File"
|
||||
"circle_video" -> "Video message"
|
||||
"image" -> "🖼"
|
||||
"video" -> "🎥"
|
||||
"audio" -> "🎵"
|
||||
"voice" -> "🎤"
|
||||
"file" -> "📎"
|
||||
"circle_video", "video_note" -> "⭕"
|
||||
else -> ""
|
||||
}
|
||||
if (raw.isNotEmpty()) return if (prefix.isBlank()) raw else "$prefix: $raw"
|
||||
val linkPrefix = if (raw.startsWith("http://", ignoreCase = true) || raw.startsWith("https://", ignoreCase = true)) {
|
||||
"🔗"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
if (raw.isNotEmpty()) {
|
||||
return when {
|
||||
prefix.isNotBlank() -> "$prefix $raw"
|
||||
linkPrefix.isNotBlank() -> "$linkPrefix $raw"
|
||||
else -> raw
|
||||
}
|
||||
}
|
||||
return when (lastMessageType) {
|
||||
"image" -> "Photo"
|
||||
"video" -> "Video"
|
||||
"audio" -> "Audio"
|
||||
"voice" -> "Voice message"
|
||||
"file" -> "File"
|
||||
"circle_video" -> "Video message"
|
||||
"image" -> "🖼 Photo"
|
||||
"video" -> "🎥 Video"
|
||||
"audio" -> "🎵 Audio"
|
||||
"voice" -> "🎤 Voice message"
|
||||
"file" -> "📎 File"
|
||||
"circle_video", "video_note" -> "⭕ Circle video"
|
||||
null, "text" -> ""
|
||||
else -> "Media"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user