From 4c088448c35df1d7bbbdb61c51d4b7af5d31841f Mon Sep 17 00:00:00 2001
From: Kudomaga <63206378+HapeLee@users.noreply.github.com>
Date: Sun, 7 Jun 2026 18:28:33 +0800
Subject: [PATCH] =?UTF-8?q?feat(reader):=20=E8=88=B9=E6=96=B0=E7=9A=84?=
=?UTF-8?q?=E5=9F=BA=E4=BA=8E=20Compose=20=E5=AE=9E=E7=8E=B0=E7=9A=84?=
=?UTF-8?q?=E9=98=85=E8=AF=BB=E8=8F=9C=E5=8D=95=E7=95=8C=E9=9D=A2=E4=B8=8E?=
=?UTF-8?q?=E5=90=84=E9=A1=B9=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 初始版本
* refactor(reader): MVI architecture + menu settings overhaul
MVI/UDF Architecture:
- Add ReadBookContract: UiState, Intent, Effect, Sheet, Dialog, ConfigUpdate
- Refactor ReadBookViewModel with onIntent() entry point
- Add ReadBookScreen/ReadBookRouteScreen stateless composables
- Add ReadBookColorTheme for reader-specific theme override
- Add ReadConfigContract for read config screen MVI
ReadStyleSheet Decomposition:
- Split ReadStyleSheet into SystemMenuPage, TextTitleSheet,
HeaderFooterPage, TitleBarIconSheet, GlobalThemePage
- Each page owns its tab state, color picker, and config mutations
Data Layer (DataStore-backed Repositories):
- Add ReadSettingsRepository with ReadPreferences flow
- Add MangaSettingsRepository with MangaPreferences flow
- Add ReadAloudSettingsRepository for TTS settings
- Add ReadStyleRepository (file I/O for read style configs)
- Add ReadBookStyleConfigRepository (repository boundary)
- Add ReadStyleResolver for background/theme resolution
- Remove legacy ReadTipConfig (merged into ReadBookConfig)
- Register all new repositories in Koin appModule
Menu Settings:
- Add unified border settings (width, color, colorNight) for
both top and bottom bars with drawWithCache + drawPath
- Add ColorModePill with light/dark toggle and '+' icon for
no-color state in TinySettingItems
- Add TinyClearColorModeSettingItem for border color config
- Add menu color customization (bg, accent, container) with
day/night variants and seed color mode
- Add bottom bar layout settings (corner radius, margins)
- Add custom icon support for menu buttons and title bar icons
- Add title bar icon position setting
Config Pipeline:
- Consolidate all ReadBookConfig mutations into typed ConfigUpdate
sealed interface with legacy UP_CONFIG code mapping
- Add resolvedMenuBorderColor (auto day/night switching)
- Add 20 new PreferKey constants for menu settings
- Update manga config dialogs to use MangaSettingsRepository
- Update ReadConfigViewModel with DataStore flow observation
* feat(reader): add liquid glass menu bars and floating bottom bar
- Integrate com.kyant.backdrop 2.0.0 for liquid glass blur/vibrancy
effects on reading menu top bar and bottom bar (Android 13+)
- Add floating bottom bar toggle replacing manual margin sliders
- Extract ReadMenuConfig data class for proper MVI state management
of all menu configuration properties
- Enable edge-to-edge in BaseComposeActivity, remove manual
navigation bar color management across base classes and controller
- Fix fullScreen() to use setDecorFitsSystemWindows(false)
* Refactor series button rendering
* 实现液态玻璃
* 优化液态玻璃
* @
fix(reader): fix MVI behavior issues and improve sheet lifecycle
Behavior fixes:
- Fix AddSourceAsNewBook empty implementation — wire intent to addToBookshelf
- Fix content edit loading stuck on null chapter/content — use onFinally
- Fix cannot save empty content — remove isEmpty guard, use title as loaded proxy
- Fix bookmark not opening — use openReadMenuRoute instead of dead activeSheet path
- Fix chapter source sheet persists — animate dismiss via show param before removal
- Fix chapter source reopens to TOC — reset showToc in initData
- Fix SaveChapterContent saves to wrong chapter — carry chapterIndex in intent
- Fix chapter source search runs after dismiss — cancel searchJob in dispose
- Fix "go to background" button in ReadAloud — use CloseReadBook intent
Architecture improvements:
- ReadMenuConfig uses ImmutableMap for Compose stability
- Fix stale remember in ReadBookMenuBar title bar icons
- Move icon picker file IO from composable to ViewModel
- Route launcher results through MVI intents instead of direct calls
- Fix ChangeChapterSource effect collector key — use viewModel not Unit
- Make ReadAloudConfigSheet UDF-compliant — stateless with intents
- Remove main menu buttons from AutoRead and ReadAloud sheets
@
* @
refactor(reader): use Material Icons and ConfirmDismissButtonsRow
- TitleBarIconSheet: replace drawable resources with Icons library, use
ConfirmDismissButtonsRow for save/cancel buttons
- SystemMenuPage MenuCustomIconSheet: same icon and button treatment
- ReadBookMenuBar: update ToolButtonDef/TitleBarIconDef to use ImageVector
instead of drawable resource Int
- Use AutoMirrored variants for List and HelpOutline icons
@
* feat: 优化液态玻璃
* feat: 优化液态玻璃
* fix(AppLogSheet): 从本地文件读取日志,替代内存列表
* @
feat(reader): highlight rules, theme refactor & MVI improvements
- Add HighlightRule entity, DAO, and config/edit sheets
- Add ThemeConfigStore, remove OldThemeConfig
- Refactor ReadBookContract/ViewModel/Screen for deeper MVI
- Add CharStyle for text rendering, refactor TextLine/TextChapterLayout
- Add SectionTitle, TinySettingItems UI components
- Remove legacy XML layouts (regex_color_config)
- Update database schema to v90
- Various sheet and widget refinements
@
* @
fix: address PR review feedback
- ReadStyleRepository: fix copy-paste bug (bgStrNight→bgStr) + add bgTypeNight import
- BgImageSpan: replace Bitmap.createScaledBitmap with BitmapShader matrix scaling
- HighlightRule: use UUID.randomUUID() instead of System.currentTimeMillis()
- ChangeSourceSearchUseCase: guard against empty chapters list
@
* @fix: remove residual regexColorRules references after merge
- ChangeSourceSearchUseCase: adapt filter lambda to new 3-param signature
- TextChapterLayout: remove globalRegexResult field, preApplyRegexColorRules
call, and the entire regexColorRules fullTextBuilder block
@
* fix: 图标间距
---
.claude/settings.local.json | 8 +-
.../io.legado.app.data.AppDatabase/90.json | 2415 ++++++++++++++++
app/src/main/AndroidManifest.xml | 16 -
app/src/main/java/io/legado/app/App.kt | 12 +-
.../main/java/io/legado/app/api/ShortCuts.kt | 5 +-
.../java/io/legado/app/base/BaseActivity.kt | 8 +-
.../app/base/BaseBottomSheetDialogFragment.kt | 5 +-
.../io/legado/app/base/BaseComposeActivity.kt | 10 +-
.../java/io/legado/app/constant/PreferKey.kt | 44 +
.../java/io/legado/app/data/AppDatabase.kt | 11 +-
.../legado/app/data/dao/HighlightRuleDao.kt | 41 +
.../legado/app/data/entities/HighlightRule.kt | 94 +
.../repository/MangaSettingsRepository.kt | 182 ++
.../repository/ReadAloudSettingsRepository.kt | 121 +
.../ReadBookStyleConfigRepository.kt | 61 +
.../data/repository/ReadSettingsRepository.kt | 537 ++++
.../data/repository/ReadStyleRepository.kt | 255 ++
.../app/data/repository/SettingsRepository.kt | 11 +
.../io/legado/app/di/appDatabaseModule.kt | 1 +
.../main/java/io/legado/app/di/appModule.kt | 19 +-
.../usecase/ChangeSourceSearchUseCase.kt | 276 ++
.../usecase/GetChapterContentUseCase.kt | 48 +
.../java/io/legado/app/help/DefaultData.kt | 8 +-
.../java/io/legado/app/help/JsExtensions.kt | 6 +-
.../io/legado/app/help/config/AppConfig.kt | 187 +-
.../legado/app/help/config/ReadBookConfig.kt | 1024 ++++---
.../app/help/config/ReadStyleResolver.kt | 141 +
.../legado/app/help/config/ReadTipConfig.kt | 120 -
...{OldThemeConfig.kt => ThemeConfigStore.kt} | 56 +-
.../java/io/legado/app/help/storage/Backup.kt | 8 +-
.../io/legado/app/help/storage/Restore.kt | 12 +-
.../app/lib/prefs/ThemeModePreference.kt | 4 +-
.../java/io/legado/app/model/BookCover.kt | 3 -
.../app/receiver/MediaButtonReceiver.kt | 4 +-
.../app/receiver/NetworkChangedListener.kt | 14 +-
.../app/service/BaseReadAloudService.kt | 7 +-
.../app/ui/animation/InteractiveHighlight.kt | 2 +
.../app/ui/association/ImportThemeDialog.kt | 6 +-
.../ui/association/ImportThemeViewModel.kt | 14 +-
.../ChangeBookSourceComposeViewModel.kt | 256 +-
.../changesource/ChangeBookSourceDialog.kt | 6 +-
.../ChangeChapterSourceAdapter.kt | 164 --
.../ChangeChapterSourceContract.kt | 86 +
.../changesource/ChangeChapterSourceDialog.kt | 420 ---
.../ChangeChapterSourceViewModel.kt | 407 ++-
.../changesource/ChangeChapterTocAdapter.kt | 74 -
.../app/ui/book/info/BookInfoActivity.kt | 10 +
.../app/ui/book/info/BookInfoContract.kt | 2 +
.../app/ui/book/info/BookInfoRouteScreen.kt | 12 +-
.../legado/app/ui/book/info/BookInfoSheets.kt | 2 +-
.../app/ui/book/info/BookInfoViewModel.kt | 7 +-
.../app/ui/book/manga/ReadMangaActivity.kt | 33 +-
.../app/ui/book/manga/ReadMangaViewModel.kt | 89 +-
.../config/MangaClickActionConfigDialog.kt | 54 +-
.../manga/config/MangaColorFilterDialog.kt | 12 +-
.../manga/config/MangaFooterSettingDialog.kt | 11 +-
.../app/ui/book/read/BaseReadBookActivity.kt | 432 ---
.../app/ui/book/read/ContentEditDialog.kt | 176 --
.../ui/book/read/EffectiveReplacesDialog.kt | 116 -
.../io/legado/app/ui/book/read/MangaMenu.kt | 17 -
.../app/ui/book/read/ReadBookActivity.kt | 2037 --------------
.../ui/book/read/ReadBookColorPickerIds.kt | 17 +
.../app/ui/book/read/ReadBookColorTheme.kt | 298 ++
.../app/ui/book/read/ReadBookContract.kt | 1018 +++++++
.../app/ui/book/read/ReadBookController.kt | 1049 +++++++
.../app/ui/book/read/ReadBookMenuBar.kt | 2310 +++++++++++++++
.../app/ui/book/read/ReadBookRouteScreen.kt | 473 ++++
.../legado/app/ui/book/read/ReadBookScreen.kt | 414 +++
.../app/ui/book/read/ReadBookSearchBar.kt | 246 ++
.../app/ui/book/read/ReadBookViewModel.kt | 2471 ++++++++++++++++-
.../io/legado/app/ui/book/read/ReadMenu.kt | 948 -------
.../io/legado/app/ui/book/read/SearchMenu.kt | 208 --
.../legado/app/ui/book/read/TextActionMenu.kt | 17 +-
.../app/ui/book/read/config/AutoReadDialog.kt | 100 -
.../app/ui/book/read/config/BgAdapter.kt | 48 -
.../app/ui/book/read/config/BgImageSpan.kt | 161 ++
.../ui/book/read/config/BgTextConfigDialog.kt | 368 ---
.../read/config/ClickActionConfigDialog.kt | 150 -
.../ui/book/read/config/DashUnderlineSpan.kt | 64 +
.../book/read/config/DoubleUnderlineSpan.kt | 66 +
.../ui/book/read/config/FontConfigDialog.kt | 206 --
.../ui/book/read/config/FontSelectDialog.kt | 258 --
.../ui/book/read/config/HighlightRuleStore.kt | 356 +++
.../ui/book/read/config/HighlightStyleSpan.kt | 23 +
.../ui/book/read/config/HttpTtsEditDialog.kt | 135 -
.../book/read/config/HttpTtsEditViewModel.kt | 76 -
.../ui/book/read/config/InfoConfigDialog.kt | 284 --
.../ui/book/read/config/MoreConfigDialog.kt | 188 --
.../book/read/config/PaddingConfigDialog.kt | 119 -
.../app/ui/book/read/config/PageKeyDialog.kt | 72 -
.../book/read/config/ReadAloudConfigDialog.kt | 211 --
.../ui/book/read/config/ReadAloudDialog.kt | 261 --
.../ui/book/read/config/ReadStyleDialog.kt | 246 --
.../read/config/RegexColorConfigDialog.kt | 204 --
.../ui/book/read/config/ShadowSetDialog.kt | 66 -
.../ui/book/read/config/SolidUnderlineSpan.kt | 62 +
.../ui/book/read/config/SpeakEngineDialog.kt | 299 --
.../book/read/config/SpeakEngineViewModel.kt | 23 -
.../app/ui/book/read/config/SvgPathParser.kt | 410 +++
.../ui/book/read/config/SvgUnderlineSpan.kt | 75 +
.../ui/book/read/config/TipConfigDialog.kt | 283 --
.../read/config/ToolButtonConfigDialog.kt | 221 --
.../book/read/config/UnderlineConfigDialog.kt | 107 -
.../ui/book/read/config/WaveUnderlineSpan.kt | 81 +
.../legado/app/ui/book/read/page/AutoPager.kt | 2 +
.../app/ui/book/read/page/ContentTextView.kt | 52 +-
.../legado/app/ui/book/read/page/PageView.kt | 121 +-
.../legado/app/ui/book/read/page/ReadView.kt | 23 +-
.../ui/book/read/page/entities/TextLine.kt | 467 +++-
.../page/entities/column/TextBaseColumn.kt | 10 +
.../read/page/entities/column/TextColumn.kt | 62 +-
.../page/entities/column/TextHtmlColumn.kt | 13 +-
.../ui/book/read/page/provider/CharStyle.kt | 20 +
.../read/page/provider/TextChapterLayout.kt | 259 +-
.../app/ui/book/read/sheet/AutoReadSheet.kt | 172 ++
.../ui/book/read/sheet/BgTextConfigSheet.kt | 285 ++
.../read/sheet/ChangeChapterSourceSheet.kt | 322 +++
.../ui/book/read/sheet/CharsetConfigSheet.kt | 67 +
.../book/read/sheet/ClickActionConfigSheet.kt | 248 ++
.../ui/book/read/sheet/ContentEditSheet.kt | 115 +
.../app/ui/book/read/sheet/DictSheet.kt | 170 ++
.../app/ui/book/read/sheet/DownloadSheet.kt | 75 +
.../book/read/sheet/EffectiveReplacesSheet.kt | 125 +
.../app/ui/book/read/sheet/FontSelectSheet.kt | 54 +
.../app/ui/book/read/sheet/GlobalThemePage.kt | 436 +++
.../ui/book/read/sheet/HeaderFooterPage.kt | 410 +++
.../read/sheet/HighlightRuleConfigSheet.kt | 195 ++
.../book/read/sheet/HighlightRuleEditSheet.kt | 513 ++++
.../app/ui/book/read/sheet/MoreConfigSheet.kt | 382 +++
.../ui/book/read/sheet/PaddingConfigSheet.kt | 205 ++
.../ui/book/read/sheet/PageAnimConfigSheet.kt | 45 +
.../ui/book/read/sheet/PageKeyConfigSheet.kt | 140 +
.../app/ui/book/read/sheet/PhotoSheet.kt | 105 +
.../book/read/sheet/ReadAloudConfigSheet.kt | 186 ++
.../app/ui/book/read/sheet/ReadAloudSheet.kt | 239 ++
.../app/ui/book/read/sheet/ReadStyleSheet.kt | 161 ++
.../app/ui/book/read/sheet/ShadowSetSheet.kt | 98 +
.../book/read/sheet/SimulatedReadingSheet.kt | 153 +
.../app/ui/book/read/sheet/SystemMenuPage.kt | 720 +++++
.../app/ui/book/read/sheet/TextTitleSheet.kt | 532 ++++
.../ui/book/read/sheet/TitleBarIconSheet.kt | 318 +++
.../book/read/sheet/ToolButtonConfigSheet.kt | 22 +
.../book/read/sheet/UnderlineConfigSheet.kt | 161 ++
.../io/legado/app/ui/book/toc/TocViewModel.kt | 43 +-
.../coverConfig/CoverConfigViewModel.kt | 7 +-
.../app/ui/config/otherConfig/OtherConfig.kt | 20 -
.../config/otherConfig/OtherConfigScreen.kt | 19 +-
.../otherConfig/OtherConfigViewModel.kt | 47 +-
.../app/ui/config/readConfig/PageKeySheet.kt | 53 +-
.../config/readConfig/ReadConfigContract.kt | 77 +
.../ui/config/readConfig/ReadConfigScreen.kt | 208 +-
.../config/readConfig/ReadConfigViewModel.kt | 253 +-
.../app/ui/config/themeConfig/ThemeConfig.kt | 9 +
.../config/themeConfig/ThemeConfigScreen.kt | 109 +-
.../io/legado/app/ui/main/MainActivity.kt | 73 +-
.../java/io/legado/app/ui/main/MainIntent.kt | 19 +
.../io/legado/app/ui/main/MainNavGraph.kt | 100 +
.../java/io/legado/app/ui/main/MainNavKey.kt | 9 +
.../io/legado/app/ui/main/MainNavigator.kt | 12 +-
.../java/io/legado/app/ui/theme/AppTheme.kt | 27 +-
.../io/legado/app/ui/theme/LegadoTheme.kt | 7 +-
.../io/legado/app/ui/theme/ThemeComponents.kt | 2 +-
.../io/legado/app/ui/theme/ThemeOverride.kt | 18 +-
.../ui/widget/components/AccentColorButton.kt | 45 +
.../ui/widget/components/FontSelectGrid.kt | 187 ++
.../ui/widget/components/FontSelectSheet.kt | 84 +
.../app/ui/widget/components/IconSwitch.kt | 36 +
.../app/ui/widget/components/SectionTitle.kt | 22 +
.../app/ui/widget/components/ValueStepper.kt | 13 +-
.../widget/components/alert/AppAlertDialog.kt | 2 +-
.../components/bookmark/BookmarkEditSheet.kt | 112 +-
.../button/series/MediumAnimatedButton.kt | 184 +-
.../button/series/MediumOutlinedButton.kt | 83 +-
.../button/series/MediumPlainButton.kt | 123 +-
.../button/series/MediumToggleButton.kt | 66 +-
.../button/series/MediumTonalButton.kt | 79 +-
.../button/series/SeriesIconButton.kt | 315 +++
.../button/series/SmallAnimatedButton.kt | 193 +-
.../button/series/SmallOutlinedButton.kt | 92 +-
.../button/series/SmallPlainButton.kt | 142 +-
.../button/series/SmallToggleButton.kt | 88 +-
.../button/series/SmallTonalButton.kt | 99 +-
.../components/dialog/ColorPickerSheet.kt | 2 +-
.../ui/widget/components/log/AppLogSheet.kt | 140 +-
.../settingItem/TinySettingItems.kt | 739 +++++
.../ui/widget/components/tabRow/CardTabRow.kt | 76 +
.../widget/components/topbar/TopBarButton.kt | 15 +-
.../io/legado/app/utils/ActivityExtensions.kt | 39 +-
.../io/legado/app/utils/ContextExtensions.kt | 32 +-
.../io/legado/app/utils/FragmentExtensions.kt | 17 +-
.../main/res/layout-land/view_read_menu.xml | 223 --
.../main/res/layout/activity_book_read.xml | 61 -
.../layout/dialog_chapter_change_source.xml | 119 -
.../main/res/layout/dialog_content_edit.xml | 58 -
.../main/res/layout/dialog_font_select.xml | 83 -
.../main/res/layout/dialog_http_tts_edit.xml | 157 --
app/src/main/res/layout/dialog_page_key.xml | 88 -
app/src/main/res/layout/dialog_read_aloud.xml | 393 ---
.../res/layout/dialog_regex_color_config.xml | 51 -
app/src/main/res/layout/dialog_shadow_set.xml | 37 -
app/src/main/res/layout/item_bg_image.xml | 36 -
app/src/main/res/layout/item_font.xml | 24 -
.../main/res/layout/item_regex_color_rule.xml | 75 -
app/src/main/res/layout/view_read_menu.xml | 191 --
app/src/main/res/layout/view_search_menu.xml | 157 --
app/src/main/res/values-zh-rCN/strings.xml | 111 +-
app/src/main/res/values/ids.xml | 7 +-
app/src/main/res/values/strings.xml | 107 +
gradle/libs.versions.toml | 2 +-
209 files changed, 25994 insertions(+), 12717 deletions(-)
create mode 100644 app/schemas/io.legado.app.data.AppDatabase/90.json
create mode 100644 app/src/main/java/io/legado/app/data/dao/HighlightRuleDao.kt
create mode 100644 app/src/main/java/io/legado/app/data/entities/HighlightRule.kt
create mode 100644 app/src/main/java/io/legado/app/data/repository/MangaSettingsRepository.kt
create mode 100644 app/src/main/java/io/legado/app/data/repository/ReadAloudSettingsRepository.kt
create mode 100644 app/src/main/java/io/legado/app/data/repository/ReadBookStyleConfigRepository.kt
create mode 100644 app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt
create mode 100644 app/src/main/java/io/legado/app/data/repository/ReadStyleRepository.kt
create mode 100644 app/src/main/java/io/legado/app/domain/usecase/ChangeSourceSearchUseCase.kt
create mode 100644 app/src/main/java/io/legado/app/domain/usecase/GetChapterContentUseCase.kt
create mode 100644 app/src/main/java/io/legado/app/help/config/ReadStyleResolver.kt
delete mode 100644 app/src/main/java/io/legado/app/help/config/ReadTipConfig.kt
rename app/src/main/java/io/legado/app/help/config/{OldThemeConfig.kt => ThemeConfigStore.kt} (84%)
delete mode 100644 app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceAdapter.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceContract.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterTocAdapter.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/BaseReadBookActivity.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/ContentEditDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/EffectiveReplacesDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/ReadBookColorPickerIds.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/ReadBookColorTheme.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/ReadBookSearchBar.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/SearchMenu.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/AutoReadDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/BgAdapter.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/BgImageSpan.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/ClickActionConfigDialog.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/DashUnderlineSpan.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/DoubleUnderlineSpan.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/FontConfigDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/FontSelectDialog.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/HighlightRuleStore.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/HighlightStyleSpan.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/HttpTtsEditDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/HttpTtsEditViewModel.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/InfoConfigDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/PageKeyDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/RegexColorConfigDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/ShadowSetDialog.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/SolidUnderlineSpan.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/SvgPathParser.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/SvgUnderlineSpan.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/ToolButtonConfigDialog.kt
delete mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/UnderlineConfigDialog.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/config/WaveUnderlineSpan.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/page/provider/CharStyle.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/AutoReadSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/BgTextConfigSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/ChangeChapterSourceSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/CharsetConfigSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/ClickActionConfigSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/ContentEditSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/DictSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/DownloadSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/EffectiveReplacesSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/FontSelectSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/GlobalThemePage.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/HeaderFooterPage.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/HighlightRuleConfigSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/HighlightRuleEditSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/MoreConfigSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/PaddingConfigSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/PageAnimConfigSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/PageKeyConfigSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/PhotoSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/ReadAloudConfigSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/ReadAloudSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/ReadStyleSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/ShadowSetSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/SimulatedReadingSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/SystemMenuPage.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/TextTitleSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/TitleBarIconSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/ToolButtonConfigSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/book/read/sheet/UnderlineConfigSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigContract.kt
create mode 100644 app/src/main/java/io/legado/app/ui/widget/components/AccentColorButton.kt
create mode 100644 app/src/main/java/io/legado/app/ui/widget/components/FontSelectGrid.kt
create mode 100644 app/src/main/java/io/legado/app/ui/widget/components/FontSelectSheet.kt
create mode 100644 app/src/main/java/io/legado/app/ui/widget/components/SectionTitle.kt
create mode 100644 app/src/main/java/io/legado/app/ui/widget/components/button/series/SeriesIconButton.kt
create mode 100644 app/src/main/java/io/legado/app/ui/widget/components/settingItem/TinySettingItems.kt
create mode 100644 app/src/main/java/io/legado/app/ui/widget/components/tabRow/CardTabRow.kt
delete mode 100644 app/src/main/res/layout-land/view_read_menu.xml
delete mode 100644 app/src/main/res/layout/activity_book_read.xml
delete mode 100644 app/src/main/res/layout/dialog_chapter_change_source.xml
delete mode 100644 app/src/main/res/layout/dialog_content_edit.xml
delete mode 100644 app/src/main/res/layout/dialog_font_select.xml
delete mode 100644 app/src/main/res/layout/dialog_http_tts_edit.xml
delete mode 100644 app/src/main/res/layout/dialog_page_key.xml
delete mode 100644 app/src/main/res/layout/dialog_read_aloud.xml
delete mode 100644 app/src/main/res/layout/dialog_regex_color_config.xml
delete mode 100644 app/src/main/res/layout/dialog_shadow_set.xml
delete mode 100644 app/src/main/res/layout/item_bg_image.xml
delete mode 100644 app/src/main/res/layout/item_font.xml
delete mode 100644 app/src/main/res/layout/item_regex_color_rule.xml
delete mode 100644 app/src/main/res/layout/view_read_menu.xml
delete mode 100644 app/src/main/res/layout/view_search_menu.xml
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 113812f8b..1e7cb12e8 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -109,10 +109,10 @@
"Bash(node *)",
"Bash(Select-String -Pattern \"BUILD|error|Error\")",
"Bash(Select-Object -First 10)",
- "Bash(git push *)",
- "Bash(gh run *)",
- "WebFetch(domain:ktor.io)",
- "WebFetch(domain:api.ktor.io)"
+ "Bash(Select-Object -Last 10)",
+ "Bash(dir /s /b \"%USERPROFILE%\\\\.gradle\\\\caches\\\\*kyant*\")",
+ "Bash(Select-String -Pattern \"\\(BUILD|error:|FAILED\\)\")",
+ "PowerShell(grep *)"
]
}
}
diff --git a/app/schemas/io.legado.app.data.AppDatabase/90.json b/app/schemas/io.legado.app.data.AppDatabase/90.json
new file mode 100644
index 000000000..b9b87d417
--- /dev/null
+++ b/app/schemas/io.legado.app.data.AppDatabase/90.json
@@ -0,0 +1,2415 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 90,
+ "identityHash": "cfd034a0567781ef4e9a7e8e3d695a9c",
+ "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, 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
+ }
+ ],
+ "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, 'cfd034a0567781ef4e9a7e8e3d695a9c')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9c6502674..5895e8ac0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -139,22 +139,6 @@
android:enableOnBackInvokedCallback="true"
android:exported="false"
android:windowSoftInputMode="adjustPan|stateHidden" />
-
-
-
-
-
-
-
-
-
{
@@ -189,6 +190,11 @@ class App : Application(), ImageLoaderFactory {
// oldConfig = Configuration(newConfig)
// }
+ override fun onTrimMemory(level: Int) {
+ super.onTrimMemory(level)
+ TextLine.trimCaches(level)
+ }
+
/**
* 尝试在安装了GMS的设备上(GMS或者MicroG)使用GMS内置的Conscrypt
* 作为首选JCE提供程序,而使Okhttp在低版本Android上
diff --git a/app/src/main/java/io/legado/app/api/ShortCuts.kt b/app/src/main/java/io/legado/app/api/ShortCuts.kt
index 0f9f6b80c..c664ed1c4 100644
--- a/app/src/main/java/io/legado/app/api/ShortCuts.kt
+++ b/app/src/main/java/io/legado/app/api/ShortCuts.kt
@@ -7,7 +7,6 @@ import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import io.legado.app.R
import io.legado.app.receiver.SharedReceiverActivity
-import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.main.MainActivity
object ShortCuts {
@@ -34,7 +33,9 @@ object ShortCuts {
val bookShelfIntent = MainActivity.createHomeIntent(context).apply {
action = Intent.ACTION_VIEW
}
- val readBookIntent = buildIntent(context)
+ val readBookIntent = MainActivity.createReadBookIntent(context).apply {
+ action = Intent.ACTION_VIEW
+ }
return ShortcutInfoCompat.Builder(context, "lastRead")
.setShortLabel(context.getString(R.string.last_read))
.setLongLabel(context.getString(R.string.last_read))
diff --git a/app/src/main/java/io/legado/app/base/BaseActivity.kt b/app/src/main/java/io/legado/app/base/BaseActivity.kt
index bc03e5d5d..c71b07b88 100644
--- a/app/src/main/java/io/legado/app/base/BaseActivity.kt
+++ b/app/src/main/java/io/legado/app/base/BaseActivity.kt
@@ -27,7 +27,7 @@ import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey
import io.legado.app.constant.Theme
import io.legado.app.help.config.AppConfig
-import io.legado.app.help.config.OldThemeConfig
+import io.legado.app.help.config.ThemeConfigStore
import io.legado.app.lib.theme.primaryColor
import io.legado.app.utils.applyOpenTint
import io.legado.app.utils.applyTint
@@ -36,7 +36,6 @@ import io.legado.app.utils.fullScreen
import io.legado.app.utils.getPrefString
import io.legado.app.utils.hideSoftInput
import io.legado.app.utils.observeEvent
-import io.legado.app.utils.setNavigationBarColorAuto
import io.legado.app.utils.setStatusBarColorAuto
import io.legado.app.utils.themeColor
import io.legado.app.utils.toastOnUi
@@ -101,7 +100,6 @@ abstract class BaseActivity(
else{
setupSystemBar()
}
- window.setNavigationBarColorAuto(themeColor(com.google.android.material.R.attr.colorSurface))
//setupSystemBar()
setContentView(binding.root)
upBackgroundImage()
@@ -231,7 +229,7 @@ abstract class BaseActivity(
open fun upBackgroundImage() {
if (imageBg) {
try {
- OldThemeConfig.getBgImage(this, windowManager.windowSize)?.let {
+ ThemeConfigStore.getBgImage(this, windowManager.windowSize)?.let {
window.decorView.background = it.toDrawable(resources)
}
} catch (e: OutOfMemoryError) {
@@ -261,4 +259,4 @@ abstract class BaseActivity(
currentFocus?.hideSoftInput()
super.finish()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/io/legado/app/base/BaseBottomSheetDialogFragment.kt b/app/src/main/java/io/legado/app/base/BaseBottomSheetDialogFragment.kt
index 14114ce63..5cda24938 100644
--- a/app/src/main/java/io/legado/app/base/BaseBottomSheetDialogFragment.kt
+++ b/app/src/main/java/io/legado/app/base/BaseBottomSheetDialogFragment.kt
@@ -12,8 +12,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import io.legado.app.constant.AppLog
import io.legado.app.help.coroutine.Coroutine
-import io.legado.app.utils.setNavigationBarColorAuto
-import io.legado.app.utils.themeColor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext
@@ -30,7 +28,6 @@ abstract class BaseBottomSheetDialogFragment(
override fun onStart() {
super.onStart()
- dialog?.window?.setNavigationBarColorAuto(requireContext().themeColor(com.google.android.material.R.attr.colorSurfaceContainer))
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
val screenHeight = resources.displayMetrics.heightPixels
(dialog as? BottomSheetDialog)?.behavior?.apply {
@@ -70,4 +67,4 @@ abstract class BaseBottomSheetDialogFragment(
open fun observeLiveBus() {
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/io/legado/app/base/BaseComposeActivity.kt b/app/src/main/java/io/legado/app/base/BaseComposeActivity.kt
index 020db124d..ca350f425 100644
--- a/app/src/main/java/io/legado/app/base/BaseComposeActivity.kt
+++ b/app/src/main/java/io/legado/app/base/BaseComposeActivity.kt
@@ -1,7 +1,9 @@
package io.legado.app.base
import android.os.Bundle
+import android.os.Build
import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.Composable
import androidx.core.graphics.drawable.toDrawable
@@ -9,7 +11,7 @@ import androidx.core.view.WindowCompat
import io.legado.app.constant.EventBus
import io.legado.app.constant.Theme
import io.legado.app.help.config.AppConfig
-import io.legado.app.help.config.OldThemeConfig
+import io.legado.app.help.config.ThemeConfigStore
import io.legado.app.ui.theme.AppTheme
import io.legado.app.utils.disableAutoFill
import io.legado.app.utils.fullScreen
@@ -34,6 +36,10 @@ abstract class BaseComposeActivity(
AppContextWrapper.applyLocaleAndFont(this)
super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ window.isNavigationBarContrastEnforced = false
+ }
setupSystemBar()
// Compose 入口
@@ -66,7 +72,7 @@ abstract class BaseComposeActivity(
open fun upBackgroundImage() {
try {
- OldThemeConfig.getBgImage(this, windowManager.windowSize)?.let {
+ ThemeConfigStore.getBgImage(this, windowManager.windowSize)?.let {
window.setBackgroundDrawable(it.toDrawable(resources))
}
} catch (_: Exception) {}
diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt
index 53c5a5b5f..d824dfc04 100644
--- a/app/src/main/java/io/legado/app/constant/PreferKey.kt
+++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt
@@ -103,6 +103,10 @@ object PreferKey {
const val showBrightnessView = "showBrightnessView"
const val useUnderline = "useUnderline"
const val regexColorRules = "regexColorRules"
+ const val highlightRuleItems = "highlightRuleItems"
+ const val highlightRuleDialog = "highlightRuleDialog"
+ const val highlightRuleBookTitle = "highlightRuleBookTitle"
+ const val highlightRuleBracketNote = "highlightRuleBracketNote"
const val adaptSpecialStyle = "adaptSpecialStyle"
const val autoClearExpired = "autoClearExpired"
const val autoChangeSource = "autoChangeSource"
@@ -288,6 +292,35 @@ object PreferKey {
const val isPredictiveBackEnabled = "isPredictiveBackEnabled"
const val replaceSortMode = "desc"
const val readBarStyle = "readBarStyle"
+ const val readMenuBgColor = "readMenuBgColor"
+ const val readMenuAccentColor = "readMenuAccentColor"
+ const val readMenuContainerColor = "readMenuContainerColor"
+ const val readMenuBgColorNight = "readMenuBgColorNight"
+ const val readMenuAccentColorNight = "readMenuAccentColorNight"
+ const val readMenuContainerColorNight = "readMenuContainerColorNight"
+ const val readMenuColorMode = "readMenuColorMode"
+ const val readMenuIconShowText = "readMenuIconShowText"
+ const val readMenuIconStyle = "readMenuIconStyle"
+ const val readMenuIconItemsPerRow = "readMenuIconItemsPerRow"
+ const val readMenuIconRowCount = "readMenuIconRowCount"
+ const val readMenuBottomCornerRadius = "readMenuBottomCornerRadius"
+ const val readMenuFloatingBottomBar = "readMenuFloatingBottomBar"
+ const val readMenuTopBarBlurMode = "readMenuTopBarBlurMode"
+ const val readMenuBottomBarBlurMode = "readMenuBottomBarBlurMode"
+ const val readMenuTopBarLiquidGlassButtons = "readMenuTopBarLiquidGlassButtons"
+ const val readMenuBottomBarLiquidGlassButtons = "readMenuBottomBarLiquidGlassButtons"
+ const val readMenuTopBarBlurStyle = "readMenuTopBarBlurStyle"
+ const val readMenuBottomBarBlurStyle = "readMenuBottomBarBlurStyle"
+ const val readMenuBlurRadius = "readMenuBlurRadius"
+ const val readMenuBlurAlpha = "readMenuBlurAlpha"
+ const val readMenuLensRadius = "readMenuLensRadius"
+ const val readMenuBorderWidth = "readMenuBorderWidth"
+ const val readMenuBorderColor = "readMenuBorderColor"
+ const val readMenuBorderColorNight = "readMenuBorderColorNight"
+ const val readMenuCustomIcons = "readMenuCustomIcons"
+ const val titleBarCustomIcons = "titleBarCustomIcons"
+ const val titleBarIconPosition = "titleBarIconPosition"
+ const val showTitleBarIcons = "showTitleBarIcons"
const val disableReturnKey = "disableReturnKey"
const val selectText = "selectText"
//我在干什么
@@ -351,3 +384,14 @@ object PreferKey {
const val eyeProtectionStartTime = "eyeProtectionStartTime"
const val eyeProtectionEndTime = "eyeProtectionEndTime"
}
+
+object ReadMenuBlurMode {
+ const val None = 0
+ const val LiquidGlass = 1
+ const val Haze = 2
+}
+
+object ReadMenuBlurStyle {
+ const val Solid = 0
+ const val Progressive = 1
+}
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 2b06f261f..a360eab47 100644
--- a/app/src/main/java/io/legado/app/data/AppDatabase.kt
+++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt
@@ -15,6 +15,7 @@ import io.legado.app.data.dao.BookmarkDao
import io.legado.app.data.dao.CacheDao
import io.legado.app.data.dao.CookieDao
import io.legado.app.data.dao.DictRuleDao
+import io.legado.app.data.dao.HighlightRuleDao
import io.legado.app.data.dao.HomepageCustomSetDao
import io.legado.app.data.dao.HomepageModuleDao
import io.legado.app.data.dao.HttpTTSDao
@@ -41,6 +42,7 @@ 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.HomepageModule
import io.legado.app.data.entities.HttpTTS
import io.legado.app.data.entities.KeyboardAssist
@@ -73,7 +75,7 @@ val appDb by lazy {
}
@Database(
- version = 89,
+ version = 90,
exportSchema = true,
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
@@ -81,7 +83,8 @@ val appDb by lazy {
RssReadRecord::class, ReadRecordDetail::class, ReadRecordSession::class,
RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class, Cache::class,
RuleSub::class, DictRule::class, KeyboardAssist::class, Server::class,
- SearchContentHistory::class, HomepageModule::class, HomepageCustomSet::class],
+ SearchContentHistory::class, HomepageModule::class, HomepageCustomSet::class,
+ HighlightRule::class],
views = [BookSourcePart::class],
autoMigrations = [
AutoMigration(from = 43, to = 44),
@@ -129,7 +132,8 @@ val appDb by lazy {
AutoMigration(from = 85, to = 86),
AutoMigration(from = 86, to = 87),
AutoMigration(from = 87, to = 88),
- AutoMigration(from = 88, to = 89)
+ AutoMigration(from = 88, to = 89),
+ AutoMigration(from = 89, to = 90)
]
)
abstract class AppDatabase : RoomDatabase() {
@@ -158,6 +162,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract val searchContentHistoryDao: SearchContentHistoryDao
abstract val homepageModuleDao: HomepageModuleDao
abstract val homepageCustomSetDao: HomepageCustomSetDao
+ abstract val highlightRuleDao: HighlightRuleDao
companion object {
diff --git a/app/src/main/java/io/legado/app/data/dao/HighlightRuleDao.kt b/app/src/main/java/io/legado/app/data/dao/HighlightRuleDao.kt
new file mode 100644
index 000000000..431d49eea
--- /dev/null
+++ b/app/src/main/java/io/legado/app/data/dao/HighlightRuleDao.kt
@@ -0,0 +1,41 @@
+package io.legado.app.data.dao
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Transaction
+import androidx.room.Update
+import io.legado.app.data.entities.HighlightRule
+
+@Dao
+interface HighlightRuleDao {
+
+ @Query("SELECT * FROM highlightRules ORDER BY position ASC")
+ fun getAll(): List
+
+ @Query("SELECT * FROM highlightRules WHERE enabled = 1 AND pattern != '' ORDER BY position ASC")
+ fun getEnabled(): List
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insertAll(rules: List)
+
+ @Update
+ fun update(rule: HighlightRule)
+
+ @Delete
+ fun delete(rule: HighlightRule)
+
+ @Query("DELETE FROM highlightRules")
+ fun deleteAll()
+
+ @Query("SELECT COUNT(*) FROM highlightRules")
+ fun count(): Int
+
+ @Transaction
+ fun replaceAll(rules: List) {
+ deleteAll()
+ insertAll(rules)
+ }
+}
diff --git a/app/src/main/java/io/legado/app/data/entities/HighlightRule.kt b/app/src/main/java/io/legado/app/data/entities/HighlightRule.kt
new file mode 100644
index 000000000..a804e429a
--- /dev/null
+++ b/app/src/main/java/io/legado/app/data/entities/HighlightRule.kt
@@ -0,0 +1,94 @@
+package io.legado.app.data.entities
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import java.util.UUID
+
+@Entity(tableName = "highlightRules")
+data class HighlightRule(
+ @PrimaryKey
+ var id: String = UUID.randomUUID().toString(),
+ var name: String = "",
+ var pattern: String = "",
+ var sampleText: String = "",
+ var targetScope: Int = TARGET_ALL,
+ var enabled: Boolean = true,
+ var position: Int = 0,
+ var textColor: Int? = null,
+ var bgColor: Int? = null,
+ var underlineMode: Int = 0,
+ var underlineColor: Int? = null,
+ var underlineWidth: Float = 1f,
+ var underlineOffset: Float = 2f,
+ var underlineSvgPath: String? = null,
+ var bgImage: String? = null,
+ var bgImageFit: Int = 0,
+ var bgImageScale: Float = 1f,
+) {
+
+ fun styleSummary(): String {
+ val parts = ArrayList(4)
+ parts.add(targetScopeLabel())
+ textColor?.let {
+ parts.add("字色 ${it.toHexColor()}")
+ }
+ bgColor?.let {
+ parts.add("背景色 ${it.toHexColor()}")
+ }
+ if (underlineMode != 0) {
+ parts.add(
+ when (underlineMode) {
+ 1 -> "实线下划线"
+ 2 -> "虚线下划线"
+ 3 -> "波浪下划线"
+ 4 -> "双下划线"
+ 5 -> "自定义SVG"
+ else -> "下划线"
+ } + underlineColor?.let { " ${it.toHexColor()}" }.orEmpty()
+ )
+ }
+ if (!bgImage.isNullOrBlank()) {
+ parts.add(
+ when (bgImageFit) {
+ 1 -> "背景图(拉伸)"
+ 2 -> "背景图(裁剪)"
+ else -> "背景图(平铺)"
+ }
+ )
+ }
+ if (parts.isEmpty()) {
+ parts.add("无样式")
+ }
+ return parts.joinToString(" / ")
+ }
+
+ fun targetScopeLabel(): String {
+ return when (targetScope) {
+ TARGET_TITLE -> "作用于标题"
+ TARGET_BODY -> "作用于正文"
+ else -> "作用于全部"
+ }
+ }
+
+ fun displayPattern(): String {
+ return pattern.ifBlank { ".*" }
+ }
+
+ fun normalizedSampleText(): String {
+ return sampleText.ifBlank {
+ "她轻声说:“今晚就出发。”\n最近在重读《百年孤独》(纪念版),节奏依然很稳。"
+ }
+ }
+
+ fun copyWithNewId(): HighlightRule {
+ return copy(id = UUID.randomUUID().toString())
+ }
+
+ companion object {
+ const val TARGET_ALL = 0
+ const val TARGET_TITLE = 1
+ const val TARGET_BODY = 2
+
+ fun Int.toHexColor(): String = String.format("#%08X", this)
+ }
+}
diff --git a/app/src/main/java/io/legado/app/data/repository/MangaSettingsRepository.kt b/app/src/main/java/io/legado/app/data/repository/MangaSettingsRepository.kt
new file mode 100644
index 000000000..a7008ca32
--- /dev/null
+++ b/app/src/main/java/io/legado/app/data/repository/MangaSettingsRepository.kt
@@ -0,0 +1,182 @@
+package io.legado.app.data.repository
+
+import android.content.Context
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.emptyPreferences
+import androidx.datastore.preferences.core.intPreferencesKey
+import androidx.datastore.preferences.core.stringPreferencesKey
+import io.legado.app.constant.PreferKey
+import io.legado.app.ui.book.manga.config.MangaScrollMode
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.map
+import java.io.IOException
+
+data class MangaPreferences(
+ val showMangaUi: Boolean = true,
+ val disableMangaScale: Boolean = true,
+ val disableMangaScrollAnimation: Boolean = false,
+ val disableMangaCrossFade: Boolean = false,
+ val disableClickScroll: Boolean = false,
+ val mangaPreDownloadNum: Int = 10,
+ val mangaAutoPageSpeed: Int = 3,
+ val mangaFooterConfig: String = "",
+ val mangaScrollMode: Int = MangaScrollMode.WEBTOON,
+ val mangaLongClick: Boolean = true,
+ val mangaBackground: Int = 0xFF000000.toInt(),
+ val mangaColorFilter: String = "",
+ val hideMangaTitle: Boolean = false,
+ val enableMangaEInk: Boolean = false,
+ val mangaEInkThreshold: Int = 150,
+ val enableMangaGray: Boolean = false,
+ val webtoonSidePaddingDp: Int = 0,
+ val mangaVolumeKeyPage: Boolean = false,
+ val reverseVolumeKeyPage: Boolean = false,
+ val clickActionTL: Int = -1,
+ val clickActionTC: Int = -1,
+ val clickActionTR: Int = 1,
+ val clickActionML: Int = 2,
+ val clickActionMC: Int = 0,
+ val clickActionMR: Int = 1,
+ val clickActionBL: Int = 2,
+ val clickActionBC: Int = 1,
+ val clickActionBR: Int = 1
+)
+
+class MangaSettingsRepository(
+ private val context: Context,
+ private val settingsRepository: SettingsRepository
+) {
+
+ val preferences: Flow = context.dataStore.data
+ .catch { exception ->
+ if (exception is IOException) {
+ emit(emptyPreferences())
+ } else {
+ throw exception
+ }
+ }
+ .map { preferences ->
+ preferences.toMangaPreferences()
+ }
+
+ suspend fun setShowMangaUi(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.showMangaUi, value)
+
+ suspend fun setMangaPreDownloadNum(value: Int) =
+ settingsRepository.putInt(PreferKey.mangaPreDownloadNum, value)
+
+ suspend fun setMangaAutoPageSpeed(value: Int) =
+ settingsRepository.putInt(PreferKey.mangaAutoPageSpeed, value)
+
+ suspend fun setDisableClickScroll(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.disableClickScroll, value)
+
+ suspend fun setDisableMangaScrollAnimation(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.disableMangaScrollAnimation, value)
+
+ suspend fun setDisableMangaCrossFade(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.disableMangaCrossFade, value)
+
+ suspend fun setDisableMangaScale(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.disableMangaScale, value)
+
+ suspend fun setEnableMangaEInk(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.enableMangaEInk, value)
+
+ suspend fun setMangaEInkThreshold(value: Int) =
+ settingsRepository.putInt(PreferKey.mangaEInkThreshold, value)
+
+ suspend fun setEnableMangaGray(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.enableMangaGray, value)
+
+ suspend fun setMangaAutoColorFilter(value: String) =
+ settingsRepository.putString(PreferKey.mangaColorFilter, value)
+
+ suspend fun setMangaBackground(value: Int) =
+ settingsRepository.putInt(PreferKey.mangaBackground, value)
+
+ suspend fun setMangaLongClick(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.mangaLongClick, value)
+
+ suspend fun setMangaVolumeKeyPage(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.mangaVolumeKeyPage, value)
+
+ suspend fun setReverseVolumeKeyPage(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.reverseVolumeKeyPage, value)
+
+ suspend fun setHideMangaTitle(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.hideMangaTitle, value)
+
+ suspend fun setMangaFooterConfig(value: String) =
+ settingsRepository.putString(PreferKey.mangaFooterConfig, value)
+
+ suspend fun setMangaClickAction(key: String, value: Int) =
+ settingsRepository.putInt(key, value)
+
+ private fun Preferences.toMangaPreferences(): MangaPreferences {
+ return MangaPreferences(
+ showMangaUi = this[Keys.ShowMangaUi] ?: true,
+ disableMangaScale = this[Keys.DisableMangaScale] ?: true,
+ disableMangaScrollAnimation = this[Keys.DisableMangaScrollAnimation] ?: false,
+ disableMangaCrossFade = this[Keys.DisableMangaCrossFade] ?: false,
+ disableClickScroll = this[Keys.DisableClickScroll] ?: false,
+ mangaPreDownloadNum = this[Keys.MangaPreDownloadNum] ?: 10,
+ mangaAutoPageSpeed = this[Keys.MangaAutoPageSpeed] ?: 3,
+ mangaFooterConfig = this[Keys.MangaFooterConfig] ?: "",
+ mangaScrollMode = this[Keys.MangaScrollMode] ?: MangaScrollMode.WEBTOON,
+ mangaLongClick = this[Keys.MangaLongClick] ?: true,
+ mangaBackground = this[Keys.MangaBackground] ?: 0xFF000000.toInt(),
+ mangaColorFilter = this[Keys.MangaColorFilter] ?: "",
+ hideMangaTitle = this[Keys.HideMangaTitle] ?: false,
+ enableMangaEInk = this[Keys.EnableMangaEInk] ?: false,
+ mangaEInkThreshold = this[Keys.MangaEInkThreshold] ?: 150,
+ enableMangaGray = this[Keys.EnableMangaGray] ?: false,
+ webtoonSidePaddingDp = this[Keys.WebtoonSidePaddingDp] ?: 0,
+ mangaVolumeKeyPage = this[Keys.MangaVolumeKeyPage] ?: false,
+ reverseVolumeKeyPage = this[Keys.ReverseVolumeKeyPage] ?: false,
+ clickActionTL = this[Keys.ClickActionTL] ?: -1,
+ clickActionTC = this[Keys.ClickActionTC] ?: -1,
+ clickActionTR = this[Keys.ClickActionTR] ?: 1,
+ clickActionML = this[Keys.ClickActionML] ?: 2,
+ clickActionMC = this[Keys.ClickActionMC] ?: 0,
+ clickActionMR = this[Keys.ClickActionMR] ?: 1,
+ clickActionBL = this[Keys.ClickActionBL] ?: 2,
+ clickActionBC = this[Keys.ClickActionBC] ?: 1,
+ clickActionBR = this[Keys.ClickActionBR] ?: 1
+ )
+ }
+
+ private object Keys {
+ val ShowMangaUi = booleanPreferencesKey(PreferKey.showMangaUi)
+ val DisableMangaScale = booleanPreferencesKey(PreferKey.disableMangaScale)
+ val DisableMangaScrollAnimation =
+ booleanPreferencesKey(PreferKey.disableMangaScrollAnimation)
+ val DisableMangaCrossFade = booleanPreferencesKey(PreferKey.disableMangaCrossFade)
+ val DisableClickScroll = booleanPreferencesKey(PreferKey.disableClickScroll)
+ val MangaPreDownloadNum = intPreferencesKey(PreferKey.mangaPreDownloadNum)
+ val MangaAutoPageSpeed = intPreferencesKey(PreferKey.mangaAutoPageSpeed)
+ val MangaFooterConfig = stringPreferencesKey(PreferKey.mangaFooterConfig)
+ val MangaScrollMode = intPreferencesKey(PreferKey.mangaScrollMode)
+ val MangaLongClick = booleanPreferencesKey(PreferKey.mangaLongClick)
+ val MangaBackground = intPreferencesKey(PreferKey.mangaBackground)
+ val MangaColorFilter = stringPreferencesKey(PreferKey.mangaColorFilter)
+ val HideMangaTitle = booleanPreferencesKey(PreferKey.hideMangaTitle)
+ val EnableMangaEInk = booleanPreferencesKey(PreferKey.enableMangaEInk)
+ val MangaEInkThreshold = intPreferencesKey(PreferKey.mangaEInkThreshold)
+ val EnableMangaGray = booleanPreferencesKey(PreferKey.enableMangaGray)
+ val WebtoonSidePaddingDp = intPreferencesKey(PreferKey.webtoonSidePaddingDp)
+ val MangaVolumeKeyPage = booleanPreferencesKey(PreferKey.mangaVolumeKeyPage)
+ val ReverseVolumeKeyPage = booleanPreferencesKey(PreferKey.reverseVolumeKeyPage)
+ val ClickActionTL = intPreferencesKey(PreferKey.mangaClickActionTL)
+ val ClickActionTC = intPreferencesKey(PreferKey.mangaClickActionTC)
+ val ClickActionTR = intPreferencesKey(PreferKey.mangaClickActionTR)
+ val ClickActionML = intPreferencesKey(PreferKey.mangaClickActionML)
+ val ClickActionMC = intPreferencesKey(PreferKey.mangaClickActionMC)
+ val ClickActionMR = intPreferencesKey(PreferKey.mangaClickActionMR)
+ val ClickActionBL = intPreferencesKey(PreferKey.mangaClickActionBL)
+ val ClickActionBC = intPreferencesKey(PreferKey.mangaClickActionBC)
+ val ClickActionBR = intPreferencesKey(PreferKey.mangaClickActionBR)
+ }
+}
diff --git a/app/src/main/java/io/legado/app/data/repository/ReadAloudSettingsRepository.kt b/app/src/main/java/io/legado/app/data/repository/ReadAloudSettingsRepository.kt
new file mode 100644
index 000000000..eadc16aeb
--- /dev/null
+++ b/app/src/main/java/io/legado/app/data/repository/ReadAloudSettingsRepository.kt
@@ -0,0 +1,121 @@
+package io.legado.app.data.repository
+
+import android.content.Context
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.emptyPreferences
+import io.legado.app.constant.PreferKey
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.map
+import java.io.IOException
+
+data class ReadAloudPreferences(
+ val ignoreAudioFocus: Boolean = false,
+ val mediaButtonOnExit: Boolean = true,
+ val readAloudByMediaButton: Boolean = false,
+ val pauseReadAloudWhilePhoneCalls: Boolean = false,
+ val readAloudWakeLock: Boolean = false,
+ val mediaButtonPerNext: Boolean = false,
+ val readAloudByPage: Boolean = false,
+ val systemMediaControlCompatibilityChange: Boolean = true,
+ val streamReadAloudAudio: Boolean = false,
+ val ttsTimer: Int = 0,
+ val ttsFollowSys: Boolean = true,
+ val ttsSpeechRate: Int = 5,
+)
+
+class ReadAloudSettingsRepository(
+ private val context: Context,
+ private val settingsRepository: SettingsRepository
+) {
+
+ val preferences: Flow = context.dataStore.data
+ .catch { exception ->
+ if (exception is IOException) {
+ emit(emptyPreferences())
+ } else {
+ throw exception
+ }
+ }
+ .map { preferences ->
+ preferences.toReadAloudPreferences()
+ }
+
+ suspend fun setIgnoreAudioFocus(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.ignoreAudioFocus, value)
+
+ suspend fun setMediaButtonOnExit(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.mediaButtonOnExit, value)
+
+ suspend fun setReadAloudByMediaButton(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.readAloudByMediaButton, value)
+
+ suspend fun setPauseReadAloudWhilePhoneCalls(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.pauseReadAloudWhilePhoneCalls, value)
+
+ suspend fun setReadAloudWakeLock(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.readAloudWakeLock, value)
+
+ suspend fun setMediaButtonPerNext(value: Boolean) =
+ settingsRepository.putBoolean(KEY_MEDIA_BUTTON_PER_NEXT, value)
+
+ suspend fun setReadAloudByPage(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.readAloudByPage, value)
+
+ suspend fun setSystemMediaControlCompatibilityChange(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.systemMediaControlCompatibilityChange, value)
+
+ suspend fun setStreamReadAloudAudio(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.streamReadAloudAudio, value)
+
+ suspend fun setTtsTimer(value: Int) =
+ settingsRepository.putInt(PreferKey.ttsTimer, value.coerceIn(0, 180))
+
+ suspend fun setTtsFollowSys(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.ttsFollowSys, value)
+
+ suspend fun setTtsSpeechRate(value: Int) =
+ settingsRepository.putInt(PreferKey.ttsSpeechRate, value.coerceIn(0, 80))
+
+ private fun Preferences.toReadAloudPreferences(): ReadAloudPreferences {
+ return ReadAloudPreferences(
+ ignoreAudioFocus = this[Keys.IgnoreAudioFocus] ?: false,
+ mediaButtonOnExit = this[Keys.MediaButtonOnExit] ?: true,
+ readAloudByMediaButton = this[Keys.ReadAloudByMediaButton] ?: false,
+ pauseReadAloudWhilePhoneCalls = this[Keys.PauseReadAloudWhilePhoneCalls] ?: false,
+ readAloudWakeLock = this[Keys.ReadAloudWakeLock] ?: false,
+ mediaButtonPerNext = this[Keys.MediaButtonPerNext] ?: false,
+ readAloudByPage = this[Keys.ReadAloudByPage] ?: false,
+ systemMediaControlCompatibilityChange =
+ this[Keys.SystemMediaControlCompatibilityChange] ?: true,
+ streamReadAloudAudio = this[Keys.StreamReadAloudAudio] ?: false,
+ ttsTimer = this[Keys.TtsTimer] ?: 0,
+ ttsFollowSys = this[Keys.TtsFollowSys] ?: true,
+ ttsSpeechRate = this[Keys.TtsSpeechRate] ?: 5,
+ )
+ }
+
+ private object Keys {
+ val IgnoreAudioFocus = booleanPreferencesKey(PreferKey.ignoreAudioFocus)
+ val MediaButtonOnExit = booleanPreferencesKey(PreferKey.mediaButtonOnExit)
+ val ReadAloudByMediaButton = booleanPreferencesKey(PreferKey.readAloudByMediaButton)
+ val PauseReadAloudWhilePhoneCalls =
+ booleanPreferencesKey(PreferKey.pauseReadAloudWhilePhoneCalls)
+ val ReadAloudWakeLock = booleanPreferencesKey(PreferKey.readAloudWakeLock)
+ val MediaButtonPerNext = booleanPreferencesKey(KEY_MEDIA_BUTTON_PER_NEXT)
+ val ReadAloudByPage = booleanPreferencesKey(PreferKey.readAloudByPage)
+ val SystemMediaControlCompatibilityChange =
+ booleanPreferencesKey(PreferKey.systemMediaControlCompatibilityChange)
+ val StreamReadAloudAudio = booleanPreferencesKey(PreferKey.streamReadAloudAudio)
+ val TtsTimer = androidx.datastore.preferences.core.intPreferencesKey(PreferKey.ttsTimer)
+ val TtsFollowSys = booleanPreferencesKey(PreferKey.ttsFollowSys)
+ val TtsSpeechRate = androidx.datastore.preferences.core.intPreferencesKey(
+ PreferKey.ttsSpeechRate
+ )
+ }
+
+ companion object {
+ const val KEY_MEDIA_BUTTON_PER_NEXT = "mediaButtonPerNext"
+ }
+}
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
new file mode 100644
index 000000000..6458f65e6
--- /dev/null
+++ b/app/src/main/java/io/legado/app/data/repository/ReadBookStyleConfigRepository.kt
@@ -0,0 +1,61 @@
+package io.legado.app.data.repository
+
+import io.legado.app.help.config.ReadBookConfig
+import java.io.InputStream
+
+/**
+ * Repository boundary for the legacy read style config file.
+ *
+ * The underlying model is still [ReadBookConfig.Config] because each read style
+ * 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 {
+
+ fun save() {
+ ReadBookConfig.save()
+ }
+
+ fun addStyle(): Int {
+ ReadBookConfig.configList.add(ReadBookConfig.Config())
+ save()
+ return ReadBookConfig.configList.lastIndex
+ }
+
+ fun deleteCurrentStyle(): Boolean {
+ val deleted = ReadBookConfig.deleteDur()
+ if (deleted) {
+ save()
+ }
+ return deleted
+ }
+
+ fun importCurrentStyle(bytes: ByteArray) {
+ ReadBookConfig.durConfig = ReadBookConfig.import(bytes)
+ save()
+ }
+
+ fun exportCurrentStyle(): ByteArray {
+ return ReadBookConfig.export()
+ }
+
+ fun saveBackgroundImage(inputStream: InputStream, displayName: String?): String {
+ return ReadBookConfig.saveBackgroundImage(inputStream, displayName)
+ }
+
+ fun setCurrentBackgroundImage(path: String) {
+ ReadBookConfig.durConfig.setCurBg(2, path)
+ save()
+ }
+
+ fun setCurrentBackgroundImageForMode(path: String, isNight: Boolean) {
+ if (isNight) {
+ ReadBookConfig.durConfig.bgTypeNight = 2
+ ReadBookConfig.durConfig.bgStrNight = path
+ } else {
+ ReadBookConfig.durConfig.bgType = 2
+ ReadBookConfig.durConfig.bgStr = path
+ }
+ save()
+ }
+}
diff --git a/app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt b/app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt
new file mode 100644
index 000000000..bdfc1ca3b
--- /dev/null
+++ b/app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt
@@ -0,0 +1,537 @@
+package io.legado.app.data.repository
+
+import android.content.Context
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.emptyPreferences
+import androidx.datastore.preferences.core.floatPreferencesKey
+import androidx.datastore.preferences.core.intPreferencesKey
+import androidx.datastore.preferences.core.stringPreferencesKey
+import io.legado.app.constant.PreferKey
+import io.legado.app.constant.ReadMenuBlurMode
+import io.legado.app.constant.ReadMenuBlurStyle
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.map
+import java.io.IOException
+
+data class ReadPreferences(
+ val screenOrientation: String = "0",
+ val keepLight: String = "0",
+ val hideStatusBar: Boolean = false,
+ val hideNavigationBar: Boolean = false,
+ val paddingDisplayCutouts: Boolean = false,
+ val titleBarMode: String = "1",
+ val menuAlpha: Int = 100,
+ val readBodyToLh: Boolean = true,
+ val defaultSourceChangeAll: Boolean = true,
+ val textFullJustify: Boolean = true,
+ val textBottomJustify: Boolean = true,
+ val adaptSpecialStyle: Boolean = true,
+ val useZhLayout: Boolean = false,
+ val showBrightnessView: Boolean = true,
+ val useUnderline: Boolean = false,
+ val readSliderMode: String = "0",
+ val doubleHorizontalPage: String = "0",
+ val progressBarBehavior: String = "page",
+ val mouseWheelPage: Boolean = true,
+ val volumeKeyPage: Boolean = true,
+ val volumeKeyPageOnPlay: Boolean = true,
+ val keyPageOnLongPress: Boolean = false,
+ val pageTouchSlop: Int = 0,
+ val sliderVibrator: Boolean = false,
+ val selectVibrator: Boolean = false,
+ val autoChangeSource: Boolean = true,
+ val selectText: Boolean = true,
+ val noAnimScrollPage: Boolean = false,
+ val clickImgWay: String = "2",
+ val optimizeRender: Boolean = false,
+ val disableReturnKey: Boolean = false,
+ val expandTextMenu: Boolean = false,
+ val showReadTitleAddition: Boolean = true,
+ val autoReadSpeed: Int = 10,
+ val prevKeys: String = "",
+ val nextKeys: String = "",
+ val tocUiUseReplace: Boolean = false,
+ val tocCountWords: Boolean = true,
+ val readStyleSelect: Int = 0,
+ val comicStyleSelect: Int = 0,
+ val shareLayout: Boolean = false,
+ val readBarStyleFollowPage: Boolean = false,
+ val readBarStyle: Int = 0,
+ val clickActionTL: Int = 2,
+ val clickActionTC: Int = 2,
+ val clickActionTR: Int = 1,
+ val clickActionML: Int = 2,
+ val clickActionMC: Int = 0,
+ val clickActionMR: Int = 1,
+ val clickActionBL: Int = 2,
+ val clickActionBC: Int = 1,
+ val clickActionBR: Int = 1,
+ val fontFolder: String = "",
+ val readMenuBgColor: Int = 0,
+ val readMenuAccentColor: Int = 0,
+ val readMenuContainerColor: Int = 0,
+ val readMenuBgColorNight: Int = 0,
+ val readMenuAccentColorNight: Int = 0,
+ val readMenuContainerColorNight: Int = 0,
+ val readMenuColorMode: Int = 1,
+ val readMenuIconShowText: Boolean = true,
+ val readMenuIconStyle: Int = 0,
+ val readMenuIconItemsPerRow: Int = 5,
+ val readMenuIconRowCount: Int = 1,
+ val readMenuBottomCornerRadius: Int = 0,
+ val readMenuFloatingBottomBar: Boolean = false,
+ val readMenuTopBarBlurMode: Int = ReadMenuBlurMode.None,
+ val readMenuBottomBarBlurMode: Int = ReadMenuBlurMode.None,
+ val readMenuTopBarLiquidGlassButtons: Boolean = false,
+ val readMenuBottomBarLiquidGlassButtons: Boolean = false,
+ val readMenuTopBarBlurStyle: Int = ReadMenuBlurStyle.Progressive,
+ val readMenuBottomBarBlurStyle: Int = ReadMenuBlurStyle.Solid,
+ val readMenuBlurRadius: Int = 24,
+ val readMenuBlurAlpha: Int = 60,
+ val readMenuLensRadius: Float = 24f,
+ val readMenuBorderWidth: Int = 0,
+ val readMenuBorderColor: Int = 0,
+ val readMenuBorderColorNight: Int = 0,
+ val readMenuCustomIcons: String = "",
+ val titleBarCustomIcons: String = "",
+ val titleBarIconPosition: Int = 0,
+ val showTitleBarIcons: Boolean = true,
+ val chineseConverterType: Int = 0,
+)
+
+class ReadSettingsRepository(
+ private val context: Context,
+ private val settingsRepository: SettingsRepository
+) {
+
+ val preferences: Flow = context.dataStore.data
+ .catch { exception ->
+ if (exception is IOException) {
+ emit(emptyPreferences())
+ } else {
+ throw exception
+ }
+ }
+ .map { preferences ->
+ preferences.toReadPreferences()
+ }
+
+ suspend fun setScreenOrientation(value: String) =
+ settingsRepository.putString(PreferKey.screenOrientation, value)
+
+ suspend fun setKeepLight(value: String) =
+ settingsRepository.putString(PreferKey.keepLight, value)
+
+ suspend fun setHideStatusBar(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.hideStatusBar, value)
+
+ suspend fun setHideNavigationBar(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.hideNavigationBar, value)
+
+ suspend fun setPaddingDisplayCutouts(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.paddingDisplayCutouts, value)
+
+ suspend fun setTitleBarMode(value: String) =
+ settingsRepository.putString(PreferKey.titleBarMode, value)
+
+ suspend fun setMenuAlpha(value: Int) =
+ settingsRepository.putInt(PreferKey.menuAlpha, value)
+
+ suspend fun setReadBodyToLh(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.readBodyToLh, value)
+
+ suspend fun setDefaultSourceChangeAll(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.defaultSourceChangeAll, value)
+
+ suspend fun setTextFullJustify(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.textFullJustify, value)
+
+ suspend fun setTextBottomJustify(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.textBottomJustify, value)
+
+ suspend fun setAdaptSpecialStyle(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.adaptSpecialStyle, value)
+
+ suspend fun setUseZhLayout(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.useZhLayout, value)
+
+ suspend fun setShowBrightnessView(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.showBrightnessView, value)
+
+ suspend fun setUseUnderline(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.useUnderline, value)
+
+ suspend fun setReadSliderMode(value: String) =
+ settingsRepository.putString(PreferKey.readSliderMode, value)
+
+ suspend fun setDoubleHorizontalPage(value: String) =
+ settingsRepository.putString(PreferKey.doublePageHorizontal, value)
+
+ suspend fun setProgressBarBehavior(value: String) =
+ settingsRepository.putString(PreferKey.progressBarBehavior, value)
+
+ suspend fun setMouseWheelPage(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.mouseWheelPage, value)
+
+ suspend fun setVolumeKeyPage(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.volumeKeyPage, value)
+
+ suspend fun setVolumeKeyPageOnPlay(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.volumeKeyPageOnPlay, value)
+
+ suspend fun setKeyPageOnLongPress(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.keyPageOnLongPress, value)
+
+ suspend fun setPageTouchSlop(value: Int) =
+ settingsRepository.putInt(PreferKey.pageTouchSlop, value)
+
+ suspend fun setSliderVibrator(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.sliderVibrator, value)
+
+ suspend fun setSelectVibrator(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.selectVibrator, value)
+
+ suspend fun setAutoChangeSource(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.autoChangeSource, value)
+
+ suspend fun setSelectText(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.selectText, value)
+
+ suspend fun setNoAnimScrollPage(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.noAnimScrollPage, value)
+
+ suspend fun setClickImgWay(value: String) =
+ settingsRepository.putString(PreferKey.clickImgWay, value)
+
+ suspend fun setOptimizeRender(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.optimizeRender, value)
+
+ suspend fun setDisableReturnKey(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.disableReturnKey, value)
+
+ suspend fun setExpandTextMenu(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.expandTextMenu, value)
+
+ suspend fun setShowReadTitleAddition(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.showReadTitleAddition, value)
+
+ suspend fun setAutoReadSpeed(value: Int) =
+ settingsRepository.putInt(PreferKey.autoReadSpeed, value)
+
+ suspend fun setPageKeys(prevKeys: String, nextKeys: String) {
+ settingsRepository.putStrings(
+ mapOf(
+ PreferKey.prevKeys to prevKeys,
+ PreferKey.nextKeys to nextKeys
+ )
+ )
+ }
+
+ suspend fun setTocUiUseReplace(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.tocUiUseReplace, value)
+
+ suspend fun setTocCountWords(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.tocCountWords, value)
+
+ suspend fun setReadStyleSelect(value: Int) =
+ settingsRepository.putInt(PreferKey.readStyleSelect, value)
+
+ suspend fun setComicStyleSelect(value: Int) =
+ settingsRepository.putInt(PreferKey.comicStyleSelect, value)
+
+ suspend fun setShareLayout(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.shareLayout, value)
+
+ suspend fun setReadBarStyleFollowPage(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.readBarStyleFollowPage, value)
+
+ suspend fun setReadBarStyle(value: Int) =
+ settingsRepository.putInt(PreferKey.readBarStyle, value.coerceIn(0, 2))
+
+ suspend fun setClickAction(key: String, value: Int) =
+ settingsRepository.putInt(key, value)
+
+ suspend fun setFontFolder(value: String) =
+ settingsRepository.putString(PreferKey.fontFolder, value)
+
+ suspend fun setReadMenuBgColor(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuBgColor, value)
+
+ suspend fun setReadMenuAccentColor(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuAccentColor, value)
+
+ suspend fun setReadMenuContainerColor(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuContainerColor, value)
+
+ suspend fun setReadMenuBgColorNight(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuBgColorNight, value)
+
+ suspend fun setReadMenuAccentColorNight(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuAccentColorNight, value)
+
+ suspend fun setReadMenuContainerColorNight(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuContainerColorNight, value)
+
+ suspend fun setReadMenuColorMode(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuColorMode, value.coerceIn(0, 1))
+
+ suspend fun setReadMenuIconShowText(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.readMenuIconShowText, value)
+
+ suspend fun setReadMenuIconStyle(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuIconStyle, value.coerceIn(0, 2))
+
+ suspend fun setReadMenuIconItemsPerRow(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuIconItemsPerRow, value.coerceIn(2, 8))
+
+ suspend fun setReadMenuIconRowCount(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuIconRowCount, value.coerceIn(1, 2))
+
+ suspend fun setReadMenuBottomCornerRadius(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuBottomCornerRadius, value.coerceIn(0, 32))
+
+ suspend fun setReadMenuFloatingBottomBar(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.readMenuFloatingBottomBar, value)
+
+ suspend fun setReadMenuTopBarBlurMode(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuTopBarBlurMode, value.coerceIn(0, 2))
+
+ suspend fun setReadMenuBottomBarBlurMode(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuBottomBarBlurMode, value.coerceIn(0, 2))
+
+ suspend fun setReadMenuTopBarLiquidGlassButtons(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.readMenuTopBarLiquidGlassButtons, value)
+
+ suspend fun setReadMenuBottomBarLiquidGlassButtons(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.readMenuBottomBarLiquidGlassButtons, value)
+
+ suspend fun setReadMenuTopBarBlurStyle(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuTopBarBlurStyle, value.coerceIn(0, 1))
+
+ suspend fun setReadMenuBottomBarBlurStyle(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuBottomBarBlurStyle, value.coerceIn(0, 1))
+
+ suspend fun setReadMenuBlurRadius(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuBlurRadius, value.coerceIn(0, 32))
+
+ suspend fun setReadMenuBlurAlpha(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuBlurAlpha, value.coerceIn(0, 100))
+
+ suspend fun setReadMenuLensRadius(value: Float) =
+ settingsRepository.putFloat(PreferKey.readMenuLensRadius, value.coerceIn(0f, 48f))
+
+ suspend fun setReadMenuBorderWidth(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuBorderWidth, value.coerceIn(0, 4))
+
+ suspend fun setReadMenuBorderColor(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuBorderColor, value)
+
+ suspend fun setReadMenuBorderColorNight(value: Int) =
+ settingsRepository.putInt(PreferKey.readMenuBorderColorNight, value)
+
+ suspend fun setReadMenuCustomIcons(value: String) =
+ settingsRepository.putString(PreferKey.readMenuCustomIcons, value)
+
+ suspend fun setTitleBarCustomIcons(value: String) =
+ settingsRepository.putString(PreferKey.titleBarCustomIcons, value)
+
+ suspend fun setTitleBarIconPosition(value: Int) =
+ settingsRepository.putInt(PreferKey.titleBarIconPosition, value.coerceIn(0, 3))
+
+ suspend fun setShowTitleBarIcons(value: Boolean) =
+ settingsRepository.putBoolean(PreferKey.showTitleBarIcons, value)
+
+ suspend fun setChineseConverterType(value: Int) =
+ settingsRepository.putInt(PreferKey.chineseConverterType, value)
+
+ suspend fun setStyleSelect(isComic: Boolean, value: Int) {
+ if (isComic) {
+ setComicStyleSelect(value)
+ } else {
+ setReadStyleSelect(value)
+ }
+ }
+
+ private fun Preferences.toReadPreferences(): ReadPreferences {
+ val readStyleSelect = this[Keys.ReadStyleSelect] ?: 0
+ return ReadPreferences(
+ screenOrientation = this[Keys.ScreenOrientation] ?: "0",
+ keepLight = this[Keys.KeepLight] ?: "0",
+ hideStatusBar = this[Keys.HideStatusBar] ?: false,
+ hideNavigationBar = this[Keys.HideNavigationBar] ?: false,
+ paddingDisplayCutouts = this[Keys.PaddingDisplayCutouts] ?: false,
+ titleBarMode = this[Keys.TitleBarMode] ?: "1",
+ menuAlpha = this[Keys.MenuAlpha] ?: 100,
+ readBodyToLh = this[Keys.ReadBodyToLh] ?: true,
+ defaultSourceChangeAll = this[Keys.DefaultSourceChangeAll] ?: true,
+ textFullJustify = this[Keys.TextFullJustify] ?: true,
+ textBottomJustify = this[Keys.TextBottomJustify] ?: true,
+ adaptSpecialStyle = this[Keys.AdaptSpecialStyle] ?: true,
+ useZhLayout = this[Keys.UseZhLayout] ?: false,
+ showBrightnessView = this[Keys.ShowBrightnessView] ?: true,
+ useUnderline = this[Keys.UseUnderline] ?: false,
+ readSliderMode = this[Keys.ReadSliderMode] ?: "0",
+ doubleHorizontalPage = this[Keys.DoubleHorizontalPage] ?: "0",
+ progressBarBehavior = this[Keys.ProgressBarBehavior] ?: "page",
+ mouseWheelPage = this[Keys.MouseWheelPage] ?: true,
+ volumeKeyPage = this[Keys.VolumeKeyPage] ?: true,
+ volumeKeyPageOnPlay = this[Keys.VolumeKeyPageOnPlay] ?: true,
+ keyPageOnLongPress = this[Keys.KeyPageOnLongPress] ?: false,
+ pageTouchSlop = this[Keys.PageTouchSlop] ?: 0,
+ sliderVibrator = this[Keys.SliderVibrator] ?: false,
+ selectVibrator = this[Keys.SelectVibrator] ?: false,
+ autoChangeSource = this[Keys.AutoChangeSource] ?: true,
+ selectText = this[Keys.SelectText] ?: true,
+ noAnimScrollPage = this[Keys.NoAnimScrollPage] ?: false,
+ clickImgWay = this[Keys.ClickImgWay] ?: "2",
+ optimizeRender = this[Keys.OptimizeRender] ?: false,
+ disableReturnKey = this[Keys.DisableReturnKey] ?: false,
+ expandTextMenu = this[Keys.ExpandTextMenu] ?: false,
+ showReadTitleAddition = this[Keys.ShowReadTitleAddition] ?: true,
+ autoReadSpeed = this[Keys.AutoReadSpeed] ?: 10,
+ prevKeys = this[Keys.PrevKeys] ?: "",
+ nextKeys = this[Keys.NextKeys] ?: "",
+ tocUiUseReplace = this[Keys.TocUiUseReplace] ?: false,
+ tocCountWords = this[Keys.TocCountWords] ?: true,
+ readStyleSelect = readStyleSelect,
+ comicStyleSelect = this[Keys.ComicStyleSelect] ?: readStyleSelect,
+ shareLayout = this[Keys.ShareLayout] ?: false,
+ readBarStyleFollowPage = this[Keys.ReadBarStyleFollowPage] ?: false,
+ readBarStyle = this[Keys.ReadBarStyle] ?: 0,
+ clickActionTL = this[Keys.ClickActionTL] ?: 2,
+ clickActionTC = this[Keys.ClickActionTC] ?: 2,
+ clickActionTR = this[Keys.ClickActionTR] ?: 1,
+ clickActionML = this[Keys.ClickActionML] ?: 2,
+ clickActionMC = this[Keys.ClickActionMC] ?: 0,
+ clickActionMR = this[Keys.ClickActionMR] ?: 1,
+ clickActionBL = this[Keys.ClickActionBL] ?: 2,
+ clickActionBC = this[Keys.ClickActionBC] ?: 1,
+ clickActionBR = this[Keys.ClickActionBR] ?: 1,
+ fontFolder = this[Keys.FontFolder] ?: "",
+ readMenuBgColor = this[Keys.ReadMenuBgColor] ?: 0,
+ readMenuAccentColor = this[Keys.ReadMenuAccentColor] ?: 0,
+ readMenuContainerColor = this[Keys.ReadMenuContainerColor] ?: 0,
+ readMenuBgColorNight = this[Keys.ReadMenuBgColorNight] ?: 0,
+ readMenuAccentColorNight = this[Keys.ReadMenuAccentColorNight] ?: 0,
+ readMenuContainerColorNight = this[Keys.ReadMenuContainerColorNight] ?: 0,
+ readMenuColorMode = this[Keys.ReadMenuColorMode] ?: 1,
+ readMenuIconShowText = this[Keys.ReadMenuIconShowText] ?: true,
+ readMenuIconStyle = this[Keys.ReadMenuIconStyle] ?: 0,
+ readMenuIconItemsPerRow = this[Keys.ReadMenuIconItemsPerRow] ?: 5,
+ readMenuIconRowCount = this[Keys.ReadMenuIconRowCount] ?: 1,
+ readMenuBottomCornerRadius = this[Keys.ReadMenuBottomCornerRadius] ?: 0,
+ readMenuFloatingBottomBar = this[Keys.ReadMenuFloatingBottomBar] ?: false,
+ readMenuTopBarBlurMode = this[Keys.ReadMenuTopBarBlurMode] ?: ReadMenuBlurMode.None,
+ readMenuBottomBarBlurMode = this[Keys.ReadMenuBottomBarBlurMode]
+ ?: ReadMenuBlurMode.None,
+ readMenuTopBarLiquidGlassButtons = this[Keys.ReadMenuTopBarLiquidGlassButtons] ?: false,
+ readMenuBottomBarLiquidGlassButtons = this[Keys.ReadMenuBottomBarLiquidGlassButtons]
+ ?: false,
+ readMenuTopBarBlurStyle = this[Keys.ReadMenuTopBarBlurStyle]
+ ?: ReadMenuBlurStyle.Progressive,
+ readMenuBottomBarBlurStyle = this[Keys.ReadMenuBottomBarBlurStyle]
+ ?: ReadMenuBlurStyle.Solid,
+ readMenuBlurRadius = this[Keys.ReadMenuBlurRadius] ?: 24,
+ readMenuBlurAlpha = this[Keys.ReadMenuBlurAlpha] ?: 60,
+ readMenuLensRadius = this[Keys.ReadMenuLensRadius] ?: 24f,
+ readMenuBorderWidth = this[Keys.ReadMenuBorderWidth] ?: 0,
+ readMenuBorderColor = this[Keys.ReadMenuBorderColor] ?: 0,
+ readMenuBorderColorNight = this[Keys.ReadMenuBorderColorNight] ?: 0,
+ readMenuCustomIcons = this[Keys.ReadMenuCustomIcons] ?: "",
+ titleBarCustomIcons = this[Keys.TitleBarCustomIcons] ?: "",
+ titleBarIconPosition = this[Keys.TitleBarIconPosition] ?: 0,
+ showTitleBarIcons = this[Keys.ShowTitleBarIcons] ?: true,
+ chineseConverterType = this[Keys.ChineseConverterType] ?: 0,
+ )
+ }
+
+ private object Keys {
+ val ScreenOrientation = stringPreferencesKey(PreferKey.screenOrientation)
+ val KeepLight = stringPreferencesKey(PreferKey.keepLight)
+ val HideStatusBar = booleanPreferencesKey(PreferKey.hideStatusBar)
+ val HideNavigationBar = booleanPreferencesKey(PreferKey.hideNavigationBar)
+ val PaddingDisplayCutouts = booleanPreferencesKey(PreferKey.paddingDisplayCutouts)
+ val TitleBarMode = stringPreferencesKey(PreferKey.titleBarMode)
+ val MenuAlpha = intPreferencesKey(PreferKey.menuAlpha)
+ val ReadBodyToLh = booleanPreferencesKey(PreferKey.readBodyToLh)
+ val DefaultSourceChangeAll = booleanPreferencesKey(PreferKey.defaultSourceChangeAll)
+ val TextFullJustify = booleanPreferencesKey(PreferKey.textFullJustify)
+ val TextBottomJustify = booleanPreferencesKey(PreferKey.textBottomJustify)
+ val AdaptSpecialStyle = booleanPreferencesKey(PreferKey.adaptSpecialStyle)
+ val UseZhLayout = booleanPreferencesKey(PreferKey.useZhLayout)
+ val ShowBrightnessView = booleanPreferencesKey(PreferKey.showBrightnessView)
+ val UseUnderline = booleanPreferencesKey(PreferKey.useUnderline)
+ val ReadSliderMode = stringPreferencesKey(PreferKey.readSliderMode)
+ val DoubleHorizontalPage = stringPreferencesKey(PreferKey.doublePageHorizontal)
+ val ProgressBarBehavior = stringPreferencesKey(PreferKey.progressBarBehavior)
+ val MouseWheelPage = booleanPreferencesKey(PreferKey.mouseWheelPage)
+ val VolumeKeyPage = booleanPreferencesKey(PreferKey.volumeKeyPage)
+ val VolumeKeyPageOnPlay = booleanPreferencesKey(PreferKey.volumeKeyPageOnPlay)
+ val KeyPageOnLongPress = booleanPreferencesKey(PreferKey.keyPageOnLongPress)
+ val PageTouchSlop = intPreferencesKey(PreferKey.pageTouchSlop)
+ val SliderVibrator = booleanPreferencesKey(PreferKey.sliderVibrator)
+ val SelectVibrator = booleanPreferencesKey(PreferKey.selectVibrator)
+ val AutoChangeSource = booleanPreferencesKey(PreferKey.autoChangeSource)
+ val SelectText = booleanPreferencesKey(PreferKey.selectText)
+ val NoAnimScrollPage = booleanPreferencesKey(PreferKey.noAnimScrollPage)
+ val ClickImgWay = stringPreferencesKey(PreferKey.clickImgWay)
+ val OptimizeRender = booleanPreferencesKey(PreferKey.optimizeRender)
+ val DisableReturnKey = booleanPreferencesKey(PreferKey.disableReturnKey)
+ val ExpandTextMenu = booleanPreferencesKey(PreferKey.expandTextMenu)
+ val ShowReadTitleAddition = booleanPreferencesKey(PreferKey.showReadTitleAddition)
+ val AutoReadSpeed = intPreferencesKey(PreferKey.autoReadSpeed)
+ val PrevKeys = stringPreferencesKey(PreferKey.prevKeys)
+ val NextKeys = stringPreferencesKey(PreferKey.nextKeys)
+ val TocUiUseReplace = booleanPreferencesKey(PreferKey.tocUiUseReplace)
+ val TocCountWords = booleanPreferencesKey(PreferKey.tocCountWords)
+ val ReadStyleSelect = intPreferencesKey(PreferKey.readStyleSelect)
+ val ComicStyleSelect = intPreferencesKey(PreferKey.comicStyleSelect)
+ val ShareLayout = booleanPreferencesKey(PreferKey.shareLayout)
+ val ReadBarStyleFollowPage = booleanPreferencesKey(PreferKey.readBarStyleFollowPage)
+ val ReadBarStyle = intPreferencesKey(PreferKey.readBarStyle)
+ val ClickActionTL = intPreferencesKey(PreferKey.clickActionTL)
+ val ClickActionTC = intPreferencesKey(PreferKey.clickActionTC)
+ val ClickActionTR = intPreferencesKey(PreferKey.clickActionTR)
+ val ClickActionML = intPreferencesKey(PreferKey.clickActionML)
+ val ClickActionMC = intPreferencesKey(PreferKey.clickActionMC)
+ val ClickActionMR = intPreferencesKey(PreferKey.clickActionMR)
+ val ClickActionBL = intPreferencesKey(PreferKey.clickActionBL)
+ val ClickActionBC = intPreferencesKey(PreferKey.clickActionBC)
+ val ClickActionBR = intPreferencesKey(PreferKey.clickActionBR)
+ val FontFolder = stringPreferencesKey(PreferKey.fontFolder)
+ val ReadMenuBgColor = intPreferencesKey(PreferKey.readMenuBgColor)
+ val ReadMenuAccentColor = intPreferencesKey(PreferKey.readMenuAccentColor)
+ val ReadMenuContainerColor = intPreferencesKey(PreferKey.readMenuContainerColor)
+ val ReadMenuBgColorNight = intPreferencesKey(PreferKey.readMenuBgColorNight)
+ val ReadMenuAccentColorNight = intPreferencesKey(PreferKey.readMenuAccentColorNight)
+ val ReadMenuContainerColorNight = intPreferencesKey(PreferKey.readMenuContainerColorNight)
+ val ReadMenuColorMode = intPreferencesKey(PreferKey.readMenuColorMode)
+ val ReadMenuIconShowText = booleanPreferencesKey(PreferKey.readMenuIconShowText)
+ val ReadMenuIconStyle = intPreferencesKey(PreferKey.readMenuIconStyle)
+ val ReadMenuIconItemsPerRow = intPreferencesKey(PreferKey.readMenuIconItemsPerRow)
+ val ReadMenuIconRowCount = intPreferencesKey(PreferKey.readMenuIconRowCount)
+ val ReadMenuBottomCornerRadius = intPreferencesKey(PreferKey.readMenuBottomCornerRadius)
+ val ReadMenuFloatingBottomBar = booleanPreferencesKey(PreferKey.readMenuFloatingBottomBar)
+ val ReadMenuTopBarBlurMode = intPreferencesKey(PreferKey.readMenuTopBarBlurMode)
+ val ReadMenuBottomBarBlurMode = intPreferencesKey(PreferKey.readMenuBottomBarBlurMode)
+ val ReadMenuTopBarLiquidGlassButtons =
+ booleanPreferencesKey(PreferKey.readMenuTopBarLiquidGlassButtons)
+ val ReadMenuBottomBarLiquidGlassButtons =
+ booleanPreferencesKey(PreferKey.readMenuBottomBarLiquidGlassButtons)
+ val ReadMenuTopBarBlurStyle = intPreferencesKey(PreferKey.readMenuTopBarBlurStyle)
+ val ReadMenuBottomBarBlurStyle = intPreferencesKey(PreferKey.readMenuBottomBarBlurStyle)
+ val ReadMenuBlurRadius = intPreferencesKey(PreferKey.readMenuBlurRadius)
+ val ReadMenuBlurAlpha = intPreferencesKey(PreferKey.readMenuBlurAlpha)
+ val ReadMenuLensRadius = floatPreferencesKey(PreferKey.readMenuLensRadius)
+ val ReadMenuBorderWidth = intPreferencesKey(PreferKey.readMenuBorderWidth)
+ val ReadMenuBorderColor = intPreferencesKey(PreferKey.readMenuBorderColor)
+ val ReadMenuBorderColorNight = intPreferencesKey(PreferKey.readMenuBorderColorNight)
+ val ReadMenuCustomIcons = stringPreferencesKey(PreferKey.readMenuCustomIcons)
+ val TitleBarCustomIcons = stringPreferencesKey(PreferKey.titleBarCustomIcons)
+ val TitleBarIconPosition = intPreferencesKey(PreferKey.titleBarIconPosition)
+ val ShowTitleBarIcons = booleanPreferencesKey(PreferKey.showTitleBarIcons)
+ val ChineseConverterType = intPreferencesKey(PreferKey.chineseConverterType)
+ }
+}
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
new file mode 100644
index 000000000..565c03f13
--- /dev/null
+++ b/app/src/main/java/io/legado/app/data/repository/ReadStyleRepository.kt
@@ -0,0 +1,255 @@
+package io.legado.app.data.repository
+
+import androidx.core.graphics.toColorInt
+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.utils.FileUtils
+import io.legado.app.utils.GSON
+import io.legado.app.utils.compress.ZipUtils
+import io.legado.app.utils.createFolderReplace
+import io.legado.app.utils.externalCache
+import io.legado.app.utils.externalFiles
+import io.legado.app.utils.fromJsonArray
+import io.legado.app.utils.fromJsonObject
+import io.legado.app.utils.getFile
+import io.legado.app.utils.printOnDebug
+import splitties.init.appCtx
+import java.io.File
+import java.io.InputStream
+
+class ReadStyleRepository {
+
+ val configFilePath: String =
+ FileUtils.getPath(appCtx.filesDir, ReadBookConfig.configFileName)
+ val shareConfigFilePath: String =
+ FileUtils.getPath(appCtx.filesDir, ReadBookConfig.shareConfigFileName)
+
+ fun readConfigs(): List {
+ val configFile = File(configFilePath)
+ if (configFile.exists()) {
+ try {
+ return GSON.fromJsonArray(configFile.readText()).getOrThrow()
+ } catch (e: Exception) {
+ AppLog.put("读取排版配置文件出错", e)
+ }
+ }
+ return DefaultData.readConfigs
+ }
+
+ fun readShareConfig(fallbackConfig: ReadBookConfig.Config): ReadBookConfig.Config {
+ val configFile = File(shareConfigFilePath)
+ if (configFile.exists()) {
+ try {
+ return GSON.fromJsonObject(configFile.readText()).getOrThrow()
+ } catch (e: Exception) {
+ e.printOnDebug()
+ }
+ }
+ return fallbackConfig
+ }
+
+ fun save(
+ configs: List,
+ shareConfig: ReadBookConfig.Config
+ ) {
+ GSON.toJson(configs).let {
+ FileUtils.delete(configFilePath)
+ FileUtils.createFileIfNotExist(configFilePath).writeText(it)
+ }
+ GSON.toJson(shareConfig).let {
+ FileUtils.delete(shareConfigFilePath)
+ FileUtils.createFileIfNotExist(shareConfigFilePath).writeText(it)
+ }
+ }
+
+ fun getAllPicBgStr(configs: List): ArrayList {
+ val list = arrayListOf()
+ configs.forEach {
+ if (it.bgType == 2) {
+ list.add(it.bgStr)
+ }
+ if (it.bgTypeNight == 2) {
+ list.add(it.bgStrNight)
+ }
+ if (it.bgTypeEInk == 2) {
+ list.add(it.bgStrEInk)
+ }
+ }
+ return list
+ }
+
+ fun clearBgAndCache(configs: List) {
+ val bgs = hashSetOf()
+ configs.forEach { config ->
+ repeat(3) {
+ config.getBgPath(it)?.let { path ->
+ bgs.add(path)
+ }
+ }
+ }
+ appCtx.externalFiles.getFile("bg").listFiles()?.forEach {
+ if (!bgs.contains(it.absolutePath)) {
+ it.delete()
+ }
+ }
+ FileUtils.delete(appCtx.externalCache.getFile("readConfig"))
+ FileUtils.delete(FileUtils.getPath(appCtx.externalCache, "readConfig.zip"))
+ }
+
+ fun saveBackgroundImage(inputStream: InputStream, displayName: String?): String {
+ val bgDir = appCtx.externalFiles.getFile("bg")
+ bgDir.mkdirs()
+ val safeName = displayName
+ ?.let { File(it).name }
+ ?.takeIf { it.isNotBlank() }
+ ?: "read_bg.jpg"
+ val baseName = File(safeName).nameWithoutExtension.ifBlank { "read_bg" }
+ val extension = File(safeName).extension.ifBlank { "jpg" }
+ val bgFile = File(bgDir, "${baseName}_${System.currentTimeMillis()}.$extension")
+ if (!FileUtils.writeInputStream(bgFile, inputStream)) {
+ error("save read background image failed")
+ }
+ return bgFile.absolutePath
+ }
+
+ fun export(config: ReadBookConfig.Config): ByteArray {
+ val exportDir = appCtx.externalCache.getFile("readConfigExport")
+ exportDir.createFolderReplace()
+ val exportConfig = config.copy(
+ regexColorRules = ArrayList(config.regexColorRules.map { it.copy() })
+ )
+ val exportFiles = arrayListOf()
+
+ addBackgroundFile(exportDir, exportConfig, 0, exportFiles)
+ addBackgroundFile(exportDir, exportConfig, 1, exportFiles)
+ addBackgroundFile(exportDir, exportConfig, 2, exportFiles)
+ exportConfig.textFont = addAssetFile(exportDir, exportConfig.textFont, exportFiles)
+ exportConfig.titleFont = addAssetFile(exportDir, exportConfig.titleFont, exportFiles)
+
+ val configFile = exportDir.getFile(ReadBookConfig.configFileName)
+ configFile.writeText(GSON.toJson(exportConfig))
+ exportFiles.add(configFile)
+
+ val zipFile = appCtx.externalCache.getFile("readConfig.zip")
+ FileUtils.delete(zipFile)
+ ZipUtils.zipFiles(exportFiles, zipFile)
+ return zipFile.readBytes()
+ }
+
+ fun import(byteArray: ByteArray): ReadBookConfig.Config {
+ val configZipPath = FileUtils.getPath(appCtx.externalCache, "readConfig.zip")
+ FileUtils.delete(configZipPath)
+ val zipFile = FileUtils.createFileIfNotExist(configZipPath)
+ zipFile.writeBytes(byteArray)
+ val configDir = appCtx.externalCache.getFile("readConfig")
+ configDir.createFolderReplace()
+ ZipUtils.unZipToPath(zipFile, configDir)
+ val configFile = configDir.getFile(ReadBookConfig.configFileName)
+ val config: ReadBookConfig.Config =
+ GSON.fromJsonObject(configFile.readText()).getOrThrow()
+
+ config.textFont = importFont(configDir, config.textFont)
+ config.titleFont = importFont(configDir, config.titleFont)
+
+ if (config.bgType == 2) {
+ val bgName = FileUtils.getName(config.bgStr)
+ config.bgStr = bgName
+ val bgPath = FileUtils.getPath(appCtx.externalFiles, "bg", bgName)
+ if (!FileUtils.exist(bgPath)) {
+ val bgFile = configDir.getFile(bgName)
+ if (bgFile.exists()) {
+ bgFile.copyTo(File(bgPath))
+ }
+ }
+ config.bgStr = bgPath
+ } else if (config.bgTypeNight == 0) {
+ config.bgStrNight.toColorInt()
+ }
+ if (config.bgTypeNight == 2) {
+ val bgName = FileUtils.getName(config.bgStrNight)
+ config.bgStrNight = bgName
+ val bgPath = FileUtils.getPath(appCtx.externalFiles, "bg", bgName)
+ if (!FileUtils.exist(bgPath)) {
+ val bgFile = configDir.getFile(bgName)
+ if (bgFile.exists()) {
+ bgFile.copyTo(File(bgPath))
+ }
+ }
+ config.bgStrNight = bgPath
+ }
+ if (config.bgTypeEInk == 2) {
+ val bgName = FileUtils.getName(config.bgStrEInk)
+ config.bgStrEInk = bgName
+ val bgPath = FileUtils.getPath(appCtx.externalFiles, "bg", bgName)
+ if (!FileUtils.exist(bgPath)) {
+ val bgFile = configDir.getFile(bgName)
+ if (bgFile.exists()) {
+ bgFile.copyTo(File(bgPath))
+ }
+ }
+ config.bgStrEInk = bgPath
+ } else if (config.bgTypeEInk == 0) {
+ config.bgStrEInk.toColorInt()
+ }
+ config.curTextColor()
+ config.curTextAccentColor()
+ config.curTextShadowColor()
+ return config
+ }
+
+ private fun addBackgroundFile(
+ exportDir: File,
+ config: ReadBookConfig.Config,
+ bgIndex: Int,
+ exportFiles: MutableList
+ ) {
+ val sourcePath = ReadStyleResolver.backgroundPath(config, bgIndex) ?: return
+ val exportedName = addAssetFile(exportDir, sourcePath, exportFiles)
+ if (exportedName.isBlank()) {
+ return
+ }
+ when (bgIndex) {
+ 0 -> config.bgStr = exportedName
+ 1 -> config.bgStrNight = exportedName
+ 2 -> config.bgStrEInk = exportedName
+ }
+ }
+
+ private fun addAssetFile(
+ exportDir: File,
+ sourcePath: String,
+ exportFiles: MutableList
+ ): String {
+ if (sourcePath.isBlank()) {
+ return ""
+ }
+ val source = File(sourcePath)
+ if (!source.exists() || !source.isFile) {
+ return ""
+ }
+ val target = exportDir.getFile(source.name)
+ source.copyTo(target, overwrite = true)
+ if (exportFiles.none { it.absolutePath == target.absolutePath }) {
+ exportFiles.add(target)
+ }
+ return target.name
+ }
+
+ private fun importFont(configDir: File, fontName: String): String {
+ if (fontName.isEmpty()) {
+ return ""
+ }
+ val fontPath = FileUtils.getPath(appCtx.externalFiles, "font", fontName)
+ val fontFile = configDir.getFile(fontName)
+ return if (fontFile.exists()) {
+ if (!FileUtils.exist(fontPath)) {
+ fontFile.copyTo(File(fontPath))
+ }
+ fontPath
+ } else {
+ ""
+ }
+ }
+}
diff --git a/app/src/main/java/io/legado/app/data/repository/SettingsRepository.kt b/app/src/main/java/io/legado/app/data/repository/SettingsRepository.kt
index f7a95a757..3b55c5fee 100644
--- a/app/src/main/java/io/legado/app/data/repository/SettingsRepository.kt
+++ b/app/src/main/java/io/legado/app/data/repository/SettingsRepository.kt
@@ -84,6 +84,17 @@ class SettingsRepository(private val context: Context) {
suspend fun putString(key: String, value: String) =
updatePreference(stringPreferencesKey(key), value)
+ suspend fun putStrings(values: Map) {
+ dataStore.edit { preferences ->
+ values.forEach { (key, value) ->
+ preferences[stringPreferencesKey(key)] = value
+ }
+ }
+ values.forEach { (key, value) ->
+ syncToSharedPrefs(key, value)
+ }
+ }
+
// Int 类型的快捷访问
fun getInt(key: String, defaultValue: Int = 0): Flow =
getPreference(intPreferencesKey(key), defaultValue)
diff --git a/app/src/main/java/io/legado/app/di/appDatabaseModule.kt b/app/src/main/java/io/legado/app/di/appDatabaseModule.kt
index a99a74df3..09fe510bf 100644
--- a/app/src/main/java/io/legado/app/di/appDatabaseModule.kt
+++ b/app/src/main/java/io/legado/app/di/appDatabaseModule.kt
@@ -38,4 +38,5 @@ val appDatabaseModule = module {
factory { get().serverDao }
factory { get().homepageModuleDao }
factory { get().homepageCustomSetDao }
+ factory { get().highlightRuleDao }
}
\ No newline at end of file
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 49486a793..ffee68a2f 100644
--- a/app/src/main/java/io/legado/app/di/appModule.kt
+++ b/app/src/main/java/io/legado/app/di/appModule.kt
@@ -25,7 +25,11 @@ import io.legado.app.data.repository.ExploreRepositoryImpl
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.MangaSettingsRepository
+import io.legado.app.data.repository.ReadBookStyleConfigRepository
+import io.legado.app.data.repository.ReadAloudSettingsRepository
import io.legado.app.data.repository.ReadRecordRepository
+import io.legado.app.data.repository.ReadSettingsRepository
import io.legado.app.data.repository.RemoteBookRepository
import io.legado.app.data.repository.RssRepository
import io.legado.app.data.repository.SearchContentRepository
@@ -57,11 +61,13 @@ import io.legado.app.domain.usecase.AppStartupMaintenanceUseCase
import io.legado.app.domain.usecase.BatchCacheDownloadUseCase
import io.legado.app.domain.usecase.CacheBookChaptersUseCase
import io.legado.app.domain.usecase.ChangeBookSourceUseCase
+import io.legado.app.domain.usecase.ChangeSourceSearchUseCase
import io.legado.app.domain.usecase.ClearBookCacheUseCase
import io.legado.app.domain.usecase.DeleteBooksUseCase
import io.legado.app.domain.usecase.ExploreBooksUseCase
import io.legado.app.domain.usecase.ExploreKindUiUseCase
import io.legado.app.domain.usecase.ExportBookshelfUseCase
+import io.legado.app.domain.usecase.GetChapterContentUseCase
import io.legado.app.domain.usecase.GetReadingProgressUseCase
import io.legado.app.domain.usecase.ImportBookshelfUseCase
import io.legado.app.domain.usecase.RefreshTocUseCase
@@ -85,6 +91,7 @@ import io.legado.app.ui.book.cache.manage.BookCacheManageViewModel
import io.legado.app.ui.book.changecover.ChangeCoverViewModel
import io.legado.app.ui.book.changesource.ChangeBookSourceComposeViewModel
import io.legado.app.ui.book.changesource.ChangeBookSourceViewModel
+import io.legado.app.ui.book.changesource.ChangeChapterSourceViewModel
import io.legado.app.ui.book.explore.ExploreShowViewModel
import io.legado.app.ui.book.group.GroupViewModel
import io.legado.app.ui.book.import.local.ImportBookViewModel
@@ -147,6 +154,10 @@ val appModule = module {
singleOf(::SearchContentRepository)
singleOf(::RemoteBookRepository)
singleOf(::SettingsRepository)
+ singleOf(::ReadSettingsRepository)
+ singleOf(::ReadAloudSettingsRepository)
+ singleOf(::ReadBookStyleConfigRepository)
+ singleOf(::MangaSettingsRepository)
singleOf(::LocalPreferencesRepository)
singleOf(::ExploreBooksUseCase)
singleOf(::ExploreKindUiUseCase)
@@ -194,6 +205,8 @@ val appModule = module {
single { get() }
single { get() }
singleOf(::SearchBooksUseCase)
+ singleOf(::ChangeSourceSearchUseCase)
+ singleOf(::GetChapterContentUseCase)
single { LlmTranslateRepositoryImpl() }
single { DictionaryRepositoryImpl() }
singleOf(::TranslateChapterUseCase)
@@ -252,12 +265,16 @@ val appModule = module {
application = get(),
getReadingProgressUseCase = get(),
uploadReadingProgressUseCase = get(),
- translateChapterUseCase = get()
+ translateChapterUseCase = get(),
+ readSettingsRepository = get(),
+ readBookStyleConfigRepository = get(),
+ readAloudSettingsRepository = get()
)
}
viewModelOf(::ChangeCoverViewModel)
viewModelOf(::ChangeBookSourceComposeViewModel)
viewModelOf(::ChangeBookSourceViewModel)
+ viewModelOf(::ChangeChapterSourceViewModel)
viewModelOf(::ExploreViewModel)
viewModelOf(::RssViewModel)
viewModelOf(::SearchViewModel)
diff --git a/app/src/main/java/io/legado/app/domain/usecase/ChangeSourceSearchUseCase.kt b/app/src/main/java/io/legado/app/domain/usecase/ChangeSourceSearchUseCase.kt
new file mode 100644
index 000000000..e1126c76b
--- /dev/null
+++ b/app/src/main/java/io/legado/app/domain/usecase/ChangeSourceSearchUseCase.kt
@@ -0,0 +1,276 @@
+package io.legado.app.domain.usecase
+
+import io.legado.app.data.entities.Book
+import io.legado.app.data.entities.BookChapter
+import io.legado.app.data.entities.BookSource
+import io.legado.app.data.entities.SearchBook
+import io.legado.app.domain.gateway.BookSearchGateway
+import io.legado.app.help.book.BookHelp
+import io.legado.app.help.book.ContentProcessor
+import io.legado.app.help.book.primaryStr
+import io.legado.app.help.book.releaseHtmlData
+import io.legado.app.help.config.AppConfig
+import io.legado.app.help.source.SourceHelp
+import io.legado.app.model.webBook.WebBook
+import io.legado.app.ui.book.changesource.ObservableSourceConfig
+import io.legado.app.ui.config.otherConfig.OtherConfig
+import io.legado.app.utils.internString
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.currentCoroutineContext
+import kotlinx.coroutines.ensureActive
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.withTimeout
+import java.util.concurrent.ConcurrentHashMap
+
+sealed interface ChangeSourceSearchEvent {
+ data object Started : ChangeSourceSearchEvent
+ data class Progress(
+ val processedSources: Int,
+ val totalSources: Int,
+ val resultCount: Int,
+ val sourceName: String,
+ ) : ChangeSourceSearchEvent
+
+ data class Result(val searchBook: SearchBook) : ChangeSourceSearchEvent
+ data class Finished(val isEmpty: Boolean) : ChangeSourceSearchEvent
+}
+
+class ChangeSourceSearchUseCase(
+ private val gateway: BookSearchGateway,
+) {
+ private val threadCount = OtherConfig.threadCount
+ private val contentProcessor by lazy {
+ // ContentProcessor needs the old book - will be set before search
+ null as ContentProcessor?
+ }
+
+ // Shared state for TOC cache
+ private val tocMap = ConcurrentHashMap>()
+ private val bookMap = ConcurrentHashMap()
+ private var tocMapChapterCount = 0
+
+ fun search(
+ name: String,
+ author: String,
+ scope: io.legado.app.ui.book.search.SearchScope,
+ oldBook: Book,
+ fromReadBookActivity: Boolean,
+ ): Flow = flow {
+ val contentProcessor = ContentProcessor.get(oldBook)
+ val bookSourceParts = scope.getBookSourceParts()
+ if (bookSourceParts.isEmpty()) {
+ throw io.legado.app.exception.NoStackTraceException("启用书源为空")
+ }
+
+ tocMap.clear()
+ bookMap.clear()
+ tocMapChapterCount = 0
+
+ emit(ChangeSourceSearchEvent.Started)
+
+ var processedSources = 0
+ val totalSources = bookSourceParts.size
+
+ for (bs in bookSourceParts) {
+ currentCoroutineContext().ensureActive()
+ val source = bs.getBookSource() ?: continue
+ try {
+ withTimeout(60000L) {
+ searchSource(
+ source, name, author, oldBook, fromReadBookActivity,
+ contentProcessor
+ )
+ }.forEach { searchBook ->
+ emit(ChangeSourceSearchEvent.Result(searchBook))
+ }
+ } catch (_: Throwable) {
+ currentCoroutineContext().ensureActive()
+ }
+ processedSources++
+ emit(
+ ChangeSourceSearchEvent.Progress(
+ processedSources = processedSources,
+ totalSources = totalSources,
+ resultCount = 0,
+ sourceName = source.bookSourceName,
+ )
+ )
+ }
+
+ emit(ChangeSourceSearchEvent.Finished(isEmpty = true))
+ }.flowOn(Dispatchers.IO)
+
+ private suspend fun searchSource(
+ source: BookSource,
+ name: String,
+ author: String,
+ oldBook: Book,
+ fromReadBookActivity: Boolean,
+ contentProcessor: ContentProcessor,
+ ): List {
+ val checkAuthor = AppConfig.changeSourceCheckAuthor
+ val loadInfo = AppConfig.changeSourceLoadInfo
+ val loadToc = AppConfig.changeSourceLoadToc
+ val loadWordCount = AppConfig.changeSourceLoadWordCount
+
+ val resultBooks = WebBook.searchBookAwait(
+ source, name,
+ filter = { fName, fAuthor, _ ->
+ fName == name && (!checkAuthor || fAuthor.contains(author))
+ }
+ )
+
+ val processedBooks = mutableListOf()
+ for (searchBook in resultBooks) {
+ currentCoroutineContext().ensureActive()
+ when {
+ loadInfo || loadToc || loadWordCount -> {
+ val book = searchBook.toBook()
+ try {
+ loadBookInfo(
+ source,
+ book,
+ loadToc,
+ loadWordCount,
+ oldBook,
+ fromReadBookActivity,
+ contentProcessor
+ )
+ val processedSearchBook = book.toSearchBook()
+ processedBooks.add(processedSearchBook)
+ } catch (e: Throwable) {
+ if (e is CancellationException) throw e
+ processedBooks.add(searchBook)
+ }
+ }
+
+ else -> {
+ processedBooks.add(searchBook)
+ }
+ }
+ }
+ return processedBooks
+ }
+
+ private suspend fun loadBookInfo(
+ source: BookSource,
+ book: Book,
+ loadToc: Boolean,
+ loadWordCount: Boolean,
+ oldBook: Book,
+ fromReadBookActivity: Boolean,
+ contentProcessor: ContentProcessor,
+ ) {
+ if (book.tocUrl.isEmpty()) {
+ WebBook.getBookInfoAwait(source, book)
+ }
+ if (loadToc || loadWordCount) {
+ loadBookToc(
+ source,
+ book,
+ loadWordCount,
+ oldBook,
+ fromReadBookActivity,
+ contentProcessor
+ )
+ }
+ }
+
+ private suspend fun loadBookToc(
+ source: BookSource,
+ book: Book,
+ loadWordCount: Boolean,
+ oldBook: Book,
+ fromReadBookActivity: Boolean,
+ contentProcessor: ContentProcessor,
+ ) {
+ val chapters = WebBook.getChapterListAwait(source, book).getOrThrow()
+ for (chapter in chapters) {
+ chapter.internString()
+ }
+ if (tocMapChapterCount < 30000) {
+ tocMapChapterCount += chapters.size
+ tocMap[book.primaryStr()] = chapters
+ }
+ bookMap[book.primaryStr()] = book
+ book.releaseHtmlData()
+ if (loadWordCount) {
+ loadBookWordCount(
+ source,
+ book,
+ chapters,
+ oldBook,
+ fromReadBookActivity,
+ contentProcessor
+ )
+ }
+ }
+
+ private suspend fun loadBookWordCount(
+ source: BookSource,
+ book: Book,
+ chapters: List,
+ oldBook: Book,
+ fromReadBookActivity: Boolean,
+ contentProcessor: ContentProcessor,
+ ) {
+ if (chapters.isEmpty()) return
+ val chapterIndex = if (fromReadBookActivity) {
+ BookHelp.getDurChapter(oldBook, chapters)
+ } else {
+ chapters.lastIndex
+ }
+ if (chapterIndex !in chapters.indices) return
+ val bookChapter = chapters[chapterIndex]
+ var title = bookChapter.title.trim()
+ if (title.length > 20) {
+ title = title.substring(0, 20) + "…"
+ }
+ val startTime = System.currentTimeMillis()
+ try {
+ val nextChapterUrl = chapters.getOrNull(chapterIndex + 1)?.url
+ var content = WebBook.getContentAwait(source, book, bookChapter, nextChapterUrl, false)
+ content = contentProcessor.getContent(oldBook, bookChapter, content, false).toString()
+ val len = content.length
+ val endTime = System.currentTimeMillis()
+ book.toSearchBook().apply {
+ chapterWordCountText = "[${chapterIndex + 1}] ${title}\n字数:${len}"
+ chapterWordCount = len
+ respondTime = (endTime - startTime).toInt()
+ }
+ } catch (t: Throwable) {
+ if (t is CancellationException) throw t
+ val endTime = System.currentTimeMillis()
+ book.toSearchBook().apply {
+ chapterWordCountText =
+ "[${chapterIndex + 1}] ${title}\n获取字数失败:${t.localizedMessage}"
+ chapterWordCount = -1
+ respondTime = (endTime - startTime).toInt()
+ }
+ }
+ }
+
+ // Source management
+ fun topSource(searchBook: SearchBook) {
+ ObservableSourceConfig.setBookScore(searchBook, 1)
+ }
+
+ fun bottomSource(searchBook: SearchBook) {
+ ObservableSourceConfig.setBookScore(searchBook, 0)
+ }
+
+ fun disableSource(searchBook: SearchBook) {
+ io.legado.app.data.appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source ->
+ source.enabled = false
+ io.legado.app.data.appDb.bookSourceDao.update(source)
+ }
+ }
+
+ fun deleteSource(searchBook: SearchBook) {
+ SourceHelp.deleteBookSource(searchBook.origin)
+ io.legado.app.data.appDb.searchBookDao.delete(searchBook)
+ }
+}
diff --git a/app/src/main/java/io/legado/app/domain/usecase/GetChapterContentUseCase.kt b/app/src/main/java/io/legado/app/domain/usecase/GetChapterContentUseCase.kt
new file mode 100644
index 000000000..eaf2d34d9
--- /dev/null
+++ b/app/src/main/java/io/legado/app/domain/usecase/GetChapterContentUseCase.kt
@@ -0,0 +1,48 @@
+package io.legado.app.domain.usecase
+
+import io.legado.app.data.dao.BookChapterDao
+import io.legado.app.data.dao.BookSourceDao
+import io.legado.app.data.entities.Book
+import io.legado.app.data.entities.BookChapter
+import io.legado.app.data.entities.BookSource
+import io.legado.app.exception.NoStackTraceException
+import io.legado.app.model.webBook.WebBook
+
+class GetChapterContentUseCase(
+ private val bookSourceDao: BookSourceDao,
+ private val bookChapterDao: BookChapterDao,
+) {
+
+ /**
+ * Get TOC for a book. If tocUrl is empty, fetches book info first.
+ */
+ suspend fun getToc(book: Book): Pair, BookSource> {
+ val source = bookSourceDao.getBookSource(book.origin)
+ ?: throw NoStackTraceException("书源不存在")
+ if (book.tocUrl.isEmpty()) {
+ WebBook.getBookInfoAwait(source, book)
+ }
+ val toc = WebBook.getChapterListAwait(source, book).getOrThrow()
+ return Pair(toc, source)
+ }
+
+ /**
+ * Get content for a specific chapter.
+ */
+ suspend fun getContent(
+ book: Book,
+ chapter: BookChapter,
+ nextChapterUrl: String?,
+ ): String {
+ val bookSource = bookSourceDao.getBookSource(book.origin)
+ ?: throw NoStackTraceException("书源不存在")
+ return WebBook.getContentAwait(bookSource, book, chapter, nextChapterUrl, false)
+ }
+
+ /**
+ * Find the chapter index in a new TOC matching the current chapter.
+ */
+ fun getDurChapterIndex(chapterIndex: Int, chapterTitle: String, toc: List): Int {
+ return io.legado.app.help.book.BookHelp.getDurChapter(chapterIndex, chapterTitle, toc)
+ }
+}
diff --git a/app/src/main/java/io/legado/app/help/DefaultData.kt b/app/src/main/java/io/legado/app/help/DefaultData.kt
index 0d70c5993..6a4ca6889 100644
--- a/app/src/main/java/io/legado/app/help/DefaultData.kt
+++ b/app/src/main/java/io/legado/app/help/DefaultData.kt
@@ -8,7 +8,7 @@ import io.legado.app.data.entities.KeyboardAssist
import io.legado.app.data.entities.RssSource
import io.legado.app.data.entities.TxtTocRule
import io.legado.app.help.config.LocalConfig
-import io.legado.app.help.config.OldThemeConfig
+import io.legado.app.help.config.ThemeConfigStore
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.BookCover
@@ -70,12 +70,12 @@ object DefaultData {
GSON.fromJsonArray(json).getOrNull() ?: emptyList()
}
- val themeConfigs: List by lazy {
+ val themeConfigs: List by lazy {
val json = String(
- appCtx.assets.open("defaultData${File.separator}${OldThemeConfig.configFileName}")
+ appCtx.assets.open("defaultData${File.separator}${ThemeConfigStore.configFileName}")
.readBytes()
)
- GSON.fromJsonArray(json).getOrNull() ?: emptyList()
+ GSON.fromJsonArray(json).getOrNull() ?: emptyList()
}
val rssSources: List by lazy {
diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt
index 60831624d..04d476a73 100644
--- a/app/src/main/java/io/legado/app/help/JsExtensions.kt
+++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt
@@ -15,7 +15,7 @@ import io.legado.app.constant.AppPattern
import io.legado.app.data.entities.BaseSource
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig
-import io.legado.app.help.config.OldThemeConfig
+import io.legado.app.help.config.ThemeConfigStore
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.http.BackstageWebView
import io.legado.app.help.http.CookieManager.cookieJarHeader
@@ -1204,12 +1204,12 @@ interface JsExtensions : JsEncodeUtils {
*/
@JavascriptInterface
fun getThemeConfig(): String {
- val themeConfig = OldThemeConfig.getDurConfig(appCtx)
+ val themeConfig = ThemeConfigStore.getDurConfig(appCtx)
return GSON.toJson(themeConfig)
}
fun getThemeConfigMap(): Map {
- return OldThemeConfig.getDurConfig(appCtx).toMap()
+ return ThemeConfigStore.getDurConfig(appCtx).toMap()
}
}
diff --git a/app/src/main/java/io/legado/app/help/config/AppConfig.kt b/app/src/main/java/io/legado/app/help/config/AppConfig.kt
index 944b5f8cd..f28dd06d8 100644
--- a/app/src/main/java/io/legado/app/help/config/AppConfig.kt
+++ b/app/src/main/java/io/legado/app/help/config/AppConfig.kt
@@ -5,6 +5,7 @@ import android.os.Build
import io.legado.app.BuildConfig
import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb
+import io.legado.app.data.repository.ReadPreferences
import io.legado.app.ui.book.manga.config.MangaScrollMode
import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import io.legado.app.utils.getPrefBoolean
@@ -64,8 +65,134 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
var adaptSpecialStyle = appCtx.getPrefBoolean(PreferKey.adaptSpecialStyle, true)
var useUnderline = appCtx.getPrefBoolean(PreferKey.useUnderline, false)
+ private var screenOrientationValue = appCtx.getPrefString(PreferKey.screenOrientation) ?: "0"
+ private var noAnimScrollPageValue = appCtx.getPrefBoolean(PreferKey.noAnimScrollPage, false)
+ private var tocUiUseReplaceValue = appCtx.getPrefBoolean(PreferKey.tocUiUseReplace)
+ private var tocCountWordsValue = appCtx.getPrefBoolean(PreferKey.tocCountWords, true)
+ private var autoChangeSourceValue = appCtx.getPrefBoolean(PreferKey.autoChangeSource, true)
+ private var clickImgWayValue = appCtx.getPrefString(PreferKey.clickImgWay, "2") ?: "2"
+ private var doublePageHorizontalValue = appCtx.getPrefString(PreferKey.doublePageHorizontal, "0") ?: "0"
+ private var progressBarBehaviorValue = appCtx.getPrefString(PreferKey.progressBarBehavior, "page") ?: "page"
+ private var keyPageOnLongPressValue = appCtx.getPrefBoolean(PreferKey.keyPageOnLongPress, false)
+ private var volumeKeyPageValue = appCtx.getPrefBoolean(PreferKey.volumeKeyPage, true)
+ private var volumeKeyPageOnPlayValue = appCtx.getPrefBoolean(PreferKey.volumeKeyPageOnPlay, true)
+ private var mouseWheelPageValue = appCtx.getPrefBoolean(PreferKey.mouseWheelPage, true)
+ private var paddingDisplayCutoutsValue = appCtx.getPrefBoolean(PreferKey.paddingDisplayCutouts, false)
+ private var pageTouchSlopValue = appCtx.getPrefInt(PreferKey.pageTouchSlop, 0)
+ private var showReadTitleBarAdditionValue =
+ appCtx.getPrefBoolean(PreferKey.showReadTitleAddition, true)
+ private var titleBarModeValue = appCtx.getPrefString(PreferKey.titleBarMode, "1") ?: "1"
+ private var menuAlphaValue = appCtx.getPrefInt(PreferKey.menuAlpha, 100)
+ private var readSliderModeValue = appCtx.getPrefString(PreferKey.readSliderMode, "0") ?: "0"
+ private var readBarStyleFollowPageValue =
+ appCtx.getPrefBoolean(PreferKey.readBarStyleFollowPage, false)
+ private var readBarStyleValue = appCtx.getPrefInt(PreferKey.readBarStyle, 0)
+ private var defaultSourceChangeAllValue =
+ appCtx.getPrefBoolean(PreferKey.defaultSourceChangeAll, true)
+ private var sliderVibratorValue = appCtx.getPrefBoolean(PreferKey.sliderVibrator, false)
+ private var selectVibratorValue = appCtx.getPrefBoolean(PreferKey.selectVibrator, false)
+
+ fun syncReadPreferences(preferences: ReadPreferences) {
+ optimizeRender = CanvasRecorderFactory.isSupport && preferences.optimizeRender
+ adaptSpecialStyle = preferences.adaptSpecialStyle
+ useUnderline = preferences.useUnderline
+ clickActionTL = preferences.clickActionTL
+ clickActionTC = preferences.clickActionTC
+ clickActionTR = preferences.clickActionTR
+ clickActionML = preferences.clickActionML
+ clickActionMC = preferences.clickActionMC
+ clickActionMR = preferences.clickActionMR
+ clickActionBL = preferences.clickActionBL
+ clickActionBC = preferences.clickActionBC
+ clickActionBR = preferences.clickActionBR
+ screenOrientationValue = preferences.screenOrientation
+ noAnimScrollPageValue = preferences.noAnimScrollPage
+ tocUiUseReplaceValue = preferences.tocUiUseReplace
+ tocCountWordsValue = preferences.tocCountWords
+ autoChangeSourceValue = preferences.autoChangeSource
+ clickImgWayValue = preferences.clickImgWay
+ doublePageHorizontalValue = preferences.doubleHorizontalPage
+ progressBarBehaviorValue = preferences.progressBarBehavior
+ keyPageOnLongPressValue = preferences.keyPageOnLongPress
+ volumeKeyPageValue = preferences.volumeKeyPage
+ volumeKeyPageOnPlayValue = preferences.volumeKeyPageOnPlay
+ mouseWheelPageValue = preferences.mouseWheelPage
+ paddingDisplayCutoutsValue = preferences.paddingDisplayCutouts
+ pageTouchSlopValue = preferences.pageTouchSlop
+ showReadTitleBarAdditionValue = preferences.showReadTitleAddition
+ titleBarModeValue = preferences.titleBarMode
+ menuAlphaValue = preferences.menuAlpha
+ readSliderModeValue = preferences.readSliderMode
+ readBarStyleFollowPageValue = preferences.readBarStyleFollowPage
+ readBarStyleValue = preferences.readBarStyle
+ defaultSourceChangeAllValue = preferences.defaultSourceChangeAll
+ sliderVibratorValue = preferences.sliderVibrator
+ selectVibratorValue = preferences.selectVibrator
+ }
+
+ fun updateReadBarStyleCache(value: Int) {
+ readBarStyleValue = value.coerceIn(0, 2)
+ }
+
+ private fun syncReadPreferenceFromSharedPreferences(key: String?) {
+ when (key) {
+ PreferKey.optimizeRender -> optimizeRender = CanvasRecorderFactory.isSupport &&
+ appCtx.getPrefBoolean(PreferKey.optimizeRender, false)
+ PreferKey.adaptSpecialStyle -> adaptSpecialStyle =
+ appCtx.getPrefBoolean(PreferKey.adaptSpecialStyle, true)
+ PreferKey.useUnderline -> useUnderline =
+ appCtx.getPrefBoolean(PreferKey.useUnderline, false)
+ PreferKey.screenOrientation -> screenOrientationValue =
+ appCtx.getPrefString(PreferKey.screenOrientation) ?: "0"
+ PreferKey.noAnimScrollPage -> noAnimScrollPageValue =
+ appCtx.getPrefBoolean(PreferKey.noAnimScrollPage, false)
+ PreferKey.tocUiUseReplace -> tocUiUseReplaceValue =
+ appCtx.getPrefBoolean(PreferKey.tocUiUseReplace)
+ PreferKey.tocCountWords -> tocCountWordsValue =
+ appCtx.getPrefBoolean(PreferKey.tocCountWords, true)
+ PreferKey.autoChangeSource -> autoChangeSourceValue =
+ appCtx.getPrefBoolean(PreferKey.autoChangeSource, true)
+ PreferKey.clickImgWay -> clickImgWayValue =
+ appCtx.getPrefString(PreferKey.clickImgWay, "2") ?: "2"
+ PreferKey.doublePageHorizontal -> doublePageHorizontalValue =
+ appCtx.getPrefString(PreferKey.doublePageHorizontal, "0") ?: "0"
+ PreferKey.progressBarBehavior -> progressBarBehaviorValue =
+ appCtx.getPrefString(PreferKey.progressBarBehavior, "page") ?: "page"
+ PreferKey.keyPageOnLongPress -> keyPageOnLongPressValue =
+ appCtx.getPrefBoolean(PreferKey.keyPageOnLongPress, false)
+ PreferKey.volumeKeyPage -> volumeKeyPageValue =
+ appCtx.getPrefBoolean(PreferKey.volumeKeyPage, true)
+ PreferKey.volumeKeyPageOnPlay -> volumeKeyPageOnPlayValue =
+ appCtx.getPrefBoolean(PreferKey.volumeKeyPageOnPlay, true)
+ PreferKey.mouseWheelPage -> mouseWheelPageValue =
+ appCtx.getPrefBoolean(PreferKey.mouseWheelPage, true)
+ PreferKey.paddingDisplayCutouts -> paddingDisplayCutoutsValue =
+ appCtx.getPrefBoolean(PreferKey.paddingDisplayCutouts, false)
+ PreferKey.pageTouchSlop -> pageTouchSlopValue =
+ appCtx.getPrefInt(PreferKey.pageTouchSlop, 0)
+ PreferKey.showReadTitleAddition -> showReadTitleBarAdditionValue =
+ appCtx.getPrefBoolean(PreferKey.showReadTitleAddition, true)
+ PreferKey.titleBarMode -> titleBarModeValue =
+ appCtx.getPrefString(PreferKey.titleBarMode, "1") ?: "1"
+ PreferKey.menuAlpha -> menuAlphaValue = appCtx.getPrefInt(PreferKey.menuAlpha, 100)
+ PreferKey.readSliderMode -> readSliderModeValue =
+ appCtx.getPrefString(PreferKey.readSliderMode, "0") ?: "0"
+ PreferKey.readBarStyleFollowPage -> readBarStyleFollowPageValue =
+ appCtx.getPrefBoolean(PreferKey.readBarStyleFollowPage, false)
+ PreferKey.readBarStyle -> readBarStyleValue =
+ appCtx.getPrefInt(PreferKey.readBarStyle, 0)
+ PreferKey.defaultSourceChangeAll -> defaultSourceChangeAllValue =
+ appCtx.getPrefBoolean(PreferKey.defaultSourceChangeAll, true)
+ PreferKey.sliderVibrator -> sliderVibratorValue =
+ appCtx.getPrefBoolean(PreferKey.sliderVibrator, false)
+ PreferKey.selectVibrator -> selectVibratorValue =
+ appCtx.getPrefBoolean(PreferKey.selectVibrator, false)
+ }
+ }
+
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+ syncReadPreferenceFromSharedPreferences(key)
when (key) {
PreferKey.adaptSpecialStyle -> adaptSpecialStyle =
@@ -230,7 +357,7 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
// get() = appCtx.getPrefBoolean(PreferKey.immNavigationBar, true)
val screenOrientation: String?
- get() = appCtx.getPrefString(PreferKey.screenOrientation)
+ get() = screenOrientationValue
var bookGroupStyle: Int
get() = appCtx.getPrefInt(PreferKey.bookGroupStyle, 0)
@@ -351,7 +478,7 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
}
val noAnimScrollPage: Boolean
- get() = appCtx.getPrefBoolean(PreferKey.noAnimScrollPage, false)
+ get() = noAnimScrollPageValue
const val defaultSpeechRate = 5
@@ -468,14 +595,16 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
}
var tocUiUseReplace: Boolean
- get() = appCtx.getPrefBoolean(PreferKey.tocUiUseReplace)
+ get() = tocUiUseReplaceValue
set(value) {
+ tocUiUseReplaceValue = value
appCtx.putPrefBoolean(PreferKey.tocUiUseReplace, value)
}
var tocCountWords: Boolean
- get() = appCtx.getPrefBoolean(PreferKey.tocCountWords, true)
+ get() = tocCountWordsValue
set(value) {
+ tocCountWordsValue = value
appCtx.putPrefBoolean(PreferKey.tocCountWords, value)
}
@@ -486,7 +615,7 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
}
val autoChangeSource: Boolean
- get() = appCtx.getPrefBoolean(PreferKey.autoChangeSource, true)
+ get() = autoChangeSourceValue
var changeSourceLoadInfo: Boolean
get() = appCtx.getPrefBoolean(PreferKey.changeSourceLoadInfo)
@@ -545,7 +674,7 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
}
val clickImgWay: String?
- get() = appCtx.getPrefString(PreferKey.clickImgWay)
+ get() = clickImgWayValue
var preDownloadNum
get() = appCtx.getPrefInt(PreferKey.preDownloadNum, 10)
set(value) {
@@ -590,25 +719,25 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
val streamReadAloudAudio get() = appCtx.getPrefBoolean(PreferKey.streamReadAloudAudio, false)
val doublePageHorizontal: String?
- get() = appCtx.getPrefString(PreferKey.doublePageHorizontal)
+ get() = doublePageHorizontalValue
val progressBarBehavior: String?
- get() = appCtx.getPrefString(PreferKey.progressBarBehavior, "page")
+ get() = progressBarBehaviorValue
val keyPageOnLongPress
- get() = appCtx.getPrefBoolean(PreferKey.keyPageOnLongPress, false)
+ get() = keyPageOnLongPressValue
val volumeKeyPage
- get() = appCtx.getPrefBoolean(PreferKey.volumeKeyPage, true)
+ get() = volumeKeyPageValue
val volumeKeyPageOnPlay
- get() = appCtx.getPrefBoolean(PreferKey.volumeKeyPageOnPlay, true)
+ get() = volumeKeyPageOnPlayValue
val mouseWheelPage
- get() = appCtx.getPrefBoolean(PreferKey.mouseWheelPage, true)
+ get() = mouseWheelPageValue
val paddingDisplayCutouts
- get() = appCtx.getPrefBoolean(PreferKey.paddingDisplayCutouts, false)
+ get() = paddingDisplayCutoutsValue
var searchScope: String
get() = appCtx.getPrefString("searchScope") ?: ""
@@ -623,8 +752,9 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
}
var pageTouchSlop: Int
- get() = appCtx.getPrefInt(PreferKey.pageTouchSlop, 0)
+ get() = pageTouchSlopValue
set(value) {
+ pageTouchSlopValue = value
appCtx.putPrefInt(PreferKey.pageTouchSlop, value)
}
@@ -666,21 +796,24 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
}
var showReadTitleBarAddition: Boolean
- get() = appCtx.getPrefBoolean(PreferKey.showReadTitleAddition, true)
+ get() = showReadTitleBarAdditionValue
set(value) {
+ showReadTitleBarAdditionValue = value
appCtx.putPrefBoolean(PreferKey.showReadTitleAddition, value)
}
var readBarStyleFollowPage: Boolean
- get() = appCtx.getPrefBoolean(PreferKey.readBarStyleFollowPage, false)
+ get() = readBarStyleFollowPageValue
set(value) {
+ readBarStyleFollowPageValue = value
appCtx.putPrefBoolean(PreferKey.readBarStyleFollowPage, value)
}
var readBarStyle: Int
- get() = appCtx.getPrefInt(PreferKey.readBarStyle, 0)
+ get() = readBarStyleValue
set(value) {
- appCtx.putPrefInt(PreferKey.readBarStyle, value)
+ readBarStyleValue = value.coerceIn(0, 2)
+ appCtx.putPrefInt(PreferKey.readBarStyle, readBarStyleValue)
}
var sourceEditMaxLine: Int
@@ -757,8 +890,9 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
}
var titleBarMode
- get() = appCtx.getPrefString(PreferKey.titleBarMode, "1")
+ get() = titleBarModeValue
set(value) {
+ titleBarModeValue = value
appCtx.putPrefString(PreferKey.titleBarMode, value)
}
@@ -903,14 +1037,16 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
appCtx.putPrefBoolean(PreferKey.enableBlur, value)
}
var menuAlpha: Int
- get() = appCtx.getPrefInt(PreferKey.menuAlpha, 100)
+ get() = menuAlphaValue
set(value) {
+ menuAlphaValue = value
appCtx.putPrefInt(PreferKey.menuAlpha, value)
}
var readSliderMode
- get() = appCtx.getPrefString(PreferKey.readSliderMode, "0")
+ get() = readSliderModeValue
set(value) {
+ readSliderModeValue = value
appCtx.putPrefString(PreferKey.readSliderMode, value)
}
@@ -945,20 +1081,23 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
}
var defaultSourceChangeAll: Boolean
- get() = appCtx.getPrefBoolean(PreferKey.defaultSourceChangeAll, true)
+ get() = defaultSourceChangeAllValue
set(value) {
+ defaultSourceChangeAllValue = value
appCtx.putPrefBoolean(PreferKey.defaultSourceChangeAll, value)
}
var sliderVibrator: Boolean
- get() = appCtx.getPrefBoolean(PreferKey.sliderVibrator, false)
+ get() = sliderVibratorValue
set(value) {
+ sliderVibratorValue = value
appCtx.putPrefBoolean(PreferKey.sliderVibrator, value)
}
var selectVibrator: Boolean
- get() = appCtx.getPrefBoolean(PreferKey.selectVibrator, false)
+ get() = selectVibratorValue
set(value) {
+ selectVibratorValue = value
appCtx.putPrefBoolean(PreferKey.selectVibrator, value)
}
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 2aad1d834..69493b433 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
@@ -1,40 +1,32 @@
package io.legado.app.help.config
-import android.graphics.Color
+import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import androidx.annotation.Keep
-import androidx.core.graphics.drawable.toDrawable
import androidx.core.graphics.toColorInt
-import com.google.android.material.color.MaterialColors
-import io.legado.app.constant.AppLog
+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 io.legado.app.data.repository.ReadPreferences
+import io.legado.app.data.repository.ReadStyleRepository
import io.legado.app.help.DefaultData
import io.legado.app.help.coroutine.Coroutine
-import io.legado.app.utils.BitmapUtils
-import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON
-import io.legado.app.utils.compress.ZipUtils
-import io.legado.app.utils.createFolderReplace
-import io.legado.app.utils.externalCache
-import io.legado.app.utils.externalFiles
-import io.legado.app.utils.fromJsonArray
import io.legado.app.utils.fromJsonObject
-import io.legado.app.utils.getFile
import io.legado.app.utils.getMeanColor
import io.legado.app.utils.getPrefBoolean
+import io.legado.app.utils.getPrefFloat
import io.legado.app.utils.getPrefInt
import io.legado.app.utils.getPrefString
import io.legado.app.utils.hexString
-import io.legado.app.utils.printOnDebug
import io.legado.app.utils.putPrefBoolean
import io.legado.app.utils.putPrefInt
-import io.legado.app.utils.putPrefString
-import io.legado.app.utils.resizeAndRecycle
import splitties.init.appCtx
-import java.io.File
+import java.io.InputStream
/**
* 阅读界面配置
@@ -42,10 +34,33 @@ import java.io.File
@Suppress("ConstPropertyName")
@Keep
object ReadBookConfig {
+ private val readStyleRepository = ReadStyleRepository()
+
+ // region Tip position constants
+ const val tipNone = 0
+ const val tipChapterTitle = 1
+ const val tipTime = 2
+ const val tipBattery = 3
+ const val tipBatteryPercentage = 10
+ const val tipPage = 4
+ const val tipTotalProgress = 5
+ const val tipPageAndTotal = 6
+ const val tipBookName = 7
+ const val tipTimeBattery = 8
+ const val tipTimeBatteryPercentage = 9
+ const val tipTotalProgress1 = 11
+ const val tipChapterTitleArrow = 12
+ const val tipBatteryInside = 13
+ const val tipBatteryIcon = 14
+ const val tipBatteryClassic = 15
+ const val tipTimeBatteryClassic = 16
+ const val tipChapterTitleArrowClassic = 17
+ // endregion
+
const val configFileName = "readConfig.json"
const val shareConfigFileName = "shareReadConfig.json"
- val configFilePath = FileUtils.getPath(appCtx.filesDir, configFileName)
- val shareConfigFilePath = FileUtils.getPath(appCtx.filesDir, shareConfigFileName)
+ val configFilePath: String get() = readStyleRepository.configFilePath
+ val shareConfigFilePath: String get() = readStyleRepository.shareConfigFilePath
val configList: ArrayList = arrayListOf()
lateinit var shareConfig: Config
var durConfig
@@ -63,7 +78,7 @@ object ReadBookConfig {
val textColor: Int get() = durConfig.curTextColor()
val textAccentColor: Int get() = durConfig.curTextAccentColor()
val textShadowColor: Int get() = durConfig.curTextShadowColor()
- val menuColor: Int get() = durConfig.curMenuAc()
+ val menuColor: Int get() = readMenuAccentColor
init {
initConfigs()
initShareConfig()
@@ -78,34 +93,14 @@ object ReadBookConfig {
}
fun initConfigs() {
- val configFile = File(configFilePath)
- var configs: List? = null
- if (configFile.exists()) {
- try {
- val json = configFile.readText()
- configs = GSON.fromJsonArray(json).getOrThrow()
- } catch (e: Exception) {
- AppLog.put("读取排版配置文件出错", e)
- }
- }
- (configs ?: DefaultData.readConfigs).let {
+ readStyleRepository.readConfigs().let {
configList.clear()
configList.addAll(it)
}
}
fun initShareConfig() {
- val configFile = File(shareConfigFilePath)
- var c: Config? = null
- if (configFile.exists()) {
- try {
- val json = configFile.readText()
- c = GSON.fromJsonObject(json).getOrThrow()
- } catch (e: Exception) {
- e.printOnDebug()
- }
- }
- shareConfig = c ?: configList.getOrNull(5) ?: Config()
+ shareConfig = readStyleRepository.readShareConfig(configList.getOrNull(5) ?: Config())
}
fun upBg(width: Int, height: Int) {
@@ -121,32 +116,13 @@ object ReadBookConfig {
fun save() {
Coroutine.async {
synchronized(this) {
- GSON.toJson(configList).let {
- FileUtils.delete(configFilePath)
- FileUtils.createFileIfNotExist(configFilePath).writeText(it)
- }
- GSON.toJson(shareConfig).let {
- FileUtils.delete(shareConfigFilePath)
- FileUtils.createFileIfNotExist(shareConfigFilePath).writeText(it)
- }
+ readStyleRepository.save(configList, shareConfig)
}
}
}
fun getAllPicBgStr(): ArrayList {
- val list = arrayListOf()
- configList.forEach {
- if (it.bgType == 2) {
- list.add(it.bgStr)
- }
- if (it.bgTypeNight == 2) {
- list.add(it.bgStrNight)
- }
- if (it.bgTypeEInk == 2) {
- list.add(it.bgStrEInk)
- }
- }
- return list
+ return readStyleRepository.getAllPicBgStr(configList)
}
fun deleteDur(): Boolean {
@@ -165,22 +141,7 @@ object ReadBookConfig {
}
fun clearBgAndCache() {
- val bgs = hashSetOf()
- configList.forEach { config ->
- repeat(3) {
- config.getBgPath(it)?.let { path ->
- bgs.add(path)
- }
- }
- }
- appCtx.externalFiles.getFile("bg").listFiles()?.forEach {
- if (!bgs.contains(it.absolutePath)) {
- it.delete()
- }
- }
- FileUtils.delete(appCtx.externalCache.getFile("readConfig"))
- val configZipPath = FileUtils.getPath(appCtx.externalCache, "readConfig.zip")
- FileUtils.delete(configZipPath)
+ readStyleRepository.clearBgAndCache(configList)
}
private fun resetAll() {
@@ -191,13 +152,22 @@ object ReadBookConfig {
}
}
- //配置写入读取
- var readBodyToLh = appCtx.getPrefBoolean(PreferKey.readBodyToLh, true)
- var autoReadSpeed = appCtx.getPrefInt(PreferKey.autoReadSpeed, 10)
+ // Runtime compatibility snapshot. New write/read flows should use ReadSettingsRepository.
+ private var readBodyToLhValue = appCtx.getPrefBoolean(PreferKey.readBodyToLh, true)
+ var readBodyToLh: Boolean
+ get() = readBodyToLhValue
set(value) {
- field = value
+ readBodyToLhValue = value
+ }
+
+ private var autoReadSpeedValue = appCtx.getPrefInt(PreferKey.autoReadSpeed, 10)
+ var autoReadSpeed: Int
+ get() = autoReadSpeedValue
+ set(value) {
+ autoReadSpeedValue = value
appCtx.putPrefInt(PreferKey.autoReadSpeed, value)
}
+
var styleSelect: Int
get() = if (isComic) comicStyleSelect else readStyleSelect
set(value) {
@@ -207,27 +177,331 @@ object ReadBookConfig {
readStyleSelect = value
}
}
- var readStyleSelect = appCtx.getPrefInt(PreferKey.readStyleSelect)
+
+ private var readStyleSelectValue = appCtx.getPrefInt(PreferKey.readStyleSelect)
+ var readStyleSelect: Int
+ get() = readStyleSelectValue
set(value) {
- field = value
+ readStyleSelectValue = value
if (appCtx.getPrefInt(PreferKey.readStyleSelect) != value) {
appCtx.putPrefInt(PreferKey.readStyleSelect, value)
}
}
- var comicStyleSelect = appCtx.getPrefInt(PreferKey.comicStyleSelect, readStyleSelect)
+
+ private var comicStyleSelectValue = appCtx.getPrefInt(PreferKey.comicStyleSelect, readStyleSelect)
+ var comicStyleSelect: Int
+ get() = comicStyleSelectValue
set(value) {
- field = value
+ comicStyleSelectValue = value
if (appCtx.getPrefInt(PreferKey.comicStyleSelect) != value) {
appCtx.putPrefInt(PreferKey.comicStyleSelect, value)
}
}
- var shareLayout = appCtx.getPrefBoolean(PreferKey.shareLayout)
+
+ private var shareLayoutValue = appCtx.getPrefBoolean(PreferKey.shareLayout)
+ var shareLayout: Boolean
+ get() = shareLayoutValue
set(value) {
- field = value
+ shareLayoutValue = value
if (appCtx.getPrefBoolean(PreferKey.shareLayout) != value) {
appCtx.putPrefBoolean(PreferKey.shareLayout, value)
}
}
+
+ private var textFullJustifyValue = appCtx.getPrefBoolean(PreferKey.textFullJustify, true)
+ private var textBottomJustifyValue = appCtx.getPrefBoolean(PreferKey.textBottomJustify, true)
+ private var hideStatusBarValue = appCtx.getPrefBoolean(PreferKey.hideStatusBar)
+ private var hideNavigationBarValue = appCtx.getPrefBoolean(PreferKey.hideNavigationBar)
+ private var useZhLayoutValue = appCtx.getPrefBoolean(PreferKey.useZhLayout)
+ private var readMenuBgColorValue = appCtx.getPrefInt(PreferKey.readMenuBgColor)
+ private var readMenuAccentColorValue = appCtx.getPrefInt(PreferKey.readMenuAccentColor)
+ private var readMenuContainerColorValue = appCtx.getPrefInt(PreferKey.readMenuContainerColor)
+ private var readMenuBgColorNightValue = appCtx.getPrefInt(PreferKey.readMenuBgColorNight)
+ private var readMenuAccentColorNightValue = appCtx.getPrefInt(PreferKey.readMenuAccentColorNight)
+ private var readMenuContainerColorNightValue = appCtx.getPrefInt(PreferKey.readMenuContainerColorNight)
+ private var readMenuColorModeValue = appCtx.getPrefInt(PreferKey.readMenuColorMode, 1)
+ private var readMenuIconShowTextValue = appCtx.getPrefBoolean(PreferKey.readMenuIconShowText, true)
+ private var readMenuIconStyleValue = appCtx.getPrefInt(PreferKey.readMenuIconStyle)
+ private var readMenuIconItemsPerRowValue = appCtx.getPrefInt(PreferKey.readMenuIconItemsPerRow, 5)
+ private var readMenuIconRowCountValue = appCtx.getPrefInt(PreferKey.readMenuIconRowCount, 1)
+ private var readMenuBottomCornerRadiusValue = appCtx.getPrefInt(PreferKey.readMenuBottomCornerRadius)
+ private var readMenuFloatingBottomBarValue = appCtx.getPrefBoolean(PreferKey.readMenuFloatingBottomBar)
+ private var readMenuTopBarBlurModeValue = appCtx.getPrefInt(
+ PreferKey.readMenuTopBarBlurMode,
+ ReadMenuBlurMode.None
+ )
+ private var readMenuBottomBarBlurModeValue = appCtx.getPrefInt(
+ PreferKey.readMenuBottomBarBlurMode,
+ ReadMenuBlurMode.None
+ )
+ private var readMenuTopBarLiquidGlassButtonsValue = appCtx.getPrefBoolean(
+ PreferKey.readMenuTopBarLiquidGlassButtons
+ )
+ private var readMenuBottomBarLiquidGlassButtonsValue = appCtx.getPrefBoolean(
+ PreferKey.readMenuBottomBarLiquidGlassButtons
+ )
+ private var readMenuTopBarBlurStyleValue = appCtx.getPrefInt(
+ PreferKey.readMenuTopBarBlurStyle,
+ ReadMenuBlurStyle.Progressive
+ )
+ private var readMenuBottomBarBlurStyleValue = appCtx.getPrefInt(
+ PreferKey.readMenuBottomBarBlurStyle,
+ ReadMenuBlurStyle.Solid
+ )
+ private var readMenuBlurRadiusValue = appCtx.getPrefInt(PreferKey.readMenuBlurRadius, 24)
+ private var readMenuBlurAlphaValue = appCtx.getPrefInt(PreferKey.readMenuBlurAlpha, 60)
+ private var readMenuLensRadiusValue = appCtx.getPrefFloat(PreferKey.readMenuLensRadius, 24f)
+ private var readMenuBorderWidthValue = appCtx.getPrefInt(PreferKey.readMenuBorderWidth)
+ private var readMenuBorderColorValue = appCtx.getPrefInt(PreferKey.readMenuBorderColor)
+ private var readMenuBorderColorNightValue = appCtx.getPrefInt(PreferKey.readMenuBorderColorNight)
+ private var readMenuCustomIconsValue =
+ parseReadMenuCustomIcons(appCtx.getPrefString(PreferKey.readMenuCustomIcons))
+ private var titleBarCustomIconsValue =
+ parseReadMenuCustomIcons(appCtx.getPrefString(PreferKey.titleBarCustomIcons))
+ private var titleBarIconPositionValue = appCtx.getPrefInt(PreferKey.titleBarIconPosition)
+ private var showTitleBarIconsValue = appCtx.getPrefBoolean(PreferKey.showTitleBarIcons, true)
+
+ fun syncPreferences(preferences: ReadPreferences) {
+ readBodyToLhValue = preferences.readBodyToLh
+ autoReadSpeedValue = preferences.autoReadSpeed
+ readStyleSelectValue = preferences.readStyleSelect
+ comicStyleSelectValue = preferences.comicStyleSelect
+ shareLayoutValue = preferences.shareLayout
+ textFullJustifyValue = preferences.textFullJustify
+ textBottomJustifyValue = preferences.textBottomJustify
+ hideStatusBarValue = preferences.hideStatusBar
+ hideNavigationBarValue = preferences.hideNavigationBar
+ useZhLayoutValue = preferences.useZhLayout
+ readMenuBgColorValue = preferences.readMenuBgColor
+ readMenuAccentColorValue = preferences.readMenuAccentColor
+ readMenuContainerColorValue = preferences.readMenuContainerColor
+ readMenuBgColorNightValue = preferences.readMenuBgColorNight
+ readMenuAccentColorNightValue = preferences.readMenuAccentColorNight
+ readMenuContainerColorNightValue = preferences.readMenuContainerColorNight
+ readMenuColorModeValue = preferences.readMenuColorMode
+ readMenuIconShowTextValue = preferences.readMenuIconShowText
+ readMenuIconStyleValue = preferences.readMenuIconStyle
+ readMenuIconItemsPerRowValue = preferences.readMenuIconItemsPerRow
+ readMenuIconRowCountValue = preferences.readMenuIconRowCount
+ readMenuBottomCornerRadiusValue = preferences.readMenuBottomCornerRadius
+ readMenuFloatingBottomBarValue = preferences.readMenuFloatingBottomBar
+ readMenuTopBarBlurModeValue = preferences.readMenuTopBarBlurMode
+ readMenuBottomBarBlurModeValue = preferences.readMenuBottomBarBlurMode
+ readMenuTopBarLiquidGlassButtonsValue = preferences.readMenuTopBarLiquidGlassButtons
+ readMenuBottomBarLiquidGlassButtonsValue = preferences.readMenuBottomBarLiquidGlassButtons
+ readMenuTopBarBlurStyleValue = preferences.readMenuTopBarBlurStyle
+ readMenuBottomBarBlurStyleValue = preferences.readMenuBottomBarBlurStyle
+ readMenuBlurRadiusValue = preferences.readMenuBlurRadius
+ readMenuBlurAlphaValue = preferences.readMenuBlurAlpha
+ readMenuLensRadiusValue = preferences.readMenuLensRadius
+ readMenuBorderWidthValue = preferences.readMenuBorderWidth
+ readMenuBorderColorValue = preferences.readMenuBorderColor
+ readMenuBorderColorNightValue = preferences.readMenuBorderColorNight
+ readMenuCustomIconsValue = parseReadMenuCustomIcons(preferences.readMenuCustomIcons)
+ titleBarCustomIconsValue = parseReadMenuCustomIcons(preferences.titleBarCustomIcons)
+ titleBarIconPositionValue = preferences.titleBarIconPosition
+ showTitleBarIconsValue = preferences.showTitleBarIcons
+ }
+
+ var readMenuBgColor: Int
+ get() = readMenuBgColorValue.takeIf { it != 0 } ?: durConfig.menuBgColor(isNight = false)
+ set(value) {
+ readMenuBgColorValue = value
+ }
+
+ var readMenuAccentColor: Int
+ get() = readMenuAccentColorValue.takeIf { it != 0 } ?: durConfig.menuAccentColor(isNight = false)
+ set(value) {
+ readMenuAccentColorValue = value
+ }
+
+ var readMenuContainerColor: Int
+ get() = readMenuContainerColorValue.takeIf { it != 0 } ?: readMenuBgColor
+ set(value) {
+ readMenuContainerColorValue = value
+ }
+
+ var readMenuBgColorNight: Int
+ get() = readMenuBgColorNightValue.takeIf { it != 0 } ?: durConfig.menuBgColor(isNight = true)
+ set(value) {
+ readMenuBgColorNightValue = value
+ }
+
+ var readMenuAccentColorNight: Int
+ get() = readMenuAccentColorNightValue.takeIf { it != 0 } ?: durConfig.menuAccentColor(isNight = true)
+ set(value) {
+ readMenuAccentColorNightValue = value
+ }
+
+ var readMenuContainerColorNight: Int
+ get() = readMenuContainerColorNightValue.takeIf { it != 0 } ?: readMenuBgColorNight
+ set(value) {
+ readMenuContainerColorNightValue = value
+ }
+
+ val resolvedMenuBgColor: Int
+ get() = if (ReadStyleResolver.isNightTheme()) readMenuBgColorNight else readMenuBgColor
+
+ val resolvedMenuAccentColor: Int
+ get() = if (ReadStyleResolver.isNightTheme()) readMenuAccentColorNight else readMenuAccentColor
+
+ val resolvedMenuContainerColor: Int
+ get() = if (ReadStyleResolver.isNightTheme()) readMenuContainerColorNight else readMenuContainerColor
+
+ var readMenuColorMode: Int
+ get() = readMenuColorModeValue.coerceIn(0, 1)
+ set(value) {
+ readMenuColorModeValue = value.coerceIn(0, 1)
+ }
+
+ var readMenuIconShowText: Boolean
+ get() = readMenuIconShowTextValue
+ set(value) {
+ readMenuIconShowTextValue = value
+ }
+
+ var readMenuIconStyle: Int
+ get() = readMenuIconStyleValue.coerceIn(0, 2)
+ set(value) {
+ readMenuIconStyleValue = value.coerceIn(0, 2)
+ }
+
+ var readMenuIconItemsPerRow: Int
+ get() = readMenuIconItemsPerRowValue.coerceIn(2, 8)
+ set(value) {
+ readMenuIconItemsPerRowValue = value.coerceIn(2, 8)
+ }
+
+ var readMenuIconRowCount: Int
+ get() = readMenuIconRowCountValue.coerceIn(1, 2)
+ set(value) {
+ readMenuIconRowCountValue = value.coerceIn(1, 2)
+ }
+
+ var readMenuBottomCornerRadius: Int
+ get() = readMenuBottomCornerRadiusValue.coerceIn(0, 32)
+ set(value) {
+ readMenuBottomCornerRadiusValue = value.coerceIn(0, 32)
+ }
+
+ var readMenuFloatingBottomBar: Boolean
+ get() = readMenuFloatingBottomBarValue
+ set(value) {
+ readMenuFloatingBottomBarValue = value
+ }
+
+ var readMenuTopBarBlurMode: Int
+ get() = readMenuTopBarBlurModeValue.coerceIn(0, 2)
+ set(value) {
+ readMenuTopBarBlurModeValue = value.coerceIn(0, 2)
+ }
+
+ var readMenuBottomBarBlurMode: Int
+ get() = readMenuBottomBarBlurModeValue.coerceIn(0, 2)
+ set(value) {
+ readMenuBottomBarBlurModeValue = value.coerceIn(0, 2)
+ }
+
+ var readMenuTopBarLiquidGlassButtons: Boolean
+ get() = readMenuTopBarLiquidGlassButtonsValue
+ set(value) {
+ readMenuTopBarLiquidGlassButtonsValue = value
+ }
+
+ var readMenuBottomBarLiquidGlassButtons: Boolean
+ get() = readMenuBottomBarLiquidGlassButtonsValue
+ set(value) {
+ readMenuBottomBarLiquidGlassButtonsValue = value
+ }
+
+ var readMenuTopBarBlurStyle: Int
+ get() = readMenuTopBarBlurStyleValue.coerceIn(0, 1)
+ set(value) {
+ readMenuTopBarBlurStyleValue = value.coerceIn(0, 1)
+ }
+
+ var readMenuBottomBarBlurStyle: Int
+ get() = readMenuBottomBarBlurStyleValue.coerceIn(0, 1)
+ set(value) {
+ readMenuBottomBarBlurStyleValue = value.coerceIn(0, 1)
+ }
+
+ var readMenuBlurRadius: Int
+ get() = readMenuBlurRadiusValue.coerceIn(0, 32)
+ set(value) {
+ readMenuBlurRadiusValue = value.coerceIn(0, 32)
+ }
+
+ var readMenuBlurAlpha: Int
+ get() = readMenuBlurAlphaValue.coerceIn(0, 100)
+ set(value) {
+ readMenuBlurAlphaValue = value.coerceIn(0, 100)
+ }
+
+ var readMenuLensRadius: Float
+ get() = readMenuLensRadiusValue.coerceIn(0f, 48f)
+ set(value) {
+ readMenuLensRadiusValue = value.coerceIn(0f, 48f)
+ }
+
+ var readMenuBorderWidth: Int
+ get() = readMenuBorderWidthValue.coerceIn(0, 4)
+ set(value) {
+ readMenuBorderWidthValue = value.coerceIn(0, 4)
+ }
+
+ var readMenuBorderColor: Int
+ get() = readMenuBorderColorValue
+ set(value) {
+ readMenuBorderColorValue = value
+ }
+
+ var readMenuBorderColorNight: Int
+ get() = readMenuBorderColorNightValue
+ set(value) {
+ readMenuBorderColorNightValue = value
+ }
+
+ val resolvedMenuBorderColor: Int
+ get() = if (ReadStyleResolver.isNightTheme()) readMenuBorderColorNight else readMenuBorderColor
+
+ var readMenuCustomIcons: Map
+ get() = readMenuCustomIconsValue
+ set(value) {
+ readMenuCustomIconsValue = value.filterValues { it.isNotBlank() }
+ }
+
+ var titleBarCustomIcons: Map
+ get() = titleBarCustomIconsValue
+ set(value) {
+ titleBarCustomIconsValue = value.filterValues { it.isNotBlank() }
+ }
+
+ // 0=top-left, 1=top-right, 2=bottom-left, 3=bottom-right
+ var titleBarIconPosition: Int
+ get() = titleBarIconPositionValue.coerceIn(0, 3)
+ set(value) {
+ titleBarIconPositionValue = value.coerceIn(0, 3)
+ }
+
+ var showTitleBarIcons: Boolean
+ get() = showTitleBarIconsValue
+ set(value) {
+ showTitleBarIconsValue = value
+ }
+
+ fun encodeReadMenuCustomIcons(value: Map): String {
+ return GSON.toJson(value.filterValues { it.isNotBlank() })
+ }
+
+ private fun parseReadMenuCustomIcons(value: String?): Map {
+ if (value.isNullOrBlank()) {
+ return emptyMap()
+ }
+ return GSON.fromJsonObject