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:
@@ -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.media.repository.NetworkMediaRepository
|
||||
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.chat.repository.ChatRepository
|
||||
import ru.daemonlord.messenger.domain.media.repository.MediaRepository
|
||||
import ru.daemonlord.messenger.domain.message.repository.MessageRepository
|
||||
import ru.daemonlord.messenger.domain.notifications.repository.NotificationSettingsRepository
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@@ -41,4 +43,10 @@ abstract class RepositoryModule {
|
||||
abstract fun bindMediaRepository(
|
||||
repository: NetworkMediaRepository,
|
||||
): 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.entity.MessageEntity
|
||||
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.model.RealtimeEvent
|
||||
import javax.inject.Inject
|
||||
@@ -26,6 +27,7 @@ class HandleRealtimeEventsUseCase @Inject constructor(
|
||||
private val messageDao: MessageDao,
|
||||
private val notificationDispatcher: NotificationDispatcher,
|
||||
private val activeChatTracker: ActiveChatTracker,
|
||||
private val shouldShowMessageNotificationUseCase: ShouldShowMessageNotificationUseCase,
|
||||
) {
|
||||
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
@@ -76,7 +78,12 @@ class HandleRealtimeEventsUseCase @Inject constructor(
|
||||
chatDao.incrementUnread(chatId = event.chatId)
|
||||
val activeChatId = activeChatTracker.activeChatId.value
|
||||
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 body = event.text?.takeIf { it.isNotBlank() }
|
||||
?: when (event.type?.lowercase()) {
|
||||
|
||||
Reference in New Issue
Block a user