From 6d70d154e69224e45dee4bc677135402fb8683e1 Mon Sep 17 00:00:00 2001 From: James Rich Date: Fri, 17 Apr 2026 09:42:44 -0500 Subject: [PATCH] refactor(notifications): share markConversationRead helper across receivers Extract the 'clear unread count + cancel message notification' pair into a single suspend helper on MeshServiceNotifications so ReplyReceiver, MarkAsReadReceiver, and ReactionReceiver use one consistent code path. ReactionReceiver now also clears unread and cancels the notification once the reaction dispatch succeeds, matching the other receivers. Receivers that only depended on PacketRepository for this pair drop that injection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- app/src/test/kotlin/org/meshtastic/app/service/Fakes.kt | 2 ++ .../meshtastic/core/repository/MeshServiceNotifications.kt | 7 +++++++ .../org/meshtastic/core/service/MarkAsReadReceiver.kt | 7 +------ .../core/service/MeshServiceNotificationsImpl.kt | 5 +++++ .../kotlin/org/meshtastic/core/service/ReactionReceiver.kt | 4 ++++ .../kotlin/org/meshtastic/core/service/ReplyReceiver.kt | 7 +------ .../core/testing/FakeMeshServiceNotifications.kt | 2 ++ .../notification/DesktopMeshServiceNotifications.kt | 4 ++++ 8 files changed, 26 insertions(+), 12 deletions(-) diff --git a/app/src/test/kotlin/org/meshtastic/app/service/Fakes.kt b/app/src/test/kotlin/org/meshtastic/app/service/Fakes.kt index 37c19f477..46d5ed27c 100644 --- a/app/src/test/kotlin/org/meshtastic/app/service/Fakes.kt +++ b/app/src/test/kotlin/org/meshtastic/app/service/Fakes.kt @@ -74,6 +74,8 @@ class FakeMeshServiceNotifications : MeshServiceNotifications { override fun cancelMessageNotification(contactKey: String) {} + override suspend fun markConversationRead(contactKey: String) {} + override fun cancelLowBatteryNotification(node: Node) {} override fun clearClientNotification(notification: ClientNotification) {} diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshServiceNotifications.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshServiceNotifications.kt index a68157943..1216f29a3 100644 --- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshServiceNotifications.kt +++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshServiceNotifications.kt @@ -67,6 +67,13 @@ interface MeshServiceNotifications { fun cancelMessageNotification(contactKey: String) + /** + * Marks the conversation for [contactKey] as read: clears its unread count in the packet repository and cancels the + * posted message notification (and the group summary). Intended for use by notification action receivers (reply, + * mark-as-read, reaction) to keep behavior consistent. + */ + suspend fun markConversationRead(contactKey: String) + fun cancelLowBatteryNotification(node: Node) fun clearClientNotification(notification: ClientNotification) diff --git a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MarkAsReadReceiver.kt b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MarkAsReadReceiver.kt index 36c26c879..8eb3af994 100644 --- a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MarkAsReadReceiver.kt +++ b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MarkAsReadReceiver.kt @@ -24,18 +24,14 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import org.meshtastic.core.common.util.nowMillis import org.meshtastic.core.di.CoroutineDispatchers import org.meshtastic.core.repository.MeshServiceNotifications -import org.meshtastic.core.repository.PacketRepository /** A [BroadcastReceiver] that handles "Mark as read" actions from notifications. */ class MarkAsReadReceiver : BroadcastReceiver(), KoinComponent { - private val packetRepository: PacketRepository by inject() - private val serviceNotifications: MeshServiceNotifications by inject() private val dispatchers: CoroutineDispatchers by inject() @@ -54,8 +50,7 @@ class MarkAsReadReceiver : scope.launch { try { - packetRepository.clearUnreadCount(contactKey, nowMillis) - serviceNotifications.cancelMessageNotification(contactKey) + serviceNotifications.markConversationRead(contactKey) } finally { pendingResult.finish() } diff --git a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceNotificationsImpl.kt b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceNotificationsImpl.kt index 3d1684cb6..97ff46766 100644 --- a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceNotificationsImpl.kt +++ b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceNotificationsImpl.kt @@ -515,6 +515,11 @@ class MeshServiceNotificationsImpl( notificationManager.cancel(SUMMARY_ID) } + override suspend fun markConversationRead(contactKey: String) { + packetRepository.value.clearUnreadCount(contactKey, nowMillis) + cancelMessageNotification(contactKey) + } + override fun cancelLowBatteryNotification(node: Node) = notificationManager.cancel(node.num) override fun clearClientNotification(notification: ClientNotification) = diff --git a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/ReactionReceiver.kt b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/ReactionReceiver.kt index f4db74403..304dff076 100644 --- a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/ReactionReceiver.kt +++ b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/ReactionReceiver.kt @@ -27,6 +27,7 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.inject import org.meshtastic.core.di.CoroutineDispatchers import org.meshtastic.core.model.service.ServiceAction +import org.meshtastic.core.repository.MeshServiceNotifications import org.meshtastic.core.repository.ServiceRepository /** @@ -41,6 +42,8 @@ class ReactionReceiver : private val serviceRepository: ServiceRepository by inject() + private val meshServiceNotifications: MeshServiceNotifications by inject() + private val dispatchers: CoroutineDispatchers by inject() private val scope by lazy { CoroutineScope(SupervisorJob() + dispatchers.io) } @@ -57,6 +60,7 @@ class ReactionReceiver : scope.launch { try { serviceRepository.onServiceAction(ServiceAction.Reaction(reaction, replyId, contactKey)) + meshServiceNotifications.markConversationRead(contactKey) } catch (e: Exception) { Logger.e(e) { "Error sending reaction" } } finally { diff --git a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/ReplyReceiver.kt b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/ReplyReceiver.kt index 13fd92758..8ab6590b7 100644 --- a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/ReplyReceiver.kt +++ b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/ReplyReceiver.kt @@ -25,12 +25,10 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import org.meshtastic.core.common.util.nowMillis import org.meshtastic.core.di.CoroutineDispatchers import org.meshtastic.core.model.DataPacket import org.meshtastic.core.model.RadioController import org.meshtastic.core.repository.MeshServiceNotifications -import org.meshtastic.core.repository.PacketRepository /** * A [BroadcastReceiver] that handles inline replies from notifications. @@ -46,8 +44,6 @@ class ReplyReceiver : private val meshServiceNotifications: MeshServiceNotifications by inject() - private val packetRepository: PacketRepository by inject() - private val dispatchers: CoroutineDispatchers by inject() private val scope by lazy { CoroutineScope(dispatchers.io + SupervisorJob()) } @@ -69,8 +65,7 @@ class ReplyReceiver : scope.launch { try { sendMessage(message, contactKey) - packetRepository.clearUnreadCount(contactKey, nowMillis) - meshServiceNotifications.cancelMessageNotification(contactKey) + meshServiceNotifications.markConversationRead(contactKey) } finally { pendingResult.finish() } diff --git a/core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeMeshServiceNotifications.kt b/core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeMeshServiceNotifications.kt index 4f0a4b153..923d2e8aa 100644 --- a/core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeMeshServiceNotifications.kt +++ b/core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeMeshServiceNotifications.kt @@ -67,6 +67,8 @@ class FakeMeshServiceNotifications : MeshServiceNotifications { override fun cancelMessageNotification(contactKey: String) {} + override suspend fun markConversationRead(contactKey: String) {} + override fun cancelLowBatteryNotification(node: Node) {} override fun clearClientNotification(notification: ClientNotification) {} diff --git a/desktop/src/main/kotlin/org/meshtastic/desktop/notification/DesktopMeshServiceNotifications.kt b/desktop/src/main/kotlin/org/meshtastic/desktop/notification/DesktopMeshServiceNotifications.kt index 4cda00251..fd30a5be0 100644 --- a/desktop/src/main/kotlin/org/meshtastic/desktop/notification/DesktopMeshServiceNotifications.kt +++ b/desktop/src/main/kotlin/org/meshtastic/desktop/notification/DesktopMeshServiceNotifications.kt @@ -154,6 +154,10 @@ class DesktopMeshServiceNotifications(private val notificationManager: Notificat notificationManager.cancel(contactKey.hashCode()) } + override suspend fun markConversationRead(contactKey: String) { + notificationManager.cancel(contactKey.hashCode()) + } + override fun cancelLowBatteryNotification(node: Node) { notificationManager.cancel(node.num) }