From 2324801f56008695b456849f0846904a960a963a Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 9 Mar 2026 21:40:04 +0300 Subject: [PATCH] android: refine chats list typography badges and time format --- android/CHANGELOG.md | 9 +++ .../messenger/ui/chats/ChatListScreen.kt | 58 +++++++++++++------ 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 7240e46..9e57f39 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -537,3 +537,12 @@ - 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. + +### Step 87 - Chats list micro-typography and time formatting +- Refined chat row typography hierarchy to be closer to Telegram density: + - title/body/presence font scale aligned and single-line ellipsis for long values. +- Tightened unread/mention badge sizing and spacing for compact right-side metadata. +- Updated trailing time formatter: + - today: `HH:mm`, + - this week: localized short weekday, + - older: `dd.MM.yy`. diff --git a/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListScreen.kt b/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListScreen.kt index d8cd740..6ec88d0 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListScreen.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListScreen.kt @@ -47,6 +47,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -60,8 +61,11 @@ import kotlinx.coroutines.flow.collectLatest import coil.compose.AsyncImage import ru.daemonlord.messenger.domain.chat.model.ChatItem import java.time.Instant +import java.time.LocalDate import java.time.ZoneId import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit +import java.util.Locale @Composable fun ChatListRoute( @@ -576,20 +580,21 @@ private fun ChatRow( ) { Text( text = chat.displayTitle, - style = MaterialTheme.typography.titleLarge, + style = MaterialTheme.typography.titleMedium, fontWeight = if (chat.unreadCount > 0) FontWeight.SemiBold else FontWeight.Normal, maxLines = 1, + overflow = TextOverflow.Ellipsis, ) if (chat.pinned) { Text( text = "📌", - style = MaterialTheme.typography.labelMedium, + style = MaterialTheme.typography.labelSmall, ) } if (chat.muted) { Text( text = "🔕", - style = MaterialTheme.typography.labelMedium, + style = MaterialTheme.typography.labelSmall, ) } } @@ -597,9 +602,10 @@ private fun ChatRow( if (preview.isNotBlank()) { Text( text = preview, - style = MaterialTheme.typography.bodyLarge, + style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, + overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(top = 1.dp), ) } @@ -619,7 +625,7 @@ private fun ChatRow( } Column( horizontalAlignment = Alignment.End, - verticalArrangement = Arrangement.spacedBy(5.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), ) { Text( text = formatChatTime(chat.lastMessageCreatedAt), @@ -636,28 +642,42 @@ private fun ChatRow( } } -@Composable -private fun BadgeChip(label: String) { - AssistChip( - onClick = {}, - enabled = false, - label = { Text(text = label) }, - colors = AssistChipDefaults.assistChipColors( - disabledContainerColor = MaterialTheme.colorScheme.primaryContainer, - disabledLabelColor = MaterialTheme.colorScheme.onPrimaryContainer, - ), - ) -} - private val chatTimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm") +private val chatDateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yy") +private val chatWeekdayFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("EEE", Locale.getDefault()) private fun formatChatTime(iso: String?): String { if (iso.isNullOrBlank()) return "" return runCatching { - Instant.parse(iso).atZone(ZoneId.systemDefault()).toLocalTime().format(chatTimeFormatter) + val zoned = Instant.parse(iso).atZone(ZoneId.systemDefault()) + val messageDate = zoned.toLocalDate() + val today = LocalDate.now(ZoneId.systemDefault()) + when { + messageDate == today -> zoned.toLocalTime().format(chatTimeFormatter) + ChronoUnit.DAYS.between(messageDate, today) in 1..6 -> zoned.format(chatWeekdayFormatter) + else -> zoned.format(chatDateFormatter) + } }.getOrElse { "" } } +@Composable +private fun BadgeChip(label: String) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(10.dp)) + .background(MaterialTheme.colorScheme.primaryContainer) + .padding(horizontal = 8.dp, vertical = 2.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = label, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onPrimaryContainer, + fontWeight = FontWeight.SemiBold, + ) + } +} + @Composable private fun FilterChip( label: String,