diff --git a/app/src/main/java/com/geeksville/mesh/DataPacket.kt b/app/src/main/java/com/geeksville/mesh/DataPacket.kt index 7d8658412..4753358be 100644 --- a/app/src/main/java/com/geeksville/mesh/DataPacket.kt +++ b/app/src/main/java/com/geeksville/mesh/DataPacket.kt @@ -190,7 +190,7 @@ data class DataPacket( const val PKC_CHANNEL_INDEX = 8 fun nodeNumToDefaultId(n: Int): String = "!%08x".format(n) - fun idToDefaultNodeNum(id: String?): Int? = id?.toLong(16)?.toInt() + fun idToDefaultNodeNum(id: String?): Int? = runCatching { id?.toLong(16)?.toInt() }.getOrNull() override fun createFromParcel(parcel: Parcel): DataPacket { return DataPacket(parcel) diff --git a/app/src/main/java/com/geeksville/mesh/database/NodeRepository.kt b/app/src/main/java/com/geeksville/mesh/database/NodeRepository.kt index 7da648fe2..007f881c7 100644 --- a/app/src/main/java/com/geeksville/mesh/database/NodeRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/database/NodeRepository.kt @@ -67,6 +67,12 @@ class NodeRepository @Inject constructor( .conflate() .stateIn(processLifecycle.coroutineScope, SharingStarted.Eagerly, emptyMap()) + fun getNode(userId: String): NodeEntity = nodeDBbyNum.value.values.find { it.user.id == userId } + ?: NodeEntity( + num = DataPacket.idToDefaultNodeNum(userId) ?: 0, + user = getUser(userId), + ) + fun getUser(nodeNum: Int): MeshProtos.User = getUser(DataPacket.nodeNumToDefaultId(nodeNum)) fun getUser(userId: String): MeshProtos.User = diff --git a/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt index 3634826c0..c36b0c6cb 100644 --- a/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt +++ b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt @@ -33,18 +33,18 @@ data class PacketEntity( @Relation(entity = ReactionEntity::class, parentColumn = "packet_id", entityColumn = "reply_id") val reactions: List = emptyList(), ) { - suspend fun toMessage(getUser: suspend (userId: String?) -> User) = with(packet) { + suspend fun toMessage(getNode: suspend (userId: String?) -> NodeEntity) = with(packet) { Message( uuid = uuid, receivedTime = received_time, - user = getUser(data.from), + node = getNode(data.from), text = data.text.orEmpty(), time = getShortDateTime(data.time), read = read, status = data.status, routingError = routingError, packetId = packetId, - emojis = reactions.toReaction(getUser), + emojis = reactions.toReaction(getNode), ) } } @@ -101,14 +101,14 @@ data class ReactionEntity( ) private suspend fun ReactionEntity.toReaction( - getUser: suspend (userId: String?) -> User + getNode: suspend (userId: String?) -> NodeEntity ) = Reaction( replyId = replyId, - user = getUser(userId), + user = getNode(userId).user, emoji = emoji, timestamp = timestamp, ) private suspend fun List.toReaction( - getUser: suspend (userId: String?) -> User -) = this.map { it.toReaction(getUser) } + getNode: suspend (userId: String?) -> NodeEntity +) = this.map { it.toReaction(getNode) } diff --git a/app/src/main/java/com/geeksville/mesh/model/Message.kt b/app/src/main/java/com/geeksville/mesh/model/Message.kt index 61ef926fc..6dc98b8be 100644 --- a/app/src/main/java/com/geeksville/mesh/model/Message.kt +++ b/app/src/main/java/com/geeksville/mesh/model/Message.kt @@ -18,9 +18,9 @@ package com.geeksville.mesh.model import com.geeksville.mesh.MeshProtos.Routing -import com.geeksville.mesh.MeshProtos.User import com.geeksville.mesh.MessageStatus import com.geeksville.mesh.R +import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.database.entity.Reaction val Routing.Error.stringRes: Int @@ -47,7 +47,7 @@ val Routing.Error.stringRes: Int data class Message( val uuid: Long, val receivedTime: Long, - val user: User, + val node: NodeEntity, val text: String, val time: String, val read: Boolean, diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index a8917719e..2008e253f 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -255,6 +255,7 @@ class UIViewModel @Inject constructor( get() = preferences.getInt(MAP_STYLE_ID, 0) set(value) = preferences.edit { putInt(MAP_STYLE_ID, value) } + fun getNode(userId: String?) = nodeDB.getNode(userId ?: DataPacket.ID_BROADCAST) fun getUser(userId: String?) = nodeDB.getUser(userId ?: DataPacket.ID_BROADCAST) private val _snackbarText = MutableLiveData(null) @@ -330,7 +331,7 @@ class UIViewModel @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) fun getMessagesFrom(contactKey: String) = packetRepository.getMessagesFrom(contactKey) - .mapLatest { list -> list.map { it.toMessage(::getUser) } } + .mapLatest { list -> list.map { it.toMessage(::getNode) } } @OptIn(ExperimentalCoroutinesApi::class) val waypoints = packetRepository.getWaypoints().mapLatest { list -> 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 32853f592..997af5a2d 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 @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card import androidx.compose.material.Chip +import androidx.compose.material.ChipDefaults import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.LocalContentColor @@ -55,16 +56,19 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp +import com.geeksville.mesh.DataPacket import com.geeksville.mesh.MessageStatus import com.geeksville.mesh.R +import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.ui.components.AutoLinkText +import com.geeksville.mesh.ui.preview.NodeEntityPreviewParameterProvider import com.geeksville.mesh.ui.theme.AppTheme @Suppress("LongMethod") @OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) @Composable internal fun MessageItem( - shortName: String?, + node: NodeEntity, messageText: String?, messageTime: String, messageStatus: MessageStatus?, @@ -81,7 +85,7 @@ internal fun MessageItem( .background(color = if (selected) Color.Gray else MaterialTheme.colors.background), verticalAlignment = Alignment.CenterVertically, ) { - val fromLocal = shortName == null + val fromLocal = node.user.id == DataPacket.ID_LOCAL val messageColor = if (fromLocal) R.color.colorMyMsg else R.color.colorMsg val (topStart, topEnd) = if (fromLocal) 12.dp to 4.dp else 4.dp to 12.dp val messageModifier = if (fromLocal) { @@ -110,15 +114,19 @@ internal fun MessageItem( .padding(horizontal = 8.dp), verticalAlignment = Alignment.CenterVertically, ) { - if (shortName != null) { + if (!fromLocal) { Chip( onClick = onChipClick, modifier = Modifier .padding(end = 8.dp) .width(72.dp), + colors = ChipDefaults.chipColors( + backgroundColor = Color(node.colors.second), + contentColor = Color(node.colors.first), + ), ) { Text( - text = shortName, + text = node.user.shortName, modifier = Modifier.fillMaxWidth(), fontSize = MaterialTheme.typography.button.fontSize, fontWeight = FontWeight.Normal, @@ -129,11 +137,14 @@ internal fun MessageItem( Column( modifier = Modifier.padding(top = 8.dp), ) { -// Text( -// text = longName ?: stringResource(id = R.string.unknown_username), -// color = MaterialTheme.colors.onSurface, -// fontSize = MaterialTheme.typography.button.fontSize, -// ) +// if (!fromLocal) { +// Text( +// text = with(node.user) { "$longName ($id)" }, +// modifier = Modifier.padding(bottom = 4.dp), +// color = MaterialTheme.colors.onSurface, +// fontSize = MaterialTheme.typography.caption.fontSize, +// ) +// } AutoLinkText( text = messageText.orEmpty(), style = LocalTextStyle.current.copy( @@ -181,8 +192,7 @@ internal fun MessageItem( private fun MessageItemPreview() { AppTheme { MessageItem( - shortName = stringResource(R.string.some_username), - // longName = stringResource(R.string.unknown_username), + node = NodeEntityPreviewParameterProvider().values.first(), messageText = stringResource(R.string.sample_message), messageTime = "10:00", messageStatus = MessageStatus.DELIVERED, diff --git a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageList.kt b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageList.kt index 42c30588d..3d8ae96a7 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageList.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageList.kt @@ -33,6 +33,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalHapticFeedback import com.geeksville.mesh.DataPacket import com.geeksville.mesh.database.entity.Reaction import com.geeksville.mesh.model.Message @@ -50,6 +52,7 @@ internal fun MessageList( onSendReaction: (String, Int) -> Unit, onClick: (Message) -> Unit = {} ) { + val haptics = LocalHapticFeedback.current val inSelectionMode by remember { derivedStateOf { selectedIds.value.isNotEmpty() } } val listState = rememberLazyListState( initialFirstVisibleItemIndex = messages.indexOfLast { !it.read }.coerceAtLeast(0) @@ -70,10 +73,10 @@ internal fun MessageList( ReactionDialog(reactions) { showReactionDialog = null } } - fun toggle(uuid: Long) = if (selectedIds.value.contains(uuid)) { - selectedIds.value -= uuid + fun MutableState>.toggle(uuid: Long) = if (value.contains(uuid)) { + value -= uuid } else { - selectedIds.value += uuid + value += uuid } LazyColumn( @@ -83,18 +86,21 @@ internal fun MessageList( contentPadding = contentPadding ) { items(messages, key = { it.uuid }) { msg -> - val fromLocal = msg.user.id == DataPacket.ID_LOCAL + val fromLocal = msg.node.user.id == DataPacket.ID_LOCAL val selected by remember { derivedStateOf { selectedIds.value.contains(msg.uuid) } } ReactionRow(fromLocal, msg.emojis) { showReactionDialog = msg.emojis } MessageItem( - shortName = msg.user.shortName.takeIf { !fromLocal }, + node = msg.node, messageText = msg.text, messageTime = msg.time, messageStatus = msg.status, selected = selected, - onClick = { if (inSelectionMode) toggle(msg.uuid) }, - onLongClick = { toggle(msg.uuid) }, + onClick = { if (inSelectionMode) selectedIds.toggle(msg.uuid) }, + onLongClick = { + selectedIds.toggle(msg.uuid) + haptics.performHapticFeedback(HapticFeedbackType.LongPress) + }, onChipClick = { onClick(msg) }, onStatusClick = { showStatusDialog = msg }, onSendReaction = { onSendReaction(it, msg.packetId) },