From 62875c54d9d4a0ff525eb3e008330029a3a5e9df Mon Sep 17 00:00:00 2001 From: HapeLee <63206378+HapeLee@users.noreply.github.com> Date: Wed, 10 Jun 2026 02:44:07 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=B9=A6=E7=AD=BE=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E4=B8=8E=E9=AB=98=E4=BA=AE=E8=A7=84=E5=88=99=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 3 +- .../io.legado.app.data.AppDatabase/91.json | 11 +- .../io.legado.app.data.AppDatabase/92.json | 2425 ----------------- .../java/io/legado/app/data/AppDatabase.kt | 7 +- .../repository/HighlightRuleRepository.kt} | 139 +- .../ReadBookStyleConfigRepository.kt | 14 +- .../data/repository/ReadStyleRepository.kt | 15 +- .../main/java/io/legado/app/di/appModule.kt | 9 +- .../legado/app/help/config/ReadBookConfig.kt | 9 +- .../ui/book/read/ReadBookColorPickerIds.kt | 2 - .../app/ui/book/read/ReadBookContract.kt | 28 +- .../app/ui/book/read/ReadBookController.kt | 6 +- .../legado/app/ui/book/read/ReadBookScreen.kt | 7 +- .../app/ui/book/read/ReadBookViewModel.kt | 170 +- .../read/page/provider/TextChapterLayout.kt | 37 +- .../read/sheet/HighlightRuleConfigSheet.kt | 120 +- .../book/read/sheet/HighlightRuleEditSheet.kt | 74 +- 17 files changed, 394 insertions(+), 2682 deletions(-) delete mode 100644 app/schemas/io.legado.app.data.AppDatabase/92.json rename app/src/main/java/io/legado/app/{ui/book/read/config/HighlightRuleStore.kt => data/repository/HighlightRuleRepository.kt} (78%) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1e7cb12e8..9c7545b12 100644 --- a/.claude/settings.local.json +++ b/.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\")" ] } } diff --git a/app/schemas/io.legado.app.data.AppDatabase/91.json b/app/schemas/io.legado.app.data.AppDatabase/91.json index af95b684b..c4ba08e45 100644 --- a/app/schemas/io.legado.app.data.AppDatabase/91.json +++ b/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')" ] } } \ No newline at end of file diff --git a/app/schemas/io.legado.app.data.AppDatabase/92.json b/app/schemas/io.legado.app.data.AppDatabase/92.json deleted file mode 100644 index f5eaf8a4d..000000000 --- a/app/schemas/io.legado.app.data.AppDatabase/92.json +++ /dev/null @@ -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')" - ] - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index 2dc1440f3..f271331dd 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/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() { diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/HighlightRuleStore.kt b/app/src/main/java/io/legado/app/data/repository/HighlightRuleRepository.kt similarity index 78% rename from app/src/main/java/io/legado/app/ui/book/read/config/HighlightRuleStore.kt rename to app/src/main/java/io/legado/app/data/repository/HighlightRuleRepository.kt index 9262390e0..69411808f 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/HighlightRuleStore.kt +++ b/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 = emptyList(), @@ -22,24 +28,19 @@ object HighlightRuleStore { val bracketNoteEnabled: Boolean = true, ) - private val dao get() = appDb.highlightRuleDao - - fun load(): List { - val configName = ReadBookConfig.durConfig.name + fun load(configName: String): List { return dao.getAll().filter { it.matchesConfig(configName) } } - fun loadEnabled(): List { - val configName = ReadBookConfig.durConfig.name + fun loadEnabled(configName: String): List { return dao.getEnabled().filter { it.matchesConfig(configName) } } /** - * 保存当前排版的规则。 + * 保存指定排版的规则。 * 仅替换当前排版绑定的规则,不影响其他排版的规则。 */ - fun save(rules: List) { - val configName = ReadBookConfig.durConfig.name + fun save(configName: String, rules: List) { 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 { - val defaults = createDefaultRules() - val configName = ReadBookConfig.durConfig.name - if (configName.isBlank()) { - dao.replaceGlobal(defaults) - } else { - saveForConfig(defaults, configName) + 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)) + } } - return defaults } - fun createBackupData(): BackupData { + fun reset(configName: String): List { + val defaults = createDefaultRules() + val rules = if (configName.isBlank()) { + defaults + } else { + defaults.map { + it.copyWithNewId().copy(configName = listOf(configName).toJsonArray()) + } + } + if (configName.isBlank()) { + dao.replaceGlobal(rules) + } else { + saveForConfig(rules, configName) + } + return rules + } + + 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 { - 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 { - return runCatching { - GSON.fromJsonArray(this).getOrNull() ?: emptyList() - }.getOrElse { emptyList() } - } - - /** - * 将排版名列表转为 JSON 数组字符串。 - */ - fun List.toJsonArray(): String { - return GSON.toJson(this) - } - // endregion } + +fun String.configNames(): List { + return runCatching { + GSON.fromJsonArray(this).getOrNull() ?: emptyList() + }.getOrElse { emptyList() } +} + +fun List.toJsonArray(): String { + return GSON.toJson(this) +} diff --git a/app/src/main/java/io/legado/app/data/repository/ReadBookStyleConfigRepository.kt b/app/src/main/java/io/legado/app/data/repository/ReadBookStyleConfigRepository.kt index ddef437be..19913fcc3 100644 --- a/app/src/main/java/io/legado/app/data/repository/ReadBookStyleConfigRepository.kt +++ b/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 { diff --git a/app/src/main/java/io/legado/app/data/repository/ReadStyleRepository.kt b/app/src/main/java/io/legado/app/data/repository/ReadStyleRepository.kt index 737ec8a46..3e37219ee 100644 --- a/app/src/main/java/io/legado/app/data/repository/ReadStyleRepository.kt +++ b/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 } diff --git a/app/src/main/java/io/legado/app/di/appModule.kt b/app/src/main/java/io/legado/app/di/appModule.kt index 5fdf8fac7..c8d8f8eee 100644 --- a/app/src/main/java/io/legado/app/di/appModule.kt +++ b/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) diff --git a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt index b86cd3124..83ba5a273 100644 --- a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt +++ b/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 diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookColorPickerIds.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookColorPickerIds.kt index e246634e2..63e3ad77a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookColorPickerIds.kt +++ b/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 } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt index d6623af0b..6672742cb 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt +++ b/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 = 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() } - // --- Highlight rules --- - data class HighlightRules(val rules: List) : ConfigUpdate { - override val actions = setOf(ConfigUpdateAction.UpdateChapterStyle, ConfigUpdateAction.ReloadContent) - } - // --- Auto read --- data class AutoReadSpeed(val value: Int) : ConfigUpdate { override val actions = emptySet() diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt index e66625e9f..f4975493f 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt +++ b/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 } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt index 5dd5a004f..db014a19b 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt +++ b/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, ) diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index e8698959e..f051eb638 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/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) { + 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 { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt index 96e9367d4..dd2d50a2a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt +++ b/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? = null + @Volatile + private var cachedHighlightRulesConfigName: String? = null + fun invalidateRegexCache() { cachedHighlightRules = null + cachedHighlightRulesConfigName = null } } private val compiledHighlightRules: List - 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 diff --git a/app/src/main/java/io/legado/app/ui/book/read/sheet/HighlightRuleConfigSheet.kt b/app/src/main/java/io/legado/app/ui/book/read/sheet/HighlightRuleConfigSheet.kt index fac76eedb..4b859094d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/sheet/HighlightRuleConfigSheet.kt +++ b/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(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, onIntent: (ReadBookIntent) -> Unit) { - onIntent(ReadBookIntent.UpdateConfig(io.legado.app.ui.book.read.ConfigUpdate.HighlightRules(rules))) -} diff --git a/app/src/main/java/io/legado/app/ui/book/read/sheet/HighlightRuleEditSheet.kt b/app/src/main/java/io/legado/app/ui/book/read/sheet/HighlightRuleEditSheet.kt index 70b5722ab..5b7214c5c 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/sheet/HighlightRuleEditSheet.kt +++ b/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), + ) + } } } }