准备切换至 DataStore 偏好设置管理
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user