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>
This commit is contained in:
James Rich
2026-04-17 09:42:44 -05:00
parent 9c75f5a3f4
commit 6d70d154e6
8 changed files with 26 additions and 12 deletions

View File

@@ -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) {}

View File

@@ -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)

View File

@@ -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()
}

View File

@@ -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) =

View File

@@ -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 {

View File

@@ -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()
}

View File

@@ -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) {}

View File

@@ -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)
}