فهرست منبع

fix: 优化了备份和恢复功能喵喵喵

HapeLee 3 ماه پیش
والد
کامیت
c242d3581c

+ 69 - 28
app/src/main/java/io/legado/app/help/storage/Backup.kt

@@ -2,7 +2,6 @@ package io.legado.app.help.storage
 
 import android.content.Context
 import android.net.Uri
-import androidx.core.content.edit
 import androidx.core.net.toUri
 import androidx.documentfile.provider.DocumentFile
 import io.legado.app.constant.AppLog
@@ -14,8 +13,8 @@ import io.legado.app.help.DirectLinkUpload
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfigStore
 import io.legado.app.help.config.LocalConfig
-import io.legado.app.help.config.ThemeConfigStore
 import io.legado.app.help.config.ReadBookConfig
+import io.legado.app.help.config.ThemeConfigStore
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.model.BookCover
 import io.legado.app.utils.FileUtils
@@ -137,32 +136,74 @@ object Backup {
         val aes = BackupAES()
         FileUtils.delete(backupPath)
         writeListToJson(appDb.bookDao.all, "bookshelf.json", backupPath)
-        writeListToJson(appDb.bookmarkDao.all, "bookmark.json", backupPath)
-        writeListToJson(appDb.bookGroupDao.all, "bookGroup.json", backupPath)
-        writeListToJson(appDb.bookSourceDao.all, "bookSource.json", backupPath)
-        writeListToJson(appDb.rssSourceDao.all, "rssSources.json", backupPath)
-        writeListToJson(appDb.rssStarDao.all, "rssStar.json", backupPath)
-        writeListToJson(appDb.replaceRuleDao.all, "replaceRule.json", backupPath)
-        writeListToJson(appDb.readRecordDao.all, "readRecord.json", backupPath)
-        writeListToJson(appDb.readRecordDao.allDetail, "readRecordDetail.json", backupPath)
-        writeListToJson(appDb.readRecordDao.allSession, "readRecordSession.json", backupPath)
-        writeListToJson(appDb.searchKeywordDao.all, "searchHistory.json", backupPath)
-        writeListToJson(appDb.ruleSubDao.all, "sourceSub.json", backupPath)
-        writeListToJson(appDb.txtTocRuleDao.all, "txtTocRule.json", backupPath)
-        writeListToJson(appDb.httpTTSDao.all, "httpTTS.json", backupPath)
-        writeListToJson(appDb.keyboardAssistsDao.all, "keyboardAssists.json", backupPath)
-        writeListToJson(appDb.dictRuleDao.all, "dictRule.json", backupPath)
-        writeListToJson(appDb.homepageModuleDao.getAll(), "homepageModules.json", backupPath)
-        writeListToJson(appDb.homepageCustomSetDao.getAll(), "homepageCustomSets.json", backupPath)
-        writeListToJson(appDb.highlightRuleDao.getAll(), "highlightRule.json", backupPath)
-        writeListToJson(appDb.highlightTagRuleDao.getAll(), "highlightTagRule.json", backupPath)
-        writeListToJson(appDb.tagGroupRuleDao.getAll(), "tagGroupRule.json", backupPath)
-        GSON.toJson(appDb.serverDao.all).let { json ->
-            aes.runCatching {
-                encryptBase64(json)
-            }.getOrDefault(json).let {
-                FileUtils.createFileIfNotExist(backupPath + File.separator + "servers.json")
-                    .writeText(it)
+        if (BackupConfig.dbIsNotIgnored("bookmark", true)) {
+            writeListToJson(appDb.bookmarkDao.all, "bookmark.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("bookGroup", true)) {
+            writeListToJson(appDb.bookGroupDao.all, "bookGroup.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("bookSource", true)) {
+            writeListToJson(appDb.bookSourceDao.all, "bookSource.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("rssSource", true)) {
+            writeListToJson(appDb.rssSourceDao.all, "rssSources.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("rssStar", true)) {
+            writeListToJson(appDb.rssStarDao.all, "rssStar.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("replaceRule", true)) {
+            writeListToJson(appDb.replaceRuleDao.all, "replaceRule.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("readRecord", true)) {
+            writeListToJson(appDb.readRecordDao.all, "readRecord.json", backupPath)
+            writeListToJson(appDb.readRecordDao.allDetail, "readRecordDetail.json", backupPath)
+            writeListToJson(appDb.readRecordDao.allSession, "readRecordSession.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("searchHistory", true)) {
+            writeListToJson(appDb.searchKeywordDao.all, "searchHistory.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("sourceSub", true)) {
+            writeListToJson(appDb.ruleSubDao.all, "sourceSub.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("txtTocRule", true)) {
+            writeListToJson(appDb.txtTocRuleDao.all, "txtTocRule.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("httpTTS", true)) {
+            writeListToJson(appDb.httpTTSDao.all, "httpTTS.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("keyboardAssists", true)) {
+            writeListToJson(appDb.keyboardAssistsDao.all, "keyboardAssists.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("dictRule", true)) {
+            writeListToJson(appDb.dictRuleDao.all, "dictRule.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("homepageModules", true)) {
+            writeListToJson(appDb.homepageModuleDao.getAll(), "homepageModules.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("homepageCustomSets", true)) {
+            writeListToJson(
+                appDb.homepageCustomSetDao.getAll(),
+                "homepageCustomSets.json",
+                backupPath
+            )
+        }
+        if (BackupConfig.dbIsNotIgnored("highlightRule", true)) {
+            writeListToJson(appDb.highlightRuleDao.getAll(), "highlightRule.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("highlightTagRule", true)) {
+            writeListToJson(appDb.highlightTagRuleDao.getAll(), "highlightTagRule.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("tagGroupRule", true)) {
+            writeListToJson(appDb.tagGroupRuleDao.getAll(), "tagGroupRule.json", backupPath)
+        }
+        if (BackupConfig.dbIsNotIgnored("server", true)) {
+            GSON.toJson(appDb.serverDao.all).let { json ->
+                aes.runCatching {
+                    encryptBase64(json)
+                }.getOrDefault(json).let {
+                    FileUtils.createFileIfNotExist(backupPath + File.separator + "servers.json")
+                        .writeText(it)
+                }
             }
         }
         currentCoroutineContext().ensureActive()

+ 286 - 6
app/src/main/java/io/legado/app/help/storage/BackupConfig.kt

@@ -36,10 +36,101 @@ object BackupConfig {
         GSON.fromJsonObject<HashMap<String, Boolean>>(json).getOrNull() ?: hashMapOf()
     }
 
+    private val backupIgnoreConfigPath = FileUtils.getPath(appCtx.filesDir, "backupIgnore.json")
+    val backupIgnoreConfig: HashMap<String, Boolean> by lazy {
+        val file = FileUtils.createFileIfNotExist(backupIgnoreConfigPath)
+        val json = file.readText()
+        GSON.fromJsonObject<HashMap<String, Boolean>>(json).getOrNull() ?: hashMapOf()
+    }
+
+    private val dbIgnoreConfigPath = FileUtils.getPath(appCtx.filesDir, "dbIgnore.json")
+    val dbIgnoreConfig: HashMap<String, Boolean> by lazy {
+        val file = FileUtils.createFileIfNotExist(dbIgnoreConfigPath)
+        val json = file.readText()
+        GSON.fromJsonObject<HashMap<String, Boolean>>(json).getOrNull() ?: hashMapOf()
+    }
+
+    private val backupDbIgnoreConfigPath = FileUtils.getPath(appCtx.filesDir, "backupDbIgnore.json")
+    val backupDbIgnoreConfig: HashMap<String, Boolean> by lazy {
+        val file = FileUtils.createFileIfNotExist(backupDbIgnoreConfigPath)
+        val json = file.readText()
+        GSON.fromJsonObject<HashMap<String, Boolean>>(json).getOrNull() ?: hashMapOf()
+    }
+
     private const val readConfigKey = "readConfig"
     private const val themeConfigKey = "themeConfig"
     private const val coverConfigKey = "coverConfig"
     private const val localBookKey = "localBook"
+    private const val mangaKey = "mangaConfig"
+
+    //数据库忽略key
+    private const val dbKeyBookmark = "bookmark"
+    private const val dbKeyBookGroup = "bookGroup"
+    private const val dbKeyBookSource = "bookSource"
+    private const val dbKeyRssSource = "rssSource"
+    private const val dbKeyRssStar = "rssStar"
+    private const val dbKeyReplaceRule = "replaceRule"
+    private const val dbKeyReadRecord = "readRecord"
+    private const val dbKeySearchHistory = "searchHistory"
+    private const val dbKeySourceSub = "sourceSub"
+    private const val dbKeyTxtTocRule = "txtTocRule"
+    private const val dbKeyHttpTts = "httpTTS"
+    private const val dbKeyKeyboardAssists = "keyboardAssists"
+    private const val dbKeyDictRule = "dictRule"
+    private const val dbKeyHomepageModules = "homepageModules"
+    private const val dbKeyHomepageCustomSets = "homepageCustomSets"
+    private const val dbKeyHighlightRule = "highlightRule"
+    private const val dbKeyHighlightTagRule = "highlightTagRule"
+    private const val dbKeyTagGroupRule = "tagGroupRule"
+    private const val dbKeyServer = "server"
+
+    val dbIgnoreKeys = arrayOf(
+        dbKeyBookmark, dbKeyBookGroup, dbKeyBookSource, dbKeyRssSource,
+        dbKeyRssStar, dbKeyReplaceRule, dbKeyReadRecord, dbKeySearchHistory,
+        dbKeySourceSub, dbKeyTxtTocRule, dbKeyHttpTts, dbKeyKeyboardAssists,
+        dbKeyDictRule, dbKeyHomepageModules, dbKeyHomepageCustomSets,
+        dbKeyHighlightRule, dbKeyHighlightTagRule, dbKeyTagGroupRule, dbKeyServer
+    )
+
+    val dbIgnoreTitle = arrayOf(
+        appCtx.getString(R.string.bookmark),
+        appCtx.getString(R.string.book_group),
+        appCtx.getString(R.string.book_source),
+        appCtx.getString(R.string.rss_source),
+        appCtx.getString(R.string.rss_star),
+        appCtx.getString(R.string.replace_rule),
+        appCtx.getString(R.string.read_record),
+        appCtx.getString(R.string.search_history),
+        appCtx.getString(R.string.source_sub),
+        appCtx.getString(R.string.txt_toc_rule),
+        appCtx.getString(R.string.http_tts),
+        appCtx.getString(R.string.keyboard_assists),
+        appCtx.getString(R.string.dict_rule),
+        appCtx.getString(R.string.homepage_modules),
+        appCtx.getString(R.string.homepage_custom_sets),
+        appCtx.getString(R.string.highlight_rule_config),
+        appCtx.getString(R.string.highlight_tag_config),
+        appCtx.getString(R.string.tag_group_rules),
+        appCtx.getString(R.string.server_config)
+    )
+
+    val backupDbIgnoreKeys = dbIgnoreKeys
+    val backupDbIgnoreTitle = dbIgnoreTitle
+
+    fun dbIsNotIgnored(key: String, isBackup: Boolean = false): Boolean {
+        val config = if (isBackup) backupDbIgnoreConfig else dbIgnoreConfig
+        return config[key] != true
+    }
+
+    fun saveDbIgnoreConfig() {
+        val json = GSON.toJson(dbIgnoreConfig)
+        FileUtils.createFileIfNotExist(dbIgnoreConfigPath).writeText(json)
+    }
+
+    fun saveBackupDbIgnoreConfig() {
+        val json = GSON.toJson(backupDbIgnoreConfig)
+        FileUtils.createFileIfNotExist(backupDbIgnoreConfigPath).writeText(json)
+    }
 
     //配置忽略key
     val ignoreKeys = arrayOf(
@@ -50,7 +141,8 @@ object BackupConfig {
         PreferKey.bookshelfLayout,
         PreferKey.showRss,
         PreferKey.threadCount,
-        localBookKey
+        localBookKey,
+        mangaKey
     )
 
     //配置忽略标题
@@ -62,7 +154,34 @@ object BackupConfig {
         appCtx.getString(R.string.bookshelf_layout),
         appCtx.getString(R.string.show_rss),
         appCtx.getString(R.string.thread_count),
-        appCtx.getString(R.string.local_book)
+        appCtx.getString(R.string.local_book),
+        appCtx.getString(R.string.manga_config)
+    )
+
+    //备份忽略key
+    val backupIgnoreKeys = arrayOf(
+        readConfigKey,
+        PreferKey.themeMode,
+        themeConfigKey,
+        coverConfigKey,
+        PreferKey.bookshelfLayout,
+        PreferKey.showRss,
+        PreferKey.threadCount,
+        localBookKey,
+        mangaKey
+    )
+
+    //备份忽略标题
+    val backupIgnoreTitle = arrayOf(
+        appCtx.getString(R.string.read_config),
+        appCtx.getString(R.string.theme_mode),
+        appCtx.getString(R.string.theme_config),
+        appCtx.getString(R.string.cover_config),
+        appCtx.getString(R.string.bookshelf_layout),
+        appCtx.getString(R.string.show_rss),
+        appCtx.getString(R.string.thread_count),
+        appCtx.getString(R.string.local_book),
+        appCtx.getString(R.string.manga_config)
     )
 
     //阅读配置
@@ -81,7 +200,51 @@ object BackupConfig {
         PreferKey.clickActionMR,
         PreferKey.clickActionBL,
         PreferKey.clickActionBC,
-        PreferKey.clickActionBR
+        PreferKey.clickActionBR,
+        // 阅读进度条
+        PreferKey.readBarStyle,
+        PreferKey.readBarStyleFollowPage,
+        // 阅读菜单
+        PreferKey.readMenuBgColor,
+        PreferKey.readMenuAccentColor,
+        PreferKey.readMenuContainerColor,
+        PreferKey.readMenuBgColorNight,
+        PreferKey.readMenuAccentColorNight,
+        PreferKey.readMenuContainerColorNight,
+        PreferKey.readMenuTextColor,
+        PreferKey.readMenuTextColorNight,
+        PreferKey.readMenuColorMode,
+        PreferKey.readMenuIconShowText,
+        PreferKey.readMenuIconStyle,
+        PreferKey.readMenuIconItemsPerRow,
+        PreferKey.readMenuIconRowCount,
+        PreferKey.readMenuBottomCornerRadius,
+        PreferKey.readMenuFloatingBottomBar,
+        PreferKey.readMenuTopBarBlurMode,
+        PreferKey.readMenuBottomBarBlurMode,
+        PreferKey.readMenuTopBarLiquidGlassButtons,
+        PreferKey.readMenuTopBarTitleCapsule,
+        PreferKey.readMenuBottomBarLiquidGlassButtons,
+        PreferKey.readMenuTopBarBlurStyle,
+        PreferKey.readMenuBottomBarBlurStyle,
+        PreferKey.readMenuBlurRadius,
+        PreferKey.readMenuBlurAlpha,
+        PreferKey.readMenuBlurColor,
+        PreferKey.readMenuBlurColorNight,
+        PreferKey.readMenuPaletteStyle,
+        PreferKey.readMenuLensRadius,
+        PreferKey.readMenuBorderWidth,
+        PreferKey.readMenuBorderColor,
+        PreferKey.readMenuBorderColorNight,
+        PreferKey.readMenuCustomIcons,
+        // 标题栏
+        PreferKey.titleBarIconStyle,
+        PreferKey.titleBarCustomIcons,
+        PreferKey.titleBarIconPosition,
+        PreferKey.showTitleBarIcons,
+        PreferKey.showMenuIcon,
+        PreferKey.titleBarMode,
+        PreferKey.shouldShowExpandButton
     )
 
     private val themePrefKeys = arrayOf(
@@ -131,24 +294,115 @@ object BackupConfig {
         PreferKey.labelContainerColorNight
     )
 
+    private val bookshelfPrefKeys = arrayOf(
+        PreferKey.bookshelfLayout,
+        PreferKey.bookshelfLayoutModePortrait,
+        PreferKey.bookshelfLayoutModeLandscape,
+        PreferKey.bookshelfLayoutCompact,
+        PreferKey.bookshelfShowDivider,
+        PreferKey.bookshelfGridLayout,
+        PreferKey.bookshelfSort,
+        PreferKey.bookshelfSortOrder,
+        PreferKey.bookshelfLayoutGridLandscape,
+        PreferKey.bookshelfLayoutGridPortrait,
+        PreferKey.bookshelfLayoutListLandscape,
+        PreferKey.bookshelfLayoutListPortrait,
+        PreferKey.bookshelfFolderLayoutModePortrait,
+        PreferKey.bookshelfFolderLayoutModeLandscape,
+        PreferKey.bookshelfFolderLayoutGridPortrait,
+        PreferKey.bookshelfFolderLayoutGridLandscape,
+        PreferKey.bookshelfFolderLayoutListPortrait,
+        PreferKey.bookshelfFolderLayoutListLandscape,
+        PreferKey.bookshelfTitleSmallFont,
+        PreferKey.bookshelfTitleCenter,
+        PreferKey.bookshelfTitleMaxLines,
+        PreferKey.bookshelfCoverShadow,
+        PreferKey.bookshelfSearchActionDirectToSearch,
+        PreferKey.bookshelfCardColor,
+        PreferKey.bookshelfCardColorDark,
+        PreferKey.bookshelfGroupListStyle,
+        PreferKey.bookshelfGroupCoverCount,
+        PreferKey.bookshelfListCoverWidth,
+        PreferKey.bookshelfGridCoverWidth,
+        PreferKey.bookshelfRefreshingLimit,
+        PreferKey.bookshelfShowIntro,
+        PreferKey.bookshelfShowTag,
+        PreferKey.bookshelfShowLatestChapter,
+        PreferKey.bookshelfIntroMaxLines
+    )
+
+    private val mangaPrefKeys = arrayOf(
+        PreferKey.showMangaUi,
+        PreferKey.mangaScrollMode,
+        PreferKey.webtoonSidePaddingDp,
+        PreferKey.mangaPreDownloadNum,
+        PreferKey.mangaAutoPageSpeed,
+        PreferKey.mangaFooterConfig,
+        PreferKey.disableClickScroll,
+        PreferKey.hideMangaTitle,
+        PreferKey.mangaColorFilter,
+        PreferKey.enableMangaEInk,
+        PreferKey.mangaEInkThreshold,
+        PreferKey.enableMangaGray,
+        PreferKey.doublePageHorizontal,
+        PreferKey.mouseWheelPage,
+        PreferKey.disableMangaScale,
+        PreferKey.disableMangaScrollAnimation,
+        PreferKey.disableMangaCrossFade,
+        PreferKey.mangaVolumeKeyPage,
+        PreferKey.reverseVolumeKeyPage,
+        PreferKey.mangaLongClick,
+        PreferKey.mangaBackground,
+        PreferKey.mangaClickActionTL,
+        PreferKey.mangaClickActionTC,
+        PreferKey.mangaClickActionTR,
+        PreferKey.mangaClickActionML,
+        PreferKey.mangaClickActionMC,
+        PreferKey.mangaClickActionMR,
+        PreferKey.mangaClickActionBL,
+        PreferKey.mangaClickActionBC,
+        PreferKey.mangaClickActionBR
+    )
+
     private val coverPrefKeys = arrayOf(
         PreferKey.useDefaultCover,
         PreferKey.loadCoverOnlyWifi,
         PreferKey.coverShowName,
         PreferKey.coverShowAuthor,
         PreferKey.coverShowNameN,
-        PreferKey.coverShowAuthorN
+        PreferKey.coverShowAuthorN,
+        PreferKey.coverShowShadow,
+        PreferKey.coverShowStroke,
+        PreferKey.coverTextColor,
+        PreferKey.coverTextColorN,
+        PreferKey.coverShadowColor,
+        PreferKey.coverShadowColorN,
+        PreferKey.coverDefaultColor,
+        PreferKey.coverInfoOrientation
     )
 
     fun keyIsNotIgnore(key: String, isBackup: Boolean = false): Boolean {
         if (key in alwaysIgnoredPreferenceKeys) return false
-        if (isBackup) return true
+        if (isBackup) {
+            return when {
+                backupIgnoreReadConfig && readPrefKeys.contains(key) -> false
+                backupIgnoreThemeConfig && themePrefKeys.contains(key) -> false
+                backupIgnoreCoverConfig && coverPrefKeys.contains(key) -> false
+                backupIgnoreBookshelfLayout && bookshelfPrefKeys.contains(key) -> false
+                backupIgnoreManga && mangaPrefKeys.contains(key) -> false
+                PreferKey.themeMode == key && backupIgnoreThemeMode -> false
+                PreferKey.showRss == key && backupIgnoreShowRss -> false
+                PreferKey.threadCount == key && backupIgnoreThreadCount -> false
+                else -> true
+            }
+        }
         return when {
             ignoreReadConfig && readPrefKeys.contains(key) -> false
             ignoreThemeConfig && themePrefKeys.contains(key) -> false
             ignoreCoverConfig && coverPrefKeys.contains(key) -> false
+            ignoreBookshelfLayout && bookshelfPrefKeys.contains(key) -> false
+            ignoreManga && mangaPrefKeys.contains(key) -> false
             PreferKey.themeMode == key && ignoreThemeMode -> false
-            PreferKey.bookshelfLayout == key && ignoreBookshelfLayout -> false
             PreferKey.showRss == key && ignoreShowRss -> false
             PreferKey.threadCount == key && ignoreThreadCount -> false
             else -> true
@@ -171,10 +425,36 @@ object BackupConfig {
         get() = ignoreConfig[PreferKey.threadCount] == true
     val ignoreLocalBook: Boolean
         get() = ignoreConfig[localBookKey] == true
+    val ignoreManga: Boolean
+        get() = ignoreConfig[mangaKey] == true
+
+    val backupIgnoreReadConfig: Boolean
+        get() = backupIgnoreConfig[readConfigKey] == true
+    val backupIgnoreThemeMode: Boolean
+        get() = backupIgnoreConfig[PreferKey.themeMode] == true
+    val backupIgnoreThemeConfig: Boolean
+        get() = backupIgnoreConfig[themeConfigKey] == true
+    val backupIgnoreCoverConfig: Boolean
+        get() = backupIgnoreConfig[coverConfigKey] == true
+    val backupIgnoreBookshelfLayout: Boolean
+        get() = backupIgnoreConfig[PreferKey.bookshelfLayout] == true
+    val backupIgnoreShowRss: Boolean
+        get() = backupIgnoreConfig[PreferKey.showRss] == true
+    val backupIgnoreThreadCount: Boolean
+        get() = backupIgnoreConfig[PreferKey.threadCount] == true
+    val backupIgnoreLocalBook: Boolean
+        get() = backupIgnoreConfig[localBookKey] == true
+    val backupIgnoreManga: Boolean
+        get() = backupIgnoreConfig[mangaKey] == true
 
     fun saveIgnoreConfig() {
         val json = GSON.toJson(ignoreConfig)
         FileUtils.createFileIfNotExist(ignoreConfigPath).writeText(json)
     }
 
+    fun saveBackupIgnoreConfig() {
+        val json = GSON.toJson(backupIgnoreConfig)
+        FileUtils.createFileIfNotExist(backupIgnoreConfigPath).writeText(json)
+    }
+
 }

+ 133 - 95
app/src/main/java/io/legado/app/help/storage/Restore.kt

@@ -8,7 +8,6 @@ import io.legado.app.BuildConfig
 import io.legado.app.R
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.PreferKey
-import io.legado.app.domain.gateway.AppLocaleGateway
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookGroup
@@ -32,18 +31,19 @@ import io.legado.app.data.entities.TxtTocRule
 import io.legado.app.data.entities.readRecord.ReadRecord
 import io.legado.app.data.entities.readRecord.ReadRecordDetail
 import io.legado.app.data.entities.readRecord.ReadRecordSession
+import io.legado.app.domain.gateway.AppLocaleGateway
 import io.legado.app.help.DirectLinkUpload
 import io.legado.app.help.LauncherIconHelp
 import io.legado.app.help.book.isLocal
 import io.legado.app.help.book.upType
 import io.legado.app.help.config.AppConfigStore
 import io.legado.app.help.config.LocalConfig
+import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.config.SettingsWriter
 import io.legado.app.help.config.ThemeConfigStore
-import io.legado.app.help.config.ReadBookConfig
-import io.legado.app.ui.config.otherConfig.OtherConfig
 import io.legado.app.model.BookCover
 import io.legado.app.model.localBook.LocalBook
+import io.legado.app.ui.config.otherConfig.OtherConfig
 import io.legado.app.utils.ACache
 import io.legado.app.utils.FileUtils
 import io.legado.app.utils.GSON
@@ -140,135 +140,173 @@ object Restore : KoinComponent {
                 appDb.bookDao.insert(*newBooks.toTypedArray())
             }
         }
-        fileToListT<Bookmark>(path, "bookmark.json")?.let {
-            try {
-                appDb.bookmarkDao.insert(*it.toTypedArray())
-            } catch (_: SQLiteConstraintException) {
+        if (BackupConfig.dbIsNotIgnored("bookmark")) {
+            fileToListT<Bookmark>(path, "bookmark.json")?.let {
+                try {
+                    appDb.bookmarkDao.insert(*it.toTypedArray())
+                } catch (_: SQLiteConstraintException) {
+                }
             }
         }
-        fileToListT<BookGroup>(path, "bookGroup.json")?.let {
-            appDb.bookGroupDao.replaceAll(it)
-        }
-        fileToListT<BookSource>(path, "bookSource.json")?.let {
-            try {
-                appDb.bookSourceDao.insert(*it.toTypedArray())
-            } catch (_: SQLiteConstraintException) {
+        if (BackupConfig.dbIsNotIgnored("bookGroup")) {
+            fileToListT<BookGroup>(path, "bookGroup.json")?.let {
+                appDb.bookGroupDao.replaceAll(it)
             }
-        } ?: run {
-            val bookSourceFile = File(path, "bookSource.json")
-            if (bookSourceFile.exists()) {
-                val json = bookSourceFile.readText()
-                ImportOldData.importOldSource(json)
+        }
+        if (BackupConfig.dbIsNotIgnored("bookSource")) {
+            fileToListT<BookSource>(path, "bookSource.json")?.let {
+                try {
+                    appDb.bookSourceDao.insert(*it.toTypedArray())
+                } catch (_: SQLiteConstraintException) {
+                }
+            } ?: run {
+                val bookSourceFile = File(path, "bookSource.json")
+                if (bookSourceFile.exists()) {
+                    val json = bookSourceFile.readText()
+                    ImportOldData.importOldSource(json)
+                }
             }
         }
-        fileToListT<RssSource>(path, "rssSources.json")?.let {
-            try {
-                appDb.rssSourceDao.insert(*it.toTypedArray())
-            } catch (_: SQLiteConstraintException) {
+        if (BackupConfig.dbIsNotIgnored("rssSource")) {
+            fileToListT<RssSource>(path, "rssSources.json")?.let {
+                try {
+                    appDb.rssSourceDao.insert(*it.toTypedArray())
+                } catch (_: SQLiteConstraintException) {
+                }
             }
         }
-        fileToListT<RssStar>(path, "rssStar.json")?.let {
-            try {
-                appDb.rssStarDao.insert(*it.toTypedArray())
-            } catch (_: SQLiteConstraintException) {
+        if (BackupConfig.dbIsNotIgnored("rssStar")) {
+            fileToListT<RssStar>(path, "rssStar.json")?.let {
+                try {
+                    appDb.rssStarDao.insert(*it.toTypedArray())
+                } catch (_: SQLiteConstraintException) {
+                }
             }
         }
-        fileToListT<ReplaceRule>(path, "replaceRule.json")?.let {
-            try {
-                appDb.replaceRuleDao.insert(*it.toTypedArray())
-            } catch (_: SQLiteConstraintException) {
+        if (BackupConfig.dbIsNotIgnored("replaceRule")) {
+            fileToListT<ReplaceRule>(path, "replaceRule.json")?.let {
+                try {
+                    appDb.replaceRuleDao.insert(*it.toTypedArray())
+                } catch (_: SQLiteConstraintException) {
+                }
             }
         }
-        fileToListT<SearchKeyword>(path, "searchHistory.json")?.let {
-            try {
-                appDb.searchKeywordDao.insert(*it.toTypedArray())
-            } catch (_: SQLiteConstraintException) {
+        if (BackupConfig.dbIsNotIgnored("searchHistory")) {
+            fileToListT<SearchKeyword>(path, "searchHistory.json")?.let {
+                try {
+                    appDb.searchKeywordDao.insert(*it.toTypedArray())
+                } catch (_: SQLiteConstraintException) {
+                }
             }
         }
-        fileToListT<RuleSub>(path, "sourceSub.json")?.let {
-            try {
-                appDb.ruleSubDao.insert(*it.toTypedArray())
-            } catch (_: SQLiteConstraintException) {
+        if (BackupConfig.dbIsNotIgnored("sourceSub")) {
+            fileToListT<RuleSub>(path, "sourceSub.json")?.let {
+                try {
+                    appDb.ruleSubDao.insert(*it.toTypedArray())
+                } catch (_: SQLiteConstraintException) {
+                }
             }
         }
-        fileToListT<TxtTocRule>(path, "txtTocRule.json")?.let {
-            try {
-                appDb.txtTocRuleDao.insert(*it.toTypedArray())
-            } catch (_: SQLiteConstraintException) {
+        if (BackupConfig.dbIsNotIgnored("txtTocRule")) {
+            fileToListT<TxtTocRule>(path, "txtTocRule.json")?.let {
+                try {
+                    appDb.txtTocRuleDao.insert(*it.toTypedArray())
+                } catch (_: SQLiteConstraintException) {
+                }
             }
         }
-        fileToListT<HttpTTS>(path, "httpTTS.json")?.let {
-            try {
-                appDb.httpTTSDao.insert(*it.toTypedArray())
-            } catch (_: SQLiteConstraintException) {
+        if (BackupConfig.dbIsNotIgnored("httpTTS")) {
+            fileToListT<HttpTTS>(path, "httpTTS.json")?.let {
+                try {
+                    appDb.httpTTSDao.insert(*it.toTypedArray())
+                } catch (_: SQLiteConstraintException) {
+                }
             }
         }
-        fileToListT<DictRule>(path, "dictRule.json")?.let {
-            try {
-                appDb.dictRuleDao.insert(*it.toTypedArray())
-            } catch (_: SQLiteConstraintException) {
+        if (BackupConfig.dbIsNotIgnored("dictRule")) {
+            fileToListT<DictRule>(path, "dictRule.json")?.let {
+                try {
+                    appDb.dictRuleDao.insert(*it.toTypedArray())
+                } catch (_: SQLiteConstraintException) {
+                }
             }
         }
-        fileToListT<KeyboardAssist>(path, "keyboardAssists.json")?.let {
-            try {
-                appDb.keyboardAssistsDao.insert(*it.toTypedArray())
-            } catch (_: SQLiteConstraintException) {
+        if (BackupConfig.dbIsNotIgnored("keyboardAssists")) {
+            fileToListT<KeyboardAssist>(path, "keyboardAssists.json")?.let {
+                try {
+                    appDb.keyboardAssistsDao.insert(*it.toTypedArray())
+                } catch (_: SQLiteConstraintException) {
+                }
             }
         }
-        fileToListT<HomepageModule>(path, "homepageModules.json")?.let {
-            appDb.homepageModuleDao.replaceAll(it)
+        if (BackupConfig.dbIsNotIgnored("homepageModules")) {
+            fileToListT<HomepageModule>(path, "homepageModules.json")?.let {
+                appDb.homepageModuleDao.replaceAll(it)
+            }
         }
-        fileToListT<HomepageCustomSet>(path, "homepageCustomSets.json")?.let {
-            appDb.homepageCustomSetDao.replaceAll(it)
+        if (BackupConfig.dbIsNotIgnored("homepageCustomSets")) {
+            fileToListT<HomepageCustomSet>(path, "homepageCustomSets.json")?.let {
+                appDb.homepageCustomSetDao.replaceAll(it)
+            }
         }
-        fileToListT<HighlightRule>(path, "highlightRule.json")?.let {
-            appDb.highlightRuleDao.replaceAll(it)
+        if (BackupConfig.dbIsNotIgnored("highlightRule")) {
+            fileToListT<HighlightRule>(path, "highlightRule.json")?.let {
+                appDb.highlightRuleDao.replaceAll(it)
+            }
         }
-        fileToListT<HighlightTagRule>(path, "highlightTagRule.json")?.let {
-            appDb.highlightTagRuleDao.replaceAll(it)
+        if (BackupConfig.dbIsNotIgnored("highlightTagRule")) {
+            fileToListT<HighlightTagRule>(path, "highlightTagRule.json")?.let {
+                appDb.highlightTagRuleDao.replaceAll(it)
+            }
         }
-        fileToListT<TagGroupRule>(path, "tagGroupRule.json")?.let {
-            appDb.tagGroupRuleDao.replaceAll(it)
+        if (BackupConfig.dbIsNotIgnored("tagGroupRule")) {
+            fileToListT<TagGroupRule>(path, "tagGroupRule.json")?.let {
+                appDb.tagGroupRuleDao.replaceAll(it)
+            }
         }
-        fileToListT<ReadRecord>(path, "readRecord.json")?.let {
-            it.forEach { readRecord ->
-                try {
-                    restoreReadRecord(readRecord)
-                } catch (_: SQLiteConstraintException) {
+        if (BackupConfig.dbIsNotIgnored("readRecord")) {
+            fileToListT<ReadRecord>(path, "readRecord.json")?.let {
+                it.forEach { readRecord ->
+                    try {
+                        restoreReadRecord(readRecord)
+                    } catch (_: SQLiteConstraintException) {
+                    }
                 }
             }
-        }
-        fileToListT<ReadRecordDetail>(path, "readRecordDetail.json")?.let {
-            it.forEach { detail ->
-                try {
-                    restoreReadRecordDetail(detail)
-                } catch (_: SQLiteConstraintException) {
+            fileToListT<ReadRecordDetail>(path, "readRecordDetail.json")?.let {
+                it.forEach { detail ->
+                    try {
+                        restoreReadRecordDetail(detail)
+                    } catch (_: SQLiteConstraintException) {
+                    }
                 }
             }
-        }
-        fileToListT<ReadRecordSession>(path, "readRecordSession.json")?.let {
-            it.forEach { session ->
-                try {
-                    restoreReadRecordSession(session)
-                } catch (_: SQLiteConstraintException) {
+            fileToListT<ReadRecordSession>(path, "readRecordSession.json")?.let {
+                it.forEach { session ->
+                    try {
+                        restoreReadRecordSession(session)
+                    } catch (_: SQLiteConstraintException) {
+                    }
                 }
             }
         }
-        File(path, "servers.json").takeIf {
-            it.exists()
-        }?.runCatching {
-            var json = readText()
-            if (!json.isJsonArray()) {
-                json = aes.decryptStr(json)
-            }
-            GSON.fromJsonArray<Server>(json).getOrNull()?.let {
-                try {
-                    appDb.serverDao.insert(*it.toTypedArray())
-                } catch (_: SQLiteConstraintException) {
+        if (BackupConfig.dbIsNotIgnored("server")) {
+            File(path, "servers.json").takeIf {
+                it.exists()
+            }?.runCatching {
+                var json = readText()
+                if (!json.isJsonArray()) {
+                    json = aes.decryptStr(json)
                 }
+                GSON.fromJsonArray<Server>(json).getOrNull()?.let {
+                    try {
+                        appDb.serverDao.insert(*it.toTypedArray())
+                    } catch (_: SQLiteConstraintException) {
+                    }
+                }
+            }?.onFailure {
+                AppLog.put("恢复服务器配置出错\n${it.localizedMessage}", it)
             }
-        }?.onFailure {
-            AppLog.put("恢复服务器配置出错\n${it.localizedMessage}", it)
         }
         File(path, DirectLinkUpload.ruleFileName).takeIf {
             it.exists()

+ 12 - 1
app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigContract.kt

@@ -13,6 +13,9 @@ data class BackupConfigUiState(
     val activeDialog: BackupConfigDialog? = null,
     val backupNames: ImmutableList<String> = persistentListOf(),
     val ignoreItems: ImmutableList<BackupIgnoreItem> = persistentListOf(),
+    val backupIgnoreItems: ImmutableList<BackupIgnoreItem> = persistentListOf(),
+    val dbIgnoreItems: ImmutableList<BackupIgnoreItem> = persistentListOf(),
+    val backupDbIgnoreItems: ImmutableList<BackupIgnoreItem> = persistentListOf(),
 )
 
 @Stable
@@ -28,6 +31,8 @@ sealed interface BackupConfigSheet {
     data object BackupOptions : BackupConfigSheet
     data object RestoreOptions : BackupConfigSheet
     data object RestoreFiles : BackupConfigSheet
+    data object IgnoreRestoreItems : BackupConfigSheet
+    data object IgnoreBackupItems : BackupConfigSheet
 }
 
 sealed interface BackupConfigDialog {
@@ -37,7 +42,6 @@ sealed interface BackupConfigDialog {
         val passwordVisible: Boolean = false,
     ) : BackupConfigDialog
 
-    data object IgnoreRestoreItems : BackupConfigDialog
     data class ConfirmLocalRestoreFallback(val error: String?) : BackupConfigDialog
     data class Loading(@StringRes val titleRes: Int) : BackupConfigDialog
 }
@@ -62,6 +66,13 @@ sealed interface BackupConfigIntent {
     data object OpenIgnoreDialog : BackupConfigIntent
     data class ToggleIgnoreItem(val key: String, val value: Boolean) : BackupConfigIntent
     data object SaveIgnoreItems : BackupConfigIntent
+    data object OpenBackupIgnoreDialog : BackupConfigIntent
+    data class ToggleBackupIgnoreItem(val key: String, val value: Boolean) : BackupConfigIntent
+    data object SaveBackupIgnoreItems : BackupConfigIntent
+    data class ToggleDbIgnoreItem(val key: String, val value: Boolean) : BackupConfigIntent
+    data object SaveDbIgnoreItems : BackupConfigIntent
+    data class ToggleBackupDbIgnoreItem(val key: String, val value: Boolean) : BackupConfigIntent
+    data object SaveBackupDbIgnoreItems : BackupConfigIntent
     data object DismissDialog : BackupConfigIntent
     data object SelectBackupDirectory : BackupConfigIntent
     data object SelectBackupAndRunDirectory : BackupConfigIntent

+ 136 - 24
app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigScreen.kt

@@ -11,8 +11,11 @@ import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Save
 import androidx.compose.material.icons.filled.Visibility
 import androidx.compose.material.icons.filled.VisibilityOff
 import androidx.compose.material3.ExperimentalMaterial3Api
@@ -22,7 +25,10 @@ import androidx.compose.material3.SnackbarHost
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
+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.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalContext
@@ -43,6 +49,7 @@ import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.AppTextField
 import io.legado.app.ui.widget.components.SplicedColumnGroup
 import io.legado.app.ui.widget.components.alert.AppAlertDialog
+import io.legado.app.ui.widget.components.button.series.MediumTonalButton
 import io.legado.app.ui.widget.components.card.SelectionItemCard
 import io.legado.app.ui.widget.components.checkBox.CheckboxItem
 import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
@@ -50,12 +57,14 @@ import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
 import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
 import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
 import io.legado.app.ui.widget.components.settingItem.InputSettingItem
+import io.legado.app.ui.widget.components.settingItem.SettingItem
 import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
 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.isContentScheme
 import io.legado.app.utils.takePersistablePermissionSafely
+import kotlinx.collections.immutable.ImmutableList
 import kotlinx.coroutines.flow.collectLatest
 import org.koin.androidx.compose.koinViewModel
 
@@ -247,6 +256,11 @@ fun BackupConfigScreen(
                         description = stringResource(R.string.restore_ignore_summary),
                         onClick = { onIntent(BackupConfigIntent.OpenIgnoreDialog) },
                     )
+                    ClickableSettingItem(
+                        title = stringResource(R.string.backup_ignore),
+                        description = stringResource(R.string.backup_ignore_summary),
+                        onClick = { onIntent(BackupConfigIntent.OpenBackupIgnoreDialog) },
+                    )
                     ClickableSettingItem(
                         title = stringResource(R.string.menu_import_old_version),
                         description = stringResource(R.string.import_old_summary),
@@ -301,7 +315,9 @@ private fun BackupConfigSheets(
         title = stringResource(R.string.select_restore_file),
     ) {
         LazyColumn(
-            modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp),
+            modifier = Modifier
+                .fillMaxWidth()
+                .padding(bottom = 16.dp),
             verticalArrangement = Arrangement.spacedBy(8.dp),
         ) {
             items(state.backupNames, key = { it }) { name ->
@@ -313,6 +329,125 @@ private fun BackupConfigSheets(
             }
         }
     }
+    IgnoreItemsSheet(
+        show = state.activeSheet == BackupConfigSheet.IgnoreRestoreItems,
+        title = stringResource(R.string.restore_ignore),
+        ignoreItems = state.ignoreItems,
+        dbIgnoreItems = state.dbIgnoreItems,
+        onToggleIgnoreItem = { key, value ->
+            onIntent(
+                BackupConfigIntent.ToggleIgnoreItem(
+                    key,
+                    value
+                )
+            )
+        },
+        onToggleDbIgnoreItem = { key, value ->
+            onIntent(
+                BackupConfigIntent.ToggleDbIgnoreItem(
+                    key,
+                    value
+                )
+            )
+        },
+        onConfirm = { onIntent(BackupConfigIntent.SaveIgnoreItems) },
+        onDismissRequest = { onIntent(BackupConfigIntent.SaveIgnoreItems) },
+    )
+    IgnoreItemsSheet(
+        show = state.activeSheet == BackupConfigSheet.IgnoreBackupItems,
+        title = stringResource(R.string.backup_ignore),
+        ignoreItems = state.backupIgnoreItems,
+        dbIgnoreItems = state.backupDbIgnoreItems,
+        onToggleIgnoreItem = { key, value ->
+            onIntent(
+                BackupConfigIntent.ToggleBackupIgnoreItem(
+                    key,
+                    value
+                )
+            )
+        },
+        onToggleDbIgnoreItem = { key, value ->
+            onIntent(
+                BackupConfigIntent.ToggleBackupDbIgnoreItem(
+                    key,
+                    value
+                )
+            )
+        },
+        onConfirm = { onIntent(BackupConfigIntent.SaveBackupIgnoreItems) },
+        onDismissRequest = { onIntent(BackupConfigIntent.SaveBackupIgnoreItems) },
+    )
+}
+
+@Composable
+private fun IgnoreItemsSheet(
+    show: Boolean,
+    title: String,
+    ignoreItems: ImmutableList<BackupIgnoreItem>,
+    dbIgnoreItems: ImmutableList<BackupIgnoreItem>,
+    onToggleIgnoreItem: (String, Boolean) -> Unit,
+    onToggleDbIgnoreItem: (String, Boolean) -> Unit,
+    onConfirm: () -> Unit,
+    onDismissRequest: () -> Unit,
+) {
+    var configExpanded by remember { mutableStateOf(true) }
+    var dbExpanded by remember { mutableStateOf(false) }
+
+    AppModalBottomSheet(
+        show = show,
+        onDismissRequest = onDismissRequest,
+        title = title,
+        endAction = {
+            MediumTonalButton(
+                onClick = onConfirm,
+                icon = Icons.Default.Save
+            )
+        },
+    ) {
+        Column(
+            modifier = Modifier
+                .fillMaxWidth()
+                .verticalScroll(rememberScrollState()),
+            verticalArrangement = Arrangement.spacedBy(8.dp),
+        ) {
+            SettingItem(
+                title = stringResource(R.string.config_ignore),
+                description = stringResource(R.string.restore_ignore_summary),
+                expanded = configExpanded,
+                onExpandChange = { configExpanded = it },
+                expandContent = {
+                    ignoreItems.forEach { item: BackupIgnoreItem ->
+                        CheckboxItem(
+                            title = item.title,
+                            checked = item.checked,
+                            onCheckedChange = { onToggleIgnoreItem(item.key, it) },
+                        )
+                        Spacer(
+                            modifier = Modifier.padding(vertical = 2.dp)
+                        )
+                    }
+                },
+            )
+            SettingItem(
+                title = stringResource(R.string.database_ignore),
+                description = stringResource(R.string.database_ignore_summary),
+                expanded = dbExpanded,
+                onExpandChange = { dbExpanded = it },
+                expandContent = {
+                    dbIgnoreItems.forEach { item: BackupIgnoreItem ->
+                        CheckboxItem(
+                            title = item.title,
+                            checked = item.checked,
+                            onCheckedChange = { onToggleDbIgnoreItem(item.key, it) },
+                        )
+                        Spacer(
+                            modifier = Modifier.padding(vertical = 2.dp)
+                        )
+                    }
+                },
+            )
+        }
+    }
 }
 
 @Composable
@@ -378,29 +513,6 @@ private fun BackupConfigDialogs(
         onDismiss = { onIntent(BackupConfigIntent.DismissDialog) },
     )
 
-    AppAlertDialog(
-        show = dialog == BackupConfigDialog.IgnoreRestoreItems,
-        onDismissRequest = { onIntent(BackupConfigIntent.SaveIgnoreItems) },
-        title = stringResource(R.string.restore_ignore),
-        content = {
-            Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
-                state.ignoreItems.forEach { item ->
-                    CheckboxItem(
-                        title = item.title,
-                        checked = item.checked,
-                        onCheckedChange = {
-                            onIntent(BackupConfigIntent.ToggleIgnoreItem(item.key, it))
-                        },
-                    )
-                }
-            }
-        },
-        confirmText = stringResource(R.string.ok),
-        onConfirm = { onIntent(BackupConfigIntent.SaveIgnoreItems) },
-        dismissText = stringResource(R.string.cancel),
-        onDismiss = { onIntent(BackupConfigIntent.SaveIgnoreItems) },
-    )
-
     val fallback = dialog as? BackupConfigDialog.ConfirmLocalRestoreFallback
     ConfirmDialog(
         show = fallback != null,

+ 117 - 3
app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigViewModel.kt

@@ -25,7 +25,12 @@ class BackupConfigViewModel(
     private val backupRestoreUseCase: BackupRestoreUseCase,
 ) : ViewModel() {
     private val _uiState = MutableStateFlow(
-        BackupConfigUiState(ignoreItems = loadIgnoreItems())
+        BackupConfigUiState(
+            ignoreItems = loadIgnoreItems(),
+            backupIgnoreItems = loadBackupIgnoreItems(),
+            dbIgnoreItems = loadDbIgnoreItems(),
+            backupDbIgnoreItems = loadBackupDbIgnoreItems()
+        )
     )
     val uiState = _uiState.asStateFlow()
 
@@ -65,9 +70,26 @@ class BackupConfigViewModel(
             BackupConfigIntent.SaveWebDavAuth -> saveWebDavAuth()
             BackupConfigIntent.TestWebDav -> testWebDav()
             BackupConfigIntent.OpenIgnoreDialog ->
-                _uiState.update { it.copy(activeDialog = BackupConfigDialog.IgnoreRestoreItems) }
+                _uiState.update { it.copy(activeSheet = BackupConfigSheet.IgnoreRestoreItems) }
             is BackupConfigIntent.ToggleIgnoreItem -> toggleIgnoreItem(intent.key, intent.value)
             BackupConfigIntent.SaveIgnoreItems -> saveIgnoreItems()
+            BackupConfigIntent.OpenBackupIgnoreDialog ->
+                _uiState.update { it.copy(activeSheet = BackupConfigSheet.IgnoreBackupItems) }
+
+            is BackupConfigIntent.ToggleBackupIgnoreItem -> toggleBackupIgnoreItem(
+                intent.key,
+                intent.value
+            )
+
+            BackupConfigIntent.SaveBackupIgnoreItems -> saveBackupIgnoreItems()
+            is BackupConfigIntent.ToggleDbIgnoreItem -> toggleDbIgnoreItem(intent.key, intent.value)
+            BackupConfigIntent.SaveDbIgnoreItems -> saveDbIgnoreItems()
+            is BackupConfigIntent.ToggleBackupDbIgnoreItem -> toggleBackupDbIgnoreItem(
+                intent.key,
+                intent.value
+            )
+
+            BackupConfigIntent.SaveBackupDbIgnoreItems -> saveBackupDbIgnoreItems()
             BackupConfigIntent.DismissDialog -> _uiState.update { it.copy(activeDialog = null) }
             BackupConfigIntent.SelectBackupDirectory -> launchDirectoryPicker(runBackup = false)
             BackupConfigIntent.SelectBackupAndRunDirectory -> launchDirectoryPicker(runBackup = true)
@@ -156,8 +178,68 @@ class BackupConfigViewModel(
         _uiState.value.ignoreItems.forEach { item ->
             io.legado.app.help.storage.BackupConfig.ignoreConfig[item.key] = item.checked
         }
+        _uiState.value.dbIgnoreItems.forEach { item ->
+            io.legado.app.help.storage.BackupConfig.dbIgnoreConfig[item.key] = item.checked
+        }
         io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
-        _uiState.update { it.copy(activeDialog = null) }
+        io.legado.app.help.storage.BackupConfig.saveDbIgnoreConfig()
+        _uiState.update { it.copy(activeSheet = null) }
+    }
+
+    private fun toggleBackupIgnoreItem(key: String, value: Boolean) {
+        _uiState.update { state ->
+            state.copy(
+                backupIgnoreItems = state.backupIgnoreItems.map { item ->
+                    if (item.key == key) item.copy(checked = value) else item
+                }.toImmutableList()
+            )
+        }
+    }
+
+    private fun saveBackupIgnoreItems() {
+        _uiState.value.backupIgnoreItems.forEach { item ->
+            io.legado.app.help.storage.BackupConfig.backupIgnoreConfig[item.key] = item.checked
+        }
+        _uiState.value.backupDbIgnoreItems.forEach { item ->
+            io.legado.app.help.storage.BackupConfig.backupDbIgnoreConfig[item.key] = item.checked
+        }
+        io.legado.app.help.storage.BackupConfig.saveBackupIgnoreConfig()
+        io.legado.app.help.storage.BackupConfig.saveBackupDbIgnoreConfig()
+        _uiState.update { it.copy(activeSheet = null) }
+    }
+
+    private fun toggleDbIgnoreItem(key: String, value: Boolean) {
+        _uiState.update { state ->
+            state.copy(
+                dbIgnoreItems = state.dbIgnoreItems.map { item ->
+                    if (item.key == key) item.copy(checked = value) else item
+                }.toImmutableList()
+            )
+        }
+    }
+
+    private fun saveDbIgnoreItems() {
+        _uiState.value.dbIgnoreItems.forEach { item ->
+            io.legado.app.help.storage.BackupConfig.dbIgnoreConfig[item.key] = item.checked
+        }
+        io.legado.app.help.storage.BackupConfig.saveDbIgnoreConfig()
+    }
+
+    private fun toggleBackupDbIgnoreItem(key: String, value: Boolean) {
+        _uiState.update { state ->
+            state.copy(
+                backupDbIgnoreItems = state.backupDbIgnoreItems.map { item ->
+                    if (item.key == key) item.copy(checked = value) else item
+                }.toImmutableList()
+            )
+        }
+    }
+
+    private fun saveBackupDbIgnoreItems() {
+        _uiState.value.backupDbIgnoreItems.forEach { item ->
+            io.legado.app.help.storage.BackupConfig.backupDbIgnoreConfig[item.key] = item.checked
+        }
+        io.legado.app.help.storage.BackupConfig.saveBackupDbIgnoreConfig()
     }
 
     private fun launchDirectoryPicker(runBackup: Boolean) {
@@ -293,5 +375,37 @@ class BackupConfigViewModel(
                 )
             }
             .toImmutableList()
+
+        fun loadBackupIgnoreItems() = io.legado.app.help.storage.BackupConfig.backupIgnoreKeys
+            .mapIndexed { index, key ->
+                BackupIgnoreItem(
+                    key = key,
+                    title = io.legado.app.help.storage.BackupConfig.backupIgnoreTitle[index],
+                    checked = io.legado.app.help.storage.BackupConfig.backupIgnoreConfig[key]
+                        ?: false,
+                )
+            }
+            .toImmutableList()
+
+        fun loadDbIgnoreItems() = io.legado.app.help.storage.BackupConfig.dbIgnoreKeys
+            .mapIndexed { index, key ->
+                BackupIgnoreItem(
+                    key = key,
+                    title = io.legado.app.help.storage.BackupConfig.dbIgnoreTitle[index],
+                    checked = io.legado.app.help.storage.BackupConfig.dbIgnoreConfig[key] ?: false,
+                )
+            }
+            .toImmutableList()
+
+        fun loadBackupDbIgnoreItems() = io.legado.app.help.storage.BackupConfig.backupDbIgnoreKeys
+            .mapIndexed { index, key ->
+                BackupIgnoreItem(
+                    key = key,
+                    title = io.legado.app.help.storage.BackupConfig.backupDbIgnoreTitle[index],
+                    checked = io.legado.app.help.storage.BackupConfig.backupDbIgnoreConfig[key]
+                        ?: false,
+                )
+            }
+            .toImmutableList()
     }
 }

+ 1 - 0
app/src/main/java/io/legado/app/ui/theme/AppTheme.kt

@@ -17,6 +17,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.materialkolor.PaletteStyle
 import io.legado.app.domain.gateway.AppShellSettingsGateway
 import io.legado.app.domain.gateway.ThemeSettingsGateway
+import io.legado.app.domain.model.settings.ThemeSettings
 import io.legado.app.domain.model.settings.customColors
 import io.legado.app.utils.sysConfiguration
 import org.koin.compose.koinInject

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

@@ -445,6 +445,7 @@
     <string name="fattening_book">养肥书籍</string>
     <string name="finish_book">完结书籍</string>
     <string name="local_book">本地书籍</string>
+    <string name="manga_config">漫画设置</string>
     <string name="status_bar_immersion">状态栏颜色透明</string>
     <string name="imm_navigation_bar">沉浸式导航栏</string>
     <string name="imm_navigation_bar_s">导航栏颜色透明</string>
@@ -885,6 +886,8 @@
     <string name="default_cover">默认封面</string>
     <string name="restore_ignore">恢复忽略列表</string>
     <string name="restore_ignore_summary">选择恢复忽略内容</string>
+    <string name="backup_ignore">备份忽略列表</string>
+    <string name="backup_ignore_summary">选择备份忽略内容</string>
     <string name="read_config">阅读界面</string>
     <string name="read_config_global_theme">全局</string>
     <string name="read_config_layout_spacing">正文间距</string>
@@ -1289,6 +1292,15 @@
     <string name="import_dict_rule">导入字典规则</string>
     <string name="keep_group">保留分组</string>
     <string name="server_config">服务器配置</string>
+    <string name="config_ignore">配置忽略</string>
+    <string name="database_ignore">数据库忽略</string>
+    <string name="database_ignore_summary">选择忽略的数据库表</string>
+    <string name="source_sub">书源订阅</string>
+    <string name="book_group">书架分组</string>
+    <string name="rss_star">RSS 收藏</string>
+    <string name="search_history">搜索历史</string>
+    <string name="keyboard_assists">键盘辅助</string>
+    <string name="homepage_custom_sets">首页自定义</string>
     <string name="sure_upload">远程 WebDav 链接已存在,是否继续</string>
     <string name="unsupport_archivefile_entry">压缩文件内没有支持的文件</string>
     <string name="no_books_dir">没有设置书籍保存位置!</string>

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

@@ -388,6 +388,7 @@
     <string name="fattening_book">養肥書籍</string>
     <string name="finish_book">完結書籍</string>
     <string name="local_book">本地書籍</string>
+    <string name="manga_config">漫畫設置</string>
     <string name="status_bar_immersion">狀態欄顏色透明</string>
     <string name="imm_navigation_bar">沉浸式导航栏</string>
     <string name="imm_navigation_bar_s">导航栏颜色透明</string>
@@ -778,6 +779,8 @@
     <string name="default_cover">默認封面</string>
     <string name="restore_ignore">恢復忽略列表</string>
     <string name="restore_ignore_summary">恢復時忽略一些內容不恢復,方便不同手機配置不同</string>
+    <string name="backup_ignore">備份忽略列表</string>
+    <string name="backup_ignore_summary">備份時忽略一些內容不備份</string>
     <string name="read_config">閱讀界面設置</string>
     <string name="rule_image_style">圖片樣式 (imageStyle)</string>
     <string name="rule_replace_regex">替換規則 (replaceRegex)</string>
@@ -1133,6 +1136,15 @@
     <string name="import_dict_rule">导入字典规则</string>
     <string name="keep_group">保留分组</string>
     <string name="server_config">服务器配置</string>
+    <string name="config_ignore">設定忽略</string>
+    <string name="database_ignore">資料庫忽略</string>
+    <string name="database_ignore_summary">選擇忽略的資料庫表</string>
+    <string name="source_sub">書源訂閱</string>
+    <string name="book_group">書架分組</string>
+    <string name="rss_star">RSS 收藏</string>
+    <string name="search_history">搜尋歷史</string>
+    <string name="keyboard_assists">鍵盤輔助</string>
+    <string name="homepage_custom_sets">首頁自定義</string>
     <string name="sure_upload">远程webDav链接已存在,是否继续</string>
     <string name="unsupport_archivefile_entry">压缩文件内没有支持的文件</string>
     <string name="no_books_dir">没有设置书籍保存位置!</string>

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

@@ -390,6 +390,7 @@
     <string name="fattening_book">養肥書籍</string>
     <string name="finish_book">完結書籍</string>
     <string name="local_book">本機書籍</string>
+    <string name="manga_config">漫畫設定</string>
     <string name="status_bar_immersion">狀態欄顏色透明</string>
     <string name="imm_navigation_bar">沉浸式導航欄</string>
     <string name="imm_navigation_bar_s">導航欄顏色透明</string>
@@ -786,6 +787,8 @@
     <string name="default_cover">預設封面</string>
     <string name="restore_ignore">復原忽略列表</string>
     <string name="restore_ignore_summary">復原時忽略一些內容不復原,方便不同手機配置不同</string>
+    <string name="backup_ignore">備份忽略列表</string>
+    <string name="backup_ignore_summary">備份時忽略一些內容不備份</string>
     <string name="read_config">閱讀介面設定</string>
     <string name="group_name">分組名稱</string>
     <string name="note_content">備註內容</string>
@@ -1136,6 +1139,15 @@
     <string name="import_dict_rule">导入字典规则</string>
     <string name="keep_group">保留分组</string>
     <string name="server_config">服务器配置</string>
+    <string name="config_ignore">設定忽略</string>
+    <string name="database_ignore">資料庫忽略</string>
+    <string name="database_ignore_summary">選擇忽略的資料庫表</string>
+    <string name="source_sub">書源訂閱</string>
+    <string name="book_group">書架分組</string>
+    <string name="rss_star">RSS 收藏</string>
+    <string name="search_history">搜尋歷史</string>
+    <string name="keyboard_assists">鍵盤輔助</string>
+    <string name="homepage_custom_sets">首頁自定義</string>
     <string name="sure_upload">远程webDav链接已存在,是否继续</string>
     <string name="unsupport_archivefile_entry">压缩文件内没有支持的文件</string>
     <string name="no_books_dir">没有设置书籍保存位置!</string>

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

@@ -466,6 +466,7 @@
     <string name="fattening_book">Awaiting more chapters books</string>
     <string name="finish_book">Finished books</string>
     <string name="local_book">Local Books</string>
+    <string name="manga_config">Manga Settings</string>
     <string name="status_bar_immersion">The status bar color becomes transparent</string>
     <string name="imm_navigation_bar">immersion navigation bar</string>
     <string name="imm_navigation_bar_s">The navigation bar becomes transparent</string>
@@ -910,6 +911,8 @@
     <string name="default_cover">Default Cover</string>
     <string name="restore_ignore">Bypass List</string>
     <string name="restore_ignore_summary">Ignore some contents while restoring</string>
+    <string name="backup_ignore">Backup Bypass List</string>
+    <string name="backup_ignore_summary">Ignore some contents while backing up</string>
     <string name="read_config">Reading Interface Settings</string>
     <string name="show_menu_icon">Show Menu Item Icons</string>
     <string name="read_config_global_theme">Global &amp; Theme</string>
@@ -1342,6 +1345,15 @@
     <string name="import_dict_rule">Import dict rule</string>
     <string name="keep_group">Keep group</string>
     <string name="server_config">Server Configuration</string>
+    <string name="config_ignore">Config Ignore</string>
+    <string name="database_ignore">Database Ignore</string>
+    <string name="database_ignore_summary">Select database tables to ignore</string>
+    <string name="source_sub">Source Subscriptions</string>
+    <string name="book_group">Book Groups</string>
+    <string name="rss_star">RSS Favorites</string>
+    <string name="search_history">Search History</string>
+    <string name="keyboard_assists">Keyboard Assists</string>
+    <string name="homepage_custom_sets">Homepage Custom Sets</string>
     <string name="sure_upload">Remote WebDAV URL exists, Continue?</string>
     <string name="unsupport_archivefile_entry">Cannot find supported files in archive</string>
     <string name="no_books_dir">There is no book save location set!</string>