diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index fed46fe2a..f310e050c 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -25,7 +25,6 @@ import io.legado.app.utils.createFolderIfNotExist import io.legado.app.utils.defaultSharedPreferences import io.legado.app.utils.externalFiles import io.legado.app.utils.getFile -import io.legado.app.utils.getSharedPreferences import io.legado.app.utils.isContentScheme import io.legado.app.utils.normalizeFileName import io.legado.app.utils.openOutputStream @@ -178,29 +177,29 @@ object Backup { .writeText(GSON.toJson(it)) } currentCoroutineContext().ensureActive() - appCtx.getSharedPreferences(backupPath, "config")?.let { sp -> - sp.edit(commit = true) { - appCtx.defaultSharedPreferences.all.forEach { (key, value) -> - if (BackupConfig.keyIsNotIgnore(key)) { - when (key) { - PreferKey.webDavPassword -> { - putString(key, aes.runCatching { - encryptBase64(value.toString()) - }.getOrDefault(value.toString())) - } + val configMap = appCtx.defaultSharedPreferences.all + val xmlBuilder = StringBuilder() + xmlBuilder.append("\n") + xmlBuilder.append("\n") + configMap.forEach { (key, value) -> + if (BackupConfig.keyIsNotIgnore(key, true)) { + val finalValue = if (key == PreferKey.webDavPassword) { + aes.runCatching { encryptBase64(value.toString()) }.getOrDefault(value.toString()) + } else value - 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) - } - } - } + when (finalValue) { + is String -> xmlBuilder.append(" ${finalValue.replace("&", "&").replace("<", "<")}\n") + is Int -> xmlBuilder.append(" \n") + is Long -> xmlBuilder.append(" \n") + is Float -> xmlBuilder.append(" \n") + is Boolean -> xmlBuilder.append(" \n") } } } + xmlBuilder.append("") + FileUtils.createFileIfNotExist(backupPath + File.separator + "config.xml") + .writeText(xmlBuilder.toString()) + currentCoroutineContext().ensureActive() val zipFileName = getNowZipFileName() val paths = arrayListOf(*backupFileNames) diff --git a/app/src/main/java/io/legado/app/help/storage/BackupConfig.kt b/app/src/main/java/io/legado/app/help/storage/BackupConfig.kt index f0b691372..a471bf195 100644 --- a/app/src/main/java/io/legado/app/help/storage/BackupConfig.kt +++ b/app/src/main/java/io/legado/app/help/storage/BackupConfig.kt @@ -88,7 +88,35 @@ object BackupConfig { PreferKey.bgImage, PreferKey.bgImageBlurring, PreferKey.bgImageN, - PreferKey.bgImageNBlurring + PreferKey.bgImageNBlurring, + PreferKey.themeColor, + PreferKey.secondaryThemeColor, + PreferKey.paletteStyle, + PreferKey.materialVersion, + PreferKey.composeEngine, + PreferKey.customContrast, + PreferKey.customMode, + PreferKey.useMiuixMonet, + PreferKey.containerOpacity, + PreferKey.topBarOpacity, + PreferKey.bottomBarOpacity, + PreferKey.enableBlur, + PreferKey.enableProgressiveBlur, + PreferKey.topBarBlurRadius, + PreferKey.bottomBarBlurRadius, + PreferKey.topBarBlurAlpha, + PreferKey.bottomBarBlurAlpha, + PreferKey.bottomBarLensRadius, + PreferKey.useFlexibleTopAppBar, + PreferKey.cBackground, + PreferKey.cBBackground, + PreferKey.cNBackground, + PreferKey.cNBBackground, + PreferKey.enableDeepPersonalization, + PreferKey.primaryTextColor, + PreferKey.secondaryTextColor, + PreferKey.themeBackgroundColor, + PreferKey.labelContainerColor ) private val coverPrefKeys = arrayOf( @@ -100,9 +128,10 @@ object BackupConfig { PreferKey.coverShowAuthorN ) - fun keyIsNotIgnore(key: String): Boolean { + fun keyIsNotIgnore(key: String, isBackup: Boolean = false): Boolean { + if (ignorePrefKeys.contains(key)) return false + if (isBackup) return true return when { - ignorePrefKeys.contains(key) -> false ignoreReadConfig && readPrefKeys.contains(key) -> false ignoreThemeConfig && themePrefKeys.contains(key) -> false ignoreCoverConfig && coverPrefKeys.contains(key) -> false @@ -116,17 +145,17 @@ object BackupConfig { val ignoreReadConfig: Boolean get() = ignoreConfig[readConfigKey] == true - private val ignoreThemeMode: Boolean + val ignoreThemeMode: Boolean get() = ignoreConfig[PreferKey.themeMode] == true - private val ignoreThemeConfig: Boolean + val ignoreThemeConfig: Boolean get() = ignoreConfig[themeConfigKey] == true - private val ignoreCoverConfig: Boolean + val ignoreCoverConfig: Boolean get() = ignoreConfig[coverConfigKey] == true - private val ignoreBookshelfLayout: Boolean + val ignoreBookshelfLayout: Boolean get() = ignoreConfig[PreferKey.bookshelfLayout] == true - private val ignoreShowRss: Boolean + val ignoreShowRss: Boolean get() = ignoreConfig[PreferKey.showRss] == true - private val ignoreThreadCount: Boolean + val ignoreThreadCount: Boolean get() = ignoreConfig[PreferKey.threadCount] == true val ignoreLocalBook: Boolean get() = ignoreConfig[localBookKey] == true 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 dab27caae..c8f794253 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 @@ -48,7 +48,6 @@ import io.legado.app.utils.fromJsonArray import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefInt import io.legado.app.utils.getPrefString -import io.legado.app.utils.getSharedPreferences import io.legado.app.utils.isContentScheme import io.legado.app.utils.isJsonArray import io.legado.app.utils.openInputStream @@ -58,6 +57,8 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent import org.koin.core.component.inject +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserFactory import splitties.init.appCtx import java.io.File import java.io.FileInputStream @@ -273,17 +274,19 @@ object Restore : KoinComponent { AppLog.put("恢复直链上传出错\n${it.localizedMessage}", it) } //恢复主题配置 - File(path, OldThemeConfig.configFileName).takeIf { - it.exists() - }?.runCatching { - FileUtils.delete(OldThemeConfig.configFilePath) - copyTo(File(OldThemeConfig.configFilePath)) - OldThemeConfig.upConfig() - }?.onFailure { - AppLog.put("恢复主题出错\n${it.localizedMessage}", it) + if (!BackupConfig.ignoreThemeConfig) { + File(path, OldThemeConfig.configFileName).takeIf { + it.exists() + }?.runCatching { + FileUtils.delete(OldThemeConfig.configFilePath) + copyTo(File(OldThemeConfig.configFilePath)) + OldThemeConfig.upConfig() + }?.onFailure { + AppLog.put("恢复主题出错\n${it.localizedMessage}", it) + } } File(path, BookCover.configFileName).takeIf { - it.exists() + it.exists() && !BackupConfig.ignoreCoverConfig }?.runCatching { val json = readText() BookCover.saveCoverRule(json) @@ -311,43 +314,19 @@ object Restore : KoinComponent { AppLog.put("恢复阅读界面出错\n${it.localizedMessage}", it) } } - //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 -> { - val password = kotlin.runCatching { - aes.decryptStr(value.toString()) - }.getOrNull() ?: let { - if (appCtx.getPrefString(PreferKey.webDavPassword).isNullOrBlank()) { - value.toString() - } else null - } - password?.let { - putString(key, it) - finalMap[key] = it - } - } - - 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 } - } - } - } - } + // 恢复配置文件 (手动解析 XML,替代反射逻辑) + val configFile = File(path, "config.xml") + if (configFile.exists()) { + try { + val map = readXmlToMap(configFile) + if (map.isNotEmpty()) { + applyConfigMap(map, aes) } + } catch (e: Exception) { + AppLog.put("恢复配置 XML 出错\n${e.localizedMessage}", e) } - // 同步恢复到 DataStore - settingsRepository.batchPutFromMap(finalMap) } + ReadBookConfig.apply { comicStyleSelect = appCtx.getPrefInt(PreferKey.comicStyleSelect) readStyleSelect = appCtx.getPrefInt(PreferKey.readStyleSelect) @@ -366,6 +345,81 @@ object Restore : KoinComponent { } } + private suspend fun applyConfigMap(map: Map, aes: BackupAES) { + val finalMap = mutableMapOf() + appCtx.defaultSharedPreferences.edit { + map.forEach { (key, value) -> + if (BackupConfig.keyIsNotIgnore(key)) { + when (key) { + PreferKey.webDavPassword -> { + val password = kotlin.runCatching { + aes.decryptStr(value.toString()) + }.getOrNull() ?: let { + if (appCtx.getPrefString(PreferKey.webDavPassword).isNullOrBlank()) { + value.toString() + } else null + } + password?.let { + putString(key, it) + finalMap[key] = it + } + } + + else -> { + if (value != null) { + 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 Double -> { // JSON 数字会被解析为 Double + val floatValue = value.toFloat() + putFloat(key, floatValue) + finalMap[key] = floatValue + } + is Float -> { putFloat(key, value); finalMap[key] = value } + is String -> { putString(key, value); finalMap[key] = value } + } + } + } + } + } + } + } + // 同步恢复到 DataStore + settingsRepository.batchPutFromMap(finalMap) + } + + private fun readXmlToMap(file: File): Map { + val map = mutableMapOf() + try { + val factory = XmlPullParserFactory.newInstance() + val parser = factory.newPullParser() + FileInputStream(file).use { fis -> + parser.setInput(fis, "UTF-8") + var eventType = parser.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tagName = parser.name + val name = parser.getAttributeValue(null, "name") + if (name != null) { + when (tagName) { + "string" -> map[name] = parser.nextText() + "int" -> map[name] = parser.getAttributeValue(null, "value").toInt() + "long" -> map[name] = parser.getAttributeValue(null, "value").toLong() + "float" -> map[name] = parser.getAttributeValue(null, "value").toFloat() + "boolean" -> map[name] = parser.getAttributeValue(null, "value").toBoolean() + } + } + } + eventType = parser.next() + } + } + } catch (e: Exception) { + e.printStackTrace() + } + return map + } + private inline fun fileToListT(path: String, fileName: String): List? { try { val file = File(path, fileName) diff --git a/app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigScreen.kt b/app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigScreen.kt index f6cd373b4..9f12146c2 100644 --- a/app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigScreen.kt +++ b/app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigScreen.kt @@ -374,7 +374,7 @@ fun BackupConfigScreen( AppTextField( value = tempPassword, onValueChange = { tempPassword = it }, - backgroundColor = MiuixTheme.colorScheme.surface, + backgroundColor = LegadoTheme.colorScheme.surface, label = stringResource(R.string.web_dav_pw), visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),