mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Signed-off-by: Artemii Vishnevskii <temaa.mann@gmail.com>
This commit is contained in:
parent
5838e205f3
commit
75c262f94d
1 changed files with 132 additions and 92 deletions
|
|
@ -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) } } }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue