From 468c4ab6b73f483b07b037c9b7380ac239cb734e Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Wed, 18 Jun 2025 16:05:02 +0000 Subject: [PATCH] fix: Fetch original message when displaying a reply (#2152) --- .../mesh/database/PacketRepository.kt | 20 ++++++++++++++++++- .../geeksville/mesh/database/dao/PacketDao.kt | 7 +++++-- .../java/com/geeksville/mesh/model/Message.kt | 1 + .../java/com/geeksville/mesh/model/UIState.kt | 16 +++++++++++++-- .../geeksville/mesh/ui/message/MessageList.kt | 5 ----- .../mesh/ui/message/components/MessageItem.kt | 3 +-- 6 files changed, 40 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt b/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt index 8f95ca750..e805c97a1 100644 --- a/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt @@ -24,8 +24,10 @@ import com.geeksville.mesh.database.dao.PacketDao import com.geeksville.mesh.database.entity.ContactSettings import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.database.entity.ReactionEntity +import com.geeksville.mesh.model.Node import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.withContext import javax.inject.Inject @@ -58,7 +60,19 @@ class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Laz packetDao.insert(packet) } - fun getMessagesFrom(contact: String) = packetDao.getMessagesFrom(contact) + suspend fun getMessagesFrom(contact: String, getNode: suspend (String?) -> Node) = + withContext(Dispatchers.IO) { + packetDao.getMessagesFrom(contact).mapLatest { packets -> + packets.map { packet -> + val message = packet.toMessage(getNode) + message.replyId.takeIf { it != null && it != 0 } + ?.let { getPacketByPacketId(it) } + ?.toMessage(getNode) + ?.let { originalMessage -> message.copy(originalMessage = originalMessage) } + ?: message + } + } + } suspend fun updateMessageStatus(d: DataPacket, m: MessageStatus) = withContext(Dispatchers.IO) { packetDao.updateMessageStatus(d, m) @@ -72,6 +86,10 @@ class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Laz packetDao.getPacketById(requestId) } + suspend fun getPacketByPacketId(packetId: Int) = withContext(Dispatchers.IO) { + packetDao.getPacketByPacketId(packetId) + } + suspend fun deleteMessages(uuidList: List) = withContext(Dispatchers.IO) { for (chunk in uuidList.chunked(500)) { // limit number of UUIDs per query packetDao.deleteMessages(chunk) diff --git a/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt b/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt index f6ef9cd43..122f5e644 100644 --- a/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt +++ b/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt @@ -19,15 +19,15 @@ package com.geeksville.mesh.database.dao import androidx.room.Dao import androidx.room.MapColumn -import androidx.room.Update import androidx.room.Query import androidx.room.Transaction +import androidx.room.Update import androidx.room.Upsert import com.geeksville.mesh.DataPacket import com.geeksville.mesh.MessageStatus import com.geeksville.mesh.database.entity.ContactSettings -import com.geeksville.mesh.database.entity.PacketEntity import com.geeksville.mesh.database.entity.Packet +import com.geeksville.mesh.database.entity.PacketEntity import com.geeksville.mesh.database.entity.ReactionEntity import kotlinx.coroutines.flow.Flow @@ -174,6 +174,9 @@ interface PacketDao { ) suspend fun getPacketById(requestId: Int): Packet? + @Query("SELECT * FROM packet WHERE packet_id = :packetId LIMIT 1") + suspend fun getPacketByPacketId(packetId: Int): PacketEntity? + @Transaction suspend fun getQueuedPackets(): List? = getDataPackets().filter { it.status == MessageStatus.QUEUED } 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 aa2a3dddb..482bcc9f5 100644 --- a/app/src/main/java/com/geeksville/mesh/model/Message.kt +++ b/app/src/main/java/com/geeksville/mesh/model/Message.kt @@ -60,6 +60,7 @@ data class Message( val rssi: Int, val hopsAway: Int, val replyId: Int?, + val originalMessage: Message? = null, ) { fun getStatusStringRes(): Pair { val title = if (routingError > 0) R.string.error else R.string.message_delivery_status 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 9f7729796..157f344e8 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -517,8 +517,20 @@ class UIViewModel @Inject constructor( initialValue = emptyList(), ) - fun getMessagesFrom(contactKey: String) = packetRepository.getMessagesFrom(contactKey) - .mapLatest { list -> list.map { it.toMessage(::getNode) } } + fun getMessagesFrom(contactKey: String): StateFlow> { + _contactKeyForMessages.value = contactKey + return messagesForContactKey + } + + private val _contactKeyForMessages: MutableStateFlow = MutableStateFlow(null) + private val messagesForContactKey: StateFlow> = + _contactKeyForMessages.filterNotNull().flatMapLatest { contactKey -> + packetRepository.getMessagesFrom(contactKey, ::getNode) + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = emptyList(), + ) val waypoints = packetRepository.getWaypoints().mapLatest { list -> list.associateBy { packet -> packet.data.waypoint!!.id } diff --git a/app/src/main/java/com/geeksville/mesh/ui/message/MessageList.kt b/app/src/main/java/com/geeksville/mesh/ui/message/MessageList.kt index 60af1fbb3..1406c1f0c 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/message/MessageList.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/message/MessageList.kt @@ -171,10 +171,6 @@ internal fun MessageList( var node by remember { mutableStateOf(nodes.find { it.num == msg.node.num } ?: msg.node) } - val originalMessage = messages.find { it.packetId == msg.replyId } - LaunchedEffect(nodes, messages) { - node = nodes.find { it.num == msg.node.num } ?: msg.node - } MessageItem( node = node, ourNode = ourNode!!, @@ -192,7 +188,6 @@ internal fun MessageList( sendReaction = { onSendReaction(it, msg.packetId) }, onShowReactions = { showReactionDialog = msg.emojis }, isConnected = isConnected, - originalMessage = originalMessage, onNavigateToOriginalMessage = { coroutineScope.launch { listState.animateScrollToItem( 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 07f05986c..a84f467eb 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 @@ -89,7 +89,6 @@ internal fun MessageItem( onAction: (NodeMenuAction) -> Unit = {}, onStatusClick: () -> Unit = {}, isConnected: Boolean, - originalMessage: Message? = null, onNavigateToOriginalMessage: (Int) -> Unit = {}, ) = Column( modifier = modifier @@ -124,7 +123,7 @@ internal fun MessageItem( modifier = Modifier .fillMaxWidth(), ) { - if (originalMessage != null) { + message.originalMessage?.let { originalMessage -> val originalMessageIsFromLocal = originalMessage.node.user.id == DataPacket.ID_LOCAL val originalMessageNode = if (originalMessageIsFromLocal) ourNode else originalMessage.node