Эх сурвалжийг харах

新的独属于MD3的雷霆主题管理与分享系统

HapeLee 6 сар өмнө
parent
commit
c748a5515a

+ 0 - 109
app/src/main/java/io/legado/app/help/config/ThemeAppearanceConfig.kt

@@ -1,109 +0,0 @@
-package io.legado.app.help.config
-
-import io.legado.app.ui.config.themeConfig.ThemeConfig
-
-data class ThemeAppearanceConfig(
-    var enableDeepPersonalization: Boolean = false,
-    var themeColor: Int = 0,
-    var secondaryThemeColor: Int = 0,
-    var primaryTextColor: Int = 0,
-    var secondaryTextColor: Int = 0,
-    var backgroundColor: Int = 0,
-    var labelContainerColor: Int = 0,
-    var bookInfoInputColor: Int = 0,
-    var appFontPath: String? = null,
-    var enableContainerBorder: Boolean = false,
-    var containerBorderWidth: Float = 1f,
-    var containerBorderStyle: String = "solid",
-    var containerBorderDashWidth: Float = 4f,
-    var containerBorderColor: Int = 0,
-    var enableItemDivider: Boolean = true,
-    var itemDividerWidth: Float = 1f,
-    var itemDividerLength: Float = 80f,
-    var itemDividerColor: Int = 0,
-    var enableCustomTagColors: Boolean = false,
-    var customTagColorsJson: String? = null,
-    var enableBlur: Boolean = false,
-    var topBarBlurRadius: Int = 24,
-    var bottomBarBlurRadius: Int = 8,
-    var topBarBlurAlpha: Int = 73,
-    var bottomBarBlurAlpha: Int = 40,
-    var bottomBarLensRadius: Float = 24f
-) {
-
-    val hasDeepColorOverrides: Boolean
-        get() = themeColor != 0 ||
-                secondaryThemeColor != 0 ||
-                primaryTextColor != 0 ||
-                secondaryTextColor != 0 ||
-                backgroundColor != 0 ||
-                labelContainerColor != 0
-
-    fun applyToThemeConfig(appFontPathOverride: String? = appFontPath) {
-        ThemeConfig.enableDeepPersonalization = enableDeepPersonalization
-        ThemeConfig.themeColor = themeColor
-        ThemeConfig.secondaryThemeColor = secondaryThemeColor
-        ThemeConfig.primaryTextColor = primaryTextColor
-        ThemeConfig.secondaryTextColor = secondaryTextColor
-        ThemeConfig.themeBackgroundColor = backgroundColor
-        ThemeConfig.labelContainerColor = labelContainerColor
-
-        ThemeConfig.bookInfoInputColor = bookInfoInputColor
-
-        ThemeConfig.appFontPath = appFontPathOverride
-
-        ThemeConfig.enableContainerBorder = enableContainerBorder
-        ThemeConfig.containerBorderWidth = containerBorderWidth
-        ThemeConfig.containerBorderStyle = containerBorderStyle.ifBlank { "solid" }
-        ThemeConfig.containerBorderDashWidth = containerBorderDashWidth
-        ThemeConfig.containerBorderColor = containerBorderColor
-
-        ThemeConfig.enableItemDivider = enableItemDivider
-        ThemeConfig.itemDividerWidth = itemDividerWidth
-        ThemeConfig.itemDividerLength = itemDividerLength
-        ThemeConfig.itemDividerColor = itemDividerColor
-
-        ThemeConfig.enableCustomTagColors = enableCustomTagColors
-        ThemeConfig.customTagColorsJson = ThemeAppearanceJson.convertTagColorsFromHex(customTagColorsJson)
-
-        ThemeConfig.enableBlur = enableBlur
-        ThemeConfig.topBarBlurRadius = topBarBlurRadius
-        ThemeConfig.bottomBarBlurRadius = bottomBarBlurRadius
-        ThemeConfig.topBarBlurAlpha = topBarBlurAlpha
-        ThemeConfig.bottomBarBlurAlpha = bottomBarBlurAlpha
-        ThemeConfig.bottomBarLensRadius = bottomBarLensRadius
-    }
-
-    companion object {
-        fun fromCurrent(): ThemeAppearanceConfig {
-            return ThemeAppearanceConfig(
-                enableDeepPersonalization = ThemeConfig.enableDeepPersonalization,
-                themeColor = ThemeConfig.themeColor,
-                secondaryThemeColor = ThemeConfig.secondaryThemeColor,
-                primaryTextColor = ThemeConfig.primaryTextColor,
-                secondaryTextColor = ThemeConfig.secondaryTextColor,
-                backgroundColor = ThemeConfig.themeBackgroundColor,
-                labelContainerColor = ThemeConfig.labelContainerColor,
-                bookInfoInputColor = ThemeConfig.bookInfoInputColor,
-                appFontPath = ThemeConfig.appFontPath,
-                enableContainerBorder = ThemeConfig.enableContainerBorder,
-                containerBorderWidth = ThemeConfig.containerBorderWidth,
-                containerBorderStyle = ThemeConfig.containerBorderStyle,
-                containerBorderDashWidth = ThemeConfig.containerBorderDashWidth,
-                containerBorderColor = ThemeConfig.containerBorderColor,
-                enableItemDivider = ThemeConfig.enableItemDivider,
-                itemDividerWidth = ThemeConfig.itemDividerWidth,
-                itemDividerLength = ThemeConfig.itemDividerLength,
-                itemDividerColor = ThemeConfig.itemDividerColor,
-                enableCustomTagColors = ThemeConfig.enableCustomTagColors,
-                customTagColorsJson = ThemeConfig.customTagColorsJson,
-                enableBlur = ThemeConfig.enableBlur,
-                topBarBlurRadius = ThemeConfig.topBarBlurRadius,
-                bottomBarBlurRadius = ThemeConfig.bottomBarBlurRadius,
-                topBarBlurAlpha = ThemeConfig.topBarBlurAlpha,
-                bottomBarBlurAlpha = ThemeConfig.bottomBarBlurAlpha,
-                bottomBarLensRadius = ThemeConfig.bottomBarLensRadius
-            )
-        }
-    }
-}

+ 0 - 159
app/src/main/java/io/legado/app/help/config/ThemeAppearanceJson.kt

@@ -1,159 +0,0 @@
-package io.legado.app.help.config
-
-import androidx.core.graphics.toColorInt
-import com.google.gson.JsonElement
-import com.google.gson.JsonObject
-import com.google.gson.JsonParser
-import io.legado.app.ui.config.themeConfig.TagColorPair
-import io.legado.app.utils.GSON
-import io.legado.app.utils.getBoolean
-import io.legado.app.utils.getFloat
-import io.legado.app.utils.getString
-
-object ThemeAppearanceJson {
-
-    fun writeTo(obj: JsonObject, src: ThemeAppearanceConfig) {
-        obj.addProperty("enableDeepPersonalization", src.enableDeepPersonalization)
-        obj.addProperty("themeColor", colorToHex(src.themeColor))
-        obj.addProperty("secondaryThemeColor", colorToHex(src.secondaryThemeColor))
-        obj.addProperty("primaryTextColor", colorToHex(src.primaryTextColor))
-        obj.addProperty("secondaryTextColor", colorToHex(src.secondaryTextColor))
-        obj.addProperty("backgroundColor", colorToHex(src.backgroundColor))
-        obj.addProperty("labelContainerColor", colorToHex(src.labelContainerColor))
-        obj.addProperty("bookInfoInputColor", colorToHex(src.bookInfoInputColor))
-        obj.addProperty("appFontPath", src.appFontPath)
-        obj.addProperty("enableContainerBorder", src.enableContainerBorder)
-        obj.addProperty("containerBorderWidth", src.containerBorderWidth)
-        obj.addProperty("containerBorderStyle", src.containerBorderStyle)
-        obj.addProperty("containerBorderDashWidth", src.containerBorderDashWidth)
-        obj.addProperty("containerBorderColor", colorToHex(src.containerBorderColor))
-        obj.addProperty("enableItemDivider", src.enableItemDivider)
-        obj.addProperty("itemDividerWidth", src.itemDividerWidth)
-        obj.addProperty("itemDividerLength", src.itemDividerLength)
-        obj.addProperty("itemDividerColor", colorToHex(src.itemDividerColor))
-        obj.addProperty("enableCustomTagColors", src.enableCustomTagColors)
-        obj.addProperty("customTagColorsJson", convertTagColorsToHex(src.customTagColorsJson))
-        obj.addProperty("enableBlur", src.enableBlur)
-        obj.addProperty("topBarBlurRadius", src.topBarBlurRadius)
-        obj.addProperty("bottomBarBlurRadius", src.bottomBarBlurRadius)
-        obj.addProperty("topBarBlurAlpha", src.topBarBlurAlpha)
-        obj.addProperty("bottomBarBlurAlpha", src.bottomBarBlurAlpha)
-        obj.addProperty("bottomBarLensRadius", src.bottomBarLensRadius)
-    }
-
-    fun readFrom(obj: JsonObject): ThemeAppearanceConfig {
-        val appearance = ThemeAppearanceConfig(
-            themeColor = readColor(obj, "themeColor", "md3Primary"),
-            secondaryThemeColor = readColor(obj, "secondaryThemeColor", "md3Secondary", "md3OnPrimary"),
-            primaryTextColor = readColor(obj, "primaryTextColor", "md3OnSurface"),
-            secondaryTextColor = readColor(obj, "secondaryTextColor", "md3OnPrimaryContainer"),
-            backgroundColor = readColor(obj, "backgroundColor", "md3Background"),
-            labelContainerColor = readColor(obj, "labelContainerColor", "md3SurfaceContainerLow"),
-            bookInfoInputColor = readColor(obj, "bookInfoInputColor"),
-            appFontPath = obj.getString("appFontPath", null),
-            enableContainerBorder = obj.getBoolean("enableContainerBorder", false),
-            containerBorderWidth = obj.getFloat("containerBorderWidth", 1f),
-            containerBorderStyle = obj.getString("containerBorderStyle", "solid") ?: "solid",
-            containerBorderDashWidth = obj.getFloat("containerBorderDashWidth", 4f),
-            containerBorderColor = readColor(obj, "containerBorderColor"),
-            enableItemDivider = obj.getBoolean("enableItemDivider", true),
-            itemDividerWidth = obj.getFloat("itemDividerWidth", 1f),
-            itemDividerLength = obj.getFloat("itemDividerLength", 80f),
-            itemDividerColor = readColor(obj, "itemDividerColor"),
-            enableCustomTagColors = obj.getBoolean("enableCustomTagColors", false),
-            customTagColorsJson = obj.getString("customTagColorsJson", null),
-            enableBlur = obj.getBoolean("enableBlur", false),
-            topBarBlurRadius = obj.getInt("topBarBlurRadius", 24),
-            bottomBarBlurRadius = obj.getInt("bottomBarBlurRadius", 8),
-            topBarBlurAlpha = obj.getInt("topBarBlurAlpha", 73),
-            bottomBarBlurAlpha = obj.getInt("bottomBarBlurAlpha", 40),
-            bottomBarLensRadius = obj.getFloat("bottomBarLensRadius", 24f)
-        )
-        appearance.enableDeepPersonalization = if (obj.has("enableDeepPersonalization")) {
-            obj.getBoolean("enableDeepPersonalization", false)
-        } else {
-            appearance.hasDeepColorOverrides
-        }
-        return appearance
-    }
-
-    fun convertTagColorsFromHex(json: String?): String? {
-        if (json.isNullOrBlank()) return json
-        return kotlin.runCatching {
-            val root = JsonParser.parseString(json)
-            if (root.isJsonArray) {
-                val arr = root.asJsonArray
-                val converted = arr.map { elem ->
-                    val obj = elem.asJsonObject
-                    val textColor = parseColorFromJson(obj.get("textColor"))
-                    val bgColor = parseColorFromJson(obj.get("bgColor"))
-                    """{"textColor":$textColor,"bgColor":$bgColor}"""
-                }
-                "[\n  ${converted.joinToString(",\n  ")}\n]"
-            } else json
-        }.getOrNull() ?: json
-    }
-
-    fun readColor(obj: JsonObject, vararg names: String): Int {
-        for (name in names) {
-            val color = obj.getInt(name, Int.MIN_VALUE)
-            if (color != Int.MIN_VALUE) return color
-        }
-        return 0
-    }
-
-    fun colorToHex(color: Int): String = "#${String.format("%08X", color)}"
-
-    private fun convertTagColorsToHex(json: String?): String? {
-        if (json.isNullOrBlank()) return json
-        return kotlin.runCatching {
-            val colors = GSON.fromJson(json, Array<TagColorPair>::class.java)
-            val converted = colors.map { pair ->
-                val textHex = colorToHex(pair.textColor)
-                val bgHex = colorToHex(pair.bgColor)
-                """{"textColor":"$textHex","bgColor":"$bgHex"}"""
-            }
-            "[\n  ${converted.joinToString(",\n  ")}\n]"
-        }.getOrNull() ?: json
-    }
-
-    private fun parseColorFromJson(elem: JsonElement?): Int {
-        if (elem == null || elem.isJsonNull) return 0
-        val prim = elem.asJsonPrimitive
-        return when {
-            prim.isNumber -> prim.asNumber.toInt()
-            prim.isString -> parseColorString(prim.asString)
-            else -> 0
-        }
-    }
-
-    private fun JsonObject.getInt(name: String, default: Int): Int {
-        val elem = get(name)
-        return when {
-            elem == null || elem.isJsonNull -> default
-            elem.isJsonPrimitive -> {
-                val prim = elem.asJsonPrimitive
-                when {
-                    prim.isNumber -> prim.asNumber.toInt()
-                    prim.isString -> parseColorString(prim.asString)
-                    else -> default
-                }
-            }
-            else -> default
-        }
-    }
-
-    private fun parseColorString(colorStr: String): Int {
-        val text = colorStr.trim()
-        if (text.isEmpty()) return 0
-        return kotlin.runCatching {
-            if (text.startsWith("#")) {
-                text.substring(1).toLong(16).toInt()
-            } else {
-                text.toLong().toInt()
-            }
-        }.getOrElse {
-            kotlin.runCatching { text.toColorInt() }.getOrDefault(0)
-        }
-    }
-}

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

@@ -58,6 +58,17 @@ object ThemeImportExport {
      */
     fun saveCurrentAsTheme(name: String): SavedTheme {
         val data = exportFromCurrent()
+        return saveThemeData(name, data)
+    }
+
+    /**
+     * 保存指定数据为新主题
+     */
+    fun saveCurrentAsTheme(name: String, data: ThemeExportData): SavedTheme {
+        return saveThemeData(name, data)
+    }
+
+    private fun saveThemeData(name: String, data: ThemeExportData): SavedTheme {
         val file = File(baseDir, "$name.json")
         baseDir.mkdirs()
         file.writeText(EXPORT_GSON.toJson(data))

+ 0 - 327
app/src/main/java/io/legado/app/help/config/ThemePackConfig.kt

@@ -1,327 +0,0 @@
-package io.legado.app.help.config
-
-import android.net.Uri
-import com.google.gson.Gson
-import com.google.gson.GsonBuilder
-import io.legado.app.ui.config.coverConfig.CoverConfig
-import io.legado.app.ui.config.mainConfig.MainConfig
-import io.legado.app.ui.config.themeConfig.ThemeConfig
-import io.legado.app.utils.FileUtils
-import io.legado.app.utils.externalFiles
-import splitties.init.appCtx
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileOutputStream
-import java.util.zip.ZipEntry
-import java.util.zip.ZipInputStream
-import java.util.zip.ZipOutputStream
-
-object ThemePackConfig {
-
-    private const val CONFIG_FILE = "theme.json"
-    private const val DIR_NAME = "theme_packs"
-    private val baseDir get() = File(appCtx.filesDir, DIR_NAME)
-
-    private val PACK_GSON: Gson by lazy {
-        GsonBuilder()
-            .registerTypeAdapter(ThemePack::class.java, ThemePackSerializer())
-            .setPrettyPrinting()
-            .disableHtmlEscaping()
-            .create()
-    }
-
-    private val _packList = mutableListOf<ThemePack>()
-    val packList: List<ThemePack> get() = _packList
-
-    init {
-        loadAll()
-    }
-
-    private fun loadAll() {
-        _packList.clear()
-        baseDir.mkdirs()
-        baseDir.listFiles()?.forEach { dir ->
-            if (dir.isDirectory) {
-                val configFile = File(dir, CONFIG_FILE)
-                if (configFile.exists()) {
-                    kotlin.runCatching {
-                        val json = configFile.readText()
-                        val pack = PACK_GSON.fromJson(json, ThemePack::class.java)
-                        if (pack.name.isNotEmpty()) {
-                            pack.folderName = dir.name
-                            _packList.add(pack)
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    fun reload() {
-        loadAll()
-    }
-
-    fun getPackDir(pack: ThemePack): File {
-        return File(baseDir, pack.folderName)
-    }
-
-    fun savePack(pack: ThemePack) {
-        val dir = getPackDir(pack)
-        dir.mkdirs()
-        val configFile = File(dir, CONFIG_FILE)
-        val json = PACK_GSON.toJson(pack)
-        FileUtils.createFileIfNotExist(configFile.absolutePath).writeText(json)
-    }
-
-    fun deletePack(pack: ThemePack) {
-        val dir = getPackDir(pack)
-        if (dir.exists()) {
-            dir.deleteRecursively()
-        }
-        _packList.remove(pack)
-    }
-
-    fun createFromCurrent(name: String): ThemePack {
-        val folderName = System.currentTimeMillis().toString()
-        val pack = ThemePack(
-            name = name,
-            folderName = folderName,
-            appearance = ThemeAppearanceConfig.fromCurrent(),
-            bgImageLight = ThemeConfig.bgImageLight,
-            bgImageDark = ThemeConfig.bgImageDark,
-            bgImageBlurring = ThemeConfig.bgImageBlurring,
-            bgImageNBlurring = ThemeConfig.bgImageNBlurring,
-            navIconBookshelf = MainConfig.navIconBookshelf,
-            navIconExplore = MainConfig.navIconExplore,
-            navIconRss = MainConfig.navIconRss,
-            navIconMy = MainConfig.navIconMy,
-            coverTextColor = CoverConfig.coverTextColor,
-            coverShadowColor = CoverConfig.coverShadowColor,
-            coverTextColorN = CoverConfig.coverTextColorN,
-            coverShadowColorN = CoverConfig.coverShadowColorN,
-            coverShowName = CoverConfig.coverShowName,
-            coverShowAuthor = CoverConfig.coverShowAuthor,
-            coverShowNameN = CoverConfig.coverShowNameN,
-            coverShowAuthorN = CoverConfig.coverShowAuthorN,
-            coverDefaultColor = CoverConfig.coverDefaultColor,
-            coverShowShadow = CoverConfig.coverShowShadow,
-            coverShowStroke = CoverConfig.coverShowStroke,
-            coverInfoOrientation = CoverConfig.coverInfoOrientation,
-            defaultCover = CoverConfig.defaultCover,
-            defaultCoverDark = CoverConfig.defaultCoverDark
-        )
-
-        val dir = File(baseDir, folderName)
-        dir.mkdirs()
-
-        copyResourceFile(pack.appearance.appFontPath, dir, "app_font")
-        copyResourceFile(pack.bgImageLight, dir, "bg_light")
-        copyResourceFile(pack.bgImageDark, dir, "bg_dark")
-        copyResourceFile(pack.navIconBookshelf, dir, "nav_bookshelf")
-        copyResourceFile(pack.navIconExplore, dir, "nav_explore")
-        copyResourceFile(pack.navIconRss, dir, "nav_rss")
-        copyResourceFile(pack.navIconMy, dir, "nav_my")
-
-        copyCoverImages(pack.defaultCover, dir, "cover_day")
-        copyCoverImages(pack.defaultCoverDark, dir, "cover_night")
-
-        savePack(pack)
-        _packList.add(pack)
-        return pack
-    }
-
-    fun applyPack(pack: ThemePack) {
-        val dir = getPackDir(pack)
-        val restoredAppearance = pack.appearance.copy(
-            appFontPath = restoreResourcePath(dir, "app_font", pack.appearance.appFontPath)
-        )
-        restoredAppearance.applyToThemeConfig()
-
-        ThemeConfig.bgImageLight = restoreResourcePath(dir, "bg_light", pack.bgImageLight)
-        ThemeConfig.bgImageDark = restoreResourcePath(dir, "bg_dark", pack.bgImageDark)
-        ThemeConfig.bgImageBlurring = pack.bgImageBlurring
-        ThemeConfig.bgImageNBlurring = pack.bgImageNBlurring
-
-        MainConfig.navIconBookshelf = restoreResourcePath(dir, "nav_bookshelf", pack.navIconBookshelf) ?: ""
-        MainConfig.navIconExplore = restoreResourcePath(dir, "nav_explore", pack.navIconExplore) ?: ""
-        MainConfig.navIconRss = restoreResourcePath(dir, "nav_rss", pack.navIconRss) ?: ""
-        MainConfig.navIconMy = restoreResourcePath(dir, "nav_my", pack.navIconMy) ?: ""
-
-        CoverConfig.coverTextColor = pack.coverTextColor
-        CoverConfig.coverShadowColor = pack.coverShadowColor
-        CoverConfig.coverTextColorN = pack.coverTextColorN
-        CoverConfig.coverShadowColorN = pack.coverShadowColorN
-        CoverConfig.coverShowName = pack.coverShowName
-        CoverConfig.coverShowAuthor = pack.coverShowAuthor
-        CoverConfig.coverShowNameN = pack.coverShowNameN
-        CoverConfig.coverShowAuthorN = pack.coverShowAuthorN
-        CoverConfig.coverDefaultColor = pack.coverDefaultColor
-        CoverConfig.coverShowShadow = pack.coverShowShadow
-        CoverConfig.coverShowStroke = pack.coverShowStroke
-        CoverConfig.coverInfoOrientation = pack.coverInfoOrientation
-        CoverConfig.defaultCover = restoreCoverImages(dir, "cover_day", pack.defaultCover)
-        CoverConfig.defaultCoverDark = restoreCoverImages(dir, "cover_night", pack.defaultCoverDark)
-    }
-
-    fun exportToZip(pack: ThemePack, destFile: File) {
-        val dir = getPackDir(pack)
-        if (!dir.exists()) return
-        ZipOutputStream(FileOutputStream(destFile)).use { zos ->
-            dir.walkTopDown().forEach { file ->
-                if (file.isFile) {
-                    val entryName = file.relativeTo(dir).path.replace('\\', '/')
-                    zos.putNextEntry(ZipEntry(entryName))
-                    FileInputStream(file).use { fis ->
-                        fis.copyTo(zos)
-                    }
-                    zos.closeEntry()
-                }
-            }
-        }
-    }
-
-    fun importFromZip(zipFile: File): ThemePack? {
-        val folderName = System.currentTimeMillis().toString()
-        val dir = File(baseDir, folderName)
-        dir.mkdirs()
-
-        return kotlin.runCatching {
-            ZipInputStream(FileInputStream(zipFile)).use { zis ->
-                var entry = zis.nextEntry
-                while (entry != null) {
-                    val outFile = resolveZipOutputFile(dir, entry)
-                    if (entry.isDirectory) {
-                        outFile.mkdirs()
-                    } else {
-                        outFile.parentFile?.mkdirs()
-                        FileOutputStream(outFile).use { fos ->
-                            zis.copyTo(fos)
-                        }
-                    }
-                    zis.closeEntry()
-                    entry = zis.nextEntry
-                }
-            }
-
-            val configFile = File(dir, CONFIG_FILE)
-            if (!configFile.exists()) {
-                dir.deleteRecursively()
-                return null
-            }
-
-            val json = configFile.readText()
-            val pack = PACK_GSON.fromJson(json, ThemePack::class.java)
-            pack.folderName = folderName
-            _packList.add(pack)
-            pack
-        }.onFailure {
-            dir.deleteRecursively()
-        }.getOrNull()
-    }
-
-    private fun resolveZipOutputFile(dir: File, entry: ZipEntry): File {
-        val outFile = File(dir, entry.name).canonicalFile
-        val canonicalDir = dir.canonicalFile
-        if (outFile.path == canonicalDir.path ||
-            outFile.path.startsWith(canonicalDir.path + File.separator)
-        ) {
-            return outFile
-        }
-        throw IllegalArgumentException("Invalid theme pack entry: ${entry.name}")
-    }
-
-    private fun copyResourceFile(sourcePath: String?, destDir: File, prefix: String) {
-        if (sourcePath.isNullOrEmpty()) return
-        val sourceUri = Uri.parse(sourcePath)
-        val sourceName = sourceUri.lastPathSegment ?: sourcePath
-        val ext = sourceName.substringAfterLast(".", "dat").ifBlank { "dat" }
-        val destFile = File(destDir, "$prefix.$ext")
-
-        if (sourceUri.scheme == "content") {
-            appCtx.contentResolver.openInputStream(sourceUri)?.use { input ->
-                FileOutputStream(destFile).use { output ->
-                    input.copyTo(output)
-                }
-            }
-            return
-        }
-
-        val sourceFile = File(sourcePath)
-        if (!sourceFile.exists()) return
-        sourceFile.copyTo(destFile, overwrite = true)
-    }
-
-    private fun restoreResourcePath(packDir: File, prefix: String, originalPath: String?): String? {
-        if (originalPath.isNullOrEmpty()) return null
-        val candidates = packDir.listFiles { file ->
-            file.name.startsWith(prefix) && file.isFile
-        }
-        if (candidates.isNullOrEmpty()) return null
-        val sourceFile = candidates.first()
-        val targetDir = File(appCtx.filesDir, "theme_pack_resources")
-        targetDir.mkdirs()
-        val targetFile = File(targetDir, "${System.currentTimeMillis()}_${sourceFile.name}")
-        sourceFile.copyTo(targetFile, overwrite = true)
-        return targetFile.absolutePath
-    }
-
-    private fun copyCoverImages(coverPaths: String, destDir: File, prefix: String) {
-        val paths = coverPaths.split(",").filter { it.isNotBlank() }
-        paths.forEachIndexed { index, path ->
-            copyResourceFile(path, destDir, "${prefix}_$index")
-        }
-    }
-
-    private fun restoreCoverImages(packDir: File, prefix: String, originalPaths: String): String {
-        if (originalPaths.isEmpty()) return ""
-        val candidates = packDir.listFiles { file ->
-            file.name.startsWith(prefix + "_") && file.isFile
-        }
-        if (candidates.isNullOrEmpty()) return originalPaths
-        val sortedCandidates = candidates.sortedBy { file ->
-            file.name.substringAfterLast("_").substringBefore(".").toIntOrNull() ?: 0
-        }
-        val coverDir = File(appCtx.externalFiles, "covers")
-        coverDir.mkdirs()
-        val restoredPaths = mutableListOf<String>()
-        sortedCandidates.forEach { sourceFile ->
-            val targetFile = File(coverDir, sourceFile.name)
-            sourceFile.copyTo(targetFile, overwrite = true)
-            restoredPaths.add(targetFile.absolutePath)
-        }
-        return if (restoredPaths.isEmpty()) originalPaths else restoredPaths.joinToString(",")
-    }
-
-    data class ThemePack(
-        var name: String = "",
-        @Transient
-        var folderName: String = "",
-        var appearance: ThemeAppearanceConfig = ThemeAppearanceConfig(),
-        var bgImageLight: String? = null,
-        var bgImageDark: String? = null,
-        var bgImageBlurring: Int = 0,
-        var bgImageNBlurring: Int = 0,
-        var navIconBookshelf: String = "",
-        var navIconExplore: String = "",
-        var navIconRss: String = "",
-        var navIconMy: String = "",
-        var coverTextColor: Int = -16777216,
-        var coverShadowColor: Int = -16777216,
-        var coverTextColorN: Int = -1,
-        var coverShadowColorN: Int = -1,
-        var coverShowName: Boolean = true,
-        var coverShowAuthor: Boolean = true,
-        var coverShowNameN: Boolean = true,
-        var coverShowAuthorN: Boolean = true,
-        var coverDefaultColor: Boolean = true,
-        var coverShowShadow: Boolean = false,
-        var coverShowStroke: Boolean = true,
-        var coverInfoOrientation: String = "0",
-        var defaultCover: String = "",
-        var defaultCoverDark: String = ""
-    ) {
-        val themeColor: Int get() = appearance.themeColor
-        val appFontPath: String? get() = appearance.appFontPath
-    }
-}
-

+ 0 - 83
app/src/main/java/io/legado/app/help/config/ThemePackSerializer.kt

@@ -1,83 +0,0 @@
-package io.legado.app.help.config
-
-import com.google.gson.JsonDeserializationContext
-import com.google.gson.JsonDeserializer
-import com.google.gson.JsonElement
-import com.google.gson.JsonObject
-import com.google.gson.JsonSerializationContext
-import com.google.gson.JsonSerializer
-import io.legado.app.utils.getBoolean
-import io.legado.app.utils.getInt
-import io.legado.app.utils.getString
-import java.lang.reflect.Type
-
-class ThemePackSerializer : JsonSerializer<ThemePackConfig.ThemePack>,
-    JsonDeserializer<ThemePackConfig.ThemePack> {
-
-    override fun serialize(
-        src: ThemePackConfig.ThemePack,
-        typeOfSrc: Type,
-        context: JsonSerializationContext
-    ): JsonElement {
-        val obj = JsonObject()
-        obj.addProperty("name", src.name)
-        ThemeAppearanceJson.writeTo(obj, src.appearance)
-        obj.addProperty("bgImageLight", src.bgImageLight)
-        obj.addProperty("bgImageDark", src.bgImageDark)
-        obj.addProperty("bgImageBlurring", src.bgImageBlurring)
-        obj.addProperty("bgImageNBlurring", src.bgImageNBlurring)
-        obj.addProperty("navIconBookshelf", src.navIconBookshelf)
-        obj.addProperty("navIconExplore", src.navIconExplore)
-        obj.addProperty("navIconRss", src.navIconRss)
-        obj.addProperty("navIconMy", src.navIconMy)
-        obj.addProperty("coverTextColor", ThemeAppearanceJson.colorToHex(src.coverTextColor))
-        obj.addProperty("coverShadowColor", ThemeAppearanceJson.colorToHex(src.coverShadowColor))
-        obj.addProperty("coverTextColorN", ThemeAppearanceJson.colorToHex(src.coverTextColorN))
-        obj.addProperty("coverShadowColorN", ThemeAppearanceJson.colorToHex(src.coverShadowColorN))
-        obj.addProperty("coverShowName", src.coverShowName)
-        obj.addProperty("coverShowAuthor", src.coverShowAuthor)
-        obj.addProperty("coverShowNameN", src.coverShowNameN)
-        obj.addProperty("coverShowAuthorN", src.coverShowAuthorN)
-        obj.addProperty("coverDefaultColor", src.coverDefaultColor)
-        obj.addProperty("coverShowShadow", src.coverShowShadow)
-        obj.addProperty("coverShowStroke", src.coverShowStroke)
-        obj.addProperty("coverInfoOrientation", src.coverInfoOrientation)
-        obj.addProperty("defaultCover", src.defaultCover)
-        obj.addProperty("defaultCoverDark", src.defaultCoverDark)
-        return obj
-    }
-
-    override fun deserialize(
-        json: JsonElement,
-        typeOfT: Type,
-        context: JsonDeserializationContext
-    ): ThemePackConfig.ThemePack {
-        val obj = json.asJsonObject
-        return ThemePackConfig.ThemePack(
-            name = obj.getString("name", "") ?: "",
-            appearance = ThemeAppearanceJson.readFrom(obj),
-            bgImageLight = obj.getString("bgImageLight", null),
-            bgImageDark = obj.getString("bgImageDark", null),
-            bgImageBlurring = obj.getInt("bgImageBlurring", 0),
-            bgImageNBlurring = obj.getInt("bgImageNBlurring", 0),
-            navIconBookshelf = obj.getString("navIconBookshelf", "") ?: "",
-            navIconExplore = obj.getString("navIconExplore", "") ?: "",
-            navIconRss = obj.getString("navIconRss", "") ?: "",
-            navIconMy = obj.getString("navIconMy", "") ?: "",
-            coverTextColor = ThemeAppearanceJson.readColor(obj, "coverTextColor").takeIf { it != 0 } ?: -16777216,
-            coverShadowColor = ThemeAppearanceJson.readColor(obj, "coverShadowColor").takeIf { it != 0 } ?: -16777216,
-            coverTextColorN = ThemeAppearanceJson.readColor(obj, "coverTextColorN").takeIf { it != 0 } ?: -1,
-            coverShadowColorN = ThemeAppearanceJson.readColor(obj, "coverShadowColorN").takeIf { it != 0 } ?: -1,
-            coverShowName = obj.getBoolean("coverShowName", true),
-            coverShowAuthor = obj.getBoolean("coverShowAuthor", true),
-            coverShowNameN = obj.getBoolean("coverShowNameN", true),
-            coverShowAuthorN = obj.getBoolean("coverShowAuthorN", true),
-            coverDefaultColor = obj.getBoolean("coverDefaultColor", true),
-            coverShowShadow = obj.getBoolean("coverShowShadow", false),
-            coverShowStroke = obj.getBoolean("coverShowStroke", true),
-            coverInfoOrientation = obj.getString("coverInfoOrientation", "0") ?: "0",
-            defaultCover = obj.getString("defaultCover", "") ?: "",
-            defaultCoverDark = obj.getString("defaultCoverDark", "") ?: ""
-        )
-    }
-}

+ 16 - 0
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt

@@ -172,6 +172,22 @@ object ThemeConfig {
 
     var defaultHomePage by prefDelegate(PreferKey.defaultHomePage, "bookshelf")
 
+    var navExtended by prefDelegate("navExtended", false)
+
+    var webServiceAutoStart by prefDelegate(PreferKey.webServiceAutoStart, false)
+
+    var autoRefreshBook by prefDelegate(PreferKey.autoRefresh, false)
+
+    var autoCheckNewBackup by prefDelegate(PreferKey.autoCheckNewBackup, true)
+
+    var navIconBookshelf by prefDelegate(PreferKey.navIconBookshelf, "")
+
+    var navIconExplore by prefDelegate(PreferKey.navIconExplore, "")
+
+    var navIconRss by prefDelegate(PreferKey.navIconRss, "")
+
+    var navIconMy by prefDelegate(PreferKey.navIconMy, "")
+
     fun hasImageBg(isDark: Boolean): Boolean =
         !(if (isDark) bgImageDark else bgImageLight).isNullOrBlank()
 

+ 17 - 6
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt

@@ -79,7 +79,6 @@ import com.google.android.material.color.DynamicColorsOptions
 import io.legado.app.R
 import io.legado.app.base.AppContextWrapper
 import io.legado.app.constant.PreferKey
-import io.legado.app.ui.config.mainConfig.MainConfig
 import io.legado.app.constant.EventBus
 import io.legado.app.help.LauncherIconHelp
 import io.legado.app.help.loadFontFiles
@@ -124,6 +123,7 @@ import top.yukonga.miuix.kmp.basic.Text as MiuixText
 fun ThemeConfigScreen(
     onBackClick: () -> Unit,
     onNavigateToCustomTheme: () -> Unit,
+    onNavigateToThemeManage: () -> Unit,
     viewModel: ThemeConfigViewModel = koinViewModel()
 ) {
     val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
@@ -412,7 +412,7 @@ fun ThemeConfigScreen(
                     )
                     AnimatedVisibility(visible = ThemeConfig.useFloatingBottomBar) {
                         Column(
-                            verticalArrangement = Arrangement.spacedBy(8.dp)
+                            verticalArrangement = Arrangement.spacedBy(2.dp)
                         ) {
                             SwitchSettingItem(
                                 title = stringResource(R.string.floating_bottom_bar_liquid_glass),
@@ -716,10 +716,10 @@ fun ThemeConfigScreen(
             item {
                 SplicedColumnGroup(title = "导航栏图标设置") {
                     val customCount = listOf(
-                        MainConfig.navIconBookshelf,
-                        MainConfig.navIconExplore,
-                        MainConfig.navIconRss,
-                        MainConfig.navIconMy
+                        ThemeConfig.navIconBookshelf,
+                        ThemeConfig.navIconExplore,
+                        ThemeConfig.navIconRss,
+                        ThemeConfig.navIconMy
                     ).count { it.isNotEmpty() }
                     ClickableSettingItem(
                         title = "导航栏图标",
@@ -728,6 +728,17 @@ fun ThemeConfigScreen(
                     )
                 }
             }
+
+            // Theme management
+            item {
+                SplicedColumnGroup(title = "主题管理") {
+                    ClickableSettingItem(
+                        title = "主题管理",
+                        description = "保存、导入、导出主题配置",
+                        onClick = onNavigateToThemeManage
+                    )
+                }
+            }
         }
     }
 

+ 369 - 0
app/src/main/java/io/legado/app/ui/config/themeManage/EditThemeSheet.kt

@@ -0,0 +1,369 @@
+package io.legado.app.ui.config.themeManage
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Done
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringArrayResource
+import androidx.compose.ui.unit.dp
+import io.legado.app.R
+import io.legado.app.help.config.ThemeExportData
+import io.legado.app.ui.widget.components.AppTextField
+import io.legado.app.ui.widget.components.button.MediumIconButton
+import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.settingItem.CompactClickableSettingItem
+import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem
+import io.legado.app.ui.widget.components.settingItem.CompactSliderSettingItem
+import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
+import io.legado.app.ui.widget.components.text.AppText
+
+@Composable
+fun EditThemeSheet(
+    show: Boolean,
+    themeData: ThemeExportData?,
+    themeName: String,
+    onDismissRequest: () -> Unit,
+    onSave: (newName: String, newData: ThemeExportData) -> Unit
+) {
+    if (!show || themeData == null) return
+
+    var data by remember(themeData) { mutableStateOf(themeData) }
+    var name by remember(themeName) { mutableStateOf(themeName) }
+    var showColorPicker by remember { mutableStateOf(false) }
+    var currentColorKey by remember { mutableStateOf("") }
+
+    AppModalBottomSheet(
+        show = show,
+        onDismissRequest = onDismissRequest,
+        title = "编辑主题",
+        endAction = {
+            MediumIconButton(
+                onClick = {
+                    if (name.isNotBlank()) {
+                        onSave(name, data)
+                    }
+                },
+                imageVector = Icons.Default.Done,
+                contentDescription = "Save"
+            )
+        }
+    ) {
+        Column(
+            modifier = Modifier
+                .fillMaxWidth()
+                .verticalScroll(rememberScrollState())
+                .navigationBarsPadding()
+                .padding(bottom = 32.dp)
+        ) {
+            // Name
+            AppTextField(
+                value = name,
+                onValueChange = { name = it },
+                placeholder = { },
+                singleLine = true,
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(horizontal = 16.dp)
+            )
+
+            Spacer(modifier = Modifier.height(8.dp))
+
+            // Basic settings
+            SectionTitle("基础设置")
+            CompactDropdownSettingItem(
+                title = "主题模式",
+                selectedValue = data.themeMode,
+                displayEntries = stringArrayResource(R.array.theme_mode),
+                entryValues = stringArrayResource(R.array.theme_mode_v),
+                onValueChange = { data = data.copy(themeMode = it) }
+            )
+            CompactDropdownSettingItem(
+                title = "调色板风格",
+                selectedValue = data.paletteStyle,
+                displayEntries = stringArrayResource(R.array.paletteStyle),
+                entryValues = stringArrayResource(R.array.paletteStyle_value),
+                onValueChange = { data = data.copy(paletteStyle = it) }
+            )
+            CompactDropdownSettingItem(
+                title = "Material 版本",
+                selectedValue = data.materialVersion,
+                displayEntries = stringArrayResource(R.array.materialVersion),
+                entryValues = stringArrayResource(R.array.materialVersion_value),
+                onValueChange = { data = data.copy(materialVersion = it) }
+            )
+            CompactDropdownSettingItem(
+                title = "对比度偏好",
+                selectedValue = data.customContrast,
+                displayEntries = stringArrayResource(R.array.customContrast),
+                entryValues = stringArrayResource(R.array.customContrast_value),
+                onValueChange = { data = data.copy(customContrast = it) }
+            )
+
+            Spacer(modifier = Modifier.height(8.dp))
+
+            // Colors
+            SectionTitle("颜色设置")
+            CompactSwitchSettingItem(
+                title = "使用色板生成颜色",
+                checked = !data.enableDeepPersonalization,
+                onCheckedChange = { data = data.copy(enableDeepPersonalization = !it) }
+            )
+            if (data.enableDeepPersonalization) {
+                ColorItem("主题色", data.themeColor) {
+                    currentColorKey = "themeColor"; showColorPicker = true
+                }
+                ColorItem("次要主题色", data.secondaryThemeColor) {
+                    currentColorKey = "secondaryThemeColor"; showColorPicker = true
+                }
+                ColorItem("主要字体色", data.primaryTextColor) {
+                    currentColorKey = "primaryTextColor"; showColorPicker = true
+                }
+                ColorItem("次要字体色", data.secondaryTextColor) {
+                    currentColorKey = "secondaryTextColor"; showColorPicker = true
+                }
+                ColorItem("背景色", data.themeBackgroundColor) {
+                    currentColorKey = "themeBackgroundColor"; showColorPicker = true
+                }
+                ColorItem("标签容器色", data.labelContainerColor) {
+                    currentColorKey = "labelContainerColor"; showColorPicker = true
+                }
+            } else {
+                ColorItem("日间种子色", data.cPrimary) {
+                    currentColorKey = "cPrimary"; showColorPicker = true
+                }
+                ColorItem("夜间种子色", data.cNPrimary) {
+                    currentColorKey = "cNPrimary"; showColorPicker = true
+                }
+            }
+
+            Spacer(modifier = Modifier.height(8.dp))
+
+            // Interface layout
+            SectionTitle("界面布局")
+            CompactSwitchSettingItem(
+                title = "发现",
+                checked = data.showDiscovery,
+                onCheckedChange = { data = data.copy(showDiscovery = it) }
+            )
+            CompactSwitchSettingItem(
+                title = "RSS",
+                checked = data.showRss,
+                onCheckedChange = { data = data.copy(showRss = it) }
+            )
+            CompactSwitchSettingItem(
+                title = "显示底栏",
+                checked = data.showBottomView,
+                onCheckedChange = { data = data.copy(showBottomView = it) }
+            )
+            CompactSwitchSettingItem(
+                title = "浮动底栏",
+                checked = data.useFloatingBottomBar,
+                onCheckedChange = { data = data.copy(useFloatingBottomBar = it) }
+            )
+            CompactSwitchSettingItem(
+                title = "状态栏",
+                checked = data.showStatusBar,
+                onCheckedChange = { data = data.copy(showStatusBar = it) }
+            )
+            CompactSwitchSettingItem(
+                title = "翻页动画",
+                checked = data.swipeAnimation,
+                onCheckedChange = { data = data.copy(swipeAnimation = it) }
+            )
+            CompactDropdownSettingItem(
+                title = "平板模式",
+                selectedValue = data.tabletInterface,
+                displayEntries = stringArrayResource(R.array.tabletInterface),
+                entryValues = stringArrayResource(R.array.tabletInterface_value),
+                onValueChange = { data = data.copy(tabletInterface = it) }
+            )
+            CompactDropdownSettingItem(
+                title = "标签显示",
+                selectedValue = data.labelVisibilityMode,
+                displayEntries = stringArrayResource(R.array.label_vis_mode),
+                entryValues = stringArrayResource(R.array.label_vis_mode_value),
+                onValueChange = { data = data.copy(labelVisibilityMode = it) }
+            )
+            CompactDropdownSettingItem(
+                title = "默认主页",
+                selectedValue = data.defaultHomePage,
+                displayEntries = stringArrayResource(R.array.default_home_page),
+                entryValues = stringArrayResource(R.array.default_home_page_value),
+                onValueChange = { data = data.copy(defaultHomePage = it) }
+            )
+
+            Spacer(modifier = Modifier.height(8.dp))
+
+            // Blur
+            SectionTitle("模糊效果")
+            CompactSwitchSettingItem(
+                title = "启用模糊",
+                checked = data.enableBlur,
+                onCheckedChange = { data = data.copy(enableBlur = it) }
+            )
+            if (data.enableBlur) {
+                CompactSliderSettingItem(
+                    title = "顶栏模糊半径",
+                    value = data.topBarBlurRadius.toFloat(),
+                    valueRange = 1f..60f,
+                    onValueChange = { data = data.copy(topBarBlurRadius = it.toInt()) }
+                )
+                CompactSliderSettingItem(
+                    title = "底栏模糊半径",
+                    value = data.bottomBarBlurRadius.toFloat(),
+                    valueRange = 1f..60f,
+                    onValueChange = { data = data.copy(bottomBarBlurRadius = it.toInt()) }
+                )
+                CompactSliderSettingItem(
+                    title = "顶栏透明度",
+                    value = data.topBarBlurAlpha.toFloat(),
+                    valueRange = 0f..255f,
+                    onValueChange = { data = data.copy(topBarBlurAlpha = it.toInt()) }
+                )
+                CompactSliderSettingItem(
+                    title = "底栏透明度",
+                    value = data.bottomBarBlurAlpha.toFloat(),
+                    valueRange = 0f..255f,
+                    onValueChange = { data = data.copy(bottomBarBlurAlpha = it.toInt()) }
+                )
+            }
+
+            Spacer(modifier = Modifier.height(8.dp))
+
+            // Opacity
+            SectionTitle("透明度")
+            CompactSliderSettingItem(
+                title = "顶栏透明度",
+                value = data.topBarOpacity.toFloat(),
+                valueRange = 0f..100f,
+                onValueChange = { data = data.copy(topBarOpacity = it.toInt()) }
+            )
+            CompactSliderSettingItem(
+                title = "底栏透明度",
+                value = data.bottomBarOpacity.toFloat(),
+                valueRange = 0f..100f,
+                onValueChange = { data = data.copy(bottomBarOpacity = it.toInt()) }
+            )
+
+            Spacer(modifier = Modifier.height(8.dp))
+
+            // Container
+            SectionTitle("容器设置")
+            CompactSliderSettingItem(
+                title = "容器不透明度",
+                value = data.containerOpacity.toFloat(),
+                valueRange = 0f..100f,
+                onValueChange = { data = data.copy(containerOpacity = it.toInt()) }
+            )
+            CompactSwitchSettingItem(
+                title = "容器边框",
+                checked = data.enableContainerBorder,
+                onCheckedChange = { data = data.copy(enableContainerBorder = it) }
+            )
+
+            Spacer(modifier = Modifier.height(8.dp))
+
+            // Other
+            SectionTitle("其他")
+            CompactSwitchSettingItem(
+                title = "纯黑模式",
+                checked = data.isPureBlack,
+                onCheckedChange = { data = data.copy(isPureBlack = it) }
+            )
+            CompactSwitchSettingItem(
+                title = "弹性顶栏",
+                checked = data.useFlexibleTopAppBar,
+                onCheckedChange = { data = data.copy(useFlexibleTopAppBar = it) }
+            )
+        }
+    }
+
+    ColorPickerSheet(
+        show = showColorPicker,
+        initialColor = when (currentColorKey) {
+            "themeColor" -> data.themeColor
+            "secondaryThemeColor" -> data.secondaryThemeColor
+            "primaryTextColor" -> data.primaryTextColor
+            "secondaryTextColor" -> data.secondaryTextColor
+            "themeBackgroundColor" -> data.themeBackgroundColor
+            "labelContainerColor" -> data.labelContainerColor
+            "cPrimary" -> data.cPrimary
+            "cNPrimary" -> data.cNPrimary
+            else -> 0
+        },
+        onDismissRequest = { showColorPicker = false },
+        onColorSelected = { color ->
+            data = when (currentColorKey) {
+                "themeColor" -> data.copy(themeColor = color)
+                "secondaryThemeColor" -> data.copy(secondaryThemeColor = color)
+                "primaryTextColor" -> data.copy(primaryTextColor = color)
+                "secondaryTextColor" -> data.copy(secondaryTextColor = color)
+                "themeBackgroundColor" -> data.copy(themeBackgroundColor = color)
+                "labelContainerColor" -> data.copy(labelContainerColor = color)
+                "cPrimary" -> data.copy(cPrimary = color)
+                "cNPrimary" -> data.copy(cNPrimary = color)
+                else -> data
+            }
+            showColorPicker = false
+        }
+    )
+}
+
+@Composable
+private fun SectionTitle(text: String) {
+    AppText(
+        text = text,
+        style = MaterialTheme.typography.titleSmall,
+        color = MaterialTheme.colorScheme.onSurfaceVariant,
+        modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
+    )
+}
+
+@Composable
+private fun ColorItem(title: String, colorValue: Int, onClick: () -> Unit) {
+    CompactClickableSettingItem(
+        title = title,
+        description = if (colorValue != 0) {
+            "#${Integer.toHexString(colorValue).uppercase()}"
+        } else {
+            null
+        },
+        onClick = onClick,
+        trailingContent = {
+            if (colorValue != 0) {
+                Box(
+                    modifier = Modifier
+                        .size(24.dp)
+                        .clip(CircleShape)
+                        .background(Color(colorValue))
+                        .border(
+                            1.dp,
+                            MaterialTheme.colorScheme.outlineVariant,
+                            CircleShape
+                        )
+                )
+            }
+        }
+    )
+}

+ 27 - 0
app/src/main/java/io/legado/app/ui/config/themeManage/ThemeManageScreen.kt

@@ -19,6 +19,7 @@ import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.Edit
 import androidx.compose.material.icons.filled.Share
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
@@ -68,6 +69,7 @@ fun ThemeManageScreen(
     var deleteTarget by remember { mutableStateOf<SavedTheme?>(null) }
     var applyTarget by remember { mutableStateOf<SavedTheme?>(null) }
     var exportTarget by remember { mutableStateOf<SavedTheme?>(null) }
+    var editTarget by remember { mutableStateOf<SavedTheme?>(null) }
     var showRestartDialog by remember { mutableStateOf(false) }
     var savedThemesVersion by remember { mutableIntStateOf(0) }
     val savedThemes = remember(savedThemesVersion) { ThemeImportExport.savedThemes.toList() }
@@ -166,6 +168,7 @@ fun ThemeManageScreen(
                     SavedThemeItem(
                         theme = theme,
                         onApply = { applyTarget = theme },
+                        onEdit = { editTarget = theme },
                         onExport = {
                             exportTarget = theme
                             exportLauncher.launch("${theme.name}.json")
@@ -257,12 +260,29 @@ fun ThemeManageScreen(
         onDismiss = { deleteTarget = null },
         text = "确定删除主题「${deleteTarget?.name}」?此操作不可恢复。"
     )
+
+    // Edit theme sheet
+    EditThemeSheet(
+        show = editTarget != null,
+        themeData = editTarget?.data,
+        themeName = editTarget?.name ?: "",
+        onDismissRequest = { editTarget = null },
+        onSave = { newName, newData ->
+            editTarget?.let { old ->
+                ThemeImportExport.deleteSavedTheme(old)
+            }
+            ThemeImportExport.saveCurrentAsTheme(newName, newData)
+            savedThemesVersion++
+            editTarget = null
+        }
+    )
 }
 
 @Composable
 private fun SavedThemeItem(
     theme: SavedTheme,
     onApply: () -> Unit,
+    onEdit: () -> Unit,
     onExport: () -> Unit,
     onDelete: () -> Unit
 ) {
@@ -320,6 +340,13 @@ private fun SavedThemeItem(
                 )
             }
 
+            IconButton(onClick = onEdit) {
+                Icon(
+                    imageVector = Icons.Default.Edit,
+                    contentDescription = "编辑",
+                    modifier = Modifier.size(20.dp)
+                )
+            }
             IconButton(onClick = onExport) {
                 Icon(
                     imageVector = Icons.Default.Share,

+ 0 - 372
app/src/main/java/io/legado/app/ui/config/themePack/ThemePackScreen.kt

@@ -1,372 +0,0 @@
-package io.legado.app.ui.config.themePack
-
-import androidx.activity.compose.rememberLauncherForActivityResult
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Delete
-import androidx.compose.material.icons.filled.Share
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
-import io.legado.app.R
-import io.legado.app.help.config.ThemePackConfig
-import io.legado.app.ui.theme.adaptiveContentPadding
-import io.legado.app.ui.widget.components.AppScaffold
-import io.legado.app.ui.widget.components.SplicedColumnGroup
-import io.legado.app.ui.widget.components.alert.AppAlertDialog
-import io.legado.app.ui.widget.components.card.GlassCard
-import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
-import io.legado.app.ui.widget.components.text.AppText
-import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
-import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
-import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
-import io.legado.app.utils.toastOnUi
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import java.io.File
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun ThemePackScreen(
-    onBackClick: () -> Unit
-) {
-    val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
-    val context = LocalContext.current
-    val scope = rememberCoroutineScope()
-    var listVersion by remember { mutableIntStateOf(0) }
-    var showAddDialog by remember { mutableStateOf(false) }
-    var newThemeName by remember { mutableStateOf("") }
-    var deleteTarget by remember { mutableStateOf<ThemePackConfig.ThemePack?>(null) }
-    var applyTarget by remember { mutableStateOf<ThemePackConfig.ThemePack?>(null) }
-    var exportingPack by remember { mutableStateOf<ThemePackConfig.ThemePack?>(null) }
-
-    val packList = remember(listVersion) { ThemePackConfig.packList.toList() }
-
-    val exportLauncher = rememberLauncherForActivityResult(
-        ActivityResultContracts.CreateDocument("application/zip")
-    ) { uri ->
-        uri?.let {
-            val pack = exportingPack ?: return@let
-            exportingPack = null
-            scope.launch(Dispatchers.IO) {
-                kotlin.runCatching {
-                    val tempFile = File(context.cacheDir, "theme_export_${pack.name}.zip")
-                    ThemePackConfig.exportToZip(pack, tempFile)
-                    context.contentResolver.openOutputStream(uri)?.use { os ->
-                        tempFile.inputStream().use { it.copyTo(os) }
-                    }
-                    tempFile.delete()
-                    withContext(Dispatchers.Main) {
-                        context.toastOnUi("导出成功")
-                    }
-                }.onFailure {
-                    withContext(Dispatchers.Main) {
-                        context.toastOnUi("导出失败: ${it.message}")
-                    }
-                }
-            }
-        }
-    }
-
-    val importLauncher = rememberLauncherForActivityResult(
-        ActivityResultContracts.OpenDocument()
-    ) { uri ->
-        uri?.let {
-            scope.launch(Dispatchers.IO) {
-                kotlin.runCatching {
-                    val tempFile = File(context.cacheDir, "theme_import_${System.currentTimeMillis()}.zip")
-                    context.contentResolver.openInputStream(it)?.use { input ->
-                        tempFile.outputStream().use { output ->
-                            input.copyTo(output)
-                        }
-                    }
-                    val pack = ThemePackConfig.importFromZip(tempFile)
-                    tempFile.delete()
-                    if (pack != null) {
-                        listVersion++
-                        withContext(Dispatchers.Main) {
-                            context.toastOnUi("导入成功: ${pack.name}")
-                        }
-                    } else {
-                        withContext(Dispatchers.Main) {
-                            context.toastOnUi("导入失败: 无效的主题包")
-                        }
-                    }
-                }.onFailure {
-                    withContext(Dispatchers.Main) {
-                        context.toastOnUi("导入失败: ${it.message}")
-                    }
-                }
-            }
-        }
-    }
-
-    AppScaffold(
-        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
-        topBar = {
-            GlassMediumFlexibleTopAppBar(
-                title = stringResource(R.string.theme_pack),
-                scrollBehavior = scrollBehavior,
-                navigationIcon = {
-                    TopBarNavigationButton(onClick = onBackClick)
-                }
-            )
-        }
-    ) { paddingValues ->
-        LazyColumn(
-            modifier = Modifier.fillMaxSize(),
-            contentPadding = adaptiveContentPadding(
-                top = paddingValues.calculateTopPadding(),
-                bottom = 120.dp
-            )
-        ) {
-            item {
-                SplicedColumnGroup {
-                    ClickableSettingItem(
-                        title = "新增主题",
-                        description = "保存当前设置为新主题",
-                        onClick = {
-                            newThemeName = ""
-                            showAddDialog = true
-                        }
-                    )
-                    ClickableSettingItem(
-                        title = "导入主题",
-                        description = "从zip包导入主题",
-                        onClick = {
-                            importLauncher.launch(arrayOf("application/zip", "application/octet-stream"))
-                        }
-                    )
-                }
-            }
-
-            if (packList.isNotEmpty()) {
-                item {
-                    AppText(
-                        text = "主题列表",
-                        style = MaterialTheme.typography.titleSmall,
-                        color = MaterialTheme.colorScheme.onSurfaceVariant,
-                        modifier = Modifier.padding(start = 16.dp, top = 16.dp, bottom = 8.dp)
-                    )
-                }
-
-                items(packList, key = { it.folderName }) { pack ->
-                    ThemePackItem(
-                        pack = pack,
-                        onApply = { applyTarget = pack },
-                        onExport = { exportingPack = pack },
-                        onDelete = { deleteTarget = pack }
-                    )
-                }
-            }
-        }
-    }
-
-    AppAlertDialog(
-        data = if (showAddDialog) "add" else null,
-        onDismissRequest = { showAddDialog = false },
-        title = "新增主题",
-        confirmText = "保存",
-        onConfirm = {
-            if (newThemeName.isNotBlank()) {
-                scope.launch(Dispatchers.IO) {
-                    ThemePackConfig.createFromCurrent(newThemeName)
-                    listVersion++
-                }
-                showAddDialog = false
-            }
-        },
-        dismissText = "取消",
-        onDismiss = { showAddDialog = false },
-        content = {
-            io.legado.app.ui.widget.components.AppTextField(
-                value = newThemeName,
-                onValueChange = { newThemeName = it },
-                placeholder = { AppText(text = "请输入主题名称") },
-                singleLine = true,
-                modifier = Modifier.fillMaxWidth()
-            )
-        }
-    )
-
-    AppAlertDialog(
-        data = applyTarget,
-        onDismissRequest = { applyTarget = null },
-        title = "应用主题",
-        confirmText = "应用",
-        onConfirm = { pack ->
-            scope.launch(Dispatchers.IO) {
-                ThemePackConfig.applyPack(pack)
-                withContext(Dispatchers.Main) {
-                    context.toastOnUi("主题已应用,请重启生效")
-                }
-            }
-            applyTarget = null
-        },
-        dismissText = "取消",
-        onDismiss = { applyTarget = null },
-        text = "确定应用主题「${applyTarget?.name}」?应用后需要重启才能完全生效。"
-    )
-
-    AppAlertDialog(
-        data = deleteTarget,
-        onDismissRequest = { deleteTarget = null },
-        title = "删除主题",
-        confirmText = "删除",
-        onConfirm = { pack ->
-            scope.launch(Dispatchers.IO) {
-                ThemePackConfig.deletePack(pack)
-                listVersion++
-            }
-            deleteTarget = null
-        },
-        dismissText = "取消",
-        onDismiss = { deleteTarget = null },
-        text = "确定删除主题「${deleteTarget?.name}」?此操作不可恢复。"
-    )
-
-    LaunchedEffect(exportingPack) {
-        exportingPack?.let { pack ->
-            kotlin.runCatching {
-                exportLauncher.launch("${pack.name}.zip")
-            }.onFailure {
-                exportingPack = null
-                context.toastOnUi("导出失败: ${it.message}")
-            }
-        }
-    }
-}
-
-@Composable
-private fun ThemePackItem(
-    pack: ThemePackConfig.ThemePack,
-    onApply: () -> Unit,
-    onExport: () -> Unit,
-    onDelete: () -> Unit
-) {
-    GlassCard(
-        onClick = onApply,
-        modifier = Modifier
-            .fillMaxWidth()
-            .padding(horizontal = 16.dp, vertical = 4.dp),
-        cornerRadius = 20.dp
-    ) {
-        Column(
-            modifier = Modifier.fillMaxWidth()
-        ) {
-            val packDir = remember { ThemePackConfig.getPackDir(pack) }
-            val bgFile = remember(pack.folderName) {
-                packDir.listFiles()?.firstOrNull {
-                    it.isFile && (it.name.startsWith("bg_light") || it.name.startsWith("bg_dark"))
-                }
-            }
-            val coverFile = remember(pack.folderName) {
-                packDir.listFiles()?.firstOrNull {
-                    it.isFile && it.name.startsWith("cover_day_")
-                }
-            }
-            val previewFile = bgFile ?: coverFile
-
-            Box(
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .height(120.dp)
-                    .clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
-                    .background(
-                        if (pack.themeColor != 0) Color(pack.themeColor)
-                        else MaterialTheme.colorScheme.primary
-                    )
-            ) {
-                if (previewFile != null) {
-                    AsyncImage(
-                        model = previewFile,
-                        contentDescription = null,
-                        contentScale = ContentScale.Crop,
-                        modifier = Modifier.fillMaxSize()
-                    )
-                }
-            }
-
-            Row(
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .padding(start = 16.dp, end = 8.dp, top = 10.dp, bottom = 10.dp),
-                verticalAlignment = Alignment.CenterVertically
-            ) {
-                Column(
-                    modifier = Modifier.weight(1f)
-                ) {
-                    AppText(
-                        text = pack.name,
-                        style = MaterialTheme.typography.bodyLarge
-                    )
-                    val hasCustomIcon = pack.navIconBookshelf.isNotEmpty() ||
-                            pack.navIconExplore.isNotEmpty() ||
-                            pack.navIconRss.isNotEmpty() ||
-                            pack.navIconMy.isNotEmpty()
-                    val hasCover = pack.defaultCover.isNotEmpty() || pack.defaultCoverDark.isNotEmpty()
-                    val features = mutableListOf<String>()
-                    if (pack.appFontPath != null) features.add("应用字体")
-                    if (pack.bgImageLight != null || pack.bgImageDark != null) features.add("应用背景")
-                    if (hasCover) features.add("封面图片")
-                    if (hasCustomIcon) features.add("图标")
-                    if (features.isNotEmpty()) {
-                        AppText(
-                            text = features.joinToString(" · "),
-                            style = MaterialTheme.typography.bodySmall,
-                            color = MaterialTheme.colorScheme.onSurfaceVariant
-                        )
-                    }
-                }
-
-                IconButton(onClick = onExport) {
-                    Icon(
-                        imageVector = Icons.Default.Share,
-                        contentDescription = "导出",
-                        modifier = Modifier.size(20.dp)
-                    )
-                }
-                IconButton(onClick = onDelete) {
-                    Icon(
-                        imageVector = Icons.Default.Delete,
-                        contentDescription = "删除",
-                        modifier = Modifier.size(20.dp),
-                        tint = MaterialTheme.colorScheme.error
-                    )
-                }
-            }
-        }
-    }
-}

+ 26 - 25
app/src/main/java/io/legado/app/ui/main/MainScreen.kt

@@ -67,7 +67,6 @@ import dev.chrisbanes.haze.hazeSource
 import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.graphics.painter.BitmapPainter
 import io.legado.app.R
-import io.legado.app.ui.config.mainConfig.MainConfig
 import io.legado.app.ui.config.themeConfig.ThemeConfig
 import io.legado.app.ui.main.bookshelf.BookshelfScreen
 import io.legado.app.ui.main.bookshelf.BookshelfViewModel
@@ -114,6 +113,7 @@ fun MainScreen(
     onNavigateToExploreShow: (title: String?, sourceUrl: String, exploreUrl: String?) -> Unit,
     onNavigateToRssSort: (sourceUrl: String, sortUrl: String?, key: String?) -> Unit,
     onNavigateToRssRead: (title: String?, origin: String, link: String?, openUrl: String?) -> Unit,
+    onNavigateToReadRecord: () -> Unit,
     sharedTransitionScope: SharedTransitionScope? = null,
     animatedVisibilityScope: AnimatedVisibilityScope? = null,
 ) {
@@ -150,6 +150,7 @@ fun MainScreen(
                 }
 
                 MainEffect.ExitApp -> (context as? ComponentActivity)?.finish()
+                MainEffect.NavigateToReadRecord -> onNavigateToReadRecord()
             }
         }
     }
@@ -281,10 +282,10 @@ fun MainScreen(
                         },
                         label = if (labelVisibilityMode != "unlabeled") {
                             val hasCustomIcon = when (destination) {
-                                MainDestination.Bookshelf -> MainConfig.navIconBookshelf.isNotEmpty()
-                                MainDestination.Explore -> MainConfig.navIconExplore.isNotEmpty()
-                                MainDestination.Rss -> MainConfig.navIconRss.isNotEmpty()
-                                MainDestination.My -> MainConfig.navIconMy.isNotEmpty()
+                                MainDestination.Bookshelf -> ThemeConfig.navIconBookshelf.isNotEmpty()
+                                MainDestination.Explore -> ThemeConfig.navIconExplore.isNotEmpty()
+                                MainDestination.Rss -> ThemeConfig.navIconRss.isNotEmpty()
+                                MainDestination.My -> ThemeConfig.navIconMy.isNotEmpty()
                             }
                             if (hasCustomIcon) null else {{ AppText(stringResource(destination.labelId)) }}
                         } else null
@@ -301,10 +302,10 @@ fun MainScreen(
                         destinations.forEachIndexed { index, destination ->
                             val selected = pagerState.targetPage == index
                             val customIconPath = when (destination) {
-                                MainDestination.Bookshelf -> MainConfig.navIconBookshelf
-                                MainDestination.Explore -> MainConfig.navIconExplore
-                                MainDestination.Rss -> MainConfig.navIconRss
-                                MainDestination.My -> MainConfig.navIconMy
+                                MainDestination.Bookshelf -> ThemeConfig.navIconBookshelf
+                                MainDestination.Explore -> ThemeConfig.navIconExplore
+                                MainDestination.Rss -> ThemeConfig.navIconRss
+                                MainDestination.My -> ThemeConfig.navIconMy
                             }
                             AppNavigationBarItem(
                                 selected = selected,
@@ -390,10 +391,10 @@ fun MainScreen(
                             MainDestination.My -> MyScreen(
                                 onOpenSettings = onOpenSettings,
                                 onNavigate = { event ->
-                                    if (event == PrefClickEvent.OpenBookCacheManage) {
-                                        onNavigateToBookCacheManage()
-                                    } else {
-                                        viewModel.onPrefClickEvent(event)
+                                    when (event) {
+                                        PrefClickEvent.OpenBookCacheManage -> onNavigateToBookCacheManage()
+                                        PrefClickEvent.OpenReadRecord -> onNavigateToReadRecord()
+                                        else -> viewModel.onPrefClickEvent(event)
                                     }
                                 }
                             )
@@ -432,20 +433,20 @@ fun MainScreen(
                             isBlurEnabled = useLiquidGlass,
                             hasCustomIcons = destinations.any { dest ->
                                 when (dest) {
-                                    MainDestination.Bookshelf -> MainConfig.navIconBookshelf.isNotEmpty()
-                                    MainDestination.Explore -> MainConfig.navIconExplore.isNotEmpty()
-                                    MainDestination.Rss -> MainConfig.navIconRss.isNotEmpty()
-                                    MainDestination.My -> MainConfig.navIconMy.isNotEmpty()
+                                    MainDestination.Bookshelf -> ThemeConfig.navIconBookshelf.isNotEmpty()
+                                    MainDestination.Explore -> ThemeConfig.navIconExplore.isNotEmpty()
+                                    MainDestination.Rss -> ThemeConfig.navIconRss.isNotEmpty()
+                                    MainDestination.My -> ThemeConfig.navIconMy.isNotEmpty()
                                 }
                             }
                         ) {
                             destinations.forEachIndexed { index, destination ->
                                 val selected = pagerState.targetPage == index
                                 val hasCustomIcon = when (destination) {
-                                    MainDestination.Bookshelf -> MainConfig.navIconBookshelf.isNotEmpty()
-                                    MainDestination.Explore -> MainConfig.navIconExplore.isNotEmpty()
-                                    MainDestination.Rss -> MainConfig.navIconRss.isNotEmpty()
-                                    MainDestination.My -> MainConfig.navIconMy.isNotEmpty()
+                                    MainDestination.Bookshelf -> ThemeConfig.navIconBookshelf.isNotEmpty()
+                                    MainDestination.Explore -> ThemeConfig.navIconExplore.isNotEmpty()
+                                    MainDestination.Rss -> ThemeConfig.navIconRss.isNotEmpty()
+                                    MainDestination.My -> ThemeConfig.navIconMy.isNotEmpty()
                                 }
                                 FloatingBottomBarItem(
                                     onClick = {
@@ -522,10 +523,10 @@ private fun NavigationIcon(
     modifier: Modifier = Modifier
 ) {
     val customIconPath = when (destination) {
-        MainDestination.Bookshelf -> MainConfig.navIconBookshelf
-        MainDestination.Explore -> MainConfig.navIconExplore
-        MainDestination.Rss -> MainConfig.navIconRss
-        MainDestination.My -> MainConfig.navIconMy
+        MainDestination.Bookshelf -> ThemeConfig.navIconBookshelf
+        MainDestination.Explore -> ThemeConfig.navIconExplore
+        MainDestination.Rss -> ThemeConfig.navIconRss
+        MainDestination.My -> ThemeConfig.navIconMy
     }
     if (customIconPath.isNotEmpty()) {
         val context = LocalContext.current