From 16353d863612fe1318797243331f705bcf9c16a0 Mon Sep 17 00:00:00 2001 From: HapeLee <63206378+HapeLee@users.noreply.github.com> Date: Thu, 14 May 2026 15:59:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=87=86=E5=A4=87=E5=88=87=E6=8D=A2=E8=87=B3?= =?UTF-8?q?=20DataStore=20=E5=81=8F=E5=A5=BD=E8=AE=BE=E7=BD=AE=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/repository/SettingsRepository.kt | 152 ++++++++++++++++++ .../main/java/io/legado/app/di/appModule.kt | 2 + .../io/legado/app/help/storage/Restore.kt | 40 +++-- 3 files changed, 178 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/io/legado/app/data/repository/SettingsRepository.kt diff --git a/app/src/main/java/io/legado/app/data/repository/SettingsRepository.kt b/app/src/main/java/io/legado/app/data/repository/SettingsRepository.kt new file mode 100644 index 000000000..f7a95a757 --- /dev/null +++ b/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 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 getPreference(key: Preferences.Key, defaultValue: T): Flow { + return dataStore.data + .catch { exception -> + if (exception is IOException) { + emit(emptyPreferences()) + } else { + throw exception + } + } + .map { preferences -> + preferences[key] ?: defaultValue + } + } + + suspend fun updatePreference(key: Preferences.Key, 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) + } + null -> context.removePref(key) + } + } + + // String 类型的快捷访问 + fun getString(key: String, defaultValue: String = ""): Flow = + getPreference(stringPreferencesKey(key), defaultValue) + + suspend fun putString(key: String, value: String) = + updatePreference(stringPreferencesKey(key), value) + + // Int 类型的快捷访问 + fun getInt(key: String, defaultValue: Int = 0): Flow = + getPreference(intPreferencesKey(key), defaultValue) + + suspend fun putInt(key: String, value: Int) = + updatePreference(intPreferencesKey(key), value) + + // Boolean 类型的快捷访问 + fun getBoolean(key: String, defaultValue: Boolean = false): Flow = + getPreference(booleanPreferencesKey(key), defaultValue) + + suspend fun putBoolean(key: String, value: Boolean) = + updatePreference(booleanPreferencesKey(key), value) + + // Long 类型的快捷访问 + fun getLong(key: String, defaultValue: Long = 0L): Flow = + getPreference(longPreferencesKey(key), defaultValue) + + suspend fun putLong(key: String, value: Long) = + updatePreference(longPreferencesKey(key), value) + + // Float 类型的快捷访问 + fun getFloat(key: String, defaultValue: Float = 0f): Flow = + getPreference(floatPreferencesKey(key), defaultValue) + + suspend fun putFloat(key: String, value: Float) = + updatePreference(floatPreferencesKey(key), value) + + // Set 类型的快捷访问 + fun getStringSet(key: String, defaultValue: Set = emptySet()): Flow> = + getPreference(stringSetPreferencesKey(key), defaultValue) + + suspend fun putStringSet(key: String, value: Set) = + updatePreference(stringSetPreferencesKey(key), value) + + // 批量从 Map 恢复到 DataStore (用于兼容 Restore 逻辑) + suspend fun batchPutFromMap(map: Map) { + 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 + } + } + } + } + } + + // 移除配置 + 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) + } +} diff --git a/app/src/main/java/io/legado/app/di/appModule.kt b/app/src/main/java/io/legado/app/di/appModule.kt index 972cef6c1..7dba6810c 100644 --- a/app/src/main/java/io/legado/app/di/appModule.kt +++ b/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) diff --git a/app/src/main/java/io/legado/app/help/storage/Restore.kt b/app/src/main/java/io/legado/app/help/storage/Restore.kt index 04c7a0d5b..befe16466 100644 --- a/app/src/main/java/io/legado/app/help/storage/Restore.kt +++ b/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() 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)