fix(auto): don't re-post conversation notif on outgoing messages

rememberDataPacket() was invoking handlePacketNotification() for
outgoing packets too, which made our own reply race with the
cancel issued by ReplyReceiver and repost the conversation with
ourselves as the visible sender (lastMessage.node == ourNode).

Also harden ensureShortcutForNotification for DMs: the remote
contact is deterministic from contactKey (channel + nodeId), so
derive the shortcut Person from the resolved contact node rather
than whatever message happens to be newest in history. This keeps
the Android Auto HUN labelled correctly even if the message list
ends with an outgoing packet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
James Rich 2026-04-17 09:12:48 -05:00
parent 72e27e32cc
commit c1073f3e12
3 changed files with 18 additions and 14 deletions

View file

@ -357,7 +357,7 @@ class MeshDataHandlerImpl(
read = fromLocal || isFiltered,
filtered = isFiltered,
)
if (!isFiltered) {
if (!isFiltered && !fromLocal) {
handlePacketNotification(dataPacket, contactKey, updateNotification)
}
}

View file

@ -782,26 +782,33 @@ class MeshServiceNotificationsImpl(
channelName: String?,
lastMessage: Message,
) {
val contactNode =
if (isBroadcast) {
null
} else {
// contactKey format: "${channel}${nodeId}"; the remote contact is the node keyed by nodeId,
// which is stable regardless of whether the latest message in history is incoming or outgoing.
val nodeId = contactKey.drop(1)
nodeRepository.value.getNode(nodeId)
}
val person =
if (isBroadcast) {
Person.Builder().setName(channelName ?: contactKey).setKey(contactKey).build()
} else {
val node = contactNode ?: lastMessage.node
Person.Builder()
.setName(lastMessage.node.user.long_name)
.setKey(lastMessage.node.user.id)
.setIcon(
createPersonIcon(
lastMessage.node.user.short_name,
lastMessage.node.colors.second,
lastMessage.node.colors.first,
),
)
.setName(node.user.long_name)
.setKey(node.user.id)
.setIcon(createPersonIcon(node.user.short_name, node.colors.second, node.colors.first))
.build()
}
val label =
when {
isBroadcast -> channelName ?: contactKey
else -> lastMessage.node.user.long_name.ifEmpty { lastMessage.node.user.short_name }
else -> {
val node = contactNode ?: lastMessage.node
node.user.long_name.ifEmpty { node.user.short_name }
}
}
shortcutManager.value.ensureConversationShortcut(contactKey, person, label)
}

View file

@ -20,7 +20,6 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.core.app.RemoteInput
import co.touchlab.kermit.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
@ -65,7 +64,6 @@ class ReplyReceiver :
if (remoteInput != null) {
val contactKey = intent.getStringExtra(CONTACT_KEY) ?: ""
val message = remoteInput.getCharSequence(KEY_TEXT_REPLY)?.toString() ?: ""
Logger.d { "ReplyReceiver: onReceive contactKey='$contactKey' msgLen=${message.length}" }
val pendingResult = goAsync()
scope.launch {
@ -73,7 +71,6 @@ class ReplyReceiver :
sendMessage(message, contactKey)
packetRepository.clearUnreadCount(contactKey, nowMillis)
meshServiceNotifications.cancelMessageNotification(contactKey)
Logger.d { "ReplyReceiver: reply flow complete for contactKey='$contactKey'" }
} finally {
pendingResult.finish()
}