diff --git a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt index 980f2bf01..7968d3b64 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt @@ -23,11 +23,11 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -102,150 +102,159 @@ internal fun MessageItem( Color(node.colors.second).copy(alpha = 0.25f) } val messageModifier = Modifier.padding(start = 8.dp, top = 8.dp, end = 8.dp) - - Card( - modifier = Modifier - .padding( - start = if (fromLocal) 0.dp else 8.dp, - end = if (!fromLocal) 0.dp else 8.dp, - ) - .combinedClickable( - onClick = onClick, - onLongClick = onLongClick, - ) - .then(messageModifier), - colors = CardDefaults.cardColors( - containerColor = messageColor, - contentColor = contentColorFor(messageColor), - ), - ) { - Column( + Box { + Card( modifier = Modifier - .fillMaxWidth(), + .align(if (fromLocal) Alignment.BottomEnd else Alignment.BottomStart) + .padding( + top = 4.dp, + start = if (!fromLocal) 0.dp else 16.dp, + end = if (fromLocal) 0.dp else 16.dp, + ) + .combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + ) + .then(messageModifier), + colors = CardDefaults.cardColors( + containerColor = messageColor, + contentColor = contentColorFor(messageColor), + ), ) { - message.originalMessage?.let { originalMessage -> - val originalMessageIsFromLocal = originalMessage.node.user.id == DataPacket.ID_LOCAL - val originalMessageNode = - if (originalMessageIsFromLocal) ourNode else originalMessage.node - OutlinedCard( - modifier = Modifier - .fillMaxWidth() - .padding(4.dp) - .clickable { onNavigateToOriginalMessage(originalMessage.packetId) }, - colors = CardDefaults.cardColors( - containerColor = Color(originalMessageNode.colors.second).copy(alpha = 0.8f), - contentColor = Color(originalMessageNode.colors.first), - ), - ) { - Row( - modifier = Modifier.padding(4.dp), - verticalAlignment = Alignment.CenterVertically + Column( + modifier = Modifier + .fillMaxWidth(), + ) { + message.originalMessage?.let { originalMessage -> + val originalMessageIsFromLocal = + originalMessage.node.user.id == DataPacket.ID_LOCAL + val originalMessageNode = + if (originalMessageIsFromLocal) ourNode else originalMessage.node + OutlinedCard( + modifier = Modifier + .fillMaxWidth() + .clickable { onNavigateToOriginalMessage(originalMessage.packetId) }, + colors = CardDefaults.cardColors( + containerColor = Color(originalMessageNode.colors.second).copy(alpha = 0.5f), + contentColor = Color(originalMessageNode.colors.first), + ), ) { - Icon( - Icons.Default.FormatQuote, - contentDescription = stringResource(R.string.reply), // Add to strings.xml - modifier = Modifier.size(14.dp), // Smaller icon - ) - Spacer(Modifier.width(6.dp)) - Column { - Text( - text = "${originalMessageNode.user.shortName} ${originalMessageNode.user.longName - ?: stringResource(R.string.unknown_username)}", - style = MaterialTheme.typography.labelMedium, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Spacer(Modifier.height(1.dp)) - Text( - text = originalMessage.text, // Should not be null if isAReply is true - style = MaterialTheme.typography.bodySmall, - maxLines = 2, // Keep snippet brief - overflow = TextOverflow.Ellipsis, + Row( + modifier = Modifier.padding(horizontal = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + Icons.Default.FormatQuote, + contentDescription = stringResource(R.string.reply), // Add to strings.xml + modifier = Modifier.size(14.dp), // Smaller icon ) + Spacer(Modifier.width(4.dp)) + Column { + Text( + text = "${originalMessageNode.user.shortName} ${ + originalMessageNode.user.longName + ?: stringResource(R.string.unknown_username) + }", + style = MaterialTheme.typography.labelMedium, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = originalMessage.text, // Should not be null if isAReply is true + style = MaterialTheme.typography.bodySmall, + maxLines = 1, // Keep snippet brief + overflow = TextOverflow.Ellipsis, + ) + } } } } - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(4.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - if (!fromLocal) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { NodeChip( - node = node, + node = if (fromLocal) ourNode else node, onAction = onAction, isConnected = isConnected, - isThisNode = false, + isThisNode = fromLocal, ) Spacer(Modifier.width(4.dp)) Text( - text = with(node.user) { "$longName ($id)" }, - modifier = Modifier.padding(bottom = 4.dp), + text = with(if (fromLocal) ourNode.user else node.user) { "$longName ($id)" }, overflow = TextOverflow.Ellipsis, maxLines = 1, style = MaterialTheme.typography.labelLarge ) + Spacer(Modifier.weight(1f)) + MessageActions( + onSendReaction = sendReaction, + onSendReply = onReply, + ) } - } - AutoLinkText( - modifier = Modifier - .fillMaxWidth() - .padding(4.dp), - text = message.text, - style = MaterialTheme.typography.bodyMedium, - ) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 4.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - if (!fromLocal) { - if (message.hopsAway == 0) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Snr( - message.snr, - fontSize = MaterialTheme.typography.labelSmall.fontSize - ) - Rssi( - message.rssi, - fontSize = MaterialTheme.typography.labelSmall.fontSize + AutoLinkText( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp), + text = message.text, + style = MaterialTheme.typography.bodyMedium, + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + if (!fromLocal) { + if (message.hopsAway == 0) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Snr( + message.snr, + fontSize = MaterialTheme.typography.labelSmall.fontSize + ) + Rssi( + message.rssi, + fontSize = MaterialTheme.typography.labelSmall.fontSize + ) + } + } else { + Text( + text = stringResource( + R.string.hops_away_template, + message.hopsAway + ), + style = MaterialTheme.typography.labelSmall, ) } - } else { - Text( - text = stringResource(R.string.hops_away_template, message.hopsAway), - style = MaterialTheme.typography.labelSmall, + } + Text( + text = message.time, + style = MaterialTheme.typography.labelSmall, + ) + AnimatedVisibility(visible = fromLocal) { + Icon( + imageVector = when (message.status) { + MessageStatus.RECEIVED -> Icons.TwoTone.HowToReg + MessageStatus.QUEUED -> Icons.TwoTone.CloudUpload + MessageStatus.DELIVERED -> Icons.TwoTone.CloudDone + MessageStatus.ENROUTE -> Icons.TwoTone.Cloud + MessageStatus.ERROR -> Icons.TwoTone.CloudOff + else -> Icons.TwoTone.Warning + }, + contentDescription = stringResource(R.string.message_delivery_status), + modifier = Modifier + .padding(start = 8.dp) + .clickable { onStatusClick() }, ) } } - Text( - text = message.time, - style = MaterialTheme.typography.labelSmall, - ) - AnimatedVisibility(visible = fromLocal) { - Icon( - imageVector = when (message.status) { - MessageStatus.RECEIVED -> Icons.TwoTone.HowToReg - MessageStatus.QUEUED -> Icons.TwoTone.CloudUpload - MessageStatus.DELIVERED -> Icons.TwoTone.CloudDone - MessageStatus.ENROUTE -> Icons.TwoTone.Cloud - MessageStatus.ERROR -> Icons.TwoTone.CloudOff - else -> Icons.TwoTone.Warning - }, - contentDescription = stringResource(R.string.message_delivery_status), - modifier = Modifier - .padding(start = 8.dp) - .clickable { onStatusClick() }, - ) - } } } } @@ -255,7 +264,6 @@ internal fun MessageItem( reactions = emojis, onSendReaction = sendReaction, onShowReactions = onShowReactions, - onSendReply = onReply ) } diff --git a/app/src/main/java/com/geeksville/mesh/ui/message/components/Reaction.kt b/app/src/main/java/com/geeksville/mesh/ui/message/components/Reaction.kt index 27a2e8c56..0b8b47654 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/message/components/Reaction.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/message/components/Reaction.kt @@ -17,6 +17,7 @@ package com.geeksville.mesh.ui.message.components +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable @@ -26,18 +27,19 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Reply import androidx.compose.material.icons.filled.EmojiEmotions -import androidx.compose.material.icons.filled.Reply import androidx.compose.material3.Badge import androidx.compose.material3.BadgedBox +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -100,7 +102,7 @@ fun ReplyButton( onClick = onClick, content = { Icon( - imageVector = Icons.Default.Reply, + imageVector = Icons.AutoMirrored.Filled.Reply, contentDescription = "reply", ) } @@ -151,44 +153,47 @@ fun ReactionRow( reactions: List = emptyList(), onSendReaction: (String) -> Unit = {}, onShowReactions: () -> Unit = {}, - onSendReply: () -> Unit = {}, ) { val emojiList = reduceEmojis( reactions.reversed().map { it.emoji } ).entries - LazyRow( - modifier = modifier - .height(48.dp) - .padding(bottom = 8.dp), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - reverseLayout = true - ) { - item { - ReplyButton { - onSendReply() + AnimatedVisibility(emojiList.isNotEmpty()) { + LazyRow( + modifier = modifier.padding(horizontal = 4.dp), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically, + ) { + items( + emojiList.size + ) { index -> + val entry = emojiList.elementAt(index) + ReactionItem( + emoji = entry.key, + emojiCount = entry.value, + onClick = { + onSendReaction(entry.key) + }, + onLongClick = onShowReactions, + ) } } - item { - ReactionButton( - onSendReaction = onSendReaction, - ) - } - items( - emojiList.size - ) { index -> - val entry = emojiList.elementAt(index) - ReactionItem( - emoji = entry.key, - emojiCount = entry.value, - onClick = { - onSendReaction(entry.key) - }, - onLongClick = onShowReactions, - ) - } + } +} + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun MessageActions( + modifier: Modifier = Modifier, + onSendReaction: (String) -> Unit = {}, + onSendReply: () -> Unit = {}, +) { + Row( + modifier = modifier.wrapContentSize(), + ) { + ReactionButton { onSendReaction(it) } + ReplyButton { onSendReply() } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/node/components/NodeChip.kt b/app/src/main/java/com/geeksville/mesh/ui/node/components/NodeChip.kt index 48d8b04a5..723c7491d 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/node/components/NodeChip.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/node/components/NodeChip.kt @@ -56,7 +56,7 @@ fun NodeChip( AssistChip( modifier = modifier .width(IntrinsicSize.Min) - .defaultMinSize(minHeight = 32.dp, minWidth = 72.dp), + .defaultMinSize(minHeight = 24.dp, minWidth = 48.dp), colors = AssistChipDefaults.assistChipColors( containerColor = Color(nodeColor), labelColor = Color(textColor),