Răsfoiți Sursa

fix: 提供旧主题迁移,保存书架卡片日间、夜间背景色

HapeLee 3 luni în urmă
părinte
comite
ccf4191a64

+ 15 - 0
app/src/main/java/io/legado/app/help/config/ThemeImportExport.kt

@@ -7,6 +7,7 @@ import com.google.gson.GsonBuilder
 import com.google.gson.JsonObject
 import com.google.gson.JsonObject
 import com.google.gson.JsonParser
 import com.google.gson.JsonParser
 import com.google.gson.reflect.TypeToken
 import com.google.gson.reflect.TypeToken
+import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
 import io.legado.app.ui.config.coverConfig.CoverConfig
 import io.legado.app.ui.config.coverConfig.CoverConfig
 import io.legado.app.ui.config.themeConfig.ThemeConfig
 import io.legado.app.ui.config.themeConfig.ThemeConfig
 import io.legado.app.utils.EncoderUtils
 import io.legado.app.utils.EncoderUtils
@@ -93,6 +94,10 @@ object ThemeImportExport {
             enableCustomTagColors = ThemeConfig.enableCustomTagColors,
             enableCustomTagColors = ThemeConfig.enableCustomTagColors,
             customTagColorsJson = ThemeConfig.customTagColorsJson,
             customTagColorsJson = ThemeConfig.customTagColorsJson,
 
 
+            // 书架卡片颜色
+            bookshelfCardColor = BookshelfConfig.bookshelfCardColor,
+            bookshelfCardColorDark = BookshelfConfig.bookshelfCardColorDark,
+
             // 主界面设置
             // 主界面设置
             showHome = ThemeConfig.showHome,
             showHome = ThemeConfig.showHome,
             showDiscovery = ThemeConfig.showDiscovery,
             showDiscovery = ThemeConfig.showDiscovery,
@@ -279,6 +284,10 @@ object ThemeImportExport {
         ThemeConfig.enableCustomTagColors = data.enableCustomTagColors
         ThemeConfig.enableCustomTagColors = data.enableCustomTagColors
         ThemeConfig.customTagColorsJson = data.customTagColorsJson
         ThemeConfig.customTagColorsJson = data.customTagColorsJson
 
 
+        // 书架卡片颜色
+        BookshelfConfig.bookshelfCardColor = data.bookshelfCardColor
+        BookshelfConfig.bookshelfCardColorDark = data.bookshelfCardColorDark
+
         // 主界面设置
         // 主界面设置
         ThemeConfig.showHome = data.showHome
         ThemeConfig.showHome = data.showHome
         ThemeConfig.showDiscovery = data.showDiscovery
         ThemeConfig.showDiscovery = data.showDiscovery
@@ -469,6 +478,8 @@ object ThemeImportExport {
         }
         }
     }
     }
 
 
+    internal fun parseLegacyThemeData(json: String): ThemeExportData? = parseThemeData(json)
+
     /**
     /**
      * 兼容曾由 R8 混淆字段名导出的主题配置。
      * 兼容曾由 R8 混淆字段名导出的主题配置。
      * 该映射对应加入首页开关、但尚未加入导航顺序和日夜独立配色的旧格式。
      * 该映射对应加入首页开关、但尚未加入导航顺序和日夜独立配色的旧格式。
@@ -671,6 +682,10 @@ data class ThemeExportData(
     val enableCustomTagColors: Boolean = false,
     val enableCustomTagColors: Boolean = false,
     val customTagColorsJson: String? = null,
     val customTagColorsJson: String? = null,
 
 
+    // 书架卡片颜色
+    val bookshelfCardColor: Int = 0,
+    val bookshelfCardColorDark: Int = 0,
+
     // 主界面设置
     // 主界面设置
     val showHome: Boolean = true,
     val showHome: Boolean = true,
     val showDiscovery: Boolean = true,
     val showDiscovery: Boolean = true,

+ 56 - 4
app/src/main/java/io/legado/app/help/config/ThemePackageManager.kt

@@ -185,6 +185,34 @@ class ThemePackageManager(
             .mapNotNull(::readSavedThemeFolder)
             .mapNotNull(::readSavedThemeFolder)
     }
     }
 
 
+    suspend fun hasLegacySavedThemes(): Boolean = withContext(Dispatchers.IO) {
+        savedThemesRoot.listFiles().orEmpty().any { it.isFile && it.extension == "json" }
+    }
+
+    suspend fun migrateLegacySavedThemes(): LegacyThemeMigrationResult =
+        withContext(Dispatchers.IO) {
+            var migratedCount = 0
+            var failedCount = 0
+            savedThemesRoot.listFiles().orEmpty()
+                .filter { it.isFile && it.extension == "json" }
+                .forEach { file ->
+                    runCatching {
+                        val data = ThemeImportExport.parseLegacyThemeData(file.readText())
+                            ?: error("Unsupported legacy theme: ${file.name}")
+                        saveTheme(
+                            name = uniqueSavedThemeName(file.nameWithoutExtension),
+                            data = data,
+                        )
+                        check(file.delete()) { "Unable to remove migrated theme: ${file.name}" }
+                    }.onSuccess {
+                        migratedCount++
+                    }.onFailure {
+                        failedCount++
+                    }
+                }
+            LegacyThemeMigrationResult(migratedCount, failedCount)
+        }
+
     suspend fun saveTheme(
     suspend fun saveTheme(
         name: String,
         name: String,
         data: ThemeExportData? = null,
         data: ThemeExportData? = null,
@@ -394,10 +422,16 @@ class ThemePackageManager(
             if (path != null) {
             if (path != null) {
                 val extension = sourceExtension(path, source.key)
                 val extension = sourceExtension(path, source.key)
                 val entryPath = "${source.entryBase}.$extension"
                 val entryPath = "${source.entryBase}.$extension"
-                openSource(path).use { input ->
-                    input.copyToPackageFile(root, entryPath)
+                val copied = runCatching {
+                    openSource(path).use { input ->
+                        input.copyToPackageFile(root, entryPath)
+                    }
+                }.isSuccess
+                if (copied) {
+                    result[source.key] = entryPath
+                } else if (config.assets?.get(source.legacyAssetKey).isNullOrBlank()) {
+                    error("无法读取主题资源: $path")
                 }
                 }
-                result[source.key] = entryPath
             }
             }
         }
         }
         copyEmbeddedAssetsToFolder(root, config, result)
         copyEmbeddedAssetsToFolder(root, config, result)
@@ -906,7 +940,20 @@ class ThemePackageManager(
         val key: String,
         val key: String,
         val sourcePath: String?,
         val sourcePath: String?,
         val entryBase: String,
         val entryBase: String,
-    )
+    ) {
+        val legacyAssetKey: String
+            get() = when (key) {
+                ASSET_BACKGROUND_LIGHT -> "bgImageLight"
+                ASSET_BACKGROUND_DARK -> "bgImageDark"
+                ASSET_NAV_HOME -> "navIconHome"
+                ASSET_NAV_BOOKSHELF -> "navIconBookshelf"
+                ASSET_NAV_EXPLORE -> "navIconExplore"
+                ASSET_NAV_RSS -> "navIconRss"
+                ASSET_NAV_MY -> "navIconMy"
+                ASSET_FONT -> "appFontPath"
+                else -> key
+            }
+    }
 
 
     private data class ExportedCoverData(
     private data class ExportedCoverData(
         val albums: List<ThemePackageCoverAlbum>,
         val albums: List<ThemePackageCoverAlbum>,
@@ -914,6 +961,11 @@ class ThemePackageManager(
     )
     )
 }
 }
 
 
+data class LegacyThemeMigrationResult(
+    val migratedCount: Int,
+    val failedCount: Int,
+)
+
 @Keep
 @Keep
 data class ThemePackageManifest(
 data class ThemePackageManifest(
     @SerializedName("formatVersion")
     @SerializedName("formatVersion")

+ 28 - 1
app/src/main/java/io/legado/app/ui/config/themeManage/ThemeManageScreen.kt

@@ -31,7 +31,6 @@ import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.setValue
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.shadow
 import androidx.compose.ui.draw.shadow
@@ -40,6 +39,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.help.config.SavedTheme
 import io.legado.app.help.config.SavedTheme
 import io.legado.app.help.config.ThemePackageManager
 import io.legado.app.help.config.ThemePackageManager
@@ -119,6 +119,22 @@ fun ThemeManageScreen(
                     showRestartDialog = true
                     showRestartDialog = true
                 }
                 }
 
 
+                is ThemeManageEffect.LegacyMigrationFinished -> {
+                    val message = if (effect.failedCount == 0) {
+                        context.getString(
+                            R.string.theme_manage_migrate_success,
+                            effect.migratedCount,
+                        )
+                    } else {
+                        context.getString(
+                            R.string.theme_manage_migrate_partial,
+                            effect.migratedCount,
+                            effect.failedCount,
+                        )
+                    }
+                    context.toastOnUi(message)
+                }
+
                 is ThemeManageEffect.ShowResult -> {
                 is ThemeManageEffect.ShowResult -> {
                     val message = buildString {
                     val message = buildString {
                         append(context.getString(effect.messageRes))
                         append(context.getString(effect.messageRes))
@@ -195,6 +211,17 @@ fun ThemeManageScreen(
                             importLegacyLauncher.launch(arrayOf("application/json", "text/json"))
                             importLegacyLauncher.launch(arrayOf("application/json", "text/json"))
                         }
                         }
                     )
                     )
+                    if (state.hasLegacyThemes) {
+                        ClickableSettingItem(
+                            title = stringResource(R.string.theme_manage_migrate_legacy),
+                            description = stringResource(
+                                R.string.theme_manage_migrate_legacy_summary
+                            ),
+                            onClick = {
+                                viewModel.onIntent(ThemeManageIntent.MigrateLegacyThemes)
+                            }
+                        )
+                    }
                 }
                 }
             }
             }
 
 

+ 25 - 0
app/src/main/java/io/legado/app/ui/config/themeManage/ThemeManageViewModel.kt

@@ -46,6 +46,7 @@ class ThemeManageViewModel(
             is ThemeManageIntent.SaveTheme -> saveTheme(intent)
             is ThemeManageIntent.SaveTheme -> saveTheme(intent)
             is ThemeManageIntent.ApplySavedTheme -> applySavedTheme(intent.theme)
             is ThemeManageIntent.ApplySavedTheme -> applySavedTheme(intent.theme)
             is ThemeManageIntent.DeleteSavedTheme -> deleteSavedTheme(intent.theme)
             is ThemeManageIntent.DeleteSavedTheme -> deleteSavedTheme(intent.theme)
+            ThemeManageIntent.MigrateLegacyThemes -> migrateLegacyThemes()
         }
         }
     }
     }
 
 
@@ -53,10 +54,12 @@ class ThemeManageViewModel(
         launchExclusive {
         launchExclusive {
             _uiState.update { it.copy(loading = true) }
             _uiState.update { it.copy(loading = true) }
             val themes = themePackageManager.loadSavedThemes()
             val themes = themePackageManager.loadSavedThemes()
+            val hasLegacyThemes = themePackageManager.hasLegacySavedThemes()
             _uiState.update {
             _uiState.update {
                 it.copy(
                 it.copy(
                     loading = false,
                     loading = false,
                     savedThemes = themes.toImmutableList(),
                     savedThemes = themes.toImmutableList(),
+                    hasLegacyThemes = hasLegacyThemes,
                 )
                 )
             }
             }
         }
         }
@@ -187,6 +190,21 @@ class ThemeManageViewModel(
         )
         )
     }
     }
 
 
+    private fun migrateLegacyThemes() {
+        launchExclusive {
+            _uiState.update { it.copy(loading = true) }
+            val result = themePackageManager.migrateLegacySavedThemes()
+            refreshSavedThemes()
+            _uiState.update { it.copy(hasLegacyThemes = result.failedCount > 0) }
+            _effects.emit(
+                ThemeManageEffect.LegacyMigrationFinished(
+                    migratedCount = result.migratedCount,
+                    failedCount = result.failedCount,
+                )
+            )
+        }
+    }
+
     private fun launchExclusive(block: suspend () -> Unit) {
     private fun launchExclusive(block: suspend () -> Unit) {
         if (!operationMutex.tryLock()) return
         if (!operationMutex.tryLock()) return
         viewModelScope.launch {
         viewModelScope.launch {
@@ -203,6 +221,7 @@ class ThemeManageViewModel(
 data class ThemeManageUiState(
 data class ThemeManageUiState(
     val loading: Boolean = false,
     val loading: Boolean = false,
     val savedThemes: ImmutableList<SavedTheme> = persistentListOf(),
     val savedThemes: ImmutableList<SavedTheme> = persistentListOf(),
+    val hasLegacyThemes: Boolean = false,
 )
 )
 
 
 sealed interface ThemeManageIntent {
 sealed interface ThemeManageIntent {
@@ -225,11 +244,17 @@ sealed interface ThemeManageIntent {
 
 
     data class ApplySavedTheme(val theme: SavedTheme) : ThemeManageIntent
     data class ApplySavedTheme(val theme: SavedTheme) : ThemeManageIntent
     data class DeleteSavedTheme(val theme: SavedTheme) : ThemeManageIntent
     data class DeleteSavedTheme(val theme: SavedTheme) : ThemeManageIntent
+    data object MigrateLegacyThemes : ThemeManageIntent
 }
 }
 
 
 sealed interface ThemeManageEffect {
 sealed interface ThemeManageEffect {
     data object RestartRequired : ThemeManageEffect
     data object RestartRequired : ThemeManageEffect
 
 
+    data class LegacyMigrationFinished(
+        val migratedCount: Int,
+        val failedCount: Int,
+    ) : ThemeManageEffect
+
     data class ShowResult(
     data class ShowResult(
         @param:StringRes val messageRes: Int,
         @param:StringRes val messageRes: Int,
         val detail: String? = null,
         val detail: String? = null,

+ 4 - 0
app/src/main/res/values-zh-rCN/strings.xml

@@ -1457,6 +1457,10 @@
     <string name="theme_manage_import_package_summary">导入包含资源和封面图集的主题包</string>
     <string name="theme_manage_import_package_summary">导入包含资源和封面图集的主题包</string>
     <string name="theme_manage_import_config">导入旧版主题配置</string>
     <string name="theme_manage_import_config">导入旧版主题配置</string>
     <string name="theme_manage_import_config_summary">从旧版 JSON 文件导入主题配置</string>
     <string name="theme_manage_import_config_summary">从旧版 JSON 文件导入主题配置</string>
+    <string name="theme_manage_migrate_legacy">迁移旧版主题</string>
+    <string name="theme_manage_migrate_legacy_summary">将已保存的 JSON 主题转换为新版主题格式</string>
+    <string name="theme_manage_migrate_success">已迁移 %1$d 个旧版主题</string>
+    <string name="theme_manage_migrate_partial">已迁移 %1$d 个主题,%2$d 个迁移失败</string>
     <string name="theme_manage_saved_themes">已保存的主题</string>
     <string name="theme_manage_saved_themes">已保存的主题</string>
     <string name="theme_manage_save_theme">保存主题</string>
     <string name="theme_manage_save_theme">保存主题</string>
     <string name="theme_manage_save">保存</string>
     <string name="theme_manage_save">保存</string>

+ 4 - 0
app/src/main/res/values-zh-rHK/strings.xml

@@ -2134,6 +2134,10 @@
     <string name="theme_manage_import_package_summary">匯入包含資源和封面圖集的主題包</string>
     <string name="theme_manage_import_package_summary">匯入包含資源和封面圖集的主題包</string>
     <string name="theme_manage_import_config">匯入舊版主題設定</string>
     <string name="theme_manage_import_config">匯入舊版主題設定</string>
     <string name="theme_manage_import_config_summary">從舊版 JSON 檔案匯入主題設定</string>
     <string name="theme_manage_import_config_summary">從舊版 JSON 檔案匯入主題設定</string>
+    <string name="theme_manage_migrate_legacy">遷移舊版主題</string>
+    <string name="theme_manage_migrate_legacy_summary">將已儲存的 JSON 主題轉換為新版主題格式</string>
+    <string name="theme_manage_migrate_success">已遷移 %1$d 個舊版主題</string>
+    <string name="theme_manage_migrate_partial">已遷移 %1$d 個主題,%2$d 個遷移失敗</string>
     <string name="theme_manage_import_failed">主題匯入失敗</string>
     <string name="theme_manage_import_failed">主題匯入失敗</string>
     <string name="theme_manage_import_success">主題已匯入。部分更改需要重啟。</string>
     <string name="theme_manage_import_success">主題已匯入。部分更改需要重啟。</string>
     <string name="theme_manage_save_failed">主題儲存失敗</string>
     <string name="theme_manage_save_failed">主題儲存失敗</string>

+ 4 - 0
app/src/main/res/values-zh-rTW/strings.xml

@@ -1601,6 +1601,10 @@
     <string name="theme_manage_import_package_summary">匯入包含資源和封面圖集的主題包</string>
     <string name="theme_manage_import_package_summary">匯入包含資源和封面圖集的主題包</string>
     <string name="theme_manage_import_config">匯入舊版主題設定</string>
     <string name="theme_manage_import_config">匯入舊版主題設定</string>
     <string name="theme_manage_import_config_summary">從舊版 JSON 檔案匯入主題設定</string>
     <string name="theme_manage_import_config_summary">從舊版 JSON 檔案匯入主題設定</string>
+    <string name="theme_manage_migrate_legacy">遷移舊版主題</string>
+    <string name="theme_manage_migrate_legacy_summary">將已儲存的 JSON 主題轉換為新版主題格式</string>
+    <string name="theme_manage_migrate_success">已遷移 %1$d 個舊版主題</string>
+    <string name="theme_manage_migrate_partial">已遷移 %1$d 個主題,%2$d 個遷移失敗</string>
     <string name="theme_manage_saved_themes">已儲存的主題</string>
     <string name="theme_manage_saved_themes">已儲存的主題</string>
     <string name="theme_manage_save_theme">儲存主題</string>
     <string name="theme_manage_save_theme">儲存主題</string>
     <string name="theme_manage_save">儲存</string>
     <string name="theme_manage_save">儲存</string>

+ 4 - 0
app/src/main/res/values/strings.xml

@@ -1492,6 +1492,10 @@
     <string name="theme_manage_import_package_summary">Import a theme package with its assets and cover albums</string>
     <string name="theme_manage_import_package_summary">Import a theme package with its assets and cover albums</string>
     <string name="theme_manage_import_config">Import legacy theme configuration</string>
     <string name="theme_manage_import_config">Import legacy theme configuration</string>
     <string name="theme_manage_import_config_summary">Import a legacy JSON theme configuration</string>
     <string name="theme_manage_import_config_summary">Import a legacy JSON theme configuration</string>
+    <string name="theme_manage_migrate_legacy">Migrate legacy themes</string>
+    <string name="theme_manage_migrate_legacy_summary">Convert saved JSON themes to the new theme format</string>
+    <string name="theme_manage_migrate_success">Migrated %1$d legacy themes</string>
+    <string name="theme_manage_migrate_partial">Migrated %1$d themes; %2$d could not be migrated</string>
     <string name="theme_manage_saved_themes">Saved Themes</string>
     <string name="theme_manage_saved_themes">Saved Themes</string>
     <string name="theme_manage_save_theme">Save Theme</string>
     <string name="theme_manage_save_theme">Save Theme</string>
     <string name="theme_manage_save">Save</string>
     <string name="theme_manage_save">Save</string>