android: add realtime foreground local notifications with active chat gating
Some checks failed
CI / test (push) Failing after 2m12s

This commit is contained in:
Codex
2026-03-09 14:48:17 +03:00
parent e8574252ca
commit 98492f869d
8 changed files with 73 additions and 1 deletions

View File

@@ -0,0 +1,23 @@
package ru.daemonlord.messenger.core.notifications
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ActiveChatTracker @Inject constructor() {
private val _activeChatId = MutableStateFlow<Long?>(null)
val activeChatId: StateFlow<Long?> = _activeChatId.asStateFlow()
fun setActiveChat(chatId: Long) {
_activeChatId.value = chatId
}
fun clearActiveChat(chatId: Long) {
if (_activeChatId.value == chatId) {
_activeChatId.value = null
}
}
}

View File

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

View File

@@ -7,6 +7,9 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
import ru.daemonlord.messenger.core.notifications.ActiveChatTracker
import ru.daemonlord.messenger.core.notifications.ChatNotificationPayload
import ru.daemonlord.messenger.core.notifications.NotificationDispatcher
import ru.daemonlord.messenger.data.message.local.dao.MessageDao
import ru.daemonlord.messenger.data.message.local.entity.MessageEntity
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
@@ -21,6 +24,8 @@ class HandleRealtimeEventsUseCase @Inject constructor(
private val chatRepository: ChatRepository,
private val chatDao: ChatDao,
private val messageDao: MessageDao,
private val notificationDispatcher: NotificationDispatcher,
private val activeChatTracker: ActiveChatTracker,
) {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
@@ -69,6 +74,27 @@ class HandleRealtimeEventsUseCase @Inject constructor(
updatedSortAt = event.createdAt,
)
chatDao.incrementUnread(chatId = event.chatId)
val activeChatId = activeChatTracker.activeChatId.value
if (activeChatId != event.chatId) {
val title = chatDao.getChatDisplayTitle(event.chatId) ?: "New message"
val body = event.text?.takeIf { it.isNotBlank() }
?: when (event.type?.lowercase()) {
"image" -> "Photo"
"video" -> "Video"
"audio" -> "Audio"
"voice" -> "Voice message"
"file" -> "File"
else -> "Open chat"
}
notificationDispatcher.showChatMessage(
ChatNotificationPayload(
chatId = event.chatId,
messageId = event.messageId,
title = title,
body = body,
)
)
}
}
is RealtimeEvent.MessageUpdated -> {

View File

@@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.daemonlord.messenger.core.notifications.ActiveChatTracker
import ru.daemonlord.messenger.domain.chat.usecase.ObserveChatUseCase
import ru.daemonlord.messenger.domain.chat.usecase.ObserveChatsUseCase
import ru.daemonlord.messenger.domain.common.AppError
@@ -53,6 +54,7 @@ class ChatViewModel @Inject constructor(
private val observeChatUseCase: ObserveChatUseCase,
private val observeChatsUseCase: ObserveChatsUseCase,
private val handleRealtimeEventsUseCase: HandleRealtimeEventsUseCase,
private val activeChatTracker: ActiveChatTracker,
) : ViewModel() {
private val chatId: Long = checkNotNull(savedStateHandle["chatId"])
@@ -62,6 +64,7 @@ class ChatViewModel @Inject constructor(
private var lastReadMessageId: Long? = null
init {
activeChatTracker.setActiveChat(chatId)
handleRealtimeEventsUseCase.start()
observeChatPermissions()
observeMessages()
@@ -587,4 +590,10 @@ class ChatViewModel @Inject constructor(
is AppError.Unknown -> "Unknown error."
}
}
override fun onCleared() {
activeChatTracker.clearActiveChat(chatId)
handleRealtimeEventsUseCase.stop()
super.onCleared()
}
}