Skip to content

Commit 49f6ffe

Browse files
authored
feat: Enhance message notifications with history and actions (#4133)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
1 parent 43aca3c commit 49f6ffe

11 files changed

Lines changed: 496 additions & 43 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@
221221
</intent-filter>
222222
</receiver>
223223
<receiver android:name="com.geeksville.mesh.service.ReplyReceiver"/>
224+
<receiver android:name="com.geeksville.mesh.service.MarkAsReadReceiver"/>
225+
<receiver android:name="com.geeksville.mesh.service.ReactionReceiver"/>
224226

225227
<!-- allow for plugin discovery -->
226228
<activity
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2025-2026 Meshtastic LLC
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
package com.geeksville.mesh.service
18+
19+
import android.content.BroadcastReceiver
20+
import android.content.Context
21+
import android.content.Intent
22+
import dagger.hilt.android.AndroidEntryPoint
23+
import kotlinx.coroutines.CoroutineScope
24+
import kotlinx.coroutines.Dispatchers
25+
import kotlinx.coroutines.SupervisorJob
26+
import kotlinx.coroutines.launch
27+
import org.meshtastic.core.data.repository.PacketRepository
28+
import org.meshtastic.core.service.MeshServiceNotifications
29+
import javax.inject.Inject
30+
31+
/** A [BroadcastReceiver] that handles "Mark as read" actions from notifications. */
32+
@AndroidEntryPoint
33+
class MarkAsReadReceiver : BroadcastReceiver() {
34+
@Inject lateinit var packetRepository: PacketRepository
35+
36+
@Inject lateinit var meshServiceNotifications: MeshServiceNotifications
37+
38+
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
39+
40+
companion object {
41+
const val MARK_AS_READ_ACTION = "com.geeksville.mesh.MARK_AS_READ_ACTION"
42+
const val CONTACT_KEY = "contactKey"
43+
}
44+
45+
override fun onReceive(context: Context, intent: Intent) {
46+
if (intent.action == MARK_AS_READ_ACTION) {
47+
val contactKey = intent.getStringExtra(CONTACT_KEY) ?: return
48+
val pendingResult = goAsync()
49+
scope.launch {
50+
try {
51+
packetRepository.clearUnreadCount(contactKey, System.currentTimeMillis())
52+
meshServiceNotifications.cancelMessageNotification(contactKey)
53+
} finally {
54+
pendingResult.finish()
55+
}
56+
}
57+
}
58+
}
59+
}

app/src/main/java/com/geeksville/mesh/service/MeshDataHandler.kt

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,11 +521,12 @@ constructor(
521521
}
522522

523523
private fun rememberReaction(packet: MeshPacket) = scope.handledLaunch {
524+
val emoji = packet.decoded.payload.toByteArray().decodeToString()
524525
val reaction =
525526
ReactionEntity(
526527
replyId = packet.decoded.replyId,
527528
userId = dataMapper.toNodeID(packet.from),
528-
emoji = packet.decoded.payload.toByteArray().decodeToString(),
529+
emoji = emoji,
529530
timestamp = System.currentTimeMillis(),
530531
snr = packet.rxSnr,
531532
rssi = packet.rxRssi,
@@ -537,6 +538,31 @@ constructor(
537538
},
538539
)
539540
packetRepository.get().insertReaction(reaction)
541+
542+
// Find the original packet to get the contactKey
543+
packetRepository.get().getPacketByPacketId(packet.decoded.replyId)?.let { original ->
544+
val contactKey = original.packet.contact_key
545+
val isMuted = packetRepository.get().getContactSettings(contactKey).isMuted
546+
if (!isMuted) {
547+
val channelName =
548+
if (original.packet.data.to == DataPacket.ID_BROADCAST) {
549+
radioConfigRepository.channelSetFlow
550+
.first()
551+
.settingsList
552+
.getOrNull(original.packet.data.channel)
553+
?.name
554+
} else {
555+
null
556+
}
557+
serviceNotifications.updateReactionNotification(
558+
contactKey,
559+
getSenderName(dataMapper.toDataPacket(packet)!!),
560+
emoji,
561+
original.packet.data.to == DataPacket.ID_BROADCAST,
562+
channelName,
563+
)
564+
}
565+
}
540566
}
541567

542568
private fun currentTransport(address: String? = meshPrefs.deviceAddress): String = when (address?.firstOrNull()) {

0 commit comments

Comments
 (0)