package com.rosetta.messenger.data import android.content.Context 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.intPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.core.stringSetPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch private val Context.dataStore: DataStore by preferencesDataStore(name = "rosetta_preferences") class PreferencesManager(private val context: Context) { companion object { // Onboarding & Theme val HAS_SEEN_ONBOARDING = booleanPreferencesKey("has_seen_onboarding") val IS_DARK_THEME = booleanPreferencesKey("is_dark_theme") val THEME_MODE = stringPreferencesKey("theme_mode") // "light", "dark", "auto" // Notifications val NOTIFICATIONS_ENABLED = booleanPreferencesKey("notifications_enabled") val NOTIFICATION_SOUND_ENABLED = booleanPreferencesKey("notification_sound_enabled") val NOTIFICATION_VIBRATE_ENABLED = booleanPreferencesKey("notification_vibrate_enabled") val NOTIFICATION_PREVIEW_ENABLED = booleanPreferencesKey("notification_preview_enabled") // Chat Settings val MESSAGE_TEXT_SIZE = intPreferencesKey("message_text_size") // 0=small, 1=medium, 2=large val SEND_BY_ENTER = booleanPreferencesKey("send_by_enter") val AUTO_DOWNLOAD_PHOTOS = booleanPreferencesKey("auto_download_photos") val AUTO_DOWNLOAD_VIDEOS = booleanPreferencesKey("auto_download_videos") val AUTO_DOWNLOAD_FILES = booleanPreferencesKey("auto_download_files") // Privacy val SHOW_ONLINE_STATUS = booleanPreferencesKey("show_online_status") val SHOW_READ_RECEIPTS = booleanPreferencesKey("show_read_receipts") val SHOW_TYPING_INDICATOR = booleanPreferencesKey("show_typing_indicator") // Language val APP_LANGUAGE = stringPreferencesKey("app_language") // "en", "ru", etc. // Appearance / Customization (legacy global key) val BACKGROUND_BLUR_COLOR_ID = stringPreferencesKey("background_blur_color_id") // id from BackgroundBlurPresets // Pinned Chats (max 3) val PINNED_CHATS = stringSetPreferencesKey("pinned_chats") // Set of opponent public keys // Muted Chats (stored as "account::opponentKey") val MUTED_CHATS = stringSetPreferencesKey("muted_chats") } // ═════════════════════════════════════════════════════════════ // 🎨 ONBOARDING & THEME // ═════════════════════════════════════════════════════════════ val hasSeenOnboarding: Flow = context.dataStore.data.map { preferences -> preferences[HAS_SEEN_ONBOARDING] ?: false } val isDarkTheme: Flow = context.dataStore.data.map { preferences -> preferences[IS_DARK_THEME] ?: true // Default to dark theme like Telegram } suspend fun setHasSeenOnboarding(value: Boolean) { context.dataStore.edit { preferences -> preferences[HAS_SEEN_ONBOARDING] = value } } suspend fun setDarkTheme(value: Boolean) { context.dataStore.edit { preferences -> preferences[IS_DARK_THEME] = value } } // In-memory cache for instant theme switching (no DataStore disk I/O delay) private val _themeMode = MutableStateFlow("dark") private val themeModeInitScope = CoroutineScope(Dispatchers.IO) init { // Load persisted value on startup themeModeInitScope.launch { val persisted = context.dataStore.data.first()[THEME_MODE] ?: "dark" _themeMode.value = persisted } } val themeMode: Flow = _themeMode suspend fun setThemeMode(value: String) { _themeMode.value = value // Instant in-memory update context.dataStore.edit { preferences -> preferences[THEME_MODE] = value } // Persist } // ═════════════════════════════════════════════════════════════ // 🔔 NOTIFICATIONS // ═════════════════════════════════════════════════════════════ val notificationsEnabled: Flow = context.dataStore.data.map { preferences -> preferences[NOTIFICATIONS_ENABLED] ?: true } val notificationSoundEnabled: Flow = context.dataStore.data.map { preferences -> preferences[NOTIFICATION_SOUND_ENABLED] ?: true } val notificationVibrateEnabled: Flow = context.dataStore.data.map { preferences -> preferences[NOTIFICATION_VIBRATE_ENABLED] ?: true } val notificationPreviewEnabled: Flow = context.dataStore.data.map { preferences -> preferences[NOTIFICATION_PREVIEW_ENABLED] ?: true } suspend fun setNotificationsEnabled(value: Boolean) { context.dataStore.edit { preferences -> preferences[NOTIFICATIONS_ENABLED] = value } } suspend fun setNotificationSoundEnabled(value: Boolean) { context.dataStore.edit { preferences -> preferences[NOTIFICATION_SOUND_ENABLED] = value } } suspend fun setNotificationVibrateEnabled(value: Boolean) { context.dataStore.edit { preferences -> preferences[NOTIFICATION_VIBRATE_ENABLED] = value } } suspend fun setNotificationPreviewEnabled(value: Boolean) { context.dataStore.edit { preferences -> preferences[NOTIFICATION_PREVIEW_ENABLED] = value } } // ═════════════════════════════════════════════════════════════ // 💬 CHAT SETTINGS // ═════════════════════════════════════════════════════════════ val messageTextSize: Flow = context.dataStore.data.map { preferences -> preferences[MESSAGE_TEXT_SIZE] ?: 1 // Default medium } val sendByEnter: Flow = context.dataStore.data.map { preferences -> preferences[SEND_BY_ENTER] ?: false } val autoDownloadPhotos: Flow = context.dataStore.data.map { preferences -> preferences[AUTO_DOWNLOAD_PHOTOS] ?: true } val autoDownloadVideos: Flow = context.dataStore.data.map { preferences -> preferences[AUTO_DOWNLOAD_VIDEOS] ?: false } val autoDownloadFiles: Flow = context.dataStore.data.map { preferences -> preferences[AUTO_DOWNLOAD_FILES] ?: false } suspend fun setMessageTextSize(value: Int) { context.dataStore.edit { preferences -> preferences[MESSAGE_TEXT_SIZE] = value } } suspend fun setSendByEnter(value: Boolean) { context.dataStore.edit { preferences -> preferences[SEND_BY_ENTER] = value } } suspend fun setAutoDownloadPhotos(value: Boolean) { context.dataStore.edit { preferences -> preferences[AUTO_DOWNLOAD_PHOTOS] = value } } suspend fun setAutoDownloadVideos(value: Boolean) { context.dataStore.edit { preferences -> preferences[AUTO_DOWNLOAD_VIDEOS] = value } } suspend fun setAutoDownloadFiles(value: Boolean) { context.dataStore.edit { preferences -> preferences[AUTO_DOWNLOAD_FILES] = value } } // ═════════════════════════════════════════════════════════════ // 🔐 PRIVACY // ═════════════════════════════════════════════════════════════ val showOnlineStatus: Flow = context.dataStore.data.map { preferences -> preferences[SHOW_ONLINE_STATUS] ?: true } val showReadReceipts: Flow = context.dataStore.data.map { preferences -> preferences[SHOW_READ_RECEIPTS] ?: true } val showTypingIndicator: Flow = context.dataStore.data.map { preferences -> preferences[SHOW_TYPING_INDICATOR] ?: true } suspend fun setShowOnlineStatus(value: Boolean) { context.dataStore.edit { preferences -> preferences[SHOW_ONLINE_STATUS] = value } } suspend fun setShowReadReceipts(value: Boolean) { context.dataStore.edit { preferences -> preferences[SHOW_READ_RECEIPTS] = value } } suspend fun setShowTypingIndicator(value: Boolean) { context.dataStore.edit { preferences -> preferences[SHOW_TYPING_INDICATOR] = value } } // ═════════════════════════════════════════════════════════════ // 🌐 LANGUAGE // ═════════════════════════════════════════════════════════════ val appLanguage: Flow = context.dataStore.data.map { preferences -> preferences[APP_LANGUAGE] ?: "en" // Default English } suspend fun setAppLanguage(value: String) { context.dataStore.edit { preferences -> preferences[APP_LANGUAGE] = value } } // ═════════════════════════════════════════════════════════════ // 🎨 APPEARANCE / CUSTOMIZATION // ═════════════════════════════════════════════════════════════ private fun buildBackgroundBlurColorKey(account: String): Preferences.Key? { val trimmedAccount = account.trim() if (trimmedAccount.isBlank()) return null return stringPreferencesKey("background_blur_color_id::$trimmedAccount") } val backgroundBlurColorId: Flow = context.dataStore.data.map { preferences -> preferences[BACKGROUND_BLUR_COLOR_ID] ?: "avatar" // Default: use avatar blur } suspend fun setBackgroundBlurColorId(value: String) { context.dataStore.edit { preferences -> preferences[BACKGROUND_BLUR_COLOR_ID] = value } } fun backgroundBlurColorIdForAccount(account: String): Flow = context.dataStore.data.map { preferences -> val scopedKey = buildBackgroundBlurColorKey(account) if (scopedKey != null) preferences[scopedKey] ?: "avatar" else "avatar" } suspend fun setBackgroundBlurColorId(account: String, value: String) { val scopedKey = buildBackgroundBlurColorKey(account) context.dataStore.edit { preferences -> if (scopedKey != null) { preferences[scopedKey] = value } } } // ═════════════════════════════════════════════════════════════ // 📌 PINNED CHATS // ═════════════════════════════════════════════════════════════ val pinnedChats: Flow> = context.dataStore.data.map { preferences -> preferences[PINNED_CHATS] ?: emptySet() } suspend fun setPinnedChats(value: Set) { context.dataStore.edit { preferences -> preferences[PINNED_CHATS] = value } } suspend fun togglePinChat(opponentKey: String): Boolean { var wasPinned = false context.dataStore.edit { preferences -> val current = preferences[PINNED_CHATS] ?: emptySet() if (current.contains(opponentKey)) { // Unpin preferences[PINNED_CHATS] = current - opponentKey wasPinned = true } else if (current.size < 3) { // Pin (max 3) preferences[PINNED_CHATS] = current + opponentKey } } return wasPinned } // ═════════════════════════════════════════════════════════════ // 🔕 MUTED CHATS // ═════════════════════════════════════════════════════════════ private fun buildMutedKey(account: String, opponentKey: String): String { val trimmedAccount = account.trim() val trimmedOpponent = opponentKey.trim() return if (trimmedAccount.isBlank()) trimmedOpponent else "$trimmedAccount::$trimmedOpponent" } private fun parseMutedKeyForAccount(rawKey: String, account: String): String? { val trimmedAccount = account.trim() if (rawKey.isBlank()) return null // Legacy format support: plain opponentKey without account prefix. if ("::" !in rawKey) return rawKey val parts = rawKey.split("::", limit = 2) if (parts.size != 2) return null val keyAccount = parts[0] val opponentKey = parts[1] return if (trimmedAccount.isBlank() || keyAccount == trimmedAccount) opponentKey else null } val mutedChats: Flow> = context.dataStore.data.map { preferences -> preferences[MUTED_CHATS] ?: emptySet() } fun mutedChatsForAccount(account: String): Flow> = mutedChats.map { muted -> muted.mapNotNull { parseMutedKeyForAccount(it, account) }.toSet() } suspend fun isChatMuted(account: String, opponentKey: String): Boolean { if (opponentKey.isBlank()) return false val current = mutedChats.first() return current.contains(buildMutedKey(account, opponentKey)) || current.contains(opponentKey) } suspend fun setChatMuted(account: String, opponentKey: String, muted: Boolean) { if (opponentKey.isBlank()) return val scopedKey = buildMutedKey(account, opponentKey) context.dataStore.edit { preferences -> val current = preferences[MUTED_CHATS] ?: emptySet() preferences[MUTED_CHATS] = if (muted) { current + scopedKey } else { current - scopedKey - opponentKey } } } suspend fun toggleMuteChat(account: String, opponentKey: String): Boolean { if (opponentKey.isBlank()) return false val mutedNow = !isChatMuted(account, opponentKey) setChatMuted(account, opponentKey, mutedNow) return mutedNow } }