Просмотр исходного кода

fix: 书签问题与高亮规则代码优化

HapeLee 5 месяцев назад
Родитель
Сommit
62875c54d9

+ 2 - 1
.claude/settings.local.json

@@ -112,7 +112,8 @@
       "Bash(Select-Object -Last 10)",
       "Bash(dir /s /b \"%USERPROFILE%\\\\.gradle\\\\caches\\\\*kyant*\")",
       "Bash(Select-String -Pattern \"\\(BUILD|error:|FAILED\\)\")",
-      "PowerShell(grep *)"
+      "PowerShell(grep *)",
+      "Bash(powershell -Command \"./gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 30\")"
     ]
   }
 }

+ 8 - 3
app/schemas/io.legado.app.data.AppDatabase/91.json

@@ -2,7 +2,7 @@
   "formatVersion": 1,
   "database": {
     "version": 91,
-    "identityHash": "ec7132bb4a28ea41ce9314f820eb6d7b",
+    "identityHash": "3b66f251365b78f6007d87da5bd12b3f",
     "entities": [
       {
         "tableName": "books",
@@ -2293,7 +2293,7 @@
       },
       {
         "tableName": "highlightRules",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `pattern` TEXT NOT NULL, `sampleText` TEXT NOT NULL, `targetScope` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `position` INTEGER NOT NULL, `textColor` INTEGER, `bgColor` INTEGER, `underlineMode` INTEGER NOT NULL, `underlineColor` INTEGER, `underlineWidth` REAL NOT NULL, `underlineOffset` REAL NOT NULL, `underlineSvgPath` TEXT, `bgImage` TEXT, `bgImageFit` INTEGER NOT NULL, `bgImageScale` REAL NOT NULL, `configName` TEXT, PRIMARY KEY(`id`))",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `pattern` TEXT NOT NULL, `sampleText` TEXT NOT NULL, `targetScope` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `position` INTEGER NOT NULL, `textColor` INTEGER, `bgColor` INTEGER, `underlineMode` INTEGER NOT NULL, `underlineColor` INTEGER, `underlineWidth` REAL NOT NULL, `underlineOffset` REAL NOT NULL, `underlineSvgPath` TEXT, `bgImage` TEXT, `bgImageFit` INTEGER NOT NULL, `bgImageScale` REAL NOT NULL, `configName` TEXT, `fontPath` TEXT, PRIMARY KEY(`id`))",
         "fields": [
           {
             "fieldPath": "id",
@@ -2396,6 +2396,11 @@
             "fieldPath": "configName",
             "columnName": "configName",
             "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "fontPath",
+            "columnName": "fontPath",
+            "affinity": "TEXT"
           }
         ],
         "primaryKey": {
@@ -2414,7 +2419,7 @@
     ],
     "setupQueries": [
       "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ec7132bb4a28ea41ce9314f820eb6d7b')"
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3b66f251365b78f6007d87da5bd12b3f')"
     ]
   }
 }

+ 0 - 2425
app/schemas/io.legado.app.data.AppDatabase/92.json

@@ -1,2425 +0,0 @@
-{
-  "formatVersion": 1,
-  "database": {
-    "version": 92,
-    "identityHash": "3b66f251365b78f6007d87da5bd12b3f",
-    "entities": [
-      {
-        "tableName": "books",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `remark` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, `syncTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`bookUrl`))",
-        "fields": [
-          {
-            "fieldPath": "bookUrl",
-            "columnName": "bookUrl",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "tocUrl",
-            "columnName": "tocUrl",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "origin",
-            "columnName": "origin",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "'loc_book'"
-          },
-          {
-            "fieldPath": "originName",
-            "columnName": "originName",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "author",
-            "columnName": "author",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "kind",
-            "columnName": "kind",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "customTag",
-            "columnName": "customTag",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "coverUrl",
-            "columnName": "coverUrl",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "customCoverUrl",
-            "columnName": "customCoverUrl",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "intro",
-            "columnName": "intro",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "customIntro",
-            "columnName": "customIntro",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "remark",
-            "columnName": "remark",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "charset",
-            "columnName": "charset",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "type",
-            "columnName": "type",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "group",
-            "columnName": "group",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "latestChapterTitle",
-            "columnName": "latestChapterTitle",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "latestChapterTime",
-            "columnName": "latestChapterTime",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "lastCheckTime",
-            "columnName": "lastCheckTime",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "lastCheckCount",
-            "columnName": "lastCheckCount",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "totalChapterNum",
-            "columnName": "totalChapterNum",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "durChapterTitle",
-            "columnName": "durChapterTitle",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "durChapterIndex",
-            "columnName": "durChapterIndex",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "durChapterPos",
-            "columnName": "durChapterPos",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "durChapterTime",
-            "columnName": "durChapterTime",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "wordCount",
-            "columnName": "wordCount",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "canUpdate",
-            "columnName": "canUpdate",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "1"
-          },
-          {
-            "fieldPath": "order",
-            "columnName": "order",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "originOrder",
-            "columnName": "originOrder",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "variable",
-            "columnName": "variable",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "readConfig",
-            "columnName": "readConfig",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "syncTime",
-            "columnName": "syncTime",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "bookUrl"
-          ]
-        },
-        "indices": [
-          {
-            "name": "index_books_name_author",
-            "unique": false,
-            "columnNames": [
-              "name",
-              "author"
-            ],
-            "orders": [],
-            "createSql": "CREATE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)"
-          },
-          {
-            "name": "index_books_durChapterTime",
-            "unique": false,
-            "columnNames": [
-              "durChapterTime"
-            ],
-            "orders": [],
-            "createSql": "CREATE INDEX IF NOT EXISTS `index_books_durChapterTime` ON `${TABLE_NAME}` (`durChapterTime`)"
-          }
-        ]
-      },
-      {
-        "tableName": "book_groups",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, `isPrivate` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`groupId`))",
-        "fields": [
-          {
-            "fieldPath": "groupId",
-            "columnName": "groupId",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "groupName",
-            "columnName": "groupName",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "cover",
-            "columnName": "cover",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "order",
-            "columnName": "order",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "enableRefresh",
-            "columnName": "enableRefresh",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "1"
-          },
-          {
-            "fieldPath": "show",
-            "columnName": "show",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "1"
-          },
-          {
-            "fieldPath": "bookSort",
-            "columnName": "bookSort",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "-1"
-          },
-          {
-            "fieldPath": "isPrivate",
-            "columnName": "isPrivate",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "groupId"
-          ]
-        }
-      },
-      {
-        "tableName": "book_sources",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL DEFAULT 1, `enabledExplore` INTEGER NOT NULL DEFAULT 1, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `exploreScreen` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, `eventListener` INTEGER NOT NULL DEFAULT 0, `customButton` INTEGER NOT NULL DEFAULT 0, `homepageModules` TEXT, PRIMARY KEY(`bookSourceUrl`))",
-        "fields": [
-          {
-            "fieldPath": "bookSourceUrl",
-            "columnName": "bookSourceUrl",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookSourceName",
-            "columnName": "bookSourceName",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookSourceGroup",
-            "columnName": "bookSourceGroup",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "bookSourceType",
-            "columnName": "bookSourceType",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookUrlPattern",
-            "columnName": "bookUrlPattern",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "customOrder",
-            "columnName": "customOrder",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "enabled",
-            "columnName": "enabled",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "1"
-          },
-          {
-            "fieldPath": "enabledExplore",
-            "columnName": "enabledExplore",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "1"
-          },
-          {
-            "fieldPath": "jsLib",
-            "columnName": "jsLib",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "enabledCookieJar",
-            "columnName": "enabledCookieJar",
-            "affinity": "INTEGER",
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "concurrentRate",
-            "columnName": "concurrentRate",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "header",
-            "columnName": "header",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "loginUrl",
-            "columnName": "loginUrl",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "loginUi",
-            "columnName": "loginUi",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "loginCheckJs",
-            "columnName": "loginCheckJs",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "coverDecodeJs",
-            "columnName": "coverDecodeJs",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "bookSourceComment",
-            "columnName": "bookSourceComment",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "variableComment",
-            "columnName": "variableComment",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "lastUpdateTime",
-            "columnName": "lastUpdateTime",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "respondTime",
-            "columnName": "respondTime",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "weight",
-            "columnName": "weight",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "exploreUrl",
-            "columnName": "exploreUrl",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "exploreScreen",
-            "columnName": "exploreScreen",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "ruleExplore",
-            "columnName": "ruleExplore",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "searchUrl",
-            "columnName": "searchUrl",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "ruleSearch",
-            "columnName": "ruleSearch",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "ruleBookInfo",
-            "columnName": "ruleBookInfo",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "ruleToc",
-            "columnName": "ruleToc",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "ruleContent",
-            "columnName": "ruleContent",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "ruleReview",
-            "columnName": "ruleReview",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "eventListener",
-            "columnName": "eventListener",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "customButton",
-            "columnName": "customButton",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "homepageModules",
-            "columnName": "homepageModules",
-            "affinity": "TEXT"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "bookSourceUrl"
-          ]
-        },
-        "indices": [
-          {
-            "name": "index_book_sources_bookSourceUrl",
-            "unique": false,
-            "columnNames": [
-              "bookSourceUrl"
-            ],
-            "orders": [],
-            "createSql": "CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)"
-          }
-        ]
-      },
-      {
-        "tableName": "chapters",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `wordCount` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, `reviewImg` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )",
-        "fields": [
-          {
-            "fieldPath": "url",
-            "columnName": "url",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "title",
-            "columnName": "title",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "isVolume",
-            "columnName": "isVolume",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "baseUrl",
-            "columnName": "baseUrl",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookUrl",
-            "columnName": "bookUrl",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "index",
-            "columnName": "index",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "isVip",
-            "columnName": "isVip",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "isPay",
-            "columnName": "isPay",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "resourceUrl",
-            "columnName": "resourceUrl",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "tag",
-            "columnName": "tag",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "wordCount",
-            "columnName": "wordCount",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "start",
-            "columnName": "start",
-            "affinity": "INTEGER"
-          },
-          {
-            "fieldPath": "end",
-            "columnName": "end",
-            "affinity": "INTEGER"
-          },
-          {
-            "fieldPath": "startFragmentId",
-            "columnName": "startFragmentId",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "endFragmentId",
-            "columnName": "endFragmentId",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "variable",
-            "columnName": "variable",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "reviewImg",
-            "columnName": "reviewImg",
-            "affinity": "TEXT"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "url",
-            "bookUrl"
-          ]
-        },
-        "indices": [
-          {
-            "name": "index_chapters_bookUrl",
-            "unique": false,
-            "columnNames": [
-              "bookUrl"
-            ],
-            "orders": [],
-            "createSql": "CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)"
-          },
-          {
-            "name": "index_chapters_bookUrl_index",
-            "unique": true,
-            "columnNames": [
-              "bookUrl",
-              "index"
-            ],
-            "orders": [],
-            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)"
-          }
-        ],
-        "foreignKeys": [
-          {
-            "table": "books",
-            "onDelete": "CASCADE",
-            "onUpdate": "NO ACTION",
-            "columns": [
-              "bookUrl"
-            ],
-            "referencedColumns": [
-              "bookUrl"
-            ]
-          }
-        ]
-      },
-      {
-        "tableName": "replace_rules",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `excludeScope` TEXT, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "group",
-            "columnName": "group",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "pattern",
-            "columnName": "pattern",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "replacement",
-            "columnName": "replacement",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "scope",
-            "columnName": "scope",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "scopeTitle",
-            "columnName": "scopeTitle",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "scopeContent",
-            "columnName": "scopeContent",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "1"
-          },
-          {
-            "fieldPath": "excludeScope",
-            "columnName": "excludeScope",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "isEnabled",
-            "columnName": "isEnabled",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "1"
-          },
-          {
-            "fieldPath": "isRegex",
-            "columnName": "isRegex",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "1"
-          },
-          {
-            "fieldPath": "timeoutMillisecond",
-            "columnName": "timeoutMillisecond",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "3000"
-          },
-          {
-            "fieldPath": "order",
-            "columnName": "sortOrder",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": true,
-          "columnNames": [
-            "id"
-          ]
-        },
-        "indices": [
-          {
-            "name": "index_replace_rules_id",
-            "unique": false,
-            "columnNames": [
-              "id"
-            ],
-            "orders": [],
-            "createSql": "CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)"
-          }
-        ]
-      },
-      {
-        "tableName": "searchBooks",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, `chapterWordCountText` TEXT, `chapterWordCount` INTEGER NOT NULL DEFAULT -1, `respondTime` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )",
-        "fields": [
-          {
-            "fieldPath": "bookUrl",
-            "columnName": "bookUrl",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "origin",
-            "columnName": "origin",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "originName",
-            "columnName": "originName",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "type",
-            "columnName": "type",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "author",
-            "columnName": "author",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "kind",
-            "columnName": "kind",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "coverUrl",
-            "columnName": "coverUrl",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "intro",
-            "columnName": "intro",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "wordCount",
-            "columnName": "wordCount",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "latestChapterTitle",
-            "columnName": "latestChapterTitle",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "tocUrl",
-            "columnName": "tocUrl",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "time",
-            "columnName": "time",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "variable",
-            "columnName": "variable",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "originOrder",
-            "columnName": "originOrder",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "chapterWordCountText",
-            "columnName": "chapterWordCountText",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "chapterWordCount",
-            "columnName": "chapterWordCount",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "-1"
-          },
-          {
-            "fieldPath": "respondTime",
-            "columnName": "respondTime",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "-1"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "bookUrl"
-          ]
-        },
-        "indices": [
-          {
-            "name": "index_searchBooks_bookUrl",
-            "unique": true,
-            "columnNames": [
-              "bookUrl"
-            ],
-            "orders": [],
-            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)"
-          },
-          {
-            "name": "index_searchBooks_origin",
-            "unique": false,
-            "columnNames": [
-              "origin"
-            ],
-            "orders": [],
-            "createSql": "CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)"
-          }
-        ],
-        "foreignKeys": [
-          {
-            "table": "book_sources",
-            "onDelete": "CASCADE",
-            "onUpdate": "NO ACTION",
-            "columns": [
-              "origin"
-            ],
-            "referencedColumns": [
-              "bookSourceUrl"
-            ]
-          }
-        ]
-      },
-      {
-        "tableName": "search_keywords",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))",
-        "fields": [
-          {
-            "fieldPath": "word",
-            "columnName": "word",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "usage",
-            "columnName": "usage",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "lastUseTime",
-            "columnName": "lastUseTime",
-            "affinity": "INTEGER",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "word"
-          ]
-        },
-        "indices": [
-          {
-            "name": "index_search_keywords_word",
-            "unique": true,
-            "columnNames": [
-              "word"
-            ],
-            "orders": [],
-            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)"
-          }
-        ]
-      },
-      {
-        "tableName": "cookies",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))",
-        "fields": [
-          {
-            "fieldPath": "url",
-            "columnName": "url",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "cookie",
-            "columnName": "cookie",
-            "affinity": "TEXT",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "url"
-          ]
-        },
-        "indices": [
-          {
-            "name": "index_cookies_url",
-            "unique": true,
-            "columnNames": [
-              "url"
-            ],
-            "orders": [],
-            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)"
-          }
-        ]
-      },
-      {
-        "tableName": "rssSources",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `shouldOverrideUrlLoading` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `preloadJs` TEXT, `startHtml` TEXT, `startStyle` TEXT, `startJs` TEXT, `showWebLog` INTEGER NOT NULL DEFAULT 0, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, `type` INTEGER NOT NULL DEFAULT 0, `preload` INTEGER NOT NULL DEFAULT 0, `cacheFirst` INTEGER NOT NULL DEFAULT 0, `searchUrl` TEXT, `redirectPolicy` TEXT NOT NULL DEFAULT 'ASK_CROSS_ORIGIN', PRIMARY KEY(`sourceUrl`))",
-        "fields": [
-          {
-            "fieldPath": "sourceUrl",
-            "columnName": "sourceUrl",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "sourceName",
-            "columnName": "sourceName",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "sourceIcon",
-            "columnName": "sourceIcon",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "sourceGroup",
-            "columnName": "sourceGroup",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "sourceComment",
-            "columnName": "sourceComment",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "enabled",
-            "columnName": "enabled",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "variableComment",
-            "columnName": "variableComment",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "jsLib",
-            "columnName": "jsLib",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "enabledCookieJar",
-            "columnName": "enabledCookieJar",
-            "affinity": "INTEGER",
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "concurrentRate",
-            "columnName": "concurrentRate",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "header",
-            "columnName": "header",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "loginUrl",
-            "columnName": "loginUrl",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "loginUi",
-            "columnName": "loginUi",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "loginCheckJs",
-            "columnName": "loginCheckJs",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "coverDecodeJs",
-            "columnName": "coverDecodeJs",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "sortUrl",
-            "columnName": "sortUrl",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "singleUrl",
-            "columnName": "singleUrl",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "articleStyle",
-            "columnName": "articleStyle",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "ruleArticles",
-            "columnName": "ruleArticles",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "ruleNextPage",
-            "columnName": "ruleNextPage",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "ruleTitle",
-            "columnName": "ruleTitle",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "rulePubDate",
-            "columnName": "rulePubDate",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "ruleDescription",
-            "columnName": "ruleDescription",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "ruleImage",
-            "columnName": "ruleImage",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "ruleLink",
-            "columnName": "ruleLink",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "ruleContent",
-            "columnName": "ruleContent",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "contentWhitelist",
-            "columnName": "contentWhitelist",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "contentBlacklist",
-            "columnName": "contentBlacklist",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "shouldOverrideUrlLoading",
-            "columnName": "shouldOverrideUrlLoading",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "style",
-            "columnName": "style",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "enableJs",
-            "columnName": "enableJs",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "1"
-          },
-          {
-            "fieldPath": "loadWithBaseUrl",
-            "columnName": "loadWithBaseUrl",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "1"
-          },
-          {
-            "fieldPath": "injectJs",
-            "columnName": "injectJs",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "preloadJs",
-            "columnName": "preloadJs",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "startHtml",
-            "columnName": "startHtml",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "startStyle",
-            "columnName": "startStyle",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "startJs",
-            "columnName": "startJs",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "showWebLog",
-            "columnName": "showWebLog",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "lastUpdateTime",
-            "columnName": "lastUpdateTime",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "customOrder",
-            "columnName": "customOrder",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "type",
-            "columnName": "type",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "preload",
-            "columnName": "preload",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "cacheFirst",
-            "columnName": "cacheFirst",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "searchUrl",
-            "columnName": "searchUrl",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "redirectPolicy",
-            "columnName": "redirectPolicy",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "'ASK_CROSS_ORIGIN'"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "sourceUrl"
-          ]
-        },
-        "indices": [
-          {
-            "name": "index_rssSources_sourceUrl",
-            "unique": false,
-            "columnNames": [
-              "sourceUrl"
-            ],
-            "orders": [],
-            "createSql": "CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)"
-          }
-        ]
-      },
-      {
-        "tableName": "bookmarks",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL DEFAULT '', `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))",
-        "fields": [
-          {
-            "fieldPath": "time",
-            "columnName": "time",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookName",
-            "columnName": "bookName",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookAuthor",
-            "columnName": "bookAuthor",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "chapterIndex",
-            "columnName": "chapterIndex",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "chapterPos",
-            "columnName": "chapterPos",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "chapterName",
-            "columnName": "chapterName",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookText",
-            "columnName": "bookText",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "content",
-            "columnName": "content",
-            "affinity": "TEXT",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "time"
-          ]
-        },
-        "indices": [
-          {
-            "name": "index_bookmarks_bookName_bookAuthor",
-            "unique": false,
-            "columnNames": [
-              "bookName",
-              "bookAuthor"
-            ],
-            "orders": [],
-            "createSql": "CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)"
-          }
-        ]
-      },
-      {
-        "tableName": "rssArticles",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `group` TEXT NOT NULL DEFAULT '默认分组', `read` INTEGER NOT NULL, `variable` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `durPos` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`origin`, `link`, `sort`))",
-        "fields": [
-          {
-            "fieldPath": "origin",
-            "columnName": "origin",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "sort",
-            "columnName": "sort",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "title",
-            "columnName": "title",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "order",
-            "columnName": "order",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "link",
-            "columnName": "link",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "pubDate",
-            "columnName": "pubDate",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "description",
-            "columnName": "description",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "content",
-            "columnName": "content",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "image",
-            "columnName": "image",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "group",
-            "columnName": "group",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "'默认分组'"
-          },
-          {
-            "fieldPath": "read",
-            "columnName": "read",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "variable",
-            "columnName": "variable",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "type",
-            "columnName": "type",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "durPos",
-            "columnName": "durPos",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "origin",
-            "link",
-            "sort"
-          ]
-        }
-      },
-      {
-        "tableName": "rssReadRecords",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `title` TEXT, `readTime` INTEGER, `read` INTEGER NOT NULL, `origin` TEXT NOT NULL DEFAULT '', `sort` TEXT NOT NULL DEFAULT '', `image` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `durPos` INTEGER NOT NULL DEFAULT 0, `pubDate` TEXT, PRIMARY KEY(`record`))",
-        "fields": [
-          {
-            "fieldPath": "record",
-            "columnName": "record",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "title",
-            "columnName": "title",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "readTime",
-            "columnName": "readTime",
-            "affinity": "INTEGER"
-          },
-          {
-            "fieldPath": "read",
-            "columnName": "read",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "origin",
-            "columnName": "origin",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "sort",
-            "columnName": "sort",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "image",
-            "columnName": "image",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "type",
-            "columnName": "type",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "durPos",
-            "columnName": "durPos",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "pubDate",
-            "columnName": "pubDate",
-            "affinity": "TEXT"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "record"
-          ]
-        },
-        "indices": [
-          {
-            "name": "index_rssReadRecords_origin",
-            "unique": false,
-            "columnNames": [
-              "origin"
-            ],
-            "orders": [],
-            "createSql": "CREATE INDEX IF NOT EXISTS `index_rssReadRecords_origin` ON `${TABLE_NAME}` (`origin`)"
-          }
-        ]
-      },
-      {
-        "tableName": "readRecordDetail",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL DEFAULT '', `date` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `readWords` INTEGER NOT NULL DEFAULT 0, `firstReadTime` INTEGER NOT NULL DEFAULT 0, `lastReadTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`, `bookAuthor`, `date`))",
-        "fields": [
-          {
-            "fieldPath": "deviceId",
-            "columnName": "deviceId",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookName",
-            "columnName": "bookName",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookAuthor",
-            "columnName": "bookAuthor",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "date",
-            "columnName": "date",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "readTime",
-            "columnName": "readTime",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "readWords",
-            "columnName": "readWords",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "firstReadTime",
-            "columnName": "firstReadTime",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "lastReadTime",
-            "columnName": "lastReadTime",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "deviceId",
-            "bookName",
-            "bookAuthor",
-            "date"
-          ]
-        }
-      },
-      {
-        "tableName": "readRecordSession",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL DEFAULT '', `startTime` INTEGER NOT NULL, `endTime` INTEGER NOT NULL, `words` INTEGER NOT NULL)",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "deviceId",
-            "columnName": "deviceId",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookName",
-            "columnName": "bookName",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookAuthor",
-            "columnName": "bookAuthor",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "startTime",
-            "columnName": "startTime",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "endTime",
-            "columnName": "endTime",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "words",
-            "columnName": "words",
-            "affinity": "INTEGER",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": true,
-          "columnNames": [
-            "id"
-          ]
-        }
-      },
-      {
-        "tableName": "rssStars",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `group` TEXT NOT NULL DEFAULT '默认分组', `variable` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `durPos` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`origin`, `link`))",
-        "fields": [
-          {
-            "fieldPath": "origin",
-            "columnName": "origin",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "sort",
-            "columnName": "sort",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "title",
-            "columnName": "title",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "starTime",
-            "columnName": "starTime",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "link",
-            "columnName": "link",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "pubDate",
-            "columnName": "pubDate",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "description",
-            "columnName": "description",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "content",
-            "columnName": "content",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "image",
-            "columnName": "image",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "group",
-            "columnName": "group",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "'默认分组'"
-          },
-          {
-            "fieldPath": "variable",
-            "columnName": "variable",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "type",
-            "columnName": "type",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "durPos",
-            "columnName": "durPos",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "origin",
-            "link"
-          ]
-        }
-      },
-      {
-        "tableName": "txtTocRules",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "rule",
-            "columnName": "rule",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "example",
-            "columnName": "example",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "serialNumber",
-            "columnName": "serialNumber",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "enable",
-            "columnName": "enable",
-            "affinity": "INTEGER",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "id"
-          ]
-        }
-      },
-      {
-        "tableName": "readRecord",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL DEFAULT '', `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`, `bookAuthor`))",
-        "fields": [
-          {
-            "fieldPath": "deviceId",
-            "columnName": "deviceId",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookName",
-            "columnName": "bookName",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookAuthor",
-            "columnName": "bookAuthor",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "readTime",
-            "columnName": "readTime",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "lastRead",
-            "columnName": "lastRead",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "deviceId",
-            "bookName",
-            "bookAuthor"
-          ]
-        }
-      },
-      {
-        "tableName": "httpTTS",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "url",
-            "columnName": "url",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "contentType",
-            "columnName": "contentType",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "concurrentRate",
-            "columnName": "concurrentRate",
-            "affinity": "TEXT",
-            "defaultValue": "'0'"
-          },
-          {
-            "fieldPath": "loginUrl",
-            "columnName": "loginUrl",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "loginUi",
-            "columnName": "loginUi",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "header",
-            "columnName": "header",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "jsLib",
-            "columnName": "jsLib",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "enabledCookieJar",
-            "columnName": "enabledCookieJar",
-            "affinity": "INTEGER",
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "loginCheckJs",
-            "columnName": "loginCheckJs",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "lastUpdateTime",
-            "columnName": "lastUpdateTime",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "id"
-          ]
-        }
-      },
-      {
-        "tableName": "caches",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))",
-        "fields": [
-          {
-            "fieldPath": "key",
-            "columnName": "key",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "value",
-            "columnName": "value",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "deadline",
-            "columnName": "deadline",
-            "affinity": "INTEGER",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "key"
-          ]
-        },
-        "indices": [
-          {
-            "name": "index_caches_key",
-            "unique": true,
-            "columnNames": [
-              "key"
-            ],
-            "orders": [],
-            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)"
-          }
-        ]
-      },
-      {
-        "tableName": "ruleSubs",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "url",
-            "columnName": "url",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "type",
-            "columnName": "type",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "customOrder",
-            "columnName": "customOrder",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "autoUpdate",
-            "columnName": "autoUpdate",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "update",
-            "columnName": "update",
-            "affinity": "INTEGER",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "id"
-          ]
-        }
-      },
-      {
-        "tableName": "dictRules",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))",
-        "fields": [
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "urlRule",
-            "columnName": "urlRule",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "showRule",
-            "columnName": "showRule",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "enabled",
-            "columnName": "enabled",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "1"
-          },
-          {
-            "fieldPath": "sortNumber",
-            "columnName": "sortNumber",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "name"
-          ]
-        }
-      },
-      {
-        "tableName": "keyboardAssists",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))",
-        "fields": [
-          {
-            "fieldPath": "type",
-            "columnName": "type",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          },
-          {
-            "fieldPath": "key",
-            "columnName": "key",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "value",
-            "columnName": "value",
-            "affinity": "TEXT",
-            "notNull": true,
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "serialNo",
-            "columnName": "serialNo",
-            "affinity": "INTEGER",
-            "notNull": true,
-            "defaultValue": "0"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "type",
-            "key"
-          ]
-        }
-      },
-      {
-        "tableName": "servers",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "type",
-            "columnName": "type",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "config",
-            "columnName": "config",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "sortNumber",
-            "columnName": "sortNumber",
-            "affinity": "INTEGER",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "id"
-          ]
-        }
-      },
-      {
-        "tableName": "search_content_history",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bookName` TEXT DEFAULT '', `bookAuthor` TEXT DEFAULT '', `query` TEXT NOT NULL, `time` INTEGER NOT NULL)",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookName",
-            "columnName": "bookName",
-            "affinity": "TEXT",
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "bookAuthor",
-            "columnName": "bookAuthor",
-            "affinity": "TEXT",
-            "defaultValue": "''"
-          },
-          {
-            "fieldPath": "query",
-            "columnName": "query",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "time",
-            "columnName": "time",
-            "affinity": "INTEGER",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": true,
-          "columnNames": [
-            "id"
-          ]
-        },
-        "indices": [
-          {
-            "name": "index_search_content_history_bookName_bookAuthor_query",
-            "unique": true,
-            "columnNames": [
-              "bookName",
-              "bookAuthor",
-              "query"
-            ],
-            "orders": [],
-            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_search_content_history_bookName_bookAuthor_query` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`, `query`)"
-          }
-        ]
-      },
-      {
-        "tableName": "homepage_modules",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `sourceUrl` TEXT NOT NULL, `moduleKey` TEXT NOT NULL, `type` TEXT NOT NULL, `title` TEXT NOT NULL, `args` TEXT, `layoutConfig` TEXT, `url` TEXT, `isEnabled` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL, `customSetId` TEXT, `isUserCreated` INTEGER NOT NULL, `customTitle` TEXT, `customSetTitle` TEXT, `sourceJsonHash` TEXT, `syncedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "sourceUrl",
-            "columnName": "sourceUrl",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "moduleKey",
-            "columnName": "moduleKey",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "type",
-            "columnName": "type",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "title",
-            "columnName": "title",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "args",
-            "columnName": "args",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "layoutConfig",
-            "columnName": "layoutConfig",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "url",
-            "columnName": "url",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "isEnabled",
-            "columnName": "isEnabled",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "sortOrder",
-            "columnName": "sortOrder",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "customSetId",
-            "columnName": "customSetId",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "isUserCreated",
-            "columnName": "isUserCreated",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "customTitle",
-            "columnName": "customTitle",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "customSetTitle",
-            "columnName": "customSetTitle",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "sourceJsonHash",
-            "columnName": "sourceJsonHash",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "syncedAt",
-            "columnName": "syncedAt",
-            "affinity": "INTEGER",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "id"
-          ]
-        }
-      },
-      {
-        "tableName": "homepage_custom_sets",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `sortOrder` INTEGER NOT NULL, PRIMARY KEY(`id`))",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "sortOrder",
-            "columnName": "sortOrder",
-            "affinity": "INTEGER",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "id"
-          ]
-        }
-      },
-      {
-        "tableName": "highlightRules",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `pattern` TEXT NOT NULL, `sampleText` TEXT NOT NULL, `targetScope` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `position` INTEGER NOT NULL, `textColor` INTEGER, `bgColor` INTEGER, `underlineMode` INTEGER NOT NULL, `underlineColor` INTEGER, `underlineWidth` REAL NOT NULL, `underlineOffset` REAL NOT NULL, `underlineSvgPath` TEXT, `bgImage` TEXT, `bgImageFit` INTEGER NOT NULL, `bgImageScale` REAL NOT NULL, `configName` TEXT, `fontPath` TEXT, PRIMARY KEY(`id`))",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "pattern",
-            "columnName": "pattern",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "sampleText",
-            "columnName": "sampleText",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "targetScope",
-            "columnName": "targetScope",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "enabled",
-            "columnName": "enabled",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "position",
-            "columnName": "position",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "textColor",
-            "columnName": "textColor",
-            "affinity": "INTEGER"
-          },
-          {
-            "fieldPath": "bgColor",
-            "columnName": "bgColor",
-            "affinity": "INTEGER"
-          },
-          {
-            "fieldPath": "underlineMode",
-            "columnName": "underlineMode",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "underlineColor",
-            "columnName": "underlineColor",
-            "affinity": "INTEGER"
-          },
-          {
-            "fieldPath": "underlineWidth",
-            "columnName": "underlineWidth",
-            "affinity": "REAL",
-            "notNull": true
-          },
-          {
-            "fieldPath": "underlineOffset",
-            "columnName": "underlineOffset",
-            "affinity": "REAL",
-            "notNull": true
-          },
-          {
-            "fieldPath": "underlineSvgPath",
-            "columnName": "underlineSvgPath",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "bgImage",
-            "columnName": "bgImage",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "bgImageFit",
-            "columnName": "bgImageFit",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bgImageScale",
-            "columnName": "bgImageScale",
-            "affinity": "REAL",
-            "notNull": true
-          },
-          {
-            "fieldPath": "configName",
-            "columnName": "configName",
-            "affinity": "TEXT"
-          },
-          {
-            "fieldPath": "fontPath",
-            "columnName": "fontPath",
-            "affinity": "TEXT"
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "id"
-          ]
-        }
-      }
-    ],
-    "views": [
-      {
-        "viewName": "book_sources_part",
-        "createSql": "CREATE VIEW `${VIEW_NAME}` AS select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore, \n    (loginUrl is not null and trim(loginUrl) <> '') hasLoginUrl, lastUpdateTime, respondTime, weight, \n    (exploreUrl is not null and trim(exploreUrl) <> '') hasExploreUrl \n    from book_sources"
-      }
-    ],
-    "setupQueries": [
-      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3b66f251365b78f6007d87da5bd12b3f')"
-    ]
-  }
-}

+ 3 - 4
app/src/main/java/io/legado/app/data/AppDatabase.kt

@@ -41,8 +41,8 @@ import io.legado.app.data.entities.Bookmark
 import io.legado.app.data.entities.Cache
 import io.legado.app.data.entities.Cookie
 import io.legado.app.data.entities.DictRule
-import io.legado.app.data.entities.HomepageCustomSet
 import io.legado.app.data.entities.HighlightRule
+import io.legado.app.data.entities.HomepageCustomSet
 import io.legado.app.data.entities.HomepageModule
 import io.legado.app.data.entities.HttpTTS
 import io.legado.app.data.entities.KeyboardAssist
@@ -75,7 +75,7 @@ val appDb by lazy {
 }
 
 @Database(
-    version = 92,
+    version = 91,
     exportSchema = true,
     entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
         ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
@@ -134,8 +134,7 @@ val appDb by lazy {
         AutoMigration(from = 87, to = 88),
         AutoMigration(from = 88, to = 89),
         AutoMigration(from = 89, to = 90),
-        AutoMigration(from = 90, to = 91),
-        AutoMigration(from = 91, to = 92)
+        AutoMigration(from = 90, to = 91)
     ]
 )
 abstract class AppDatabase : RoomDatabase() {

+ 79 - 52
app/src/main/java/io/legado/app/ui/book/read/config/HighlightRuleStore.kt → app/src/main/java/io/legado/app/data/repository/HighlightRuleRepository.kt

@@ -1,9 +1,10 @@
-package io.legado.app.ui.book.read.config
+package io.legado.app.data.repository
 
+import android.content.Context
 import io.legado.app.constant.PreferKey
 import io.legado.app.data.appDb
+import io.legado.app.data.dao.HighlightRuleDao
 import io.legado.app.data.entities.HighlightRule
-import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.utils.GSON
 import io.legado.app.utils.fromJsonArray
 import io.legado.app.utils.getPrefBoolean
@@ -11,9 +12,14 @@ import io.legado.app.utils.putPrefBoolean
 import splitties.init.appCtx
 import java.io.File
 
-object HighlightRuleStore {
+class HighlightRuleRepository(
+    private val dao: HighlightRuleDao = appDb.highlightRuleDao,
+    private val context: Context = appCtx,
+) {
 
-    const val backupFileName = "highlightRule.json"
+    companion object {
+        const val backupFileName = "highlightRule.json"
+    }
 
     data class BackupData(
         val rules: List<HighlightRule> = emptyList(),
@@ -22,24 +28,19 @@ object HighlightRuleStore {
         val bracketNoteEnabled: Boolean = true,
     )
 
-    private val dao get() = appDb.highlightRuleDao
-
-    fun load(): List<HighlightRule> {
-        val configName = ReadBookConfig.durConfig.name
+    fun load(configName: String): List<HighlightRule> {
         return dao.getAll().filter { it.matchesConfig(configName) }
     }
 
-    fun loadEnabled(): List<HighlightRule> {
-        val configName = ReadBookConfig.durConfig.name
+    fun loadEnabled(configName: String): List<HighlightRule> {
         return dao.getEnabled().filter { it.matchesConfig(configName) }
     }
 
     /**
-     * 保存当前排版的规则。
+     * 保存指定排版的规则。
      * 仅替换当前排版绑定的规则,不影响其他排版的规则。
      */
-    fun save(rules: List<HighlightRule>) {
-        val configName = ReadBookConfig.durConfig.name
+    fun save(configName: String, rules: List<HighlightRule>) {
         saveForConfig(rules, configName.ifBlank { null })
     }
 
@@ -59,11 +60,16 @@ object HighlightRuleStore {
             }
             val newIds = sanitized.map { it.id }.toSet()
             // 被移除的旧规则:从 configName 列表中去掉当前排版
-            // 未绑定任何排版时保留规则(configName="[]"),不删除
+            // 未绑定任何排版时删除,避免留下无法在管理界面看到的规则
             for (old in oldBound) {
                 if (old.id !in newIds) {
-                    val remaining = old.configName.orEmpty().configNames().filter { it != configName }
-                    dao.update(old.copy(configName = remaining.toJsonArray()))
+                    val remaining =
+                        old.configName.orEmpty().configNames().filter { it != configName }
+                    if (remaining.isEmpty()) {
+                        dao.delete(old)
+                    } else {
+                        dao.update(old.copy(configName = remaining.toJsonArray()))
+                    }
                 }
             }
             // 插入所有规则(全局规则也一起,否则会被 replaceGlobal 删掉)
@@ -72,31 +78,46 @@ object HighlightRuleStore {
         cleanupUnusedBgImages()
     }
 
-    fun update(rule: HighlightRule) {
-        dao.update(sanitizeRule(rule))
-    }
-
     fun delete(rule: HighlightRule) {
         dao.delete(rule)
+        cleanupUnusedBgImages()
     }
 
-    fun reset(): List<HighlightRule> {
+    fun removeConfigBinding(configName: String) {
+        if (configName.isBlank()) return
+        dao.getAll().forEach { rule ->
+            val names = rule.configName.orEmpty().configNames()
+            if (configName in names) {
+                val remaining = names.filter { it != configName }
+                val updatedConfigName = remaining.takeIf { it.isNotEmpty() }?.toJsonArray()
+                dao.update(rule.copy(configName = updatedConfigName))
+            }
+        }
+    }
+
+    fun reset(configName: String): List<HighlightRule> {
         val defaults = createDefaultRules()
-        val configName = ReadBookConfig.durConfig.name
+        val rules = if (configName.isBlank()) {
+            defaults
+        } else {
+            defaults.map {
+                it.copyWithNewId().copy(configName = listOf(configName).toJsonArray())
+            }
+        }
         if (configName.isBlank()) {
-            dao.replaceGlobal(defaults)
+            dao.replaceGlobal(rules)
         } else {
-            saveForConfig(defaults, configName)
+            saveForConfig(rules, configName)
         }
-        return defaults
+        return rules
     }
 
-    fun createBackupData(): BackupData {
+    fun createBackupData(configName: String): BackupData {
         return BackupData(
-            rules = load(),
-            dialogEnabled = appCtx.getPrefBoolean(PreferKey.highlightRuleDialog, true),
-            bookTitleEnabled = appCtx.getPrefBoolean(PreferKey.highlightRuleBookTitle, true),
-            bracketNoteEnabled = appCtx.getPrefBoolean(PreferKey.highlightRuleBracketNote, true),
+            rules = load(configName),
+            dialogEnabled = context.getPrefBoolean(PreferKey.highlightRuleDialog, true),
+            bookTitleEnabled = context.getPrefBoolean(PreferKey.highlightRuleBookTitle, true),
+            bracketNoteEnabled = context.getPrefBoolean(PreferKey.highlightRuleBracketNote, true),
         )
     }
 
@@ -109,9 +130,9 @@ object HighlightRuleStore {
         // 备份恢复是全量替换
         dao.replaceAll(rules)
         cleanupUnusedBgImages()
-        appCtx.putPrefBoolean(PreferKey.highlightRuleDialog, backupData.dialogEnabled)
-        appCtx.putPrefBoolean(PreferKey.highlightRuleBookTitle, backupData.bookTitleEnabled)
-        appCtx.putPrefBoolean(PreferKey.highlightRuleBracketNote, backupData.bracketNoteEnabled)
+        context.putPrefBoolean(PreferKey.highlightRuleDialog, backupData.dialogEnabled)
+        context.putPrefBoolean(PreferKey.highlightRuleBookTitle, backupData.bookTitleEnabled)
+        context.putPrefBoolean(PreferKey.highlightRuleBracketNote, backupData.bracketNoteEnabled)
     }
 
     fun sanitizeRule(rule: HighlightRule): HighlightRule {
@@ -119,22 +140,30 @@ object HighlightRuleStore {
         val pattern = runCatching { rule.pattern }.getOrNull().orEmpty()
         val sampleText = runCatching { rule.sampleText }.getOrNull().orEmpty()
         val id = runCatching { rule.id }.getOrNull().orEmpty().ifBlank {
-            "${System.currentTimeMillis()}_${listOf(name, pattern).joinToString("|").hashCode().toUInt().toString(16)}"
+            "${System.currentTimeMillis()}_${
+                listOf(name, pattern).joinToString("|").hashCode().toUInt().toString(16)
+            }"
         }
         return HighlightRule(
             id = id,
             name = name,
             pattern = pattern,
             sampleText = sampleText,
-            targetScope = normalizeTargetScope(runCatching { rule.targetScope }.getOrDefault(HighlightRule.TARGET_ALL)),
+            targetScope = normalizeTargetScope(
+                runCatching { rule.targetScope }.getOrDefault(
+                    HighlightRule.TARGET_ALL
+                )
+            ),
             enabled = runCatching { rule.enabled }.getOrDefault(true),
             position = runCatching { rule.position }.getOrDefault(0),
             textColor = runCatching { rule.textColor }.getOrNull(),
             bgColor = runCatching { rule.bgColor }.getOrNull(),
             underlineMode = runCatching { rule.underlineMode }.getOrDefault(0).coerceIn(0, 5),
             underlineColor = runCatching { rule.underlineColor }.getOrNull(),
-            underlineWidth = runCatching { rule.underlineWidth }.getOrDefault(1f).coerceIn(0.1f, 10f),
-            underlineOffset = runCatching { rule.underlineOffset }.getOrDefault(2f).coerceIn(0f, 20f),
+            underlineWidth = runCatching { rule.underlineWidth }.getOrDefault(1f)
+                .coerceIn(0.1f, 10f),
+            underlineOffset = runCatching { rule.underlineOffset }.getOrDefault(2f)
+                .coerceIn(0f, 20f),
             underlineSvgPath = runCatching { rule.underlineSvgPath }.getOrNull(),
             bgImage = runCatching { rule.bgImage }.getOrNull()?.takeIf { it.isNotBlank() },
             bgImageFit = runCatching { rule.bgImageFit }.getOrDefault(0).coerceIn(0, 2),
@@ -149,12 +178,13 @@ object HighlightRuleStore {
             HighlightRule.TARGET_ALL,
             HighlightRule.TARGET_TITLE,
             HighlightRule.TARGET_BODY -> value
+
             else -> fallback
         }
     }
 
     fun createDefaultRules(): List<HighlightRule> {
-        val ctx = appCtx
+        val ctx = context
         return listOf(
             HighlightRule(
                 id = "dialog_default",
@@ -288,7 +318,7 @@ object HighlightRuleStore {
         val usedPaths = allRules.mapNotNull { it.bgImage }
             .filter { it.isNotBlank() && !it.startsWith("assets://") }
             .toSet()
-        val dir = File(appCtx.filesDir, "bg_images")
+        val dir = File(context.filesDir, "bg_images")
         if (!dir.exists()) return
         dir.listFiles()?.forEach { file ->
             if (file.absolutePath !in usedPaths) {
@@ -304,7 +334,7 @@ object HighlightRuleStore {
         val backupFile = File(rootPath, "highlightRuleBg${File.separator}${File(path).name}")
             .takeIf { it.exists() && it.isFile }
             ?: return path
-        val dir = File(appCtx.filesDir, "bg_images")
+        val dir = File(context.filesDir, "bg_images")
         if (!dir.exists()) dir.mkdirs()
         val targetFile = File(dir, backupFile.name)
         if (!targetFile.exists() || targetFile.length() != backupFile.length()) {
@@ -329,18 +359,15 @@ object HighlightRuleStore {
     /**
      * 解析 configName JSON 数组为列表。
      */
-    fun String.configNames(): List<String> {
-        return runCatching {
-            GSON.fromJsonArray<String>(this).getOrNull() ?: emptyList()
-        }.getOrElse { emptyList() }
-    }
+    // endregion
+}
 
-    /**
-     * 将排版名列表转为 JSON 数组字符串。
-     */
-    fun List<String>.toJsonArray(): String {
-        return GSON.toJson(this)
-    }
+fun String.configNames(): List<String> {
+    return runCatching {
+        GSON.fromJsonArray<String>(this).getOrNull() ?: emptyList()
+    }.getOrElse { emptyList() }
+}
 
-    // endregion
+fun List<String>.toJsonArray(): String {
+    return GSON.toJson(this)
 }

+ 11 - 3
app/src/main/java/io/legado/app/data/repository/ReadBookStyleConfigRepository.kt

@@ -10,7 +10,10 @@ import java.io.InputStream
  * keeps a full layout/background/text configuration. This wrapper centralizes
  * file mutations so UI and ViewModel code do not call persistence helpers directly.
  */
-class ReadBookStyleConfigRepository {
+class ReadBookStyleConfigRepository(
+    private val readStyleRepository: ReadStyleRepository,
+    private val highlightRuleRepository: HighlightRuleRepository,
+) {
 
     fun save() {
         ReadBookConfig.save()
@@ -23,20 +26,25 @@ class ReadBookStyleConfigRepository {
     }
 
     fun deleteCurrentStyle(): Boolean {
+        val deletedConfigName = ReadBookConfig.durConfig.name
         val deleted = ReadBookConfig.deleteDur()
         if (deleted) {
+            highlightRuleRepository.removeConfigBinding(deletedConfigName)
             save()
         }
         return deleted
     }
 
     fun importCurrentStyle(bytes: ByteArray) {
-        ReadBookConfig.durConfig = ReadBookConfig.import(bytes)
+        ReadBookConfig.durConfig = readStyleRepository.import(bytes)
         save()
     }
 
     fun exportCurrentStyle(): ByteArray {
-        return ReadBookConfig.export()
+        val config = ReadBookConfig.getExportConfig().copy(
+            highlightRules = ArrayList(highlightRuleRepository.load(ReadBookConfig.durConfig.name))
+        )
+        return readStyleRepository.export(config)
     }
 
     fun saveBackgroundImage(inputStream: InputStream, displayName: String?): String {

+ 12 - 3
app/src/main/java/io/legado/app/data/repository/ReadStyleRepository.kt

@@ -5,7 +5,6 @@ import io.legado.app.constant.AppLog
 import io.legado.app.help.DefaultData
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.config.ReadStyleResolver
-import io.legado.app.ui.book.read.config.HighlightRuleStore
 import io.legado.app.utils.FileUtils
 import io.legado.app.utils.GSON
 import io.legado.app.utils.compress.ZipUtils
@@ -20,7 +19,9 @@ import splitties.init.appCtx
 import java.io.File
 import java.io.InputStream
 
-class ReadStyleRepository {
+class ReadStyleRepository(
+    private val highlightRuleRepository: HighlightRuleRepository,
+) {
 
     val configFilePath: String =
         FileUtils.getPath(appCtx.filesDir, ReadBookConfig.configFileName)
@@ -199,7 +200,15 @@ class ReadStyleRepository {
         config.curTextShadowColor()
         if (config.highlightRules.isNotEmpty()) {
             val targetConfigName = config.name.ifBlank { null }
-            HighlightRuleStore.saveForConfig(config.highlightRules, targetConfigName)
+            val highlightRules = config.highlightRules.map { rule ->
+                if (targetConfigName.isNullOrBlank()) {
+                    rule.copy(configName = null)
+                } else {
+                    rule.copyWithNewId().copy(configName = listOf(targetConfigName).toJsonArray())
+                }
+            }
+            config.highlightRules = ArrayList(highlightRules)
+            highlightRuleRepository.saveForConfig(highlightRules, targetConfigName)
         }
         return config
     }

+ 7 - 2
app/src/main/java/io/legado/app/di/appModule.kt

@@ -22,13 +22,15 @@ import io.legado.app.data.repository.DictionaryRepositoryImpl
 import io.legado.app.data.repository.DirectLinkUploadRepository
 import io.legado.app.data.repository.ExploreRepository
 import io.legado.app.data.repository.ExploreRepositoryImpl
+import io.legado.app.data.repository.HighlightRuleRepository
 import io.legado.app.data.repository.HomepageModulesRepository
 import io.legado.app.data.repository.LlmTranslateRepositoryImpl
 import io.legado.app.data.repository.LocalBookRepository
-import io.legado.app.data.repository.ReadBookStyleConfigRepository
 import io.legado.app.data.repository.ReadAloudSettingsRepository
+import io.legado.app.data.repository.ReadBookStyleConfigRepository
 import io.legado.app.data.repository.ReadRecordRepository
 import io.legado.app.data.repository.ReadSettingsRepository
+import io.legado.app.data.repository.ReadStyleRepository
 import io.legado.app.data.repository.RemoteBookRepository
 import io.legado.app.data.repository.RssRepository
 import io.legado.app.data.repository.SearchContentRepository
@@ -155,6 +157,8 @@ val appModule = module {
     singleOf(::SettingsRepository)
     singleOf(::ReadSettingsRepository)
     singleOf(::ReadAloudSettingsRepository)
+    singleOf(::HighlightRuleRepository)
+    singleOf(::ReadStyleRepository)
     singleOf(::ReadBookStyleConfigRepository)
     singleOf(::LocalPreferencesRepository)
     singleOf(::ExploreBooksUseCase)
@@ -267,7 +271,8 @@ val appModule = module {
             readSettingsRepository = get(),
             readBookStyleConfigRepository = get(),
             readAloudSettingsRepository = get(),
-            localPreferencesRepository = get()
+            localPreferencesRepository = get(),
+            highlightRuleRepository = get()
         )
     }
     viewModelOf(::ChangeCoverViewModel)

+ 5 - 4
app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt

@@ -6,16 +6,15 @@ import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.ColorDrawable
 import android.graphics.drawable.Drawable
 import androidx.annotation.Keep
+import androidx.compose.runtime.State
 import androidx.core.graphics.toColorInt
 import io.legado.app.R
 import io.legado.app.constant.PageAnim
 import io.legado.app.constant.PreferKey
 import io.legado.app.constant.ReadMenuBlurMode
 import io.legado.app.constant.ReadMenuBlurStyle
-import androidx.compose.runtime.State
 import io.legado.app.data.entities.HighlightRule
 import io.legado.app.data.repository.ReadStyleRepository
-import io.legado.app.ui.book.read.config.HighlightRuleStore
 import io.legado.app.help.DefaultData
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.ui.config.PrefDelegate
@@ -24,6 +23,7 @@ import io.legado.app.utils.GSON
 import io.legado.app.utils.fromJsonObject
 import io.legado.app.utils.getMeanColor
 import io.legado.app.utils.hexString
+import org.koin.core.context.GlobalContext
 import splitties.init.appCtx
 import java.io.InputStream
 import kotlin.properties.ReadWriteProperty
@@ -35,7 +35,8 @@ import kotlin.reflect.KProperty
 @Suppress("ConstPropertyName")
 @Keep
 object ReadBookConfig {
-    private val readStyleRepository = ReadStyleRepository()
+    private val readStyleRepository: ReadStyleRepository
+        get() = GlobalContext.get().get()
 
     // region prefDelegate helpers
 
@@ -773,7 +774,7 @@ object ReadBookConfig {
     // endregion
 
     fun getExportConfig(): Config {
-        val exportConfig = durConfig.copy(highlightRules = ArrayList(HighlightRuleStore.load()))
+        val exportConfig = durConfig.copy(highlightRules = arrayListOf())
         if (shareLayout) {
             exportConfig.textFont = shareConfig.textFont
             exportConfig.titleFont = shareConfig.titleFont

+ 0 - 2
app/src/main/java/io/legado/app/ui/book/read/ReadBookColorPickerIds.kt

@@ -5,7 +5,6 @@ object ReadBookColorPickerIds {
     const val TEXT_COLOR = 121
     const val TEXT_ACCENT_COLOR = 125
     const val BG_COLOR = 122
-    const val HIGHLIGHT_RULE_COLOR = 7900
     const val TITLE_COLOR = 7896
     const val TIP_HEADER_COLOR = 7897
     const val TIP_DIVIDER_COLOR = 7898
@@ -13,5 +12,4 @@ object ReadBookColorPickerIds {
     const val MENU_BG_COLOR = 114
     const val MENU_ACCENT_COLOR = 514
     const val UNDERLINE_COLOR = 810
-    var pendingHighlightRulePosition = -1
 }

+ 22 - 6
app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt

@@ -9,6 +9,8 @@ import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookProgress
 import io.legado.app.data.entities.BookSource
+import io.legado.app.data.entities.Bookmark
+import io.legado.app.data.entities.HighlightRule
 import io.legado.app.data.entities.HttpTTS
 import io.legado.app.ui.book.read.page.entities.TextChapter
 import io.legado.app.ui.book.read.page.entities.TextPage
@@ -153,11 +155,20 @@ data class ReadBookUiState(
     val styleConfig: ReadBookStyleConfig = ReadBookStyleConfig(),
     // Menu config (from ReadBookConfig via repository)
     val menuConfig: ReadMenuConfig = ReadMenuConfig(),
+    val highlightRuleConfig: HighlightRuleConfigUiState = HighlightRuleConfigUiState(),
 ) {
     val menuVisible: Boolean
         get() = menuState.visible
 }
 
+@Stable
+data class HighlightRuleConfigUiState(
+    val rules: ImmutableList<HighlightRule> = persistentListOf(),
+    val editingRule: HighlightRule? = null,
+    val showNewRule: Boolean = false,
+    val deleteRule: HighlightRule? = null,
+)
+
 @Stable
 data class ReadMenuConfig(
     val titleBarIconPosition: Int = 0,
@@ -381,6 +392,16 @@ sealed interface ReadBookIntent {
     // Typed config mutation — single entry point for all ReadBookConfig changes
     data class UpdateConfig(val update: ConfigUpdate) : ReadBookIntent
 
+    // Highlight rules
+    data object AddHighlightRule : ReadBookIntent
+    data class EditHighlightRule(val rule: HighlightRule) : ReadBookIntent
+    data class ToggleHighlightRule(val rule: HighlightRule, val enabled: Boolean) : ReadBookIntent
+    data class SaveHighlightRule(val rule: HighlightRule) : ReadBookIntent
+    data object DismissHighlightRuleEdit : ReadBookIntent
+    data class RequestDeleteHighlightRule(val rule: HighlightRule) : ReadBookIntent
+    data object ConfirmDeleteHighlightRule : ReadBookIntent
+    data object DismissDeleteHighlightRule : ReadBookIntent
+
     // Icon picker — file IO handled by ViewModel
     data class SaveMenuCustomIcon(val id: String, val uri: Uri) : ReadBookIntent
     data class SaveTitleBarCustomIcon(val id: String, val uri: Uri) : ReadBookIntent
@@ -398,7 +419,7 @@ sealed interface ReadBookIntent {
     // Default font picker (needs Activity for AlertDialog)
     // Text action menu (moved from Activity)
     data class TextActionAloud(val text: String) : ReadBookIntent
-    data class TextActionBookmark(val text: String) : ReadBookIntent
+    data class TextActionBookmark(val bookmark: Bookmark) : ReadBookIntent
     data class TextActionReplace(val text: String) : ReadBookIntent
     data class TextActionSearchContent(val text: String) : ReadBookIntent
     data class TextActionDict(val text: String) : ReadBookIntent
@@ -1032,11 +1053,6 @@ sealed interface ConfigUpdate {
         override val actions = emptySet<ConfigUpdateAction>()
     }
 
-    // --- Highlight rules ---
-    data class HighlightRules(val rules: List<io.legado.app.data.entities.HighlightRule>) : ConfigUpdate {
-        override val actions = setOf(ConfigUpdateAction.UpdateChapterStyle, ConfigUpdateAction.ReloadContent)
-    }
-
     // --- Auto read ---
     data class AutoReadSpeed(val value: Int) : ConfigUpdate {
         override val actions = emptySet<ConfigUpdateAction>()

+ 3 - 3
app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt

@@ -1,7 +1,6 @@
 package io.legado.app.ui.book.read
 
 import android.annotation.SuppressLint
-import android.content.Intent
 import android.content.pm.ActivityInfo
 import android.os.Build
 import android.view.Gravity
@@ -43,7 +42,6 @@ import io.legado.app.ui.book.read.page.ReadView
 import io.legado.app.ui.book.read.page.entities.PageDirection
 import io.legado.app.ui.book.read.page.provider.ChapterProvider
 import io.legado.app.ui.book.read.page.provider.TextPageFactory
-import io.legado.app.ui.book.searchContent.SearchResult
 import io.legado.app.ui.login.SourceLoginJsExtensions
 import io.legado.app.ui.widget.PopupAction
 import io.legado.app.utils.ColorUtils
@@ -529,7 +527,9 @@ class ReadBookController(
             }
 
             R.id.menu_bookmark -> {
-                viewModel.onIntent(ReadBookIntent.TextActionBookmark(selectedText))
+                refs?.readView?.curPage?.createBookmark()?.let {
+                    viewModel.onIntent(ReadBookIntent.TextActionBookmark(it))
+                } ?: activity.toastOnUi(R.string.create_bookmark_error)
                 return true
             }
 

+ 4 - 3
app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt

@@ -10,7 +10,6 @@ import androidx.compose.runtime.setValue
 import androidx.compose.ui.res.stringResource
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
-import io.legado.app.ui.widget.components.log.AppLogSheet
 import io.legado.app.ui.book.read.sheet.BgTextConfigSheet
 import io.legado.app.ui.book.read.sheet.ChangeChapterSourceSheet
 import io.legado.app.ui.book.read.sheet.CharsetConfigSheet
@@ -20,22 +19,23 @@ import io.legado.app.ui.book.read.sheet.DictSheet
 import io.legado.app.ui.book.read.sheet.DownloadSheet
 import io.legado.app.ui.book.read.sheet.EffectiveReplacesSheet
 import io.legado.app.ui.book.read.sheet.FontSelectSheet
+import io.legado.app.ui.book.read.sheet.HighlightRuleConfigSheet
+import io.legado.app.ui.book.read.sheet.HttpTtsEditSheet
 import io.legado.app.ui.book.read.sheet.MoreConfigSheet
 import io.legado.app.ui.book.read.sheet.PageAnimConfigSheet
 import io.legado.app.ui.book.read.sheet.PageKeyConfigSheet
 import io.legado.app.ui.book.read.sheet.PhotoSheet
 import io.legado.app.ui.book.read.sheet.ReadAloudConfigSheet
 import io.legado.app.ui.book.read.sheet.ReadAloudNumberConfigSheet
-import io.legado.app.ui.book.read.sheet.HighlightRuleConfigSheet
 import io.legado.app.ui.book.read.sheet.ShadowSetSheet
 import io.legado.app.ui.book.read.sheet.SimulatedReadingSheet
-import io.legado.app.ui.book.read.sheet.HttpTtsEditSheet
 import io.legado.app.ui.book.read.sheet.SpeakEngineConfigSheet
 import io.legado.app.ui.book.read.sheet.TitleBarIconSheet
 import io.legado.app.ui.book.read.sheet.ToolButtonConfigSheet
 import io.legado.app.ui.book.read.sheet.UnderlineConfigSheet
 import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.changeSource.ChangeSourceSheet
+import io.legado.app.ui.widget.components.log.AppLogSheet
 import io.legado.app.utils.toastOnUi
 import kotlinx.coroutines.flow.collectLatest
 
@@ -169,6 +169,7 @@ fun ReadBookScreen(
     )
     HighlightRuleConfigSheet(
         show = state.activeSheet is ReadBookSheet.HighlightRuleConfig,
+        state = state.highlightRuleConfig,
         onDismissRequest = dismissSheet,
         onIntent = onIntent,
     )

+ 126 - 44
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

@@ -17,12 +17,14 @@ import io.legado.app.constant.ReadMenuBlurMode
 import io.legado.app.constant.Status
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
-import io.legado.app.data.local.preferences.LocalPreferencesKeys
-import io.legado.app.data.local.preferences.LocalPreferencesRepository
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookProgress
 import io.legado.app.data.entities.Bookmark
+import io.legado.app.data.entities.HighlightRule
 import io.legado.app.data.entities.HttpTTS
+import io.legado.app.data.local.preferences.LocalPreferencesKeys
+import io.legado.app.data.local.preferences.LocalPreferencesRepository
+import io.legado.app.data.repository.HighlightRuleRepository
 import io.legado.app.data.repository.ReadAloudSettingsRepository
 import io.legado.app.data.repository.ReadBookStyleConfigRepository
 import io.legado.app.data.repository.ReadPreferences
@@ -42,7 +44,6 @@ import io.legado.app.help.book.isMobi
 import io.legado.app.help.book.removeType
 import io.legado.app.help.book.simulatedTotalChapterNum
 import io.legado.app.help.config.AppConfig
-import io.legado.app.utils.openUrl
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.source.getSourceType
@@ -57,7 +58,6 @@ import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext
 import io.legado.app.model.localBook.LocalBook
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.service.BaseReadAloudService
-import io.legado.app.ui.book.read.config.HighlightRuleStore
 import io.legado.app.ui.book.read.page.entities.TextChapter
 import io.legado.app.ui.book.read.page.entities.TextPage
 import io.legado.app.ui.book.read.page.provider.ChapterProvider
@@ -74,6 +74,7 @@ import io.legado.app.utils.hexString
 import io.legado.app.utils.isAbsUrl
 import io.legado.app.utils.isTrue
 import io.legado.app.utils.mapParallelSafe
+import io.legado.app.utils.openUrl
 import io.legado.app.utils.postEvent
 import io.legado.app.utils.putPrefInt
 import io.legado.app.utils.toStringArray
@@ -120,7 +121,8 @@ class ReadBookViewModel(
     private val readSettingsRepository: ReadSettingsRepository,
     private val readBookStyleConfigRepository: ReadBookStyleConfigRepository,
     private val readAloudSettingsRepository: ReadAloudSettingsRepository,
-    private val localPreferencesRepository: LocalPreferencesRepository
+    private val localPreferencesRepository: LocalPreferencesRepository,
+    private val highlightRuleRepository: HighlightRuleRepository,
 ) : BaseViewModel(application), ReadBook.CallBack {
 
     // --- MVI State ---
@@ -296,6 +298,9 @@ class ReadBookViewModel(
                 if (intent.sheet is ReadBookSheet.Bookmark) {
                     // Bookmark is shown as a menu route, not a sheet
                     openReadMenuRoute(ReadBookMenuRoute.Bookmark(intent.sheet.bookmark))
+                } else if (intent.sheet is ReadBookSheet.HighlightRuleConfig) {
+                    loadHighlightRules()
+                    _uiState.update { it.copy(activeSheet = intent.sheet) }
                 } else {
                     _uiState.update { it.copy(activeSheet = intent.sheet) }
                 }
@@ -309,6 +314,15 @@ class ReadBookViewModel(
                         contentEditLoading = false,
                         contentEditSaveToSource = false,
                     )
+                } else if (it.activeSheet is ReadBookSheet.HighlightRuleConfig) {
+                    it.copy(
+                        activeSheet = null,
+                        highlightRuleConfig = it.highlightRuleConfig.copy(
+                            editingRule = null,
+                            showNewRule = false,
+                            deleteRule = null,
+                        ),
+                    )
                 } else {
                     it.copy(activeSheet = null)
                 }
@@ -506,6 +520,48 @@ class ReadBookViewModel(
             is ReadBookIntent.UpdateConfig -> {
                 handleConfigUpdate(intent.update)
             }
+            is ReadBookIntent.AddHighlightRule -> _uiState.update {
+                it.copy(highlightRuleConfig = it.highlightRuleConfig.copy(showNewRule = true))
+            }
+
+            is ReadBookIntent.EditHighlightRule -> _uiState.update {
+                it.copy(highlightRuleConfig = it.highlightRuleConfig.copy(editingRule = intent.rule))
+            }
+
+            is ReadBookIntent.ToggleHighlightRule -> {
+                val rules = _uiState.value.highlightRuleConfig.rules.map {
+                    if (it.id == intent.rule.id) it.copy(enabled = intent.enabled) else it
+                }
+                saveHighlightRules(rules)
+            }
+
+            is ReadBookIntent.SaveHighlightRule -> {
+                val currentRules = _uiState.value.highlightRuleConfig.rules
+                val updatedRules = if (currentRules.any { it.id == intent.rule.id }) {
+                    currentRules.map { if (it.id == intent.rule.id) intent.rule else it }
+                } else {
+                    currentRules + intent.rule
+                }
+                saveHighlightRules(updatedRules)
+            }
+
+            is ReadBookIntent.DismissHighlightRuleEdit -> _uiState.update {
+                it.copy(
+                    highlightRuleConfig = it.highlightRuleConfig.copy(
+                        editingRule = null,
+                        showNewRule = false,
+                    ),
+                )
+            }
+
+            is ReadBookIntent.RequestDeleteHighlightRule -> _uiState.update {
+                it.copy(highlightRuleConfig = it.highlightRuleConfig.copy(deleteRule = intent.rule))
+            }
+
+            is ReadBookIntent.ConfirmDeleteHighlightRule -> deletePendingHighlightRule()
+            is ReadBookIntent.DismissDeleteHighlightRule -> _uiState.update {
+                it.copy(highlightRuleConfig = it.highlightRuleConfig.copy(deleteRule = null))
+            }
             is ReadBookIntent.SaveMenuCustomIcon -> saveMenuCustomIcon(intent.id, intent.uri)
             is ReadBookIntent.SaveTitleBarCustomIcon -> saveTitleBarCustomIcon(intent.id, intent.uri)
             is ReadBookIntent.OpenMenuCustomIconPicker -> {
@@ -842,28 +898,16 @@ class ReadBookViewModel(
             }
 
             is ReadBookIntent.TextActionBookmark -> {
-                val book = ReadBook.book
-                val page = ReadBook.curTextChapter?.getPage(ReadBook.durPageIndex)
-                if (book != null && page != null) {
-                    val bookmark = book.createBookMark().apply {
-                        chapterIndex = ReadBook.durChapterIndex
-                        chapterPos = ReadBook.durChapterPos
-                        chapterName = page.title
-                        bookText = page.text.replace(Regex("[袮꧁]"), "").trim()
-                    }
-                    _uiState.update {
-                        it.copy(
-                            menuState = ReadBookMenuState(
-                                visible = true,
-                                routeStack = kotlinx.collections.immutable.persistentListOf(
-                                    ReadBookMenuRoute.Main,
-                                    ReadBookMenuRoute.Bookmark(bookmark),
-                                ),
+                _uiState.update {
+                    it.copy(
+                        menuState = ReadBookMenuState(
+                            visible = true,
+                            routeStack = kotlinx.collections.immutable.persistentListOf(
+                                ReadBookMenuRoute.Main,
+                                ReadBookMenuRoute.Bookmark(intent.bookmark),
                             ),
-                        )
-                    }
-                } else {
-                    context.toastOnUi(R.string.create_bookmark_error)
+                        ),
+                    )
                 }
             }
 
@@ -2685,12 +2729,6 @@ class ReadBookViewModel(
                 postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
             }
 
-            // --- Highlight rules ---
-            is ConfigUpdate.HighlightRules -> {
-                HighlightRuleStore.save(update.rules)
-                TextChapterLayout.invalidateRegexCache()
-            }
-
             // --- Auto read ---
             is ConfigUpdate.AutoReadSpeed -> {
                 ReadBookConfig.autoReadSpeed = update.value
@@ -2709,6 +2747,62 @@ class ReadBookViewModel(
         }
     }
 
+    private fun loadHighlightRules() {
+        val configName = ReadBookConfig.durConfig.name
+        _uiState.update {
+            it.copy(
+                highlightRuleConfig = it.highlightRuleConfig.copy(
+                    rules = highlightRuleRepository.load(configName).toImmutableList(),
+                    editingRule = null,
+                    showNewRule = false,
+                    deleteRule = null,
+                ),
+            )
+        }
+    }
+
+    private fun saveHighlightRules(rules: List<HighlightRule>) {
+        val configName = ReadBookConfig.durConfig.name
+        val sanitizedRules = rules.map(highlightRuleRepository::sanitizeRule)
+        highlightRuleRepository.save(configName, sanitizedRules)
+        TextChapterLayout.invalidateRegexCache()
+        _uiState.update {
+            it.copy(
+                highlightRuleConfig = it.highlightRuleConfig.copy(
+                    rules = highlightRuleRepository.load(configName).toImmutableList(),
+                    editingRule = null,
+                    showNewRule = false,
+                    deleteRule = null,
+                ),
+            )
+        }
+        _effects.tryEmit(
+            ReadBookEffect.UpdateReadViewConfig(
+                setOf(ConfigUpdateAction.UpdateChapterStyle, ConfigUpdateAction.ReloadContent)
+            )
+        )
+    }
+
+    private fun deletePendingHighlightRule() {
+        val rule = _uiState.value.highlightRuleConfig.deleteRule ?: return
+        val configName = ReadBookConfig.durConfig.name
+        highlightRuleRepository.delete(rule)
+        TextChapterLayout.invalidateRegexCache()
+        _uiState.update {
+            it.copy(
+                highlightRuleConfig = it.highlightRuleConfig.copy(
+                    rules = highlightRuleRepository.load(configName).toImmutableList(),
+                    deleteRule = null,
+                ),
+            )
+        }
+        _effects.tryEmit(
+            ReadBookEffect.UpdateReadViewConfig(
+                setOf(ConfigUpdateAction.UpdateChapterStyle, ConfigUpdateAction.ReloadContent)
+            )
+        )
+    }
+
     private fun saveMenuCustomIcon(id: String, uri: Uri) {
         execute {
             val iconFile = java.io.File(context.filesDir, "read_menu_icons/$id.png")
@@ -2925,18 +3019,6 @@ class ReadBookViewModel(
                     ))
                 }
 
-                ReadBookColorPickerIds.HIGHLIGHT_RULE_COLOR -> {
-                    val pos = ReadBookColorPickerIds.pendingHighlightRulePosition
-                    val rules = HighlightRuleStore.load()
-                    if (pos in rules.indices) {
-                        HighlightRuleStore.update(rules[pos].copy(textColor = color))
-                        TextChapterLayout.invalidateRegexCache()
-                        _effects.tryEmit(ReadBookEffect.UpdateReadViewConfig(
-                            setOf(ConfigUpdateAction.UpdateChapterStyle, ConfigUpdateAction.ReloadContent)
-                        ))
-                    }
-                }
-
                 ReadBookColorPickerIds.MENU_BG_COLOR -> {
                     ReadBookConfig.readMenuBgColor = color
                     viewModelScope.launch {

+ 27 - 10
app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt

@@ -22,13 +22,13 @@ import io.legado.app.constant.PageAnim
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookChapter
+import io.legado.app.data.entities.HighlightRule
+import io.legado.app.data.repository.HighlightRuleRepository
 import io.legado.app.help.book.BookContent
 import io.legado.app.help.book.BookHelp
 import io.legado.app.help.book.getBookSource
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.ReadBookConfig
-import io.legado.app.data.entities.HighlightRule
-import io.legado.app.ui.book.read.config.HighlightRuleStore
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.model.ImageProvider
 import io.legado.app.model.ReadBook
@@ -60,6 +60,7 @@ import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.currentCoroutineContext
 import kotlinx.coroutines.ensureActive
 import kotlinx.coroutines.launch
+import org.koin.core.context.GlobalContext
 import java.util.LinkedList
 import kotlin.math.roundToInt
 
@@ -75,20 +76,36 @@ class TextChapterLayout(
         @Volatile
         private var cachedHighlightRules: List<CompiledHighlightRule>? = null
 
+        @Volatile
+        private var cachedHighlightRulesConfigName: String? = null
+
         fun invalidateRegexCache() {
             cachedHighlightRules = null
+            cachedHighlightRulesConfigName = null
         }
     }
 
     private val compiledHighlightRules: List<CompiledHighlightRule>
-        get() = cachedHighlightRules ?: HighlightRuleStore.loadEnabled().mapNotNull { rule ->
-            runCatching {
-                CompiledHighlightRule(
-                    rule = rule,
-                    regex = Regex(rule.pattern)
-                )
-            }.getOrNull()
-        }.also { cachedHighlightRules = it }
+        get() {
+            val configName = ReadBookConfig.durConfig.name
+            cachedHighlightRules?.takeIf {
+                cachedHighlightRulesConfigName == configName
+            }?.let { return it }
+            return highlightRuleRepository.loadEnabled(configName).mapNotNull { rule ->
+                runCatching {
+                    CompiledHighlightRule(
+                        rule = rule,
+                        regex = Regex(rule.pattern)
+                    )
+                }.getOrNull()
+            }.also {
+                cachedHighlightRulesConfigName = configName
+                cachedHighlightRules = it
+            }
+        }
+
+    private val highlightRuleRepository: HighlightRuleRepository
+        get() = GlobalContext.get().get()
 
     @Volatile
     private var listener: LayoutProgressListener? = textChapter

+ 35 - 85
app/src/main/java/io/legado/app/ui/book/read/sheet/HighlightRuleConfigSheet.kt

@@ -3,37 +3,27 @@ package io.legado.app.ui.book.read.sheet
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.itemsIndexed
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.Delete
 import androidx.compose.material.icons.filled.Edit
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.data.entities.HighlightRule
+import io.legado.app.data.repository.configNames
+import io.legado.app.ui.book.read.HighlightRuleConfigUiState
 import io.legado.app.ui.book.read.ReadBookIntent
-import io.legado.app.ui.book.read.config.HighlightRuleStore
-import io.legado.app.ui.book.read.config.HighlightRuleStore.configNames
 import io.legado.app.ui.widget.components.TinySwitch
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.button.series.SmallTonalButton
 import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
 import io.legado.app.ui.widget.components.settingItem.TinySettingItem
@@ -41,19 +31,20 @@ import io.legado.app.ui.widget.components.settingItem.TinySettingItem
 @Composable
 fun HighlightRuleConfigSheet(
     show: Boolean,
+    state: HighlightRuleConfigUiState,
     onDismissRequest: () -> Unit,
     onIntent: (ReadBookIntent) -> Unit,
 ) {
-    var rules by remember { mutableStateOf(HighlightRuleStore.load()) }
-    var showDeleteConfirm by remember { mutableStateOf(false) }
-    var deleteIndex by remember { mutableIntStateOf(-1) }
-    var editingRule by remember { mutableStateOf<HighlightRule?>(null) }
-    var showNewRule by remember { mutableStateOf(false) }
-
     AppModalBottomSheet(
         show = show,
         onDismissRequest = onDismissRequest,
         title = stringResource(R.string.highlight_rule_config),
+        endAction = {
+            SmallTonalButton(
+                onClick = { onIntent(ReadBookIntent.AddHighlightRule) },
+                icon = Icons.Default.Add
+            )
+        }
     ) {
         Column(
             modifier = Modifier
@@ -64,100 +55,63 @@ fun HighlightRuleConfigSheet(
                 verticalArrangement = Arrangement.spacedBy(8.dp),
                 modifier = Modifier.weight(1f, fill = false),
             ) {
-                itemsIndexed(rules, key = { _, rule -> rule.id }) { index, rule ->
+                itemsIndexed(state.rules, key = { _, rule -> rule.id }) { _, rule ->
                     HighlightRuleItem(
                         rule = rule,
                         onToggle = { enabled ->
-                            rules = rules.toMutableList().also {
-                                it[index] = it[index].copy(enabled = enabled)
-                            }
-                            saveRules(rules, onIntent)
+                            onIntent(ReadBookIntent.ToggleHighlightRule(rule, enabled))
                         },
-                        onEditClick = { editingRule = rule },
+                        onEditClick = { onIntent(ReadBookIntent.EditHighlightRule(rule)) },
                         onDeleteClick = {
-                            deleteIndex = index
-                            showDeleteConfirm = true
+                            onIntent(ReadBookIntent.RequestDeleteHighlightRule(rule))
                         },
                     )
                 }
             }
-
-            Row(
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .padding(top = 8.dp),
-                horizontalArrangement = Arrangement.End,
-            ) {
-                TextButton(
-                    onClick = { showNewRule = true },
-                ) {
-                    Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(18.dp))
-                    Spacer(modifier = Modifier.width(4.dp))
-                    Text(stringResource(R.string.add))
-                }
-            }
         }
     }
 
     // Edit existing rule
-    val editingRuleValue = editingRule
+    val editingRuleValue = state.editingRule
+
     if (show && editingRuleValue != null) {
         HighlightRuleEditSheet(
             show = true,
             rule = editingRuleValue,
-            onDismissRequest = { editingRule = null },
+            onDismissRequest = { onIntent(ReadBookIntent.DismissHighlightRuleEdit) },
             onSave = { updated ->
-                rules = rules.map { if (it.id == updated.id) updated else it }
-                saveRules(rules, onIntent)
-                editingRule = null
+                onIntent(ReadBookIntent.SaveHighlightRule(updated))
             },
         )
     }
 
+
     // Add new rule
-    if (show && showNewRule) {
+    if (show && state.showNewRule) {
         HighlightRuleEditSheet(
             show = true,
             rule = null,
-            onDismissRequest = { showNewRule = false },
+            onDismissRequest = { onIntent(ReadBookIntent.DismissHighlightRuleEdit) },
             onSave = { newRule ->
-                rules = rules + newRule
-                saveRules(rules, onIntent)
-                showNewRule = false
+                onIntent(ReadBookIntent.SaveHighlightRule(newRule))
             },
         )
     }
 
     // Delete confirmation
-    if (showDeleteConfirm && deleteIndex in rules.indices) {
-        AlertDialog(
-            onDismissRequest = {
-                showDeleteConfirm = false
-                deleteIndex = -1
-            },
-            containerColor = io.legado.app.ui.theme.LegadoTheme.colorScheme.surfaceContainer,
-            title = { Text(stringResource(R.string.delete)) },
-            text = { Text(stringResource(R.string.sure_delete) + " \"${rules[deleteIndex].name}\"?") },
-            confirmButton = {
-                TextButton(onClick = {
-                    rules = rules.toMutableList().also { it.removeAt(deleteIndex) }
-                    saveRules(rules, onIntent)
-                    showDeleteConfirm = false
-                    deleteIndex = -1
-                }) {
-                    Text(stringResource(android.R.string.ok))
-                }
-            },
-            dismissButton = {
-                TextButton(onClick = {
-                    showDeleteConfirm = false
-                    deleteIndex = -1
-                }) {
-                    Text(stringResource(android.R.string.cancel))
-                }
-            },
-        )
-    }
+    val deletingRule = state.deleteRule
+    val sureDeleteText = stringResource(R.string.sure_delete)
+    AppAlertDialog(
+        show = show && deletingRule != null,
+        onDismissRequest = { onIntent(ReadBookIntent.DismissDeleteHighlightRule) },
+        title = stringResource(R.string.delete),
+        text = deletingRule?.let { "$sureDeleteText \"${it.name}\"?" },
+        confirmText = stringResource(android.R.string.ok),
+        onConfirm = { onIntent(ReadBookIntent.ConfirmDeleteHighlightRule) },
+        dismissText = stringResource(android.R.string.cancel),
+        onDismiss = { onIntent(ReadBookIntent.DismissDeleteHighlightRule) },
+    )
+
 }
 
 @Composable
@@ -191,7 +145,3 @@ private fun HighlightRuleItem(
         },
     )
 }
-
-private fun saveRules(rules: List<HighlightRule>, onIntent: (ReadBookIntent) -> Unit) {
-    onIntent(ReadBookIntent.UpdateConfig(io.legado.app.ui.book.read.ConfigUpdate.HighlightRules(rules)))
-}

+ 46 - 28
app/src/main/java/io/legado/app/ui/book/read/sheet/HighlightRuleEditSheet.kt

@@ -11,9 +11,10 @@ import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.itemsIndexed
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.FilterChip
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Done
 import androidx.compose.material3.MaterialTheme
@@ -39,13 +40,13 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import io.legado.app.R
 import io.legado.app.data.entities.HighlightRule
+import io.legado.app.data.repository.configNames
+import io.legado.app.data.repository.toJsonArray
 import io.legado.app.help.config.ReadBookConfig
-import io.legado.app.ui.book.read.config.HighlightRuleStore
-import io.legado.app.ui.book.read.config.HighlightRuleStore.configNames
-import io.legado.app.ui.book.read.config.HighlightRuleStore.toJsonArray
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.AppTextField
 import io.legado.app.ui.widget.components.SectionTitle
+import io.legado.app.ui.widget.components.card.NormalCard
 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.TinyClickableSettingItem
@@ -147,7 +148,7 @@ fun HighlightRuleEditSheet(
                     }
                 }
                 patternError = null
-                val sanitized = HighlightRuleStore.sanitizeRule(
+                onSave(
                     HighlightRule(
                         id = initial.id,
                         name = name,
@@ -170,7 +171,6 @@ fun HighlightRuleEditSheet(
                         fontPath = if (hasFont) fontPath.ifBlank { null } else null,
                     )
                 )
-                onSave(sanitized)
             }) {
                 androidx.compose.material3.Icon(
                     Icons.Default.Done,
@@ -372,33 +372,51 @@ fun HighlightRuleEditSheet(
             // === Section 3: Config Binding ===
             if (allConfigNames.isNotEmpty()) {
                 SectionTitle("应用排版")
-                AppText(
-                    text = if (configNames.isEmpty()) "全局(所有排版生效)"
-                    else "已选: ${configNames.joinToString("、")}",
-                    style = MaterialTheme.typography.bodySmall,
-                    color = MaterialTheme.colorScheme.onSurfaceVariant,
-                    modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp),
-                )
-                androidx.compose.foundation.layout.FlowRow(
-                    modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp),
-                    horizontalArrangement = Arrangement.spacedBy(8.dp),
-                    verticalArrangement = Arrangement.spacedBy(4.dp),
+                LazyRow(
+                    modifier = Modifier.padding(vertical = 4.dp),
+                    horizontalArrangement = Arrangement.spacedBy(8.dp)
                 ) {
                     // Global toggle
-                    FilterChip(
-                        selected = configNames.isEmpty(),
-                        onClick = { configNames = emptySet() },
-                        label = { AppText("全局") },
-                    )
-                    allConfigNames.forEach { cn ->
-                        FilterChip(
-                            selected = cn in configNames,
+                    item {
+                        val selected = configNames.isEmpty()
+                        val bg = if (selected) LegadoTheme.colorScheme.secondaryContainer
+                        else LegadoTheme.colorScheme.surfaceContainerLow
+                        val fg = if (selected) LegadoTheme.colorScheme.onSecondaryContainer
+                        else LegadoTheme.colorScheme.onSurfaceVariant
+                        NormalCard(
+                            onClick = { configNames = emptySet() },
+                            containerColor = bg,
+                            cornerRadius = 8.dp,
+                        ) {
+                            AppText(
+                                "全局",
+                                style = LegadoTheme.typography.labelMedium,
+                                color = fg,
+                                modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
+                            )
+                        }
+                    }
+                    itemsIndexed(allConfigNames) { _, cn ->
+                        val selected = cn in configNames
+                        val bg = if (selected) LegadoTheme.colorScheme.secondaryContainer
+                        else LegadoTheme.colorScheme.surfaceContainerLow
+                        val fg = if (selected) LegadoTheme.colorScheme.onSecondaryContainer
+                        else LegadoTheme.colorScheme.onSurfaceVariant
+                        NormalCard(
                             onClick = {
-                                configNames = if (cn in configNames) configNames - cn
+                                configNames = if (selected) configNames - cn
                                 else configNames + cn
                             },
-                            label = { AppText(cn) },
-                        )
+                            containerColor = bg,
+                            cornerRadius = 8.dp,
+                        ) {
+                            AppText(
+                                cn,
+                                style = LegadoTheme.typography.labelMedium,
+                                color = fg,
+                                modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
+                            )
+                        }
                     }
                 }
             }