android: add mention override for muted chat notifications
Some checks failed
CI / test (push) Failing after 2m18s

This commit is contained in:
Codex
2026-03-09 14:52:28 +03:00
parent 98492f869d
commit 670fcd721d
8 changed files with 60 additions and 2 deletions

View File

@@ -295,3 +295,9 @@
- Wired realtime receive-message handling to trigger local notification via `NotificationDispatcher` when chat is not active.
- Added chat title lookup helper in `ChatDao` for notification titles.
- Added explicit realtime stop in `ChatViewModel.onCleared()` to avoid stale collectors.
### Step 49 - Mention override for muted chats
- Extended realtime receive-message model/parsing with `isMention` flag support.
- Added muted-chat guard in realtime notification flow: muted chats stay silent unless message is a mention.
- Routed mention notifications to mentions channel/priority via `NotificationDispatcher`.
- Added parser unit test for mention-flag mapping.

View File

@@ -84,6 +84,9 @@ interface ChatDao {
@Query("SELECT display_title FROM chats WHERE id = :chatId LIMIT 1")
suspend fun getChatDisplayTitle(chatId: Long): String?
@Query("SELECT muted FROM chats WHERE id = :chatId LIMIT 1")
suspend fun isChatMuted(chatId: Long): Boolean?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertChats(chats: List<ChatEntity>)

View File

@@ -37,6 +37,11 @@ class RealtimeEventParser @Inject constructor(
text = messageObject?.get("text").stringOrNull(),
type = messageObject?.get("type").stringOrNull(),
createdAt = messageObject?.get("created_at").stringOrNull(),
isMention = messageObject?.get("is_mention").boolOrNull()
?: payload["is_mention"].boolOrNull()
?: messageObject?.get("mentions_me").boolOrNull()
?: payload["mentions_me"].boolOrNull()
?: false,
)
}
@@ -119,4 +124,13 @@ class RealtimeEventParser @Inject constructor(
private fun JsonElement?.longOrNull(): Long? {
return this?.jsonPrimitive?.contentOrNull?.toLongOrNull()
}
private fun JsonElement?.boolOrNull(): Boolean? {
val raw = this?.jsonPrimitive?.contentOrNull?.trim()?.lowercase() ?: return null
return when (raw) {
"true", "1" -> true
"false", "0" -> false
else -> null
}
}
}

View File

@@ -11,6 +11,7 @@ sealed interface RealtimeEvent {
val text: String?,
val type: String?,
val createdAt: String?,
val isMention: Boolean,
) : RealtimeEvent
data class MessageUpdated(

View File

@@ -75,7 +75,8 @@ class HandleRealtimeEventsUseCase @Inject constructor(
)
chatDao.incrementUnread(chatId = event.chatId)
val activeChatId = activeChatTracker.activeChatId.value
if (activeChatId != event.chatId) {
val muted = chatDao.isChatMuted(event.chatId) == true
if (activeChatId != event.chatId && (!muted || event.isMention)) {
val title = chatDao.getChatDisplayTitle(event.chatId) ?: "New message"
val body = event.text?.takeIf { it.isNotBlank() }
?: when (event.type?.lowercase()) {
@@ -92,6 +93,7 @@ class HandleRealtimeEventsUseCase @Inject constructor(
messageId = event.messageId,
title = title,
body = body,
isMention = event.isMention,
)
)
}

View File

@@ -141,6 +141,10 @@ class NetworkChatRepositoryTest {
return chats.value.firstOrNull { it.id == chatId }?.displayTitle
}
override suspend fun isChatMuted(chatId: Long): Boolean? {
return chats.value.firstOrNull { it.id == chatId }?.muted
}
override suspend fun upsertChats(chats: List<ChatEntity>) {
val merged = this.chats.value.associateBy { it.id }.toMutableMap()
chats.forEach { merged[it.id] = it }

View File

@@ -2,6 +2,7 @@ package ru.daemonlord.messenger.data.realtime
import kotlinx.serialization.json.Json
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import ru.daemonlord.messenger.domain.realtime.model.RealtimeEvent
@@ -70,6 +71,33 @@ class RealtimeEventParserTest {
assertEquals(5L, mapped.senderId)
assertEquals(100L, mapped.replyToMessageId)
assertEquals("hi", mapped.text)
assertFalse(mapped.isMention)
}
@Test
fun parseReceiveMessage_withMentionFlag_mapsMention() {
val payload = """
{
"event": "receive_message",
"payload": {
"chat_id": 88,
"message": {
"id": 9002,
"chat_id": 88,
"sender_id": 5,
"type": "text",
"text": "@you hi",
"created_at": "2026-03-08T12:00:00Z",
"is_mention": true
}
}
}
""".trimIndent()
val event = parser.parse(payload)
assertTrue(event is RealtimeEvent.ReceiveMessage)
val mapped = event as RealtimeEvent.ReceiveMessage
assertTrue(mapped.isMention)
}
@Test

View File

@@ -92,7 +92,7 @@
- [x] Локальные уведомления для foreground
- [x] Notification channels (Android)
- [x] Deep links: open chat/message
- [ ] Mention override для muted чатов
- [x] Mention override для muted чатов
## 13. UI/UX и темы
- [ ] Светлая/темная тема (читаемая)