Ver Fonte

准备切换至 DataStore 偏好设置管理

HapeLee há 6 meses atrás
pai
commit
16353d8636

+ 152 - 0
app/src/main/java/io/legado/app/data/repository/SettingsRepository.kt

@@ -0,0 +1,152 @@
+package io.legado.app.data.repository
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.SharedPreferencesMigration
+import androidx.datastore.preferences.core.*
+import androidx.datastore.preferences.preferencesDataStore
+import io.legado.app.utils.putPrefBoolean
+import io.legado.app.utils.putPrefFloat
+import io.legado.app.utils.putPrefInt
+import io.legado.app.utils.putPrefLong
+import io.legado.app.utils.putPrefString
+import io.legado.app.utils.putPrefStringSet
+import io.legado.app.utils.removePref
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.map
+import java.io.IOException
+
+val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
+    name = "settings",
+    produceMigrations = { context ->
+        listOf(
+            SharedPreferencesMigration(
+                context,
+                "${context.packageName}_preferences"
+            )
+        )
+    }
+)
+
+/**
+ * 设置仓储
+ * 采用 DataStore + SharedPreferences 桥接模式
+ * 写入时同时写入两者,读取时以 DataStore 为准
+ * 这样可以保持现有的基于 SP 的备份恢复功能正常工作
+ */
+class SettingsRepository(private val context: Context) {
+
+    private val dataStore = context.dataStore
+
+    fun <T> getPreference(key: Preferences.Key<T>, defaultValue: T): Flow<T> {
+        return dataStore.data
+            .catch { exception ->
+                if (exception is IOException) {
+                    emit(emptyPreferences())
+                } else {
+                    throw exception
+                }
+            }
+            .map { preferences ->
+                preferences[key] ?: defaultValue
+            }
+    }
+
+    suspend fun <T> updatePreference(key: Preferences.Key<T>, value: T) {
+        // 1. 写入 DataStore (UI 响应式来源)
+        dataStore.edit { preferences ->
+            preferences[key] = value
+        }
+        // 2. 桥接:写回 SharedPreferences (保证备份功能正常)
+        syncToSharedPrefs(key.name, value)
+    }
+
+    private fun syncToSharedPrefs(key: String, value: Any?) {
+        when (value) {
+            is String -> context.putPrefString(key, value)
+            is Int -> context.putPrefInt(key, value)
+            is Boolean -> context.putPrefBoolean(key, value)
+            is Long -> context.putPrefLong(key, value)
+            is Float -> context.putPrefFloat(key, value)
+            is Set<*> -> {
+                @Suppress("UNCHECKED_CAST")
+                context.putPrefStringSet(key, value as MutableSet<String>)
+            }
+            null -> context.removePref(key)
+        }
+    }
+
+    // String 类型的快捷访问
+    fun getString(key: String, defaultValue: String = ""): Flow<String> =
+        getPreference(stringPreferencesKey(key), defaultValue)
+
+    suspend fun putString(key: String, value: String) =
+        updatePreference(stringPreferencesKey(key), value)
+
+    // Int 类型的快捷访问
+    fun getInt(key: String, defaultValue: Int = 0): Flow<Int> =
+        getPreference(intPreferencesKey(key), defaultValue)
+
+    suspend fun putInt(key: String, value: Int) =
+        updatePreference(intPreferencesKey(key), value)
+
+    // Boolean 类型的快捷访问
+    fun getBoolean(key: String, defaultValue: Boolean = false): Flow<Boolean> =
+        getPreference(booleanPreferencesKey(key), defaultValue)
+
+    suspend fun putBoolean(key: String, value: Boolean) =
+        updatePreference(booleanPreferencesKey(key), value)
+
+    // Long 类型的快捷访问
+    fun getLong(key: String, defaultValue: Long = 0L): Flow<Long> =
+        getPreference(longPreferencesKey(key), defaultValue)
+
+    suspend fun putLong(key: String, value: Long) =
+        updatePreference(longPreferencesKey(key), value)
+
+    // Float 类型的快捷访问
+    fun getFloat(key: String, defaultValue: Float = 0f): Flow<Float> =
+        getPreference(floatPreferencesKey(key), defaultValue)
+
+    suspend fun putFloat(key: String, value: Float) =
+        updatePreference(floatPreferencesKey(key), value)
+
+    // Set<String> 类型的快捷访问
+    fun getStringSet(key: String, defaultValue: Set<String> = emptySet()): Flow<Set<String>> =
+        getPreference(stringSetPreferencesKey(key), defaultValue)
+
+    suspend fun putStringSet(key: String, value: Set<String>) =
+        updatePreference(stringSetPreferencesKey(key), value)
+
+    // 批量从 Map 恢复到 DataStore (用于兼容 Restore 逻辑)
+    suspend fun batchPutFromMap(map: Map<String, *>) {
+        dataStore.edit { preferences ->
+            map.forEach { (key, value) ->
+                when (value) {
+                    is String -> preferences[stringPreferencesKey(key)] = value
+                    is Int -> preferences[intPreferencesKey(key)] = value
+                    is Boolean -> preferences[booleanPreferencesKey(key)] = value
+                    is Long -> preferences[longPreferencesKey(key)] = value
+                    is Float -> preferences[floatPreferencesKey(key)] = value
+                    is Set<*> -> {
+                        @Suppress("UNCHECKED_CAST")
+                        preferences[stringSetPreferencesKey(key)] = value as Set<String>
+                    }
+                }
+            }
+        }
+    }
+
+    // 移除配置
+    suspend fun remove(key: String) {
+        dataStore.edit { preferences ->
+            preferences.remove(stringPreferencesKey(key))
+            preferences.remove(intPreferencesKey(key))
+            preferences.remove(booleanPreferencesKey(key))
+            preferences.remove(longPreferencesKey(key))
+            preferences.remove(floatPreferencesKey(key))
+        }
+        context.removePref(key)
+    }
+}

+ 2 - 0
app/src/main/java/io/legado/app/di/appModule.kt

@@ -25,6 +25,7 @@ import io.legado.app.data.repository.RssRepository
 import io.legado.app.data.repository.SearchRepository
 import io.legado.app.data.repository.SearchRepositoryImpl
 import io.legado.app.data.repository.SearchContentRepository
+import io.legado.app.data.repository.SettingsRepository
 import io.legado.app.data.repository.UploadRepository
 import io.legado.app.data.repository.WebDavBackupRepository
 import io.legado.app.data.repository.WebDavReadingProgressRepository
@@ -119,6 +120,7 @@ val appModule = module {
     singleOf(::DictRuleRepository)
     singleOf(::SearchContentRepository)
     singleOf(::RemoteBookRepository)
+    singleOf(::SettingsRepository)
     singleOf(::ExploreKindUiUseCase)
     singleOf(::AppStartupMaintenanceUseCase)
     singleOf(::BatchCacheDownloadUseCase)

+ 24 - 16
app/src/main/java/io/legado/app/help/storage/Restore.kt

@@ -28,6 +28,7 @@ import io.legado.app.data.entities.TxtTocRule
 import io.legado.app.data.entities.readRecord.ReadRecord
 import io.legado.app.data.entities.readRecord.ReadRecordDetail
 import io.legado.app.data.entities.readRecord.ReadRecordSession
+import io.legado.app.data.repository.SettingsRepository
 import io.legado.app.help.DirectLinkUpload
 import io.legado.app.help.LauncherIconHelp
 import io.legado.app.help.book.isLocal
@@ -57,6 +58,8 @@ import kotlinx.coroutines.delay
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.withContext
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
 import splitties.init.appCtx
 import java.io.File
 import java.io.FileInputStream
@@ -64,8 +67,9 @@ import java.io.FileInputStream
 /**
  * 恢复
  */
-object Restore {
+object Restore : KoinComponent {
 
+    private val settingsRepository: SettingsRepository by inject()
     private val mutex = Mutex()
 
     private const val TAG = "Restore"
@@ -306,36 +310,40 @@ object Restore {
         }
         //AppWebDav.downBgs()
         appCtx.getSharedPreferences(path, "config")?.all?.let { map ->
+            val finalMap = mutableMapOf<String, Any>()
             appCtx.defaultSharedPreferences.edit {
-
                 map.forEach { (key, value) ->
                     if (BackupConfig.keyIsNotIgnore(key)) {
                         when (key) {
                             PreferKey.webDavPassword -> {
-                                kotlin.runCatching {
+                                val password = kotlin.runCatching {
                                     aes.decryptStr(value.toString())
-                                }.getOrNull()?.let {
+                                }.getOrNull() ?: let {
+                                    if (appCtx.getPrefString(PreferKey.webDavPassword).isNullOrBlank()) {
+                                        value.toString()
+                                    } else null
+                                }
+                                password?.let {
                                     putString(key, it)
-                                } ?: let {
-                                    if (appCtx.getPrefString(PreferKey.webDavPassword)
-                                            .isNullOrBlank()
-                                    ) {
-                                        putString(key, value.toString())
-                                    }
+                                    finalMap[key] = it
                                 }
                             }
 
-                            else -> when (value) {
-                                is Int -> putInt(key, value)
-                                is Boolean -> putBoolean(key, value)
-                                is Long -> putLong(key, value)
-                                is Float -> putFloat(key, value)
-                                is String -> putString(key, value)
+                            else -> {
+                                when (value) {
+                                    is Int -> { putInt(key, value); finalMap[key] = value }
+                                    is Boolean -> { putBoolean(key, value); finalMap[key] = value }
+                                    is Long -> { putLong(key, value); finalMap[key] = value }
+                                    is Float -> { putFloat(key, value); finalMap[key] = value }
+                                    is String -> { putString(key, value); finalMap[key] = value }
+                                }
                             }
                         }
                     }
                 }
             }
+            // 同步恢复到 DataStore
+            settingsRepository.batchPutFromMap(finalMap)
         }
         ReadBookConfig.apply {
             comicStyleSelect = appCtx.getPrefInt(PreferKey.comicStyleSelect)