Fix #3542 Conversations list item layout. (#3553)

Signed-off-by: Artemii Vishnevskii <temaa.mann@gmail.com>
This commit is contained in:
Artemii Vishnevskii 2025-10-30 22:54:52 +01:00 committed by GitHub
parent 5838e205f3
commit 75c262f94d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -22,10 +22,13 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
@ -72,88 +75,118 @@ fun ContactItem(
.combinedClickable(onClick = onClick, onLongClick = onLongClick) .combinedClickable(onClick = onClick, onLongClick = onLongClick)
.background(color = if (selected) Color.Gray else MaterialTheme.colorScheme.background) .background(color = if (selected) Color.Gray else MaterialTheme.colorScheme.background)
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 6.dp) .padding(horizontal = 8.dp, vertical = 4.dp)
.semantics { contentDescription = shortName }, .semantics { contentDescription = shortName },
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
) { ) {
val colors = Column(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
if (contact.nodeColors != null) { ContactHeader(contact = contact, channels = channels, onNodeChipClick = onNodeChipClick)
AssistChipDefaults.assistChipColors(
labelColor = Color(contact.nodeColors.first),
containerColor = Color(contact.nodeColors.second),
)
} else {
AssistChipDefaults.assistChipColors()
}
Row(modifier = Modifier.fillMaxWidth().padding(8.dp), verticalAlignment = Alignment.CenterVertically) { ChatMetadata(modifier = Modifier.padding(top = 4.dp), contact = contact)
AssistChip( }
onClick = onNodeChipClick, }
modifier = Modifier.padding(end = 8.dp).width(72.dp).semantics { contentDescription = shortName }, }
label = {
Text( @Composable
text = shortName, private fun ContactHeader(
modifier = Modifier.fillMaxWidth(), contact: Contact,
fontSize = MaterialTheme.typography.labelLarge.fontSize, channels: AppOnlyProtos.ChannelSet?,
fontWeight = FontWeight.Normal, modifier: Modifier = Modifier,
textAlign = TextAlign.Center, onNodeChipClick: () -> Unit = {},
) ) {
}, val colors =
colors = colors, if (contact.nodeColors != null) {
AssistChipDefaults.assistChipColors(
labelColor = Color(contact.nodeColors.first),
containerColor = Color(contact.nodeColors.second),
) )
Column(modifier = Modifier.weight(1f)) { } else {
Row( AssistChipDefaults.assistChipColors()
modifier = Modifier.fillMaxWidth(), }
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
// Show unlock icon for broadcast with default PSK
val isBroadcast =
contact.contactKey.getOrNull(1) == '^' ||
contact.contactKey.endsWith("^all") ||
contact.contactKey.endsWith("^broadcast")
if (isBroadcast && channels != null) {
val channelIndex = contact.contactKey[0].digitToIntOrNull()
channelIndex?.let { index -> SecurityIcon(channels, index) }
}
Text(text = longName) Row(modifier = modifier.padding(0.dp), verticalAlignment = Alignment.CenterVertically) {
Spacer(modifier = Modifier.weight(1f)) AssistChip(
Text( onClick = onNodeChipClick,
text = lastMessageTime.orEmpty(), modifier =
color = MaterialTheme.colorScheme.onSurface, Modifier.width(IntrinsicSize.Min).height(32.dp).semantics { contentDescription = contact.shortName },
fontSize = MaterialTheme.typography.labelLarge.fontSize, label = {
modifier = Modifier.width(80.dp), Text(
) text = contact.shortName,
modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.labelLarge,
textAlign = TextAlign.Center,
)
},
colors = colors,
)
// Show unlock icon for broadcast with default PSK
val isBroadcast = with(contact.contactKey) { getOrNull(1) == '^' || endsWith("^all") || endsWith("^broadcast") }
if (isBroadcast && channels != null) {
val channelIndex = contact.contactKey[0].digitToIntOrNull()
channelIndex?.let { index -> SecurityIcon(channels, index) }
}
Text(
modifier = Modifier.padding(start = 8.dp).weight(1f),
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Medium,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = contact.longName,
)
Text(
text = contact.lastMessageTime.orEmpty(),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.labelSmall,
modifier = Modifier,
)
}
}
private const val UNREAD_MESSAGE_LIMIT = 99
@Composable
private fun ChatMetadata(contact: Contact, modifier: Modifier = Modifier) {
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = contact.lastMessageText.orEmpty(),
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.bodyMedium,
overflow = TextOverflow.Ellipsis,
maxLines = 2,
)
AnimatedVisibility(visible = contact.isMuted) {
Icon(
modifier = Modifier.padding(start = 4.dp).size(20.dp),
imageVector = Icons.AutoMirrored.TwoTone.VolumeOff,
contentDescription = null,
)
}
AnimatedVisibility(modifier = Modifier.padding(start = 4.dp), visible = contact.unreadCount > 0) {
val text =
if (contact.unreadCount > UNREAD_MESSAGE_LIMIT) {
"$UNREAD_MESSAGE_LIMIT+"
} else {
contact.unreadCount.toString()
} }
Row(
modifier = Modifier.fillMaxWidth().padding(top = 8.dp), Text(
horizontalArrangement = Arrangement.SpaceBetween, text = text,
verticalAlignment = Alignment.CenterVertically, modifier =
) { Modifier.background(MaterialTheme.colorScheme.primary, shape = CircleShape)
Text( .defaultMinSize(minWidth = 20.dp)
text = lastMessageText.orEmpty(), .padding(horizontal = 6.dp, vertical = 2.dp),
modifier = Modifier.weight(1f), textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onPrimary,
fontSize = MaterialTheme.typography.labelLarge.fontSize, style = MaterialTheme.typography.labelSmall,
overflow = TextOverflow.Ellipsis, maxLines = 1,
maxLines = 2, )
)
AnimatedVisibility(visible = isMuted) {
Icon(imageVector = Icons.AutoMirrored.TwoTone.VolumeOff, contentDescription = null)
}
AnimatedVisibility(visible = unreadCount > 0) {
Text(
text = unreadCount.toString(),
modifier =
Modifier.background(MaterialTheme.colorScheme.primary, shape = CircleShape)
.padding(horizontal = 6.dp, vertical = 3.dp),
color = MaterialTheme.colorScheme.onPrimary,
style = MaterialTheme.typography.bodySmall,
)
}
}
}
} }
} }
} }
@ -161,21 +194,28 @@ fun ContactItem(
@PreviewLightDark @PreviewLightDark
@Composable @Composable
private fun ContactItemPreview() { private fun ContactItemPreview() {
AppTheme { val sampleContact =
ContactItem( Contact(
contact = contactKey = "0^all",
Contact( shortName = stringResource(R.string.some_username),
contactKey = "0^all", longName = stringResource(R.string.unknown_username),
shortName = stringResource(R.string.some_username), lastMessageTime = "Mon",
longName = stringResource(R.string.unknown_username), lastMessageText = stringResource(R.string.sample_message),
lastMessageTime = "3 minutes ago", unreadCount = 2,
lastMessageText = stringResource(R.string.sample_message), messageCount = 10,
unreadCount = 2, isMuted = true,
messageCount = 10, isUnmessageable = false,
isMuted = true,
isUnmessageable = false,
),
selected = false,
) )
}
val contactsList =
listOf(
sampleContact,
sampleContact.copy(
shortName = "0",
longName = "A very long contact name that should be truncated.",
lastMessageTime = "15 minutes ago",
),
)
AppTheme { Column { contactsList.forEach { contact -> ContactItem(contact = contact, selected = false) } } }
} }