android: add datastore notification settings and gating usecase
Some checks failed
CI / test (push) Failing after 2m6s
Some checks failed
CI / test (push) Failing after 2m6s
This commit is contained in:
@@ -301,3 +301,9 @@
|
|||||||
- Added muted-chat guard in realtime notification flow: muted chats stay silent unless message is a mention.
|
- 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`.
|
- Routed mention notifications to mentions channel/priority via `NotificationDispatcher`.
|
||||||
- Added parser unit test for mention-flag mapping.
|
- Added parser unit test for mention-flag mapping.
|
||||||
|
|
||||||
|
### Step 50 - Notification settings storage (DataStore)
|
||||||
|
- Added domain notification settings models/repository contracts (global + per-chat override).
|
||||||
|
- Added `DataStoreNotificationSettingsRepository` with persistence for global flags and per-chat override mode.
|
||||||
|
- Added `ShouldShowMessageNotificationUseCase` and wired realtime notifications through it.
|
||||||
|
- Added unit tests for DataStore notification settings repository and notification visibility use case.
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package ru.daemonlord.messenger.data.notifications.repository
|
||||||
|
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import ru.daemonlord.messenger.domain.notifications.model.ChatNotificationOverride
|
||||||
|
import ru.daemonlord.messenger.domain.notifications.model.NotificationSettings
|
||||||
|
import ru.daemonlord.messenger.domain.notifications.repository.NotificationSettingsRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class DataStoreNotificationSettingsRepository @Inject constructor(
|
||||||
|
private val dataStore: DataStore<Preferences>,
|
||||||
|
) : NotificationSettingsRepository {
|
||||||
|
|
||||||
|
override fun observeSettings(): Flow<NotificationSettings> {
|
||||||
|
return dataStore.data.map { preferences ->
|
||||||
|
NotificationSettings(
|
||||||
|
globalEnabled = preferences[GLOBAL_ENABLED_KEY] ?: true,
|
||||||
|
previewEnabled = preferences[PREVIEW_ENABLED_KEY] ?: true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getSettings(): NotificationSettings {
|
||||||
|
return observeSettings().first()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setGlobalEnabled(enabled: Boolean) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[GLOBAL_ENABLED_KEY] = enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setPreviewEnabled(enabled: Boolean) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[PREVIEW_ENABLED_KEY] = enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun observeChatOverride(chatId: Long): Flow<ChatNotificationOverride> {
|
||||||
|
return dataStore.data.map { preferences ->
|
||||||
|
preferences.chatOverride(chatId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getChatOverride(chatId: Long): ChatNotificationOverride {
|
||||||
|
return observeChatOverride(chatId).first()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setChatOverride(chatId: Long, mode: ChatNotificationOverride) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[chatOverrideKey(chatId)] = mode.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clearChatOverride(chatId: Long) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences.remove(chatOverrideKey(chatId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Preferences.chatOverride(chatId: Long): ChatNotificationOverride {
|
||||||
|
return this[chatOverrideKey(chatId)]
|
||||||
|
?.let { runCatching { ChatNotificationOverride.valueOf(it) }.getOrNull() }
|
||||||
|
?: ChatNotificationOverride.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun chatOverrideKey(chatId: Long) = stringPreferencesKey("notification_chat_override_$chatId")
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
val GLOBAL_ENABLED_KEY = booleanPreferencesKey("notification_global_enabled")
|
||||||
|
val PREVIEW_ENABLED_KEY = booleanPreferencesKey("notification_preview_enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -8,10 +8,12 @@ import ru.daemonlord.messenger.data.auth.repository.NetworkAuthRepository
|
|||||||
import ru.daemonlord.messenger.data.chat.repository.NetworkChatRepository
|
import ru.daemonlord.messenger.data.chat.repository.NetworkChatRepository
|
||||||
import ru.daemonlord.messenger.data.media.repository.NetworkMediaRepository
|
import ru.daemonlord.messenger.data.media.repository.NetworkMediaRepository
|
||||||
import ru.daemonlord.messenger.data.message.repository.NetworkMessageRepository
|
import ru.daemonlord.messenger.data.message.repository.NetworkMessageRepository
|
||||||
|
import ru.daemonlord.messenger.data.notifications.repository.DataStoreNotificationSettingsRepository
|
||||||
import ru.daemonlord.messenger.domain.auth.repository.AuthRepository
|
import ru.daemonlord.messenger.domain.auth.repository.AuthRepository
|
||||||
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
||||||
import ru.daemonlord.messenger.domain.media.repository.MediaRepository
|
import ru.daemonlord.messenger.domain.media.repository.MediaRepository
|
||||||
import ru.daemonlord.messenger.domain.message.repository.MessageRepository
|
import ru.daemonlord.messenger.domain.message.repository.MessageRepository
|
||||||
|
import ru.daemonlord.messenger.domain.notifications.repository.NotificationSettingsRepository
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@@ -41,4 +43,10 @@ abstract class RepositoryModule {
|
|||||||
abstract fun bindMediaRepository(
|
abstract fun bindMediaRepository(
|
||||||
repository: NetworkMediaRepository,
|
repository: NetworkMediaRepository,
|
||||||
): MediaRepository
|
): MediaRepository
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun bindNotificationSettingsRepository(
|
||||||
|
repository: DataStoreNotificationSettingsRepository,
|
||||||
|
): NotificationSettingsRepository
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package ru.daemonlord.messenger.domain.notifications.model
|
||||||
|
|
||||||
|
enum class ChatNotificationOverride {
|
||||||
|
DEFAULT,
|
||||||
|
ENABLED,
|
||||||
|
MUTED,
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.daemonlord.messenger.domain.notifications.model
|
||||||
|
|
||||||
|
data class NotificationSettings(
|
||||||
|
val globalEnabled: Boolean = true,
|
||||||
|
val previewEnabled: Boolean = true,
|
||||||
|
)
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package ru.daemonlord.messenger.domain.notifications.repository
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import ru.daemonlord.messenger.domain.notifications.model.ChatNotificationOverride
|
||||||
|
import ru.daemonlord.messenger.domain.notifications.model.NotificationSettings
|
||||||
|
|
||||||
|
interface NotificationSettingsRepository {
|
||||||
|
fun observeSettings(): Flow<NotificationSettings>
|
||||||
|
suspend fun getSettings(): NotificationSettings
|
||||||
|
suspend fun setGlobalEnabled(enabled: Boolean)
|
||||||
|
suspend fun setPreviewEnabled(enabled: Boolean)
|
||||||
|
|
||||||
|
fun observeChatOverride(chatId: Long): Flow<ChatNotificationOverride>
|
||||||
|
suspend fun getChatOverride(chatId: Long): ChatNotificationOverride
|
||||||
|
suspend fun setChatOverride(chatId: Long, mode: ChatNotificationOverride)
|
||||||
|
suspend fun clearChatOverride(chatId: Long)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package ru.daemonlord.messenger.domain.notifications.usecase
|
||||||
|
|
||||||
|
import ru.daemonlord.messenger.domain.notifications.model.ChatNotificationOverride
|
||||||
|
import ru.daemonlord.messenger.domain.notifications.repository.NotificationSettingsRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ShouldShowMessageNotificationUseCase @Inject constructor(
|
||||||
|
private val notificationSettingsRepository: NotificationSettingsRepository,
|
||||||
|
) {
|
||||||
|
suspend operator fun invoke(
|
||||||
|
chatId: Long,
|
||||||
|
isMention: Boolean,
|
||||||
|
serverMuted: Boolean,
|
||||||
|
): Boolean {
|
||||||
|
val settings = notificationSettingsRepository.getSettings()
|
||||||
|
if (!settings.globalEnabled) return false
|
||||||
|
|
||||||
|
val chatOverride = notificationSettingsRepository.getChatOverride(chatId)
|
||||||
|
val effectiveMuted = when (chatOverride) {
|
||||||
|
ChatNotificationOverride.DEFAULT -> serverMuted
|
||||||
|
ChatNotificationOverride.ENABLED -> false
|
||||||
|
ChatNotificationOverride.MUTED -> true
|
||||||
|
}
|
||||||
|
|
||||||
|
return !effectiveMuted || isMention
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ import ru.daemonlord.messenger.core.notifications.NotificationDispatcher
|
|||||||
import ru.daemonlord.messenger.data.message.local.dao.MessageDao
|
import ru.daemonlord.messenger.data.message.local.dao.MessageDao
|
||||||
import ru.daemonlord.messenger.data.message.local.entity.MessageEntity
|
import ru.daemonlord.messenger.data.message.local.entity.MessageEntity
|
||||||
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
||||||
|
import ru.daemonlord.messenger.domain.notifications.usecase.ShouldShowMessageNotificationUseCase
|
||||||
import ru.daemonlord.messenger.domain.realtime.RealtimeManager
|
import ru.daemonlord.messenger.domain.realtime.RealtimeManager
|
||||||
import ru.daemonlord.messenger.domain.realtime.model.RealtimeEvent
|
import ru.daemonlord.messenger.domain.realtime.model.RealtimeEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -26,6 +27,7 @@ class HandleRealtimeEventsUseCase @Inject constructor(
|
|||||||
private val messageDao: MessageDao,
|
private val messageDao: MessageDao,
|
||||||
private val notificationDispatcher: NotificationDispatcher,
|
private val notificationDispatcher: NotificationDispatcher,
|
||||||
private val activeChatTracker: ActiveChatTracker,
|
private val activeChatTracker: ActiveChatTracker,
|
||||||
|
private val shouldShowMessageNotificationUseCase: ShouldShowMessageNotificationUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
@@ -76,7 +78,12 @@ class HandleRealtimeEventsUseCase @Inject constructor(
|
|||||||
chatDao.incrementUnread(chatId = event.chatId)
|
chatDao.incrementUnread(chatId = event.chatId)
|
||||||
val activeChatId = activeChatTracker.activeChatId.value
|
val activeChatId = activeChatTracker.activeChatId.value
|
||||||
val muted = chatDao.isChatMuted(event.chatId) == true
|
val muted = chatDao.isChatMuted(event.chatId) == true
|
||||||
if (activeChatId != event.chatId && (!muted || event.isMention)) {
|
val shouldNotify = shouldShowMessageNotificationUseCase(
|
||||||
|
chatId = event.chatId,
|
||||||
|
isMention = event.isMention,
|
||||||
|
serverMuted = muted,
|
||||||
|
)
|
||||||
|
if (activeChatId != event.chatId && shouldNotify) {
|
||||||
val title = chatDao.getChatDisplayTitle(event.chatId) ?: "New message"
|
val title = chatDao.getChatDisplayTitle(event.chatId) ?: "New message"
|
||||||
val body = event.text?.takeIf { it.isNotBlank() }
|
val body = event.text?.takeIf { it.isNotBlank() }
|
||||||
?: when (event.type?.lowercase()) {
|
?: when (event.type?.lowercase()) {
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ package ru.daemonlord.messenger.core.token
|
|||||||
|
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
import androidx.datastore.preferences.core.emptyPreferences
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import okio.Path.Companion.toPath
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class DataStoreTokenRepositoryTest {
|
class DataStoreTokenRepositoryTest {
|
||||||
@@ -46,10 +46,18 @@ class DataStoreTokenRepositoryTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createTestDataStore(): DataStore<Preferences> {
|
private fun createTestDataStore(): DataStore<Preferences> {
|
||||||
val file = File.createTempFile("tokens", ".preferences_pb")
|
return InMemoryPreferencesDataStore()
|
||||||
file.deleteOnExit()
|
}
|
||||||
return PreferenceDataStoreFactory.createWithPath(
|
|
||||||
produceFile = { file.absolutePath.toPath() }
|
private class InMemoryPreferencesDataStore : DataStore<Preferences> {
|
||||||
)
|
private val state = MutableStateFlow<Preferences>(emptyPreferences())
|
||||||
|
|
||||||
|
override val data: Flow<Preferences> = state
|
||||||
|
|
||||||
|
override suspend fun updateData(transform: suspend (t: Preferences) -> Preferences): Preferences {
|
||||||
|
val updated = transform(state.value)
|
||||||
|
state.value = updated
|
||||||
|
return updated
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package ru.daemonlord.messenger.data.notifications.repository
|
||||||
|
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.emptyPreferences
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import ru.daemonlord.messenger.domain.notifications.model.ChatNotificationOverride
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class DataStoreNotificationSettingsRepositoryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun saveAndReadGlobalSettings_returnsPersistedValues() = runTest {
|
||||||
|
val repository = DataStoreNotificationSettingsRepository(createTestDataStore())
|
||||||
|
|
||||||
|
repository.setGlobalEnabled(enabled = false)
|
||||||
|
repository.setPreviewEnabled(enabled = false)
|
||||||
|
|
||||||
|
val settings = repository.getSettings()
|
||||||
|
assertFalse(settings.globalEnabled)
|
||||||
|
assertFalse(settings.previewEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun saveAndReadChatOverride_returnsPersistedValue() = runTest {
|
||||||
|
val repository = DataStoreNotificationSettingsRepository(createTestDataStore())
|
||||||
|
|
||||||
|
repository.setChatOverride(chatId = 42L, mode = ChatNotificationOverride.MUTED)
|
||||||
|
|
||||||
|
val mode = repository.getChatOverride(chatId = 42L)
|
||||||
|
assertEquals(ChatNotificationOverride.MUTED, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun clearChatOverride_resetsToDefault() = runTest {
|
||||||
|
val repository = DataStoreNotificationSettingsRepository(createTestDataStore())
|
||||||
|
repository.setChatOverride(chatId = 42L, mode = ChatNotificationOverride.ENABLED)
|
||||||
|
|
||||||
|
repository.clearChatOverride(chatId = 42L)
|
||||||
|
|
||||||
|
val mode = repository.getChatOverride(chatId = 42L)
|
||||||
|
assertTrue(mode == ChatNotificationOverride.DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTestDataStore(): DataStore<Preferences> {
|
||||||
|
return InMemoryPreferencesDataStore()
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InMemoryPreferencesDataStore : DataStore<Preferences> {
|
||||||
|
private val state = MutableStateFlow<Preferences>(emptyPreferences())
|
||||||
|
|
||||||
|
override val data: Flow<Preferences> = state
|
||||||
|
|
||||||
|
override suspend fun updateData(transform: suspend (t: Preferences) -> Preferences): Preferences {
|
||||||
|
val updated = transform(state.value)
|
||||||
|
state.value = updated
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package ru.daemonlord.messenger.domain.notifications.usecase
|
||||||
|
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import ru.daemonlord.messenger.domain.notifications.model.ChatNotificationOverride
|
||||||
|
import ru.daemonlord.messenger.domain.notifications.model.NotificationSettings
|
||||||
|
import ru.daemonlord.messenger.domain.notifications.repository.NotificationSettingsRepository
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class ShouldShowMessageNotificationUseCaseTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun globalDisabled_blocksNotifications() = runTest {
|
||||||
|
val repository = FakeNotificationSettingsRepository(
|
||||||
|
settings = NotificationSettings(globalEnabled = false, previewEnabled = true),
|
||||||
|
)
|
||||||
|
val useCase = ShouldShowMessageNotificationUseCase(repository)
|
||||||
|
|
||||||
|
val result = useCase(chatId = 1L, isMention = true, serverMuted = false)
|
||||||
|
|
||||||
|
assertFalse(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mutedChat_allowsMention() = runTest {
|
||||||
|
val repository = FakeNotificationSettingsRepository(
|
||||||
|
settings = NotificationSettings(globalEnabled = true, previewEnabled = true),
|
||||||
|
overrides = mutableMapOf(1L to ChatNotificationOverride.MUTED),
|
||||||
|
)
|
||||||
|
val useCase = ShouldShowMessageNotificationUseCase(repository)
|
||||||
|
|
||||||
|
val result = useCase(chatId = 1L, isMention = true, serverMuted = true)
|
||||||
|
|
||||||
|
assertTrue(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun chatOverrideEnabled_unmutesServerMutedChat() = runTest {
|
||||||
|
val repository = FakeNotificationSettingsRepository(
|
||||||
|
settings = NotificationSettings(globalEnabled = true, previewEnabled = true),
|
||||||
|
overrides = mutableMapOf(1L to ChatNotificationOverride.ENABLED),
|
||||||
|
)
|
||||||
|
val useCase = ShouldShowMessageNotificationUseCase(repository)
|
||||||
|
|
||||||
|
val result = useCase(chatId = 1L, isMention = false, serverMuted = true)
|
||||||
|
|
||||||
|
assertTrue(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakeNotificationSettingsRepository(
|
||||||
|
settings: NotificationSettings,
|
||||||
|
overrides: MutableMap<Long, ChatNotificationOverride> = mutableMapOf(),
|
||||||
|
) : NotificationSettingsRepository {
|
||||||
|
private val settingsFlow = MutableStateFlow(settings)
|
||||||
|
private val chatOverrides = overrides
|
||||||
|
|
||||||
|
override fun observeSettings(): Flow<NotificationSettings> = settingsFlow
|
||||||
|
|
||||||
|
override suspend fun getSettings(): NotificationSettings = settingsFlow.value
|
||||||
|
|
||||||
|
override suspend fun setGlobalEnabled(enabled: Boolean) {
|
||||||
|
settingsFlow.value = settingsFlow.value.copy(globalEnabled = enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setPreviewEnabled(enabled: Boolean) {
|
||||||
|
settingsFlow.value = settingsFlow.value.copy(previewEnabled = enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun observeChatOverride(chatId: Long): Flow<ChatNotificationOverride> {
|
||||||
|
return settingsFlow.map { chatOverrides[chatId] ?: ChatNotificationOverride.DEFAULT }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getChatOverride(chatId: Long): ChatNotificationOverride {
|
||||||
|
return chatOverrides[chatId] ?: ChatNotificationOverride.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setChatOverride(chatId: Long, mode: ChatNotificationOverride) {
|
||||||
|
chatOverrides[chatId] = mode
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clearChatOverride(chatId: Long) {
|
||||||
|
chatOverrides.remove(chatId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -93,6 +93,7 @@
|
|||||||
- [x] Notification channels (Android)
|
- [x] Notification channels (Android)
|
||||||
- [x] Deep links: open chat/message
|
- [x] Deep links: open chat/message
|
||||||
- [x] Mention override для muted чатов
|
- [x] Mention override для muted чатов
|
||||||
|
- [x] DataStore настройки уведомлений (global + per-chat override)
|
||||||
|
|
||||||
## 13. UI/UX и темы
|
## 13. UI/UX и темы
|
||||||
- [ ] Светлая/темная тема (читаемая)
|
- [ ] Светлая/темная тема (читаемая)
|
||||||
|
|||||||
Reference in New Issue
Block a user