android: refine chats list typography badges and time format
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user