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

refactor: 以功能 Gateway 收口书架设置与交互

新增 BookshelfSettings 模型、Gateway 与数据层实现,统一书架分组、排序、刷新和展示配置的数据源。

完善书架 UiState、Intent 和 Effect,并移除 Screen、配置 Sheet 与书籍条目对 BookshelfConfig 和 ThemeConfig 的直接依赖。
5151561 3 месяцев назад
Родитель
Сommit
6fdf46676f

+ 129 - 0
app/src/main/java/io/legado/app/data/repository/BookshelfSettingsRepository.kt

@@ -0,0 +1,129 @@
+package io.legado.app.data.repository
+
+import androidx.datastore.preferences.core.Preferences
+import io.legado.app.constant.PreferKey
+import io.legado.app.data.entities.BookGroup
+import io.legado.app.domain.gateway.BookshelfBooleanSetting
+import io.legado.app.domain.gateway.BookshelfIntSetting
+import io.legado.app.domain.gateway.BookshelfSettingsGateway
+import io.legado.app.domain.gateway.BookshelfSettingsUpdate
+import io.legado.app.domain.model.settings.BookshelfSettings
+import io.legado.app.help.config.AppConfigStore
+import io.legado.app.help.config.compatDsBoolean
+import io.legado.app.help.config.compatDsInt
+import io.legado.app.help.config.compatDsLong
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+class BookshelfSettingsRepository : BookshelfSettingsGateway {
+    override val currentSettings: BookshelfSettings
+        get() = AppConfigStore.preferences.toBookshelfSettings()
+
+    override val settings: Flow<BookshelfSettings> = AppConfigStore.preferencesFlow
+        .map(Preferences::toBookshelfSettings)
+        .distinctUntilChanged()
+
+    override suspend fun update(update: BookshelfSettingsUpdate) {
+        val (key, value) = when (update) {
+            is BookshelfSettingsUpdate.SaveTabPosition -> PreferKey.saveTabPosition to update.value
+            is BookshelfSettingsUpdate.BooleanValue -> when (update.setting) {
+                BookshelfBooleanSetting.ShowUnread -> PreferKey.showUnread
+                BookshelfBooleanSetting.ShowUnreadNew -> PreferKey.showUnreadNew
+                BookshelfBooleanSetting.ShowTip -> PreferKey.showTip
+                BookshelfBooleanSetting.ShowBookCount -> PreferKey.showBookCount
+                BookshelfBooleanSetting.ShowLastUpdateTime -> PreferKey.showLastUpdateTime
+                BookshelfBooleanSetting.ShowBookIntro -> PreferKey.showBookIntro
+                BookshelfBooleanSetting.ShowIntro -> PreferKey.bookshelfShowIntro
+                BookshelfBooleanSetting.ShowTag -> PreferKey.bookshelfShowTag
+                BookshelfBooleanSetting.ShowLatestChapter -> PreferKey.bookshelfShowLatestChapter
+                BookshelfBooleanSetting.ShowWaitUpCount -> PreferKey.showWaitUpCount
+                BookshelfBooleanSetting.ShowFastScroller -> PreferKey.showBookshelfFastScroller
+                BookshelfBooleanSetting.ShowExpandButton -> PreferKey.shouldShowExpandButton
+                BookshelfBooleanSetting.LayoutCompact -> PreferKey.bookshelfLayoutCompact
+                BookshelfBooleanSetting.ShowDivider -> PreferKey.bookshelfShowDivider
+                BookshelfBooleanSetting.TitleSmallFont -> PreferKey.bookshelfTitleSmallFont
+                BookshelfBooleanSetting.TitleCenter -> PreferKey.bookshelfTitleCenter
+                BookshelfBooleanSetting.CoverShadow -> PreferKey.bookshelfCoverShadow
+                BookshelfBooleanSetting.SearchActionDirectToSearch -> PreferKey.bookshelfSearchActionDirectToSearch
+                BookshelfBooleanSetting.AutoRefresh -> PreferKey.autoRefresh
+            } to update.value
+            is BookshelfSettingsUpdate.IntValue -> when (update.setting) {
+                BookshelfIntSetting.BookGroupStyle -> PreferKey.bookGroupStyle
+                BookshelfIntSetting.Sort -> PreferKey.bookshelfSort
+                BookshelfIntSetting.SortOrder -> PreferKey.bookshelfSortOrder
+                BookshelfIntSetting.IntroMaxLines -> PreferKey.bookshelfIntroMaxLines
+                BookshelfIntSetting.RefreshingLimit -> PreferKey.bookshelfRefreshingLimit
+                BookshelfIntSetting.LayoutModePortrait -> PreferKey.bookshelfLayoutModePortrait
+                BookshelfIntSetting.LayoutGridPortrait -> PreferKey.bookshelfLayoutGridPortrait
+                BookshelfIntSetting.LayoutModeLandscape -> PreferKey.bookshelfLayoutModeLandscape
+                BookshelfIntSetting.LayoutGridLandscape -> PreferKey.bookshelfLayoutGridLandscape
+                BookshelfIntSetting.LayoutListPortrait -> PreferKey.bookshelfLayoutListPortrait
+                BookshelfIntSetting.LayoutListLandscape -> PreferKey.bookshelfLayoutListLandscape
+                BookshelfIntSetting.FolderLayoutModePortrait -> PreferKey.bookshelfFolderLayoutModePortrait
+                BookshelfIntSetting.FolderLayoutGridPortrait -> PreferKey.bookshelfFolderLayoutGridPortrait
+                BookshelfIntSetting.FolderLayoutModeLandscape -> PreferKey.bookshelfFolderLayoutModeLandscape
+                BookshelfIntSetting.FolderLayoutGridLandscape -> PreferKey.bookshelfFolderLayoutGridLandscape
+                BookshelfIntSetting.FolderLayoutListPortrait -> PreferKey.bookshelfFolderLayoutListPortrait
+                BookshelfIntSetting.FolderLayoutListLandscape -> PreferKey.bookshelfFolderLayoutListLandscape
+                BookshelfIntSetting.GridLayout -> PreferKey.bookshelfGridLayout
+                BookshelfIntSetting.TitleMaxLines -> PreferKey.bookshelfTitleMaxLines
+                BookshelfIntSetting.CardColor -> PreferKey.bookshelfCardColor
+                BookshelfIntSetting.CardColorDark -> PreferKey.bookshelfCardColorDark
+                BookshelfIntSetting.GroupListStyle -> PreferKey.bookshelfGroupListStyle
+                BookshelfIntSetting.GroupCoverCount -> PreferKey.bookshelfGroupCoverCount
+                BookshelfIntSetting.ListCoverWidth -> PreferKey.bookshelfListCoverWidth
+                BookshelfIntSetting.GridCoverWidth -> PreferKey.bookshelfGridCoverWidth
+            } to update.value
+        }
+        AppConfigStore.putAll(mapOf(key to value))
+    }
+}
+
+private fun Preferences.toBookshelfSettings() = BookshelfSettings(
+    bookGroupStyle = compatDsInt(PreferKey.bookGroupStyle) ?: 0,
+    bookshelfSort = compatDsInt(PreferKey.bookshelfSort) ?: 0,
+    bookshelfSortOrder = compatDsInt(PreferKey.bookshelfSortOrder) ?: 1,
+    showUnread = compatDsBoolean(PreferKey.showUnread) ?: true,
+    showUnreadNew = compatDsBoolean(PreferKey.showUnreadNew) ?: true,
+    showTip = compatDsBoolean(PreferKey.showTip) ?: false,
+    showBookCount = compatDsBoolean(PreferKey.showBookCount) ?: true,
+    showLastUpdateTime = compatDsBoolean(PreferKey.showLastUpdateTime) ?: false,
+    showBookIntro = compatDsBoolean(PreferKey.showBookIntro) ?: false,
+    bookshelfShowIntro = compatDsBoolean(PreferKey.bookshelfShowIntro) ?: true,
+    bookshelfShowTag = compatDsBoolean(PreferKey.bookshelfShowTag) ?: true,
+    bookshelfShowLatestChapter = compatDsBoolean(PreferKey.bookshelfShowLatestChapter) ?: true,
+    bookshelfIntroMaxLines = compatDsInt(PreferKey.bookshelfIntroMaxLines) ?: 0,
+    showWaitUpCount = compatDsBoolean(PreferKey.showWaitUpCount) ?: false,
+    showBookshelfFastScroller = compatDsBoolean(PreferKey.showBookshelfFastScroller) ?: false,
+    shouldShowExpandButton = compatDsBoolean(PreferKey.shouldShowExpandButton) ?: false,
+    bookshelfRefreshingLimit = compatDsInt(PreferKey.bookshelfRefreshingLimit) ?: 0,
+    bookshelfLayoutModePortrait = compatDsInt(PreferKey.bookshelfLayoutModePortrait) ?: 1,
+    bookshelfLayoutGridPortrait = compatDsInt(PreferKey.bookshelfLayoutGridPortrait) ?: 3,
+    bookshelfLayoutModeLandscape = compatDsInt(PreferKey.bookshelfLayoutModeLandscape) ?: 1,
+    bookshelfLayoutGridLandscape = compatDsInt(PreferKey.bookshelfLayoutGridLandscape) ?: 7,
+    bookshelfLayoutListPortrait = compatDsInt(PreferKey.bookshelfLayoutListPortrait) ?: 1,
+    bookshelfLayoutListLandscape = compatDsInt(PreferKey.bookshelfLayoutListLandscape) ?: 1,
+    bookshelfFolderLayoutModePortrait = compatDsInt(PreferKey.bookshelfFolderLayoutModePortrait) ?: 1,
+    bookshelfFolderLayoutGridPortrait = compatDsInt(PreferKey.bookshelfFolderLayoutGridPortrait) ?: 3,
+    bookshelfFolderLayoutModeLandscape = compatDsInt(PreferKey.bookshelfFolderLayoutModeLandscape) ?: 1,
+    bookshelfFolderLayoutGridLandscape = compatDsInt(PreferKey.bookshelfFolderLayoutGridLandscape) ?: 7,
+    bookshelfFolderLayoutListPortrait = compatDsInt(PreferKey.bookshelfFolderLayoutListPortrait) ?: 1,
+    bookshelfFolderLayoutListLandscape = compatDsInt(PreferKey.bookshelfFolderLayoutListLandscape) ?: 1,
+    bookshelfGridLayout = compatDsInt(PreferKey.bookshelfGridLayout) ?: 0,
+    bookshelfLayoutCompact = compatDsBoolean(PreferKey.bookshelfLayoutCompact) ?: false,
+    bookshelfShowDivider = compatDsBoolean(PreferKey.bookshelfShowDivider) ?: true,
+    bookshelfTitleSmallFont = compatDsBoolean(PreferKey.bookshelfTitleSmallFont) ?: false,
+    bookshelfTitleCenter = compatDsBoolean(PreferKey.bookshelfTitleCenter) ?: true,
+    bookshelfTitleMaxLines = compatDsInt(PreferKey.bookshelfTitleMaxLines) ?: 2,
+    bookshelfCoverShadow = compatDsBoolean(PreferKey.bookshelfCoverShadow) ?: false,
+    bookshelfCardColor = compatDsInt(PreferKey.bookshelfCardColor) ?: 0,
+    bookshelfCardColorDark = compatDsInt(PreferKey.bookshelfCardColorDark) ?: 0,
+    bookshelfGroupListStyle = compatDsInt(PreferKey.bookshelfGroupListStyle) ?: 0,
+    bookshelfGroupCoverCount = compatDsInt(PreferKey.bookshelfGroupCoverCount) ?: 4,
+    bookshelfListCoverWidth = compatDsInt(PreferKey.bookshelfListCoverWidth) ?: 84,
+    bookshelfGridCoverWidth = compatDsInt(PreferKey.bookshelfGridCoverWidth) ?: 120,
+    bookshelfSearchActionDirectToSearch = compatDsBoolean(PreferKey.bookshelfSearchActionDirectToSearch) ?: true,
+    autoRefreshBook = compatDsBoolean(PreferKey.autoRefresh) ?: false,
+    saveTabPosition = compatDsLong(PreferKey.saveTabPosition) ?: BookGroup.IdAll,
+)

+ 4 - 0
app/src/main/java/io/legado/app/data/repository/FeatureSettingsRepositories.kt

@@ -141,6 +141,7 @@ class ThemeSettingsRepository : ThemeSettingsGateway {
                 ThemeBooleanSetting.EyeProtectionSchedule -> PreferKey.eyeProtectionSchedule to update.value
                 ThemeBooleanSetting.ShowRefactorTip ->
                     io.legado.app.data.local.preferences.LocalPreferencesKeys.SHOW_THEME_REFACTOR_TIP.name to update.value
+                ThemeBooleanSetting.EnableCustomTagColors -> PreferKey.enableCustomTagColors to update.value
             }
             is ThemeSettingsUpdate.IntValue -> when (update.setting) {
                 ThemeIntSetting.ContainerOpacity -> PreferKey.containerOpacity to update.value
@@ -169,6 +170,7 @@ class ThemeSettingsRepository : ThemeSettingsGateway {
                 ThemeStringSetting.BackgroundImageDark -> PreferKey.bgImageN to update.value
                 ThemeStringSetting.EyeProtectionStartTime -> PreferKey.eyeProtectionStartTime to update.value
                 ThemeStringSetting.EyeProtectionEndTime -> PreferKey.eyeProtectionEndTime to update.value
+                ThemeStringSetting.CustomTagColorsJson -> PreferKey.customTagColors to update.value
             }
             }
             key to value
@@ -442,6 +444,8 @@ private fun Preferences.toThemeSettings(): ThemeSettings = ThemeSettings(
     showRefactorTip = compatDsBoolean(
         io.legado.app.data.local.preferences.LocalPreferencesKeys.SHOW_THEME_REFACTOR_TIP.name
     ) ?: true,
+    enableCustomTagColors = compatDsBoolean(PreferKey.enableCustomTagColors) ?: false,
+    customTagColorsJson = compatDsString(PreferKey.customTagColors),
 )
 
 class OtherSettingsRepository : OtherSettingsGateway {

+ 3 - 0
app/src/main/java/io/legado/app/di/appModule.kt

@@ -25,6 +25,7 @@ import io.legado.app.data.repository.BookRepository
 import io.legado.app.data.repository.BookSourceCallbackRepository
 import io.legado.app.data.repository.BookSourceRepository
 import io.legado.app.data.repository.BookshelfRepository
+import io.legado.app.data.repository.BookshelfSettingsRepository
 import io.legado.app.data.repository.CacheBookDownloadRepository
 import io.legado.app.data.repository.CoverAlbumRepository
 import io.legado.app.data.repository.CoverSettingsRepository
@@ -78,6 +79,7 @@ import io.legado.app.domain.gateway.BookCacheDownloadGateway
 import io.legado.app.domain.gateway.BookContentProcessGateway
 import io.legado.app.domain.gateway.BookSearchGateway
 import io.legado.app.domain.gateway.BookSourceCallbackGateway
+import io.legado.app.domain.gateway.BookshelfSettingsGateway
 import io.legado.app.domain.gateway.CoverAlbumGateway
 import io.legado.app.domain.gateway.CoverSettingsGateway
 import io.legado.app.domain.gateway.DatabaseMaintenanceGateway
@@ -241,6 +243,7 @@ val appModule = module {
     single<BackupSettingsGateway> { BackupSettingsRepository() }
     single<LabSettingsGateway> { LabSettingsRepository() }
     single<TranslationSettingsGateway> { TranslationSettingsRepository() }
+    single<BookshelfSettingsGateway> { BookshelfSettingsRepository() }
     singleOf(::ReadSettingsRepository)
     single<ReadSettingsGateway> { get<ReadSettingsRepository>() }
     singleOf(::ReadAloudSettingsRepository)

+ 66 - 0
app/src/main/java/io/legado/app/domain/gateway/BookshelfSettingsGateway.kt

@@ -0,0 +1,66 @@
+package io.legado.app.domain.gateway
+
+import io.legado.app.domain.model.settings.BookshelfSettings
+import kotlinx.coroutines.flow.Flow
+
+interface BookshelfSettingsGateway {
+    val currentSettings: BookshelfSettings
+    val settings: Flow<BookshelfSettings>
+    suspend fun update(update: BookshelfSettingsUpdate)
+}
+
+sealed interface BookshelfSettingsUpdate {
+    data class BooleanValue(val setting: BookshelfBooleanSetting, val value: Boolean) : BookshelfSettingsUpdate
+    data class IntValue(val setting: BookshelfIntSetting, val value: Int) : BookshelfSettingsUpdate
+    data class SaveTabPosition(val value: Long) : BookshelfSettingsUpdate
+}
+
+enum class BookshelfBooleanSetting {
+    ShowUnread,
+    ShowUnreadNew,
+    ShowTip,
+    ShowBookCount,
+    ShowLastUpdateTime,
+    ShowBookIntro,
+    ShowIntro,
+    ShowTag,
+    ShowLatestChapter,
+    ShowWaitUpCount,
+    ShowFastScroller,
+    ShowExpandButton,
+    LayoutCompact,
+    ShowDivider,
+    TitleSmallFont,
+    TitleCenter,
+    CoverShadow,
+    SearchActionDirectToSearch,
+    AutoRefresh,
+}
+
+enum class BookshelfIntSetting {
+    BookGroupStyle,
+    Sort,
+    SortOrder,
+    IntroMaxLines,
+    RefreshingLimit,
+    LayoutModePortrait,
+    LayoutGridPortrait,
+    LayoutModeLandscape,
+    LayoutGridLandscape,
+    LayoutListPortrait,
+    LayoutListLandscape,
+    FolderLayoutModePortrait,
+    FolderLayoutGridPortrait,
+    FolderLayoutModeLandscape,
+    FolderLayoutGridLandscape,
+    FolderLayoutListPortrait,
+    FolderLayoutListLandscape,
+    GridLayout,
+    TitleMaxLines,
+    CardColor,
+    CardColorDark,
+    GroupListStyle,
+    GroupCoverCount,
+    ListCoverWidth,
+    GridCoverWidth,
+}

+ 2 - 0
app/src/main/java/io/legado/app/domain/gateway/ThemeSettingsGateway.kt

@@ -49,6 +49,7 @@ enum class ThemeBooleanSetting {
     EyeProtectionEnabled,
     EyeProtectionSchedule,
     ShowRefactorTip,
+    EnableCustomTagColors,
 }
 
 enum class ThemeIntSetting {
@@ -78,6 +79,7 @@ enum class ThemeStringSetting {
     BackgroundImageDark,
     EyeProtectionStartTime,
     EyeProtectionEndTime,
+    CustomTagColorsJson,
 }
 
 enum class ThemeColorSlot {

+ 51 - 0
app/src/main/java/io/legado/app/domain/model/settings/BookshelfSettings.kt

@@ -0,0 +1,51 @@
+package io.legado.app.domain.model.settings
+
+import io.legado.app.data.entities.BookGroup
+
+data class BookshelfSettings(
+    val bookGroupStyle: Int = 0,
+    val bookshelfSort: Int = 0,
+    val bookshelfSortOrder: Int = 1,
+    val showUnread: Boolean = true,
+    val showUnreadNew: Boolean = true,
+    val showTip: Boolean = false,
+    val showBookCount: Boolean = true,
+    val showLastUpdateTime: Boolean = false,
+    val showBookIntro: Boolean = false,
+    val bookshelfShowIntro: Boolean = true,
+    val bookshelfShowTag: Boolean = true,
+    val bookshelfShowLatestChapter: Boolean = true,
+    val bookshelfIntroMaxLines: Int = 0,
+    val showWaitUpCount: Boolean = false,
+    val showBookshelfFastScroller: Boolean = false,
+    val shouldShowExpandButton: Boolean = false,
+    val bookshelfRefreshingLimit: Int = 0,
+    val bookshelfLayoutModePortrait: Int = 1,
+    val bookshelfLayoutGridPortrait: Int = 3,
+    val bookshelfLayoutModeLandscape: Int = 1,
+    val bookshelfLayoutGridLandscape: Int = 7,
+    val bookshelfLayoutListPortrait: Int = 1,
+    val bookshelfLayoutListLandscape: Int = 1,
+    val bookshelfFolderLayoutModePortrait: Int = 1,
+    val bookshelfFolderLayoutGridPortrait: Int = 3,
+    val bookshelfFolderLayoutModeLandscape: Int = 1,
+    val bookshelfFolderLayoutGridLandscape: Int = 7,
+    val bookshelfFolderLayoutListPortrait: Int = 1,
+    val bookshelfFolderLayoutListLandscape: Int = 1,
+    val bookshelfGridLayout: Int = 0,
+    val bookshelfLayoutCompact: Boolean = false,
+    val bookshelfShowDivider: Boolean = true,
+    val bookshelfTitleSmallFont: Boolean = false,
+    val bookshelfTitleCenter: Boolean = true,
+    val bookshelfTitleMaxLines: Int = 2,
+    val bookshelfCoverShadow: Boolean = false,
+    val bookshelfCardColor: Int = 0,
+    val bookshelfCardColorDark: Int = 0,
+    val bookshelfGroupListStyle: Int = 0,
+    val bookshelfGroupCoverCount: Int = 4,
+    val bookshelfListCoverWidth: Int = 84,
+    val bookshelfGridCoverWidth: Int = 120,
+    val bookshelfSearchActionDirectToSearch: Boolean = true,
+    val autoRefreshBook: Boolean = false,
+    val saveTabPosition: Long = BookGroup.IdAll,
+)

+ 2 - 0
app/src/main/java/io/legado/app/domain/model/settings/ThemeSettings.kt

@@ -51,6 +51,8 @@ data class ThemeSettings(
     val eyeProtectionStartTime: String = "22:00",
     val eyeProtectionEndTime: String = "07:00",
     val showRefactorTip: Boolean = true,
+    val enableCustomTagColors: Boolean = false,
+    val customTagColorsJson: String? = null,
 )
 
 data class ThemeCustomColors(

+ 8 - 6
app/src/main/java/io/legado/app/ui/config/themeConfig/LabelColorManageSheet.kt

@@ -39,12 +39,14 @@ import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
 @Composable
 fun LabelColorManageSheet(
     show: Boolean,
+    themeColor: Int,
+    colors: List<TagColorPair>,
+    onColorsChange: (List<TagColorPair>) -> Unit,
     onDismissRequest: () -> Unit
 ) {
     val primaryColor = LegadoTheme.colorScheme.primary
-    val themeColor = ThemeConfig.themeColor
-    val tagColors = remember(show) {
-        ThemeConfig.getCustomTagColors().toMutableStateList()
+    val tagColors = remember(show, colors) {
+        colors.toMutableStateList()
     }
     var showColorPicker by remember { mutableStateOf(false) }
     var editingIndex by remember { mutableIntStateOf(-1) }
@@ -61,7 +63,7 @@ fun LabelColorManageSheet(
                     val generatedColors = TagColorGenerator.generateTagColors(baseColor)
                     tagColors.clear()
                     tagColors.addAll(generatedColors)
-                    ThemeConfig.saveCustomTagColors(tagColors)
+                    onColorsChange(tagColors.toList())
                 },
                 icon = Icons.Default.AutoAwesome,
                 contentDescription = stringResource(R.string.ai_generate)
@@ -124,7 +126,7 @@ fun LabelColorManageSheet(
                             SmallPlainButton(
                                 onClick = {
                                     tagColors.removeAt(index)
-                                    ThemeConfig.saveCustomTagColors(tagColors)
+                                    onColorsChange(tagColors.toList())
                                 },
                                 icon = Icons.Default.Delete,
                                 contentDescription = stringResource(R.string.delete)
@@ -151,7 +153,7 @@ fun LabelColorManageSheet(
                     textColor = selectedColor,
                     bgColor = bgColor
                 )
-                ThemeConfig.saveCustomTagColors(tagColors)
+                onColorsChange(tagColors.toList())
                 showColorPicker = false
             }
         )

+ 44 - 25
app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt

@@ -51,8 +51,8 @@ import androidx.compose.ui.text.withStyle
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.constant.BookType
-import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
-import io.legado.app.ui.config.themeConfig.ThemeConfig
+import io.legado.app.domain.model.settings.BookshelfSettings
+import io.legado.app.ui.config.themeConfig.TagColorPair
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.card.NormalCard
 import io.legado.app.ui.widget.components.card.TextCard
@@ -61,6 +61,7 @@ import io.legado.app.ui.widget.components.image.cover.BookshelfCover
 import io.legado.app.ui.widget.components.image.cover.CoilBookCover
 import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.toTimeAgo
+import kotlinx.collections.immutable.ImmutableList
 
 /**
  * 通用的书架条目布局组件
@@ -69,6 +70,7 @@ import io.legado.app.utils.toTimeAgo
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 fun BookshelfItem(
+    settings: BookshelfSettings,
     isGrid: Boolean,
     gridStyle: Int, // 0: Standard, 1: Compact, 2: Cover Only
     isCompact: Boolean, // For List Mode
@@ -96,7 +98,7 @@ fun BookshelfItem(
 ) {
     val isDark = LegadoTheme.isDark
     val bookshelfCardColor =
-        if (isDark) BookshelfConfig.bookshelfCardColorDark else BookshelfConfig.bookshelfCardColor
+        if (isDark) settings.bookshelfCardColorDark else settings.bookshelfCardColor
     val containerColor = if (!isGrid && bookshelfCardColor != 0) {
         Color(bookshelfCardColor)
     } else {
@@ -252,7 +254,7 @@ fun BookshelfItem(
                             } else {
                                 LegadoTheme.typography.titleMediumEmphasized
                             },
-                            maxLines = BookshelfConfig.bookshelfTitleMaxLines,
+                            maxLines = settings.bookshelfTitleMaxLines,
                             overflow = TextOverflow.Ellipsis,
                             modifier = Modifier.weight(1f)
                         )
@@ -300,7 +302,7 @@ fun BookshelfItem(
                 }
                 bottomContent?.invoke()
             }
-            if (BookshelfConfig.bookshelfShowDivider)
+            if (settings.bookshelfShowDivider)
                 HorizontalDivider(
                     modifier = Modifier.padding(horizontal = 16.dp),
                     thickness = 0.5.dp,
@@ -312,6 +314,7 @@ fun BookshelfItem(
 
 @Composable
 fun BookGroupCover(
+    settings: BookshelfSettings,
     books: List<BookUiItem>,
     coverPath: String? = null,
     leftBottomText: String? = null,
@@ -332,7 +335,7 @@ fun BookGroupCover(
         } else {
             Box(
                 modifier = Modifier.run {
-                    if (BookshelfConfig.bookshelfCoverShadow) {
+                    if (settings.bookshelfCoverShadow) {
                         background(LegadoTheme.colorScheme.surface)
                     } else {
                         this
@@ -428,6 +431,7 @@ fun BookGroupCover(
 
 @Composable
 fun BookGroupItemGrid(
+    settings: BookshelfSettings,
     group: BookGroupUi,
     previewBooks: List<BookUiItem>,
     countText: String? = null,
@@ -441,11 +445,13 @@ fun BookGroupItemGrid(
     onLongClick: (() -> Unit)?
 ) {
     BookshelfItem(
+        settings = settings,
         isGrid = true,
         gridStyle = gridStyle,
         isCompact = false,
         cover = {
             BookGroupCover(
+                settings = settings,
                 books = previewBooks,
                 coverPath = group.cover,
                 leftBottomText = countText,
@@ -459,7 +465,7 @@ fun BookGroupItemGrid(
         titleCenter = titleCenter,
         titleMaxLines = titleMaxLines,
         coverShadow = coverShadow,
-        coverWidth = BookshelfConfig.bookshelfGridCoverWidth,
+        coverWidth = settings.bookshelfGridCoverWidth,
         onClick = onClick,
         onLongClick = onLongClick
     )
@@ -467,6 +473,7 @@ fun BookGroupItemGrid(
 
 @Composable
 fun BookGroupItemList(
+    settings: BookshelfSettings,
     group: BookGroupUi,
     previewBooks: List<BookUiItem>,
     onClick: () -> Unit,
@@ -480,8 +487,9 @@ fun BookGroupItemList(
     onLongClick: (() -> Unit)? = null,
     onBookClick: ((BookShelfItem) -> Unit)? = null
 ) {
-    if (BookshelfConfig.bookshelfGroupListStyle == 2) {
+    if (settings.bookshelfGroupListStyle == 2) {
         BookGroupItemHorizontalCovers(
+            settings = settings,
             group = group,
             previewBooks = previewBooks,
             onClick = onClick,
@@ -504,10 +512,18 @@ fun BookGroupItemList(
         null
     }
     BookshelfItem(
+        settings = settings,
         isGrid = false,
         gridStyle = 0,
-        isCompact = BookshelfConfig.bookshelfGroupListStyle == 1 || isCompact,
-        cover = { BookGroupCover(books = previewBooks, coverPath = group.cover, modifier = it) },
+        isCompact = settings.bookshelfGroupListStyle == 1 || isCompact,
+        cover = {
+            BookGroupCover(
+                settings = settings,
+                books = previewBooks,
+                coverPath = group.cover,
+                modifier = it,
+            )
+        },
         title = group.groupName,
         subTitle = countText,
         descAnnotated = descAnnotated,
@@ -520,7 +536,7 @@ fun BookGroupItemList(
         titleCenter = titleCenter,
         titleMaxLines = titleMaxLines,
         coverShadow = coverShadow,
-        coverWidth = BookshelfConfig.bookshelfListCoverWidth,
+        coverWidth = settings.bookshelfListCoverWidth,
         modifier = modifier,
         onClick = onClick,
         onLongClick = onLongClick
@@ -529,6 +545,7 @@ fun BookGroupItemList(
 
 @Composable
 fun BookGroupItemHorizontalCovers(
+    settings: BookshelfSettings,
     group: BookGroupUi,
     previewBooks: List<BookUiItem>,
     onClick: () -> Unit,
@@ -540,7 +557,7 @@ fun BookGroupItemHorizontalCovers(
     Column {
         val isDark = LegadoTheme.isDark
         val bookshelfCardColor =
-            if (isDark) BookshelfConfig.bookshelfCardColorDark else BookshelfConfig.bookshelfCardColor
+            if (isDark) settings.bookshelfCardColorDark else settings.bookshelfCardColor
         NormalCard(
             modifier = modifier
                 .fillMaxWidth()
@@ -597,7 +614,7 @@ fun BookGroupItemHorizontalCovers(
                         .padding(horizontal = 4.dp),
                     horizontalArrangement = Arrangement.spacedBy(4.dp)
                 ) {
-                    val coverCount = BookshelfConfig.bookshelfGroupCoverCount
+                    val coverCount = settings.bookshelfGroupCoverCount
                     previewBooks.take(coverCount).forEach { bookUi ->
                         val book = bookUi.book
                         Box(
@@ -631,7 +648,7 @@ fun BookGroupItemHorizontalCovers(
                 }
             }
         }
-        if (BookshelfConfig.bookshelfShowDivider)
+        if (settings.bookshelfShowDivider)
             HorizontalDivider(
                 modifier = Modifier.padding(horizontal = 16.dp),
                 thickness = 0.5.dp,
@@ -643,6 +660,8 @@ fun BookGroupItemHorizontalCovers(
 @OptIn(ExperimentalSharedTransitionApi::class)
 @Composable
 fun BookItem(
+    settings: BookshelfSettings,
+    customTagColors: ImmutableList<TagColorPair>,
     bookUi: BookUiItem,
     layoutMode: Int,
     modifier: Modifier = Modifier,
@@ -664,9 +683,9 @@ fun BookItem(
 ) {
     val book = bookUi.book
     val unreadCount = book.getUnreadChapterNum()
-    val unreadText = if (BookshelfConfig.showUnread && unreadCount > 0) unreadCount.toString() else null
-    val showUpdateBadge = BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && book.isNew
-    val bookTypeLabel = if (BookshelfConfig.showTip) {
+    val unreadText = if (settings.showUnread && unreadCount > 0) unreadCount.toString() else null
+    val showUpdateBadge = settings.showUnread && settings.showUnreadNew && book.isNew
+    val bookTypeLabel = if (settings.showTip) {
         when {
             book.isAudio -> stringResource(R.string.audio)
             book.isImage -> stringResource(R.string.manga)
@@ -688,6 +707,7 @@ fun BookItem(
     }
 
     BookshelfItem(
+        settings = settings,
         isGrid = layoutMode != 0,
         gridStyle = gridStyle,
         isCompact = isCompact,
@@ -732,12 +752,11 @@ fun BookItem(
             book.author
         },
         desc = book.durChapterTitle ?: "",
-        columnContent = if (layoutMode == 0 && !isCompact && BookshelfConfig.showBookIntro) {
+        columnContent = if (layoutMode == 0 && !isCompact && settings.showBookIntro) {
             {
                 val kindList = bookUi.displayTags
                 val intro = book.intro?.takeIf { it.isNotBlank() }
-                val customTagColors = if (ThemeConfig.enableCustomTagColors) ThemeConfig.getCustomTagColors() else emptyList()
-                if (BookshelfConfig.bookshelfShowTag && kindList.isNotEmpty()) {
+                if (settings.bookshelfShowTag && kindList.isNotEmpty()) {
                     Row(
                         modifier = Modifier
                             .fillMaxWidth()
@@ -764,8 +783,8 @@ fun BookItem(
                         }
                     }
                 }
-                if (BookshelfConfig.bookshelfShowIntro && intro != null) {
-                    val maxLines = if (BookshelfConfig.bookshelfIntroMaxLines == 0) Int.MAX_VALUE else BookshelfConfig.bookshelfIntroMaxLines
+                if (settings.bookshelfShowIntro && intro != null) {
+                    val maxLines = if (settings.bookshelfIntroMaxLines == 0) Int.MAX_VALUE else settings.bookshelfIntroMaxLines
                     AppText(
                         text = intro,
                         style = LegadoTheme.typography.bodySmall,
@@ -780,9 +799,9 @@ fun BookItem(
             }
         } else null,
         bottomContent = null,
-        extra = if (layoutMode == 0 && !isCompact && BookshelfConfig.showBookIntro && BookshelfConfig.bookshelfShowLatestChapter) {
+        extra = if (layoutMode == 0 && !isCompact && settings.showBookIntro && settings.bookshelfShowLatestChapter) {
             {
-                if (BookshelfConfig.showLastUpdateTime && !book.isLocal) {
+                if (settings.showLastUpdateTime && !book.isLocal) {
                     AppText(
                         text = book.latestChapterTime.toTimeAgo(),
                         style = LegadoTheme.typography.labelSmallEmphasized,
@@ -814,7 +833,7 @@ fun BookItem(
             matchedSourceLabel,
             bookTypeLabel,
         ),
-        coverWidth = if (layoutMode == 0) BookshelfConfig.bookshelfListCoverWidth else BookshelfConfig.bookshelfGridCoverWidth,
+        coverWidth = if (layoutMode == 0) settings.bookshelfListCoverWidth else settings.bookshelfGridCoverWidth,
         onClick = onClick,
         onLongClick = onLongClick
     )

+ 155 - 107
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfConfigSheet.kt

@@ -31,9 +31,14 @@ import androidx.compose.ui.res.stringArrayResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
-import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
+import io.legado.app.domain.gateway.BookshelfBooleanSetting
+import io.legado.app.domain.gateway.BookshelfIntSetting
+import io.legado.app.domain.gateway.BookshelfSettingsUpdate
+import io.legado.app.domain.model.settings.BookshelfSettings
+import io.legado.app.domain.gateway.ThemeBooleanSetting
+import io.legado.app.domain.gateway.ThemeSettingsUpdate
 import io.legado.app.ui.config.themeConfig.LabelColorManageSheet
-import io.legado.app.ui.config.themeConfig.ThemeConfig
+import io.legado.app.ui.config.themeConfig.TagColorPair
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
 import io.legado.app.ui.widget.components.divider.PillDivider
@@ -47,6 +52,13 @@ import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
 @Composable
 fun BookshelfConfigSheet(
     show: Boolean,
+    settings: BookshelfSettings,
+    onUpdate: (BookshelfSettingsUpdate) -> Unit,
+    enableCustomTagColors: Boolean,
+    customTagColors: List<TagColorPair>,
+    themeColor: Int,
+    onThemeUpdate: (ThemeSettingsUpdate) -> Unit,
+    onCustomTagColorsChange: (List<TagColorPair>) -> Unit,
     onDismissRequest: () -> Unit
 ) {
     val configuration = LocalConfiguration.current
@@ -69,43 +81,43 @@ fun BookshelfConfigSheet(
         ) {
             CompactDropdownSettingItem(
                 title = stringResource(R.string.group_style),
-                selectedValue = BookshelfConfig.bookGroupStyle.toString(),
+                selectedValue = settings.bookGroupStyle.toString(),
                 displayEntries = stringArrayResource(R.array.group_style),
                 entryValues = Array(stringArrayResource(R.array.group_style).size) { it.toString() },
-                onValueChange = { BookshelfConfig.bookGroupStyle = it.toInt() }
+                onValueChange = { onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.BookGroupStyle, it.toInt())) }
             )
 
             // Sort
             CompactDropdownSettingItem(
                 title = stringResource(R.string.sort),
-                selectedValue = BookshelfConfig.bookshelfSort.toString(),
+                selectedValue = settings.bookshelfSort.toString(),
                 displayEntries = stringArrayResource(R.array.bookshelf_px_array),
                 entryValues = Array(stringArrayResource(R.array.bookshelf_px_array).size) { it.toString() },
-                onValueChange = { BookshelfConfig.bookshelfSort = it.toInt() }
+                onValueChange = { onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.Sort, it.toInt())) }
             )
 
             // Sort Order
             CompactDropdownSettingItem(
                 title = stringResource(R.string.sort_order),
-                selectedValue = BookshelfConfig.bookshelfSortOrder.toString(),
+                selectedValue = settings.bookshelfSortOrder.toString(),
                 displayEntries = arrayOf(
                     stringResource(R.string.ascending_order),
                     stringResource(R.string.descending_order)
                 ),
                 entryValues = arrayOf("0", "1"),
-                onValueChange = { BookshelfConfig.bookshelfSortOrder = it.toInt() }
+                onValueChange = { onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.SortOrder, it.toInt())) }
             )
 
             // Layout Mode (non-folder)
             val layoutMode =
-                if (isLandscape) BookshelfConfig.bookshelfLayoutModeLandscape
-                else BookshelfConfig.bookshelfLayoutModePortrait
+                if (isLandscape) settings.bookshelfLayoutModeLandscape
+                else settings.bookshelfLayoutModePortrait
             val folderLayoutMode =
-                if (isLandscape) BookshelfConfig.bookshelfFolderLayoutModeLandscape
-                else BookshelfConfig.bookshelfFolderLayoutModePortrait
+                if (isLandscape) settings.bookshelfFolderLayoutModeLandscape
+                else settings.bookshelfFolderLayoutModePortrait
 
             // Folder Layout Mode
-            AnimatedVisibility(visible = BookshelfConfig.bookGroupStyle == 2) {
+            AnimatedVisibility(visible = settings.bookGroupStyle == 2) {
                 Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
 
                     PillDivider()
@@ -117,31 +129,41 @@ fun BookshelfConfigSheet(
                         displayEntries = arrayOf(stringResource(R.string.layout_mode_list), stringResource(R.string.layout_mode_grid)),
                         entryValues = arrayOf("0", "1"),
                         onValueChange = {
-                            if (isLandscape) BookshelfConfig.bookshelfFolderLayoutModeLandscape = it.toInt()
-                            else BookshelfConfig.bookshelfFolderLayoutModePortrait = it.toInt()
+                            onUpdate(
+                                BookshelfSettingsUpdate.IntValue(
+                                    if (isLandscape) BookshelfIntSetting.FolderLayoutModeLandscape
+                                    else BookshelfIntSetting.FolderLayoutModePortrait,
+                                    it.toInt(),
+                                )
+                            )
                         }
                     )
 
                     AnimatedVisibility(visible = folderLayoutMode == 1) {
                         val folderGridCount =
-                            if (isLandscape) BookshelfConfig.bookshelfFolderLayoutGridLandscape
-                            else BookshelfConfig.bookshelfFolderLayoutGridPortrait
+                            if (isLandscape) settings.bookshelfFolderLayoutGridLandscape
+                            else settings.bookshelfFolderLayoutGridPortrait
                         CompactSliderSettingItem(
                             title = stringResource(R.string.number_rows_columns),
                             value = folderGridCount.toFloat(),
                             valueRange = 1f..15f,
                             steps = 14,
                             onValueChange = {
-                                if (isLandscape) BookshelfConfig.bookshelfFolderLayoutGridLandscape = it.toInt()
-                                else BookshelfConfig.bookshelfFolderLayoutGridPortrait = it.toInt()
+                                onUpdate(
+                                    BookshelfSettingsUpdate.IntValue(
+                                        if (isLandscape) BookshelfIntSetting.FolderLayoutGridLandscape
+                                        else BookshelfIntSetting.FolderLayoutGridPortrait,
+                                        it.toInt(),
+                                    )
+                                )
                             }
                         )
                     }
 
                     AnimatedVisibility(visible = folderLayoutMode != 1) {
                         val folderListCount =
-                            if (isLandscape) BookshelfConfig.bookshelfFolderLayoutListLandscape
-                            else BookshelfConfig.bookshelfFolderLayoutListPortrait
+                            if (isLandscape) settings.bookshelfFolderLayoutListLandscape
+                            else settings.bookshelfFolderLayoutListPortrait
                         Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
                             CompactSliderSettingItem(
                                 title = stringResource(R.string.number_rows_columns),
@@ -149,48 +171,51 @@ fun BookshelfConfigSheet(
                                 valueRange = 1f..5f,
                                 steps = 4,
                                 onValueChange = {
-                                    if (isLandscape) BookshelfConfig.bookshelfFolderLayoutListLandscape =
-                                        it.toInt()
-                                    else BookshelfConfig.bookshelfFolderLayoutListPortrait =
-                                        it.toInt()
+                                    onUpdate(
+                                        BookshelfSettingsUpdate.IntValue(
+                                            if (isLandscape) BookshelfIntSetting.FolderLayoutListLandscape
+                                            else BookshelfIntSetting.FolderLayoutListPortrait,
+                                            it.toInt(),
+                                        )
+                                    )
                                 }
                             )
 
                             CompactSliderSettingItem(
                                 title = stringResource(R.string.list_cover_width),
-                                value = BookshelfConfig.bookshelfListCoverWidth.toFloat(),
+                                value = settings.bookshelfListCoverWidth.toFloat(),
                                 valueRange = 40f..120f,
                                 steps = 80,
                                 onValueChange = {
-                                    BookshelfConfig.bookshelfListCoverWidth = it.toInt()
+                                    onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.ListCoverWidth, it.toInt()))
                                 }
                             )
                         }
                     }
 
                     AnimatedVisibility(
-                        visible = BookshelfConfig.bookGroupStyle == 2 && folderLayoutMode == 0
+                        visible = settings.bookGroupStyle == 2 && folderLayoutMode == 0
                     ) {
                         Column(
                             verticalArrangement = Arrangement.spacedBy(8.dp)
                         ) {
                             CompactDropdownSettingItem(
                                 title = stringResource(R.string.folder_list_style),
-                                selectedValue = BookshelfConfig.bookshelfGroupListStyle.toString(),
+                                selectedValue = settings.bookshelfGroupListStyle.toString(),
                                 displayEntries = arrayOf(stringResource(R.string.group), stringResource(R.string.compact_list), stringResource(R.string.horizontal_cover_count)),
                                 entryValues = arrayOf("0", "1", "2"),
                                 onValueChange = {
-                                    BookshelfConfig.bookshelfGroupListStyle = it.toInt()
+                                    onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.GroupListStyle, it.toInt()))
                                 }
                             )
-                            AnimatedVisibility(visible = BookshelfConfig.bookshelfGroupListStyle == 2) {
+                            AnimatedVisibility(visible = settings.bookshelfGroupListStyle == 2) {
                                 CompactSliderSettingItem(
                                     title = stringResource(R.string.horizontal_cover_count),
-                                    value = BookshelfConfig.bookshelfGroupCoverCount.toFloat(),
+                                    value = settings.bookshelfGroupCoverCount.toFloat(),
                                     valueRange = 1f..10f,
                                     steps = 9,
                                     onValueChange = {
-                                        BookshelfConfig.bookshelfGroupCoverCount = it.toInt()
+                                        onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.GroupCoverCount, it.toInt()))
                                     }
                                 )
                             }
@@ -211,8 +236,13 @@ fun BookshelfConfigSheet(
                 ),
                 entryValues = arrayOf("0", "1"),
                 onValueChange = {
-                    if (isLandscape) BookshelfConfig.bookshelfLayoutModeLandscape = it.toInt()
-                    else BookshelfConfig.bookshelfLayoutModePortrait = it.toInt()
+                    onUpdate(
+                        BookshelfSettingsUpdate.IntValue(
+                            if (isLandscape) BookshelfIntSetting.LayoutModeLandscape
+                            else BookshelfIntSetting.LayoutModePortrait,
+                            it.toInt(),
+                        )
+                    )
                 }
             )
 
@@ -224,46 +254,50 @@ fun BookshelfConfigSheet(
                 ) {
                     CompactDropdownSettingItem(
                         title = stringResource(R.string.grid_style),
-                        selectedValue = BookshelfConfig.bookshelfGridLayout.toString(),
+                        selectedValue = settings.bookshelfGridLayout.toString(),
                         displayEntries = stringArrayResource(R.array.bookshelf_grid_layout),
                         entryValues = Array(stringArrayResource(R.array.bookshelf_grid_layout).size) { it.toString() },
-                        onValueChange = { BookshelfConfig.bookshelfGridLayout = it.toInt() }
+                        onValueChange = { onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.GridLayout, it.toInt())) }
                     )
 
                     val gridCount =
-                        if (isLandscape) BookshelfConfig.bookshelfLayoutGridLandscape else BookshelfConfig.bookshelfLayoutGridPortrait
+                        if (isLandscape) settings.bookshelfLayoutGridLandscape else settings.bookshelfLayoutGridPortrait
                     CompactSliderSettingItem(
                         title = stringResource(R.string.number_rows_columns),
                         value = gridCount.toFloat(),
                         valueRange = 1f..15f,
                         steps = 14,
                         onValueChange = {
-                            if (isLandscape) BookshelfConfig.bookshelfLayoutGridLandscape =
-                                it.toInt()
-                            else BookshelfConfig.bookshelfLayoutGridPortrait = it.toInt()
+                            onUpdate(
+                                BookshelfSettingsUpdate.IntValue(
+                                    if (isLandscape) BookshelfIntSetting.LayoutGridLandscape
+                                    else BookshelfIntSetting.LayoutGridPortrait,
+                                    it.toInt(),
+                                )
+                            )
                         }
                     )
 
                     CompactSwitchSettingItem(
                         title = stringResource(R.string.compact_title_font),
-                        checked = BookshelfConfig.bookshelfTitleSmallFont,
+                        checked = settings.bookshelfTitleSmallFont,
                         color = LegadoTheme.colorScheme.surface,
-                        onCheckedChange = { BookshelfConfig.bookshelfTitleSmallFont = it }
+                        onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.TitleSmallFont, it)) }
                     )
 
                     CompactSwitchSettingItem(
                         title = stringResource(R.string.center_aligned_title),
-                        checked = BookshelfConfig.bookshelfTitleCenter,
+                        checked = settings.bookshelfTitleCenter,
                         color = LegadoTheme.colorScheme.surface,
-                        onCheckedChange = { BookshelfConfig.bookshelfTitleCenter = it }
+                        onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.TitleCenter, it)) }
                     )
 
                     CompactSliderSettingItem(
                         title = stringResource(R.string.grid_cover_width),
-                        value = BookshelfConfig.bookshelfGridCoverWidth.toFloat(),
+                        value = settings.bookshelfGridCoverWidth.toFloat(),
                         valueRange = 40f..150f,
                         steps = 110,
-                        onValueChange = { BookshelfConfig.bookshelfGridCoverWidth = it.toInt() }
+                        onValueChange = { onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.GridCoverWidth, it.toInt())) }
                     )
                 }
             }
@@ -276,9 +310,9 @@ fun BookshelfConfigSheet(
                 ) {
                     CompactSwitchSettingItem(
                         title = stringResource(R.string.show_divider_line),
-                        checked = BookshelfConfig.bookshelfShowDivider,
+                        checked = settings.bookshelfShowDivider,
                         color = LegadoTheme.colorScheme.surface,
-                        onCheckedChange = { BookshelfConfig.bookshelfShowDivider = it }
+                        onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.ShowDivider, it)) }
                     )
 
                     CompactClickableSettingItem(
@@ -286,12 +320,12 @@ fun BookshelfConfigSheet(
                         color = LegadoTheme.colorScheme.surface,
                         onClick = { showColorPicker = true },
                         trailingContent = {
-                            if (BookshelfConfig.bookshelfCardColor != 0) {
+                            if (settings.bookshelfCardColor != 0) {
                                 Box(
                                     modifier = Modifier
                                         .size(20.dp)
                                         .clip(CircleShape)
-                                        .background(Color(BookshelfConfig.bookshelfCardColor))
+                                        .background(Color(settings.bookshelfCardColor))
                                         .border(
                                             1.dp,
                                             MaterialTheme.colorScheme.outlineVariant,
@@ -307,12 +341,12 @@ fun BookshelfConfigSheet(
                         color = LegadoTheme.colorScheme.surface,
                         onClick = { showColorPickerDark = true },
                         trailingContent = {
-                            if (BookshelfConfig.bookshelfCardColorDark != 0) {
+                            if (settings.bookshelfCardColorDark != 0) {
                                 Box(
                                     modifier = Modifier
                                         .size(20.dp)
                                         .clip(CircleShape)
-                                        .background(Color(BookshelfConfig.bookshelfCardColorDark))
+                                        .background(Color(settings.bookshelfCardColorDark))
                                         .border(
                                             1.dp,
                                             MaterialTheme.colorScheme.outlineVariant,
@@ -325,19 +359,19 @@ fun BookshelfConfigSheet(
 
                     CompactSwitchSettingItem(
                         title = stringResource(R.string.compact_details),
-                        checked = BookshelfConfig.bookshelfLayoutCompact,
+                        checked = settings.bookshelfLayoutCompact,
                         color = LegadoTheme.colorScheme.surface,
-                        onCheckedChange = { BookshelfConfig.bookshelfLayoutCompact = it }
+                        onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.LayoutCompact, it)) }
                     )
 
                     val listColCount =
-                        if (isLandscape) BookshelfConfig.bookshelfLayoutListLandscape else BookshelfConfig.bookshelfLayoutListPortrait
+                        if (isLandscape) settings.bookshelfLayoutListLandscape else settings.bookshelfLayoutListPortrait
                     CompactSliderSettingItem(
                         title = stringResource(R.string.list_cover_width),
-                        value = BookshelfConfig.bookshelfListCoverWidth.toFloat(),
+                        value = settings.bookshelfListCoverWidth.toFloat(),
                         valueRange = 40f..120f,
                         steps = 80,
-                        onValueChange = { BookshelfConfig.bookshelfListCoverWidth = it.toInt() }
+                        onValueChange = { onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.ListCoverWidth, it.toInt())) }
                     )
 
                     CompactSliderSettingItem(
@@ -346,71 +380,82 @@ fun BookshelfConfigSheet(
                         valueRange = 1f..5f,
                         steps = 4,
                         onValueChange = {
-                            if (isLandscape) BookshelfConfig.bookshelfLayoutListLandscape =
-                                it.toInt()
-                            else BookshelfConfig.bookshelfLayoutListPortrait = it.toInt()
+                            onUpdate(
+                                BookshelfSettingsUpdate.IntValue(
+                                    if (isLandscape) BookshelfIntSetting.LayoutListLandscape
+                                    else BookshelfIntSetting.LayoutListPortrait,
+                                    it.toInt(),
+                                )
+                            )
                         }
                     )
 
                     CompactSwitchSettingItem(
                         title = stringResource(R.string.show_more_info),
-                        checked = BookshelfConfig.showBookIntro,
+                        checked = settings.showBookIntro,
                         color = LegadoTheme.colorScheme.surface,
-                        onCheckedChange = { BookshelfConfig.showBookIntro = it }
+                        onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.ShowBookIntro, it)) }
                     )
 
-                    AnimatedVisibility(visible = BookshelfConfig.showBookIntro) {
+                    AnimatedVisibility(visible = settings.showBookIntro) {
                         Column(
                             verticalArrangement = Arrangement.spacedBy(8.dp)
                         ) {
                             CompactSwitchSettingItem(
                                 title = stringResource(R.string.show_latest_chapter),
-                                checked = BookshelfConfig.bookshelfShowLatestChapter,
+                                checked = settings.bookshelfShowLatestChapter,
                                 color = LegadoTheme.colorScheme.surface,
                                 onCheckedChange = {
-                                    BookshelfConfig.bookshelfShowLatestChapter = it
+                                    onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.ShowLatestChapter, it))
                                 }
                             )
 
                             CompactSwitchSettingItem(
                                 title = stringResource(R.string.show_synopsis),
-                                checked = BookshelfConfig.bookshelfShowIntro,
+                                checked = settings.bookshelfShowIntro,
                                 color = LegadoTheme.colorScheme.surface,
-                                onCheckedChange = { BookshelfConfig.bookshelfShowIntro = it }
+                                onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.ShowIntro, it)) }
                             )
                             AnimatedVisibility(
-                                visible = BookshelfConfig.bookshelfShowIntro
+                                visible = settings.bookshelfShowIntro
                             ) {
                                 CompactSliderSettingItem(
                                     title = stringResource(R.string.synopsis_lines),
-                                    description = if (BookshelfConfig.bookshelfIntroMaxLines == 0) stringResource(R.string.show_all_synopsis) else stringResource(R.string.show_lines_synopsis, BookshelfConfig.bookshelfIntroMaxLines),
-                                    value = BookshelfConfig.bookshelfIntroMaxLines.toFloat(),
+                                    description = if (settings.bookshelfIntroMaxLines == 0) stringResource(R.string.show_all_synopsis) else stringResource(R.string.show_lines_synopsis, settings.bookshelfIntroMaxLines),
+                                    value = settings.bookshelfIntroMaxLines.toFloat(),
                                     valueRange = 0f..10f,
                                     steps = 10,
                                     onValueChange = {
-                                        BookshelfConfig.bookshelfIntroMaxLines = it.toInt()
+                                        onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.IntroMaxLines, it.toInt()))
                                     }
                                 )
                             }
                             CompactSwitchSettingItem(
                                 title = stringResource(R.string.show_tags),
-                                checked = BookshelfConfig.bookshelfShowTag,
+                                checked = settings.bookshelfShowTag,
                                 color = LegadoTheme.colorScheme.surface,
-                                onCheckedChange = { BookshelfConfig.bookshelfShowTag = it }
+                                onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.ShowTag, it)) }
                             )
                             AnimatedVisibility(
-                                visible = BookshelfConfig.bookshelfShowTag
+                                visible = settings.bookshelfShowTag
                             ) {
                                 Column(
                                     verticalArrangement = Arrangement.spacedBy(8.dp)
                                 ) {
                                     CompactSwitchSettingItem(
                                         title = stringResource(R.string.custom_tag_colors),
-                                        checked = ThemeConfig.enableCustomTagColors,
+                                        checked = enableCustomTagColors,
                                         color = LegadoTheme.colorScheme.surface,
-                                        onCheckedChange = { ThemeConfig.enableCustomTagColors = it }
+                                        onCheckedChange = {
+                                            onThemeUpdate(
+                                                ThemeSettingsUpdate.BooleanValue(
+                                                    ThemeBooleanSetting.EnableCustomTagColors,
+                                                    it,
+                                                )
+                                            )
+                                        }
                                     )
-                                    AnimatedVisibility(visible = ThemeConfig.enableCustomTagColors) {
+                                    AnimatedVisibility(visible = enableCustomTagColors) {
                                         CompactClickableSettingItem(
                                             title = stringResource(R.string.manage_tag_colors),
                                             color = LegadoTheme.colorScheme.surface,
@@ -428,91 +473,91 @@ fun BookshelfConfigSheet(
 
             CompactSliderSettingItem(
                 title = stringResource(R.string.max_title_lines),
-                value = BookshelfConfig.bookshelfTitleMaxLines.toFloat(),
+                value = settings.bookshelfTitleMaxLines.toFloat(),
                 valueRange = 1f..5f,
                 steps = 4,
-                onValueChange = { BookshelfConfig.bookshelfTitleMaxLines = it.toInt() }
+                onValueChange = { onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.TitleMaxLines, it.toInt())) }
             )
 
             CompactSwitchSettingItem(
                 title = stringResource(R.string.cover_shadow),
-                checked = BookshelfConfig.bookshelfCoverShadow,
+                checked = settings.bookshelfCoverShadow,
                 color = LegadoTheme.colorScheme.surface,
-                onCheckedChange = { BookshelfConfig.bookshelfCoverShadow = it }
+                onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.CoverShadow, it)) }
             )
 
             CompactSwitchSettingItem(
                 title = stringResource(R.string.search_filter_first),
-                checked = BookshelfConfig.bookshelfSearchActionDirectToSearch,
+                checked = settings.bookshelfSearchActionDirectToSearch,
                 color = LegadoTheme.colorScheme.surface,
-                onCheckedChange = { BookshelfConfig.bookshelfSearchActionDirectToSearch = it }
+                onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.SearchActionDirectToSearch, it)) }
             )
 
             // Switches
             CompactSwitchSettingItem(
                 title = stringResource(R.string.show_unread),
-                checked = BookshelfConfig.showUnread,
+                checked = settings.showUnread,
                 color = LegadoTheme.colorScheme.surface,
-                onCheckedChange = { BookshelfConfig.showUnread = it }
+                onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.ShowUnread, it)) }
             )
 
             CompactSwitchSettingItem(
                 title = stringResource(R.string.show_unread_new),
-                checked = BookshelfConfig.showUnreadNew,
+                checked = settings.showUnreadNew,
                 color = LegadoTheme.colorScheme.surface,
-                onCheckedChange = { BookshelfConfig.showUnreadNew = it }
+                onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.ShowUnreadNew, it)) }
             )
 
             CompactSwitchSettingItem(
                 title = stringResource(R.string.show_tip),
-                checked = BookshelfConfig.showTip,
+                checked = settings.showTip,
                 color = LegadoTheme.colorScheme.surface,
-                onCheckedChange = { BookshelfConfig.showTip = it }
+                onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.ShowTip, it)) }
             )
 
             CompactSwitchSettingItem(
                 title = stringResource(R.string.show_book_count),
-                checked = BookshelfConfig.showBookCount,
+                checked = settings.showBookCount,
                 color = LegadoTheme.colorScheme.surface,
-                onCheckedChange = { BookshelfConfig.showBookCount = it }
+                onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.ShowBookCount, it)) }
             )
 
             CompactSwitchSettingItem(
                 title = stringResource(R.string.show_last_update_time),
-                checked = BookshelfConfig.showLastUpdateTime,
+                checked = settings.showLastUpdateTime,
                 color = LegadoTheme.colorScheme.surface,
-                onCheckedChange = { BookshelfConfig.showLastUpdateTime = it }
+                onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.ShowLastUpdateTime, it)) }
             )
 
             CompactSwitchSettingItem(
                 title = stringResource(R.string.show_wait_up_count),
-                checked = BookshelfConfig.showWaitUpCount,
+                checked = settings.showWaitUpCount,
                 color = LegadoTheme.colorScheme.surface,
-                onCheckedChange = { BookshelfConfig.showWaitUpCount = it }
+                onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.ShowWaitUpCount, it)) }
             )
 
             CompactSwitchSettingItem(
                 title = stringResource(R.string.show_bookshelf_fast_scroller),
-                checked = BookshelfConfig.showBookshelfFastScroller,
+                checked = settings.showBookshelfFastScroller,
                 color = LegadoTheme.colorScheme.surface,
-                onCheckedChange = { BookshelfConfig.showBookshelfFastScroller = it }
+                onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.ShowFastScroller, it)) }
             )
 
             CompactSwitchSettingItem(
                 title = stringResource(R.string.show_bookshelf_tab_menu),
-                checked = BookshelfConfig.shouldShowExpandButton,
+                checked = settings.shouldShowExpandButton,
                 color = LegadoTheme.colorScheme.surface,
-                onCheckedChange = { BookshelfConfig.shouldShowExpandButton = it }
+                onCheckedChange = { onUpdate(BookshelfSettingsUpdate.BooleanValue(BookshelfBooleanSetting.ShowExpandButton, it)) }
             )
 
             // Refresh Limit
             CompactSliderSettingItem(
                 title = stringResource(R.string.bookshelf_update_limit),
-                description = if (BookshelfConfig.bookshelfRefreshingLimit <= 0) stringResource(R.string.refresh_limit_unlimited) else stringResource(R.string.refresh_limit_books, BookshelfConfig.bookshelfRefreshingLimit),
-                value = BookshelfConfig.bookshelfRefreshingLimit.toFloat(),
+                description = if (settings.bookshelfRefreshingLimit <= 0) stringResource(R.string.refresh_limit_unlimited) else stringResource(R.string.refresh_limit_books, settings.bookshelfRefreshingLimit),
+                value = settings.bookshelfRefreshingLimit.toFloat(),
                 valueRange = 0f..100f,
                 steps = 100,
-                onValueChange = { BookshelfConfig.bookshelfRefreshingLimit = it.toInt() }
+                onValueChange = { onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.RefreshingLimit, it.toInt())) }
             )
 
             Spacer(modifier = Modifier.height(32.dp))
@@ -521,21 +566,24 @@ fun BookshelfConfigSheet(
 
         LabelColorManageSheet(
             show = showLabelColorManage,
+            themeColor = themeColor,
+            colors = customTagColors,
+            onColorsChange = onCustomTagColorsChange,
             onDismissRequest = { showLabelColorManage = false }
         )
 
         ColorPickerSheet(
             show = showColorPicker,
-            initialColor = if (BookshelfConfig.bookshelfCardColor != 0) BookshelfConfig.bookshelfCardColor else LegadoTheme.colorScheme.surfaceVariant.toArgb(),
+            initialColor = if (settings.bookshelfCardColor != 0) settings.bookshelfCardColor else LegadoTheme.colorScheme.surfaceVariant.toArgb(),
             onDismissRequest = { showColorPicker = false },
-            onColorSelected = { BookshelfConfig.bookshelfCardColor = it }
+            onColorSelected = { onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.CardColor, it)) }
         )
 
         ColorPickerSheet(
             show = showColorPickerDark,
-            initialColor = if (BookshelfConfig.bookshelfCardColorDark != 0) BookshelfConfig.bookshelfCardColorDark else LegadoTheme.colorScheme.surfaceVariant.toArgb(),
+            initialColor = if (settings.bookshelfCardColorDark != 0) settings.bookshelfCardColorDark else LegadoTheme.colorScheme.surfaceVariant.toArgb(),
             onDismissRequest = { showColorPickerDark = false },
-            onColorSelected = { BookshelfConfig.bookshelfCardColorDark = it }
+            onColorSelected = { onUpdate(BookshelfSettingsUpdate.IntValue(BookshelfIntSetting.CardColorDark, it)) }
         )
     }
 }

+ 124 - 107
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt

@@ -102,12 +102,9 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.Dialog
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
-import io.legado.app.base.BaseRuleEvent
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.ui.book.group.GroupEditSheet
 import io.legado.app.ui.book.info.GroupSelectSheet
-import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
-import io.legado.app.ui.config.themeConfig.ThemeConfig
 import io.legado.app.ui.main.bookCoverSharedElementKey
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.ThemeResolver
@@ -179,20 +176,20 @@ fun BookshelfScreen(
     val snackbarHostState = remember { SnackbarHostState() }
 
     LaunchedEffect(Unit) {
-        viewModel.events.collect { event ->
-            when (event) {
-                is BaseRuleEvent.ShowSnackbar -> {
+        viewModel.effects.collect { effect ->
+            when (effect) {
+                is BookshelfEffect.ShowSnackbar -> {
                     val result = snackbarHostState.showSnackbar(
-                        message = event.message,
-                        actionLabel = event.actionLabel,
+                        message = effect.message,
+                        actionLabel = effect.actionLabel,
                         withDismissAction = true
                     )
-                    if (result == SnackbarResult.ActionPerformed && event.url != null) {
+                    if (result == SnackbarResult.ActionPerformed && effect.url != null) {
                         clipboardManager.setClipEntry(
                             ClipEntry(
                                 ClipData.newPlainText(
                                     "url",
-                                    event.url
+                                    effect.url
                                 )
                             )
                         )
@@ -207,7 +204,7 @@ fun BookshelfScreen(
         onResult = { uri ->
             uri?.let {
                 val groupId = uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
-                viewModel.importBookshelf(it, groupId)
+                viewModel.onIntent(BookshelfIntent.ImportFromUri(it, groupId))
             }
         }
     )
@@ -215,7 +212,7 @@ fun BookshelfScreen(
     val exportLauncher = rememberLauncherForActivityResult(
         contract = ActivityResultContracts.CreateDocument("application/json"),
         onResult = { uri ->
-            uri?.let { viewModel.exportToUri(it, uiState.items) }
+            uri?.let { viewModel.onIntent(BookshelfIntent.ExportToUri(it, uiState.items)) }
         }
     )
 
@@ -268,7 +265,7 @@ fun BookshelfScreen(
                 if (groups.isNotEmpty() && page in groups.indices) {
                     val targetGroupId = groups[page].groupId
                     if (latestSelectedGroupId != targetGroupId) {
-                        viewModel.changeGroup(targetGroupId)
+                        viewModel.onIntent(BookshelfIntent.ChangeGroup(targetGroupId))
                     }
                 }
             }
@@ -315,16 +312,16 @@ fun BookshelfScreen(
     val currentGroupBookCount by remember { derivedStateOf { uiState.currentGroupBookCount } }
 
     val clearSelection = {
-        viewModel.clearSelection()
+        viewModel.onIntent(BookshelfIntent.ClearSelection)
     }
     val exitEditMode = {
-        viewModel.exitEditMode()
+        viewModel.onIntent(BookshelfIntent.ExitEditMode)
     }
     val toggleEditMode = {
-        viewModel.toggleEditMode()
+        viewModel.onIntent(BookshelfIntent.ToggleEditMode)
     }
     val toggleBookSelection: (String) -> Unit = { bookUrl ->
-        viewModel.toggleBookSelection(bookUrl)
+        viewModel.onIntent(BookshelfIntent.ToggleBookSelection(bookUrl))
     }
 
     LaunchedEffect(pagerState.currentPage, isInFolderRoot) {
@@ -346,7 +343,7 @@ fun BookshelfScreen(
             progress.collect { backEvent ->
                 transitionState.seekTo(backEvent.progress, targetState = true)
             }
-            viewModel.setInFolderRoot(true)
+            viewModel.onIntent(BookshelfIntent.SetInFolderRoot(true))
             transitionState.animateTo(true)
         } catch (e: CancellationException) {
             transitionState.animateTo(false)
@@ -357,20 +354,20 @@ fun BookshelfScreen(
     val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
     val bookshelfFolderLayoutMode by remember(isLandscape) {
         derivedStateOf {
-            if (isLandscape) BookshelfConfig.bookshelfFolderLayoutModeLandscapeState.value
-            else BookshelfConfig.bookshelfFolderLayoutModePortraitState.value
+            if (isLandscape) uiState.settings.bookshelfFolderLayoutModeLandscape
+            else uiState.settings.bookshelfFolderLayoutModePortrait
         }
     }
     val bookshelfFolderLayoutGrid by remember(isLandscape) {
         derivedStateOf {
-            if (isLandscape) BookshelfConfig.bookshelfFolderLayoutGridLandscapeState.value
-            else BookshelfConfig.bookshelfFolderLayoutGridPortraitState.value
+            if (isLandscape) uiState.settings.bookshelfFolderLayoutGridLandscape
+            else uiState.settings.bookshelfFolderLayoutGridPortrait
         }
     }
     val bookshelfFolderLayoutList by remember(isLandscape) {
         derivedStateOf {
-            if (isLandscape) BookshelfConfig.bookshelfFolderLayoutListLandscapeState.value
-            else BookshelfConfig.bookshelfFolderLayoutListPortraitState.value
+            if (isLandscape) uiState.settings.bookshelfFolderLayoutListLandscape
+            else uiState.settings.bookshelfFolderLayoutListPortrait
         }
     }
     val currentMenuGroupId by remember {
@@ -394,13 +391,13 @@ fun BookshelfScreen(
     val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
     var showTopBarMenu by remember { mutableStateOf(false) }
     val onSearchClick = {
-        if (BookshelfConfig.bookshelfSearchActionDirectToSearchState.value) {
+        if (uiState.settings.bookshelfSearchActionDirectToSearch) {
             onNavigateToSearch(uiState.searchKey.trim())
         } else {
             val active = !uiState.isSearch
-            viewModel.setSearchMode(active)
+            viewModel.onIntent(BookshelfIntent.SetSearchMode(active))
             if (!active && uiState.selectedGroupId != currentTabGroupId) {
-                viewModel.changeGroup(currentTabGroupId)
+                viewModel.onIntent(BookshelfIntent.ChangeGroup(currentTabGroupId))
             }
         }
     }
@@ -424,24 +421,24 @@ fun BookshelfScreen(
                 uiState = uiState,
                 scrollBehavior = scrollBehavior,
                 onSearchClick = onSearchClick,
-                onSearchQueryChange = { viewModel.setSearchKey(it) },
+                onSearchQueryChange = { viewModel.onIntent(BookshelfIntent.SetSearchKey(it)) },
                 onSearchSubmit = { rawQuery ->
                     rawQuery.trim()
                         .takeIf { it.isNotEmpty() }
                         ?.let(onNavigateToSearch)
                 },
-                onClearSearch = { viewModel.setSearchKey("") },
+                onClearSearch = { viewModel.onIntent(BookshelfIntent.SetSearchKey("")) },
                 actions = {
                     AnimatedVisibility(visible = isEditMode) {
                         TopBarActionButton(
-                            onClick = { viewModel.selectAllVisible() },
+                            onClick = { viewModel.onIntent(BookshelfIntent.SelectAllVisible) },
                             imageVector = Icons.Default.SelectAll,
                             contentDescription = stringResource(R.string.select_all)
                         )
                     }
                     AnimatedVisibility(visible = isEditMode) {
                         TopBarActionButton(
-                            onClick = { viewModel.invertVisibleSelection() },
+                            onClick = { viewModel.onIntent(BookshelfIntent.InvertVisibleSelection) },
                             imageVector = Icons.Default.Refresh,
                             contentDescription = stringResource(R.string.revert_selection)
                         )
@@ -450,7 +447,7 @@ fun BookshelfScreen(
                         TopBarActionButton(
                             onClick = {
                                 if (selectedBookUrls.isNotEmpty()) {
-                                    viewModel.showOverlay(BookshelfOverlay.BatchDownloadConfirmDialog)
+                                    viewModel.onIntent(BookshelfIntent.ShowOverlay(BookshelfOverlay.BatchDownloadConfirmDialog))
                                 }
                             },
                             imageVector = Icons.Default.Download,
@@ -461,7 +458,7 @@ fun BookshelfScreen(
                         TopBarActionButton(
                             onClick = {
                                 if (selectedBookUrls.isNotEmpty()) {
-                                    viewModel.showOverlay(BookshelfOverlay.GroupSelectSheet)
+                                    viewModel.onIntent(BookshelfIntent.ShowOverlay(BookshelfOverlay.GroupSelectSheet))
                                 }
                             },
                             imageVector = Icons.Default.Bookmarks,
@@ -492,13 +489,16 @@ fun BookshelfScreen(
                                 )
                                 RoundDropdownMenuItem(
                                     text = stringResource(R.string.update_toc),
-                                    onClick = { viewModel.upToc(uiState.items); dismiss() },
+                                    onClick = {
+                                        viewModel.onIntent(BookshelfIntent.RefreshToc(uiState.items))
+                                        dismiss()
+                                    },
                                     leadingIcon = { Icon(Icons.Default.Refresh, null) }
                                 )
                                 RoundDropdownMenuItem(
                                     text = stringResource(R.string.layout_setting),
                                     onClick = {
-                                        viewModel.showOverlay(BookshelfOverlay.ConfigSheet)
+                                        viewModel.onIntent(BookshelfIntent.ShowOverlay(BookshelfOverlay.ConfigSheet))
                                         dismiss()
                                     },
                                     leadingIcon = { Icon(Icons.Default.GridView, null) }
@@ -506,7 +506,7 @@ fun BookshelfScreen(
                                 RoundDropdownMenuItem(
                                     text = stringResource(R.string.group_manage),
                                     onClick = {
-                                        viewModel.showOverlay(BookshelfOverlay.GroupManageSheet)
+                                        viewModel.onIntent(BookshelfIntent.ShowOverlay(BookshelfOverlay.GroupManageSheet))
                                         dismiss()
                                     },
                                     leadingIcon = { Icon(Icons.Outlined.ViewCarousel, null) }
@@ -514,7 +514,7 @@ fun BookshelfScreen(
                                 RoundDropdownMenuItem(
                                     text = stringResource(R.string.add_url),
                                     onClick = {
-                                        viewModel.showOverlay(BookshelfOverlay.AddUrlDialog)
+                                        viewModel.onIntent(BookshelfIntent.ShowOverlay(BookshelfOverlay.AddUrlDialog))
                                         dismiss()
                                     },
                                     leadingIcon = { Icon(Icons.Default.Link, null) }
@@ -541,7 +541,7 @@ fun BookshelfScreen(
                                 RoundDropdownMenuItem(
                                     text = stringResource(R.string.export_bookshelf),
                                     onClick = {
-                                        viewModel.showOverlay(BookshelfOverlay.ExportSheet)
+                                        viewModel.onIntent(BookshelfIntent.ShowOverlay(BookshelfOverlay.ExportSheet))
                                         dismiss()
                                     },
                                     leadingIcon = { Icon(Icons.Default.UploadFile, null) }
@@ -549,7 +549,7 @@ fun BookshelfScreen(
                                 RoundDropdownMenuItem(
                                     text = stringResource(R.string.import_bookshelf),
                                     onClick = {
-                                        viewModel.showOverlay(BookshelfOverlay.ImportSheet)
+                                        viewModel.onIntent(BookshelfIntent.ShowOverlay(BookshelfOverlay.ImportSheet))
                                         dismiss()
                                     },
                                     leadingIcon = { Icon(Icons.Default.CloudDownload, null) }
@@ -557,7 +557,7 @@ fun BookshelfScreen(
                                 RoundDropdownMenuItem(
                                     text = stringResource(R.string.log),
                                     onClick = {
-                                        viewModel.showOverlay(BookshelfOverlay.LogSheet)
+                                        viewModel.onIntent(BookshelfIntent.ShowOverlay(BookshelfOverlay.LogSheet))
                                         dismiss()
                                     },
                                     leadingIcon = { Icon(Icons.Default.History, null) }
@@ -589,16 +589,16 @@ fun BookshelfScreen(
                                 modifier = Modifier.weight(1f)
                             )
 
-                            val showExpandButton by BookshelfConfig.shouldShowExpandButtonState
+                            val showExpandButton = uiState.settings.shouldShowExpandButton
                             if (showExpandButton) {
                                 Box(modifier = Modifier) {
                                     SmallToggleButton(
                                         checked = showGroupMenu,
                                         onCheckedChange = {
                                             if (it) {
-                                                viewModel.showOverlay(BookshelfOverlay.GroupMenu)
+                                                viewModel.onIntent(BookshelfIntent.ShowOverlay(BookshelfOverlay.GroupMenu))
                                             } else {
-                                                viewModel.dismissOverlay()
+                                                viewModel.onIntent(BookshelfIntent.DismissOverlay)
                                             }
                                         },
                                         style = ToggleStyle.Outlined,
@@ -607,14 +607,14 @@ fun BookshelfScreen(
                                     )
                                     RoundDropdownMenu(
                                         expanded = showGroupMenu,
-                                        onDismissRequest = { viewModel.dismissOverlay() }
+                                        onDismissRequest = { viewModel.onIntent(BookshelfIntent.DismissOverlay) }
                                     ) { dismiss ->
                                         uiState.groups.forEachIndexed { index, group ->
                                             RoundDropdownMenuItem(
                                                 text = group.groupName,
                                                 onClick = {
                                                     if (uiState.isSearch) {
-                                                        viewModel.changeGroup(group.groupId)
+                                                        viewModel.onIntent(BookshelfIntent.ChangeGroup(group.groupId))
                                                     }
                                                     scope.launch {
                                                         pagerState.animateScrollToPage(
@@ -661,7 +661,7 @@ fun BookshelfScreen(
                                                     RoundDropdownMenuItem(
                                                         text = group.groupName,
                                                         onClick = {
-                                                            viewModel.changeGroup(group.groupId)
+                                                            viewModel.onIntent(BookshelfIntent.ChangeGroup(group.groupId))
                                                             dismiss()
                                                         },
                                                         trailingIcon = {
@@ -680,7 +680,7 @@ fun BookshelfScreen(
                                                     RoundDropdownMenuItem(
                                                         text = group.groupName,
                                                         onClick = {
-                                                            viewModel.changeGroup(group.groupId)
+                                                            viewModel.onIntent(BookshelfIntent.ChangeGroup(group.groupId))
                                                             dismiss()
                                                         },
                                                         trailingIcon = {
@@ -721,7 +721,7 @@ fun BookshelfScreen(
         Box(Modifier.fillMaxSize()) {
             AppPullToRefresh(
                 isRefreshing = uiState.isRefreshing,
-                onRefresh = { viewModel.refreshBooks(uiState.items) },
+                onRefresh = { viewModel.onIntent(BookshelfIntent.RefreshBooks(uiState.items)) },
                 enabled = pullToRefreshEnabled,
                 topPadding = paddingValues.calculateTopPadding(),
                 scrollBehavior = scrollBehavior
@@ -785,17 +785,17 @@ fun BookshelfScreen(
                             ),
                         contentPadding = adaptiveContentPaddingBookshelf(
                             top = paddingValues.calculateTopPadding(),
-                            bottom = if (ThemeConfig.useFloatingBottomBar || ThemeConfig.enableBlur) 120.dp else 8.dp,
+                            bottom = if (uiState.useRaisedBottomInset) 120.dp else 8.dp,
                             horizontal = 4.dp
                         ),
                         verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
                         horizontalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
-                        showFastScroll = BookshelfConfig.showBookshelfFastScrollerState.value
+                        showFastScroll = uiState.settings.showBookshelfFastScroller
                     ) {
                         itemsIndexed(
                             uiState.groups,
                             key = { _, it -> it.groupId }) { index, group ->
-                            val countText = if (BookshelfConfig.showBookCountState.value) {
+                            val countText = if (uiState.settings.showBookCount) {
                                 uiState.groupBookCounts[group.groupId]?.let {
                                     stringResource(R.string.book_count, it)
                                 }
@@ -804,41 +804,43 @@ fun BookshelfScreen(
                             }
                             if (bookshelfFolderLayoutMode == 0) {
                                 BookGroupItemList(
+                                    settings = uiState.settings,
                                     group = group,
                                     previewBooks = uiState.groupPreviews[group.groupId]
                                         ?: emptyList(),
                                     countText = countText,
-                                    isCompact = BookshelfConfig.bookshelfLayoutCompactState.value,
-                                    titleSmallFont = BookshelfConfig.bookshelfTitleSmallFontState.value,
-                                    titleCenter = BookshelfConfig.bookshelfTitleCenterState.value,
-                                    titleMaxLines = BookshelfConfig.bookshelfTitleMaxLinesState.value,
-                                    coverShadow = BookshelfConfig.bookshelfCoverShadowState.value,
+                                    isCompact = uiState.settings.bookshelfLayoutCompact,
+                                    titleSmallFont = uiState.settings.bookshelfTitleSmallFont,
+                                    titleCenter = uiState.settings.bookshelfTitleCenter,
+                                    titleMaxLines = uiState.settings.bookshelfTitleMaxLines,
+                                    coverShadow = uiState.settings.bookshelfCoverShadow,
                                     onClick = {
                                         scope.launch { pagerState.scrollToPage(index) }
-                                        viewModel.setInFolderRoot(false)
+                                        viewModel.onIntent(BookshelfIntent.SetInFolderRoot(false))
                                     },
                                     onLongClick = {
-                                        viewModel.showOverlay(BookshelfOverlay.GroupEditSheet(group.groupId))
+                                        viewModel.onIntent(BookshelfIntent.ShowOverlay(BookshelfOverlay.GroupEditSheet(group.groupId)))
                                     },
                                     onBookClick = onBookClick
                                 )
                             } else {
                                 BookGroupItemGrid(
+                                    settings = uiState.settings,
                                     group = group,
                                     previewBooks = uiState.groupPreviews[group.groupId]
                                         ?: emptyList(),
                                     countText = countText,
-                                    gridStyle = BookshelfConfig.bookshelfGridLayoutState.value,
-                                    titleSmallFont = BookshelfConfig.bookshelfTitleSmallFontState.value,
-                                    titleCenter = BookshelfConfig.bookshelfTitleCenterState.value,
-                                    titleMaxLines = BookshelfConfig.bookshelfTitleMaxLinesState.value,
-                                    coverShadow = BookshelfConfig.bookshelfCoverShadowState.value,
+                                    gridStyle = uiState.settings.bookshelfGridLayout,
+                                    titleSmallFont = uiState.settings.bookshelfTitleSmallFont,
+                                    titleCenter = uiState.settings.bookshelfTitleCenter,
+                                    titleMaxLines = uiState.settings.bookshelfTitleMaxLines,
+                                    coverShadow = uiState.settings.bookshelfCoverShadow,
                                     onClick = {
                                         scope.launch { pagerState.scrollToPage(index) }
-                                        viewModel.setInFolderRoot(false)
+                                        viewModel.onIntent(BookshelfIntent.SetInFolderRoot(false))
                                     },
                                     onLongClick = {
-                                        viewModel.showOverlay(BookshelfOverlay.GroupEditSheet(group.groupId))
+                                        viewModel.onIntent(BookshelfIntent.ShowOverlay(BookshelfOverlay.GroupEditSheet(group.groupId)))
                                     }
                                 )
                             }
@@ -909,15 +911,15 @@ fun BookshelfScreen(
                                         null
                                     },
                                     onDragStarted = {
-                                        if (isSelectedGroup) viewModel.startDraggingBooks(it)
+                                        if (isSelectedGroup) viewModel.onIntent(BookshelfIntent.StartDragging(it))
                                     },
                                     onMoveBook = { from, to, currentBooks ->
                                         if (isSelectedGroup) {
-                                            viewModel.moveDraggingBook(from, to, currentBooks)
+                                            viewModel.onIntent(BookshelfIntent.MoveDragging(from, to, currentBooks))
                                         }
                                     },
                                     onDragFinished = {
-                                        if (isSelectedGroup) viewModel.finishDraggingBooks()
+                                        if (isSelectedGroup) viewModel.onIntent(BookshelfIntent.FinishDragging)
                                     },
                                     onGlobalSearch = { onNavigateToSearch(uiState.searchKey.trim()) },
                                     onBookClick = onBookClick,
@@ -993,7 +995,7 @@ fun BookshelfScreen(
                                         role = Role.Button
                                     },
                                     onClick = {
-                                        viewModel.showOverlay(BookshelfOverlay.GroupMenu)
+                                        viewModel.onIntent(BookshelfIntent.ShowOverlay(BookshelfOverlay.GroupMenu))
                                     }
                                 )
                             }
@@ -1004,7 +1006,7 @@ fun BookshelfScreen(
                     if (summary.showGroupName) {
                         RoundDropdownMenu(
                             expanded = showGroupMenu,
-                            onDismissRequest = { viewModel.dismissOverlay() }
+                            onDismissRequest = { viewModel.onIntent(BookshelfIntent.DismissOverlay) }
                         ) { dismiss ->
                             uiState.groups.forEach { group ->
                                 RoundDropdownMenuItem(
@@ -1020,10 +1022,10 @@ fun BookshelfScreen(
                                             }
                                         }
                                         if (uiState.isSearch || uiState.selectedGroupId != group.groupId) {
-                                            viewModel.changeGroup(group.groupId)
+                                            viewModel.onIntent(BookshelfIntent.ChangeGroup(group.groupId))
                                         }
                                         if (bookGroupStyle == 2) {
-                                            viewModel.setInFolderRoot(false)
+                                            viewModel.onIntent(BookshelfIntent.SetInFolderRoot(false))
                                         }
                                         dismiss()
                                     },
@@ -1127,12 +1129,21 @@ private fun BookshelfOverlays(
 ) {
     BookshelfConfigSheet(
         show = activeOverlay == BookshelfOverlay.ConfigSheet,
-        onDismissRequest = { viewModel.dismissOverlay() }
+        settings = uiState.settings,
+        onUpdate = { viewModel.onIntent(BookshelfIntent.UpdateSetting(it)) },
+        enableCustomTagColors = uiState.enableCustomTagColors,
+        customTagColors = uiState.customTagColors,
+        themeColor = uiState.themeColor,
+        onThemeUpdate = { viewModel.onIntent(BookshelfIntent.UpdateThemeSetting(it)) },
+        onCustomTagColorsChange = {
+            viewModel.onIntent(BookshelfIntent.SetCustomTagColors(it))
+        },
+        onDismissRequest = { viewModel.onIntent(BookshelfIntent.DismissOverlay) }
     )
 
     GroupManageSheet(
         show = activeOverlay == BookshelfOverlay.GroupManageSheet,
-        onDismissRequest = { viewModel.dismissOverlay() }
+        onDismissRequest = { viewModel.onIntent(BookshelfIntent.DismissOverlay) }
     )
 
     val groups by viewModel.allGroupsFlow.collectAsStateWithLifecycle()
@@ -1143,7 +1154,7 @@ private fun BookshelfOverlays(
             GroupEditSheet(
                 show = true,
                 group = editGroup,
-                onDismissRequest = { viewModel.dismissOverlay() }
+                onDismissRequest = { viewModel.onIntent(BookshelfIntent.DismissOverlay) }
             )
         }
     }
@@ -1152,10 +1163,10 @@ private fun BookshelfOverlays(
         show = activeOverlay == BookshelfOverlay.GroupSelectSheet,
         groups = groups.filter { it.groupId > 0 },
         currentGroupId = 0L,
-        onDismissRequest = { viewModel.dismissOverlay() },
+        onDismissRequest = { viewModel.onIntent(BookshelfIntent.DismissOverlay) },
         onConfirm = { groupId ->
-            viewModel.moveBooksToGroup(selectedBookUrls, groupId)
-            viewModel.dismissOverlay()
+            viewModel.onIntent(BookshelfIntent.MoveBooksToGroup(selectedBookUrls, groupId))
+            viewModel.onIntent(BookshelfIntent.DismissOverlay)
             clearSelection()
         }
     )
@@ -1163,58 +1174,58 @@ private fun BookshelfOverlays(
     SourceInputDialog(
         show = activeOverlay == BookshelfOverlay.AddUrlDialog,
         title = stringResource(R.string.add_book_url),
-        onDismissRequest = { viewModel.dismissOverlay() },
+        onDismissRequest = { viewModel.onIntent(BookshelfIntent.DismissOverlay) },
         onConfirm = { url ->
-            viewModel.addBookByUrl(url)
-            viewModel.dismissOverlay()
+            viewModel.onIntent(BookshelfIntent.AddBookByUrl(url))
+            viewModel.onIntent(BookshelfIntent.DismissOverlay)
         }
     )
 
     FilePickerSheet(
         show = activeOverlay == BookshelfOverlay.ImportSheet,
-        onDismissRequest = { viewModel.dismissOverlay() },
+        onDismissRequest = { viewModel.onIntent(BookshelfIntent.DismissOverlay) },
         title = stringResource(R.string.import_bookshelf),
         onSelectSysFile = { types ->
             importLauncher.launch(types)
-            viewModel.dismissOverlay()
+            viewModel.onIntent(BookshelfIntent.DismissOverlay)
         },
         onManualInput = {
-            viewModel.showOverlay(BookshelfOverlay.AddUrlDialog)
+            viewModel.onIntent(BookshelfIntent.ShowOverlay(BookshelfOverlay.AddUrlDialog))
         },
         allowExtensions = arrayOf("json", "txt")
     )
 
     FilePickerSheet(
         show = activeOverlay == BookshelfOverlay.ExportSheet,
-        onDismissRequest = { viewModel.dismissOverlay() },
+        onDismissRequest = { viewModel.onIntent(BookshelfIntent.DismissOverlay) },
         title = stringResource(R.string.export_bookshelf),
         onSelectSysDir = {
-            viewModel.dismissOverlay()
+            viewModel.onIntent(BookshelfIntent.DismissOverlay)
             exportLauncher.launch("bookshelf.json")
         },
         onUpload = {
-            viewModel.dismissOverlay()
-            viewModel.uploadBookshelf(uiState.items)
+            viewModel.onIntent(BookshelfIntent.DismissOverlay)
+            viewModel.onIntent(BookshelfIntent.UploadBookshelf(uiState.items))
         }
     )
 
     AppLogSheet(
         show = activeOverlay == BookshelfOverlay.LogSheet,
-        onDismissRequest = { viewModel.dismissOverlay() }
+        onDismissRequest = { viewModel.onIntent(BookshelfIntent.DismissOverlay) }
     )
 
     AppAlertDialog(
         show = activeOverlay == BookshelfOverlay.BatchDownloadConfirmDialog,
-        onDismissRequest = { viewModel.dismissOverlay() },
+        onDismissRequest = { viewModel.onIntent(BookshelfIntent.DismissOverlay) },
         title = stringResource(R.string.draw),
         text = stringResource(R.string.sure_cache_book),
         confirmText = stringResource(android.R.string.ok),
         onConfirm = {
-            viewModel.dismissOverlay()
-            viewModel.downloadBooks(selectedBookUrls)
+            viewModel.onIntent(BookshelfIntent.DismissOverlay)
+            viewModel.onIntent(BookshelfIntent.DownloadBooks(selectedBookUrls))
         },
         dismissText = stringResource(android.R.string.cancel),
-        onDismiss = { viewModel.dismissOverlay() }
+        onDismiss = { viewModel.onIntent(BookshelfIntent.DismissOverlay) }
     )
 
     if (uiState.isLoading) {
@@ -1306,20 +1317,20 @@ fun BookshelfPage(
     val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
     val bookshelfLayoutMode by remember(isLandscape) {
         derivedStateOf {
-            if (isLandscape) BookshelfConfig.bookshelfLayoutModeLandscapeState.value
-            else BookshelfConfig.bookshelfLayoutModePortraitState.value
+            if (isLandscape) uiState.settings.bookshelfLayoutModeLandscape
+            else uiState.settings.bookshelfLayoutModePortrait
         }
     }
     val bookshelfLayoutGrid by remember(isLandscape) {
         derivedStateOf {
-            if (isLandscape) BookshelfConfig.bookshelfLayoutGridLandscapeState.value
-            else BookshelfConfig.bookshelfLayoutGridPortraitState.value
+            if (isLandscape) uiState.settings.bookshelfLayoutGridLandscape
+            else uiState.settings.bookshelfLayoutGridPortrait
         }
     }
     val bookshelfLayoutList by remember(isLandscape) {
         derivedStateOf {
-            if (isLandscape) BookshelfConfig.bookshelfLayoutListLandscapeState.value
-            else BookshelfConfig.bookshelfLayoutListPortraitState.value
+            if (isLandscape) uiState.settings.bookshelfLayoutListLandscape
+            else uiState.settings.bookshelfLayoutListPortrait
         }
     }
     val columns by remember {
@@ -1328,13 +1339,13 @@ fun BookshelfPage(
         }
     }
     val isGridMode by remember { derivedStateOf { bookshelfLayoutMode != 0 } }
-    val bookItemGridStyle by BookshelfConfig.bookshelfGridLayoutState
-    val bookItemIsCompact by BookshelfConfig.bookshelfLayoutCompactState
-    val bookItemTitleSmallFont by BookshelfConfig.bookshelfTitleSmallFontState
-    val bookItemTitleCenter by BookshelfConfig.bookshelfTitleCenterState
-    val bookItemTitleMaxLines by BookshelfConfig.bookshelfTitleMaxLinesState
-    val bookItemCoverShadow by BookshelfConfig.bookshelfCoverShadowState
-    val showFastScroll by BookshelfConfig.showBookshelfFastScrollerState
+    val bookItemGridStyle = uiState.settings.bookshelfGridLayout
+    val bookItemIsCompact = uiState.settings.bookshelfLayoutCompact
+    val bookItemTitleSmallFont = uiState.settings.bookshelfTitleSmallFont
+    val bookItemTitleCenter = uiState.settings.bookshelfTitleCenter
+    val bookItemTitleMaxLines = uiState.settings.bookshelfTitleMaxLines
+    val bookItemCoverShadow = uiState.settings.bookshelfCoverShadow
+    val showFastScroll = uiState.settings.showBookshelfFastScroller
     val listContentDescription = stringResource(R.string.bookshelf)
     val totalHorizontalPadding =
         if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) 12.dp else 16.dp
@@ -1376,7 +1387,7 @@ fun BookshelfPage(
                 ),
             contentPadding = adaptiveContentPaddingBookshelf(
                 top = paddingValues.calculateTopPadding(),
-                bottom = if (ThemeConfig.useFloatingBottomBar || ThemeConfig.enableBlur) 120.dp else 8.dp,
+                bottom = if (uiState.useRaisedBottomInset) 120.dp else 8.dp,
                 horizontal = 8.dp
             ),
             verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
@@ -1395,6 +1406,12 @@ fun BookshelfPage(
                     enabled = canReorderBooks
                 ) { isDragging ->
                     BookItem(
+                        settings = uiState.settings,
+                        customTagColors = if (uiState.enableCustomTagColors) {
+                            uiState.customTagColors
+                        } else {
+                            persistentListOf()
+                        },
                         bookUi = bookUi,
                         modifier = Modifier
                             .reorderAccessibility(

+ 50 - 1
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfUiState.kt

@@ -1,7 +1,12 @@
 package io.legado.app.ui.main.bookshelf
 
 import androidx.compose.runtime.Stable
+import android.net.Uri
 import io.legado.app.data.entities.BookGroup
+import io.legado.app.domain.model.settings.BookshelfSettings
+import io.legado.app.domain.gateway.BookshelfSettingsUpdate
+import io.legado.app.domain.gateway.ThemeSettingsUpdate
+import io.legado.app.ui.config.themeConfig.TagColorPair
 import io.legado.app.ui.widget.components.list.ListUiState
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.ImmutableMap
@@ -31,6 +36,45 @@ sealed interface BookshelfOverlay {
     data object BatchDownloadConfirmDialog : BookshelfOverlay
 }
 
+sealed interface BookshelfIntent {
+    data class ChangeGroup(val groupId: Long) : BookshelfIntent
+    data class SetSearchKey(val value: String) : BookshelfIntent
+    data class SetSearchMode(val active: Boolean) : BookshelfIntent
+    data class ShowOverlay(val overlay: BookshelfOverlay) : BookshelfIntent
+    data object DismissOverlay : BookshelfIntent
+    data object ToggleEditMode : BookshelfIntent
+    data object ExitEditMode : BookshelfIntent
+    data object ClearSelection : BookshelfIntent
+    data object SelectAllVisible : BookshelfIntent
+    data object InvertVisibleSelection : BookshelfIntent
+    data class ToggleBookSelection(val bookUrl: String) : BookshelfIntent
+    data class SetInFolderRoot(val value: Boolean) : BookshelfIntent
+    data class MoveBooksToGroup(val bookUrls: Set<String>, val groupId: Long) : BookshelfIntent
+    data class DownloadBooks(val bookUrls: Set<String>, val allChapters: Boolean = false) : BookshelfIntent
+    data class RefreshBooks(val books: List<BookUiItem>) : BookshelfIntent
+    data class StartDragging(val books: List<BookUiItem>) : BookshelfIntent
+    data class MoveDragging(val from: Int, val to: Int, val books: List<BookUiItem>) : BookshelfIntent
+    data object FinishDragging : BookshelfIntent
+    data object ScrollToTop : BookshelfIntent
+    data object RefreshAll : BookshelfIntent
+    data class RefreshToc(val books: List<BookUiItem>) : BookshelfIntent
+    data class AddBookByUrl(val urls: String) : BookshelfIntent
+    data class ExportToUri(val uri: Uri, val books: List<BookUiItem>) : BookshelfIntent
+    data class UploadBookshelf(val books: List<BookUiItem>) : BookshelfIntent
+    data class ImportFromUri(val uri: Uri, val groupId: Long) : BookshelfIntent
+    data class UpdateSetting(val update: BookshelfSettingsUpdate) : BookshelfIntent
+    data class UpdateThemeSetting(val update: ThemeSettingsUpdate) : BookshelfIntent
+    data class SetCustomTagColors(val colors: List<TagColorPair>) : BookshelfIntent
+}
+
+sealed interface BookshelfEffect {
+    data class ShowSnackbar(
+        val message: String,
+        val actionLabel: String? = null,
+        val url: String? = null,
+    ) : BookshelfEffect
+}
+
 @Stable
 data class BookshelfUiState(
     override val items: ImmutableList<BookUiItem> = persistentListOf(),
@@ -63,5 +107,10 @@ data class BookshelfUiState(
     val currentGroupName: String? = null,
     val draggingBooks: ImmutableList<BookUiItem>? = null,
     val pendingSavedBooks: ImmutableList<BookUiItem>? = null,
-    val visibleGroupBooks: ImmutableMap<Long, ImmutableList<BookUiItem>> = persistentMapOf()
+    val visibleGroupBooks: ImmutableMap<Long, ImmutableList<BookUiItem>> = persistentMapOf(),
+    val settings: BookshelfSettings = BookshelfSettings(),
+    val useRaisedBottomInset: Boolean = false,
+    val enableCustomTagColors: Boolean = false,
+    val customTagColors: ImmutableList<TagColorPair> = persistentListOf(),
+    val themeColor: Int = 0,
 ) : ListUiState<BookUiItem>

+ 125 - 44
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt

@@ -2,10 +2,8 @@ package io.legado.app.ui.main.bookshelf
 
 import android.app.Application
 import android.net.Uri
-import androidx.compose.runtime.snapshotFlow
 import androidx.lifecycle.viewModelScope
 import io.legado.app.R
-import io.legado.app.base.BaseRuleEvent
 import io.legado.app.base.BaseViewModel
 import io.legado.app.constant.AppConst
 import io.legado.app.constant.AppLog
@@ -25,21 +23,27 @@ import io.legado.app.domain.usecase.ExportBookshelfUseCase
 import io.legado.app.domain.usecase.ImportBookshelfUseCase
 import io.legado.app.domain.usecase.RefreshTocUseCase
 import io.legado.app.domain.usecase.UpdateBooksGroupUseCase
+import io.legado.app.domain.gateway.BookshelfSettingsGateway
+import io.legado.app.domain.gateway.BookshelfSettingsUpdate
+import io.legado.app.domain.gateway.AppShellSettingsGateway
+import io.legado.app.domain.gateway.ThemeSettingsGateway
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.model.CacheBook
 import io.legado.app.model.SourceCallBack
 import io.legado.app.service.CacheBookService
-import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
+import io.legado.app.ui.config.themeConfig.TagColorPair
 import io.legado.app.utils.eventBus.FlowEventBus
 import io.legado.app.utils.move
 import io.legado.app.utils.onEachParallel
 import io.legado.app.utils.postEvent
 import io.legado.app.utils.toastOnUi
+import io.legado.app.utils.GSON
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.ImmutableMap
 import kotlinx.collections.immutable.persistentMapOf
+import kotlinx.collections.immutable.persistentListOf
 import kotlinx.collections.immutable.toImmutableList
 import kotlinx.collections.immutable.toImmutableMap
 import kotlinx.collections.immutable.toImmutableSet
@@ -47,7 +51,6 @@ import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -66,7 +69,7 @@ import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.isActive
@@ -88,20 +91,24 @@ class BookshelfViewModel(
     private val refreshTocUseCase: RefreshTocUseCase,
     private val addBookUseCase: AddBookUseCase,
     private val importBookshelfUseCase: ImportBookshelfUseCase,
-    private val exportBookshelfUseCase: ExportBookshelfUseCase
+    private val exportBookshelfUseCase: ExportBookshelfUseCase,
+    private val bookshelfSettingsGateway: BookshelfSettingsGateway,
+    private val appShellSettingsGateway: AppShellSettingsGateway,
+    private val themeSettingsGateway: ThemeSettingsGateway,
 ) : BaseViewModel(application) {
     private var addBookJob: Coroutine<*>? = null
 
-    private val groupIdFlow = MutableStateFlow(BookshelfConfig.saveTabPosition)
+    private val initialSettings = bookshelfSettingsGateway.currentSettings
+    private val groupIdFlow = MutableStateFlow(initialSettings.saveTabPosition)
     private val searchKeyFlow = MutableStateFlow("")
     private val searchModeFlow = MutableStateFlow(false)
     private val loadingTextFlow = MutableStateFlow<String?>(null)
     private val activeOverlayFlow = MutableStateFlow<BookshelfOverlay?>(null)
     private val isEditModeFlow = MutableStateFlow(false)
     private val selectedBookUrlsFlow = MutableStateFlow<Set<String>>(emptySet())
-    private val isInFolderRootFlow = MutableStateFlow(BookshelfConfig.bookGroupStyle == 2)
+    private val isInFolderRootFlow = MutableStateFlow(initialSettings.bookGroupStyle == 2)
     private val isRefreshingFlow = MutableStateFlow(false)
-    private val bookGroupStyleFlow = MutableStateFlow(BookshelfConfig.bookGroupStyle)
+    private val bookGroupStyleFlow = MutableStateFlow(initialSettings.bookGroupStyle)
     private val draggingBooksFlow = MutableStateFlow<List<BookUiItem>?>(null)
     private val pendingSavedBooksFlow = MutableStateFlow<List<BookUiItem>?>(null)
     private val isInitialLoadingFlow = MutableStateFlow(true)
@@ -111,15 +118,19 @@ class BookshelfViewModel(
         val sortOrder: Int
     )
 
-    private fun readSortConfig() = BookshelfSortConfig(
-        sort = BookshelfConfig.bookshelfSort,
-        sortOrder = BookshelfConfig.bookshelfSortOrder
-    )
+    private val bookshelfSettings = bookshelfSettingsGateway.settings
+        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), initialSettings)
+    private val initialAppShellSettings = appShellSettingsGateway.currentSettings
+    private val initialThemeSettings = themeSettingsGateway.currentSettings
 
-    private val sortConfigFlow: StateFlow<BookshelfSortConfig> = snapshotFlow {
-        readSortConfig()
-    }.distinctUntilChanged()
-        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), readSortConfig())
+    private val sortConfigFlow: StateFlow<BookshelfSortConfig> = bookshelfSettings
+        .map { BookshelfSortConfig(it.bookshelfSort, it.bookshelfSortOrder) }
+        .distinctUntilChanged()
+        .stateIn(
+            viewModelScope,
+            SharingStarted.WhileSubscribed(5000),
+            BookshelfSortConfig(initialSettings.bookshelfSort, initialSettings.bookshelfSortOrder)
+        )
 
     // 更新相关
     private val updateQueueLock = Any()
@@ -140,8 +151,8 @@ class BookshelfViewModel(
     private val updateDispatcher: CoroutineDispatcher
         get() = Dispatchers.IO.limitedParallelism(updateConcurrency)
 
-    protected val _eventChannel = Channel<BaseRuleEvent>()
-    val events = _eventChannel.receiveAsFlow()
+    private val _effects = MutableSharedFlow<BookshelfEffect>(extraBufferCapacity = 16)
+    val effects = _effects.asSharedFlow()
 
     val groupsFlow: SharedFlow<List<BookGroup>> = bookGroupRepository.flowShow()
         .onEach {
@@ -436,7 +447,7 @@ class BookshelfViewModel(
         val allGroupBooks: ImmutableMap<Long, ImmutableList<BookUiItem>>
     )
 
-    val uiState: StateFlow<BookshelfUiState> = combine(
+    private val contentUiState: Flow<BookshelfUiState> = combine(
         dataStateFlow,
         interactionStateFlow,
         isInitialLoadingFlow
@@ -517,7 +528,39 @@ class BookshelfViewModel(
             pendingSavedBooks = interaction.pendingSavedBooks?.toImmutableList(),
             visibleGroupBooks = visibleGroupBooks
         )
-    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), BookshelfUiState())
+    }
+
+    val uiState: StateFlow<BookshelfUiState> = combine(
+        contentUiState,
+        bookshelfSettings,
+        appShellSettingsGateway.settings,
+        themeSettingsGateway.settings,
+    ) { state, settings, appShellSettings, themeSettings ->
+        state.copy(
+            settings = settings,
+            useRaisedBottomInset = appShellSettings.useFloatingBottomBar || themeSettings.enableBlur,
+            enableCustomTagColors = themeSettings.enableCustomTagColors,
+            customTagColors = parseTagColors(themeSettings.customTagColorsJson),
+            themeColor = themeSettings.themeColor,
+        )
+    }.stateIn(
+        viewModelScope,
+        SharingStarted.WhileSubscribed(5000),
+        BookshelfUiState(
+            settings = initialSettings,
+            useRaisedBottomInset = initialAppShellSettings.useFloatingBottomBar || initialThemeSettings.enableBlur,
+            enableCustomTagColors = initialThemeSettings.enableCustomTagColors,
+            customTagColors = parseTagColors(initialThemeSettings.customTagColorsJson),
+            themeColor = initialThemeSettings.themeColor,
+        ),
+    )
+
+    private fun parseTagColors(json: String?): ImmutableList<TagColorPair> = try {
+        if (json.isNullOrBlank()) persistentListOf()
+        else GSON.fromJson(json, Array<TagColorPair>::class.java).toImmutableList()
+    } catch (_: Exception) {
+        persistentListOf()
+    }
 
     init {
         viewModelScope.launch {
@@ -530,18 +573,10 @@ class BookshelfViewModel(
             }
         }
         viewModelScope.launch {
-            snapshotFlow { BookshelfConfig.bookGroupStyle }
-                .distinctUntilChanged()
-                .collect { style ->
-                    updateBookGroupStyle(style)
-                }
-        }
-        viewModelScope.launch {
-            snapshotFlow { BookshelfConfig.showWaitUpCount }
-                .distinctUntilChanged()
-                .collect {
+            bookshelfSettings.collect { settings ->
+                updateBookGroupStyle(settings.bookGroupStyle)
                     postUpBooksCount()
-                }
+            }
         }
 
         viewModelScope.launch {
@@ -557,13 +592,57 @@ class BookshelfViewModel(
 
         viewModelScope.launch {
             isInitialLoadingFlow.filter { !it }.collect {
-                if (BookshelfConfig.autoRefreshBook) {
+                if (bookshelfSettings.value.autoRefreshBook) {
                     upAllBookToc()
                 }
             }
         }
     }
 
+    fun onIntent(intent: BookshelfIntent) {
+        when (intent) {
+            is BookshelfIntent.ChangeGroup -> changeGroup(intent.groupId)
+            is BookshelfIntent.SetSearchKey -> setSearchKey(intent.value)
+            is BookshelfIntent.SetSearchMode -> setSearchMode(intent.active)
+            is BookshelfIntent.ShowOverlay -> showOverlay(intent.overlay)
+            BookshelfIntent.DismissOverlay -> dismissOverlay()
+            BookshelfIntent.ToggleEditMode -> toggleEditMode()
+            BookshelfIntent.ExitEditMode -> exitEditMode()
+            BookshelfIntent.ClearSelection -> clearSelection()
+            BookshelfIntent.SelectAllVisible -> selectAllVisible()
+            BookshelfIntent.InvertVisibleSelection -> invertVisibleSelection()
+            is BookshelfIntent.ToggleBookSelection -> toggleBookSelection(intent.bookUrl)
+            is BookshelfIntent.SetInFolderRoot -> setInFolderRoot(intent.value)
+            is BookshelfIntent.MoveBooksToGroup -> moveBooksToGroup(intent.bookUrls, intent.groupId)
+            is BookshelfIntent.DownloadBooks -> downloadBooks(intent.bookUrls, intent.allChapters)
+            is BookshelfIntent.RefreshBooks -> refreshBooks(intent.books)
+            is BookshelfIntent.StartDragging -> startDraggingBooks(intent.books)
+            is BookshelfIntent.MoveDragging -> moveDraggingBook(intent.from, intent.to, intent.books)
+            BookshelfIntent.FinishDragging -> finishDraggingBooks()
+            BookshelfIntent.ScrollToTop -> gotoTop()
+            BookshelfIntent.RefreshAll -> upAllBookToc()
+            is BookshelfIntent.RefreshToc -> upToc(intent.books)
+            is BookshelfIntent.AddBookByUrl -> addBookByUrl(intent.urls)
+            is BookshelfIntent.ExportToUri -> exportToUri(intent.uri, intent.books)
+            is BookshelfIntent.UploadBookshelf -> uploadBookshelf(intent.books)
+            is BookshelfIntent.ImportFromUri -> importBookshelf(intent.uri, intent.groupId)
+            is BookshelfIntent.UpdateSetting -> viewModelScope.launch {
+                bookshelfSettingsGateway.update(intent.update)
+            }
+            is BookshelfIntent.UpdateThemeSetting -> viewModelScope.launch {
+                themeSettingsGateway.update(intent.update)
+            }
+            is BookshelfIntent.SetCustomTagColors -> viewModelScope.launch {
+                themeSettingsGateway.update(
+                    io.legado.app.domain.gateway.ThemeSettingsUpdate.StringValue(
+                        io.legado.app.domain.gateway.ThemeStringSetting.CustomTagColorsJson,
+                        GSON.toJson(intent.colors),
+                    )
+                )
+            }
+        }
+    }
+
     private fun filterBooks(
         books: List<BookUiItem>,
         searchKey: String,
@@ -607,7 +686,9 @@ class BookshelfViewModel(
     fun changeGroup(groupId: Long) {
         if (groupIdFlow.value != groupId) {
             groupIdFlow.value = groupId
-            BookshelfConfig.saveTabPosition = groupId
+            viewModelScope.launch {
+                bookshelfSettingsGateway.update(BookshelfSettingsUpdate.SaveTabPosition(groupId))
+            }
             clearSelection()
             clearDragState()
         }
@@ -704,7 +785,7 @@ class BookshelfViewModel(
 
     fun saveBookOrder(reorderedBooks: List<BookUiItem>) {
         if (reorderedBooks.isEmpty()) return
-        val isDescending = BookshelfConfig.bookshelfSortOrder == 1
+        val isDescending = bookshelfSettings.value.bookshelfSortOrder == 1
         val maxOrder = reorderedBooks.size
         execute {
             val updates = reorderedBooks.mapIndexedNotNull { index, bookUi ->
@@ -742,7 +823,7 @@ class BookshelfViewModel(
     fun refreshBooks(books: List<BookUiItem>) {
         if (isRefreshingFlow.value) return
         isRefreshingFlow.value = true
-        val limit = BookshelfConfig.bookshelfRefreshingLimit
+        val limit = bookshelfSettings.value.bookshelfRefreshingLimit
         val list = if (limit > 0) books.take(limit) else books
         enqueueTocUpdate(list.map { it.book }, resetRefreshWhenIdle = true)
     }
@@ -793,7 +874,7 @@ class BookshelfViewModel(
     }
 
     fun upToc(books: List<BookUiItem>) {
-        val limit = BookshelfConfig.bookshelfRefreshingLimit
+        val limit = bookshelfSettings.value.bookshelfRefreshingLimit
         val list = if (limit > 0) books.take(limit) else books
         enqueueTocUpdate(list.map { it.book }, resetRefreshWhenIdle = false)
     }
@@ -917,7 +998,7 @@ class BookshelfViewModel(
     }
 
     private fun postUpBooksCount() {
-        val count = if (BookshelfConfig.showWaitUpCount) {
+        val count = if (bookshelfSettings.value.showWaitUpCount) {
             synchronized(updateQueueLock) {
                 waitUpTocBooks.size + onUpTocBooks.size
             }
@@ -983,9 +1064,9 @@ class BookshelfViewModel(
         execute {
             exportBookshelfUseCase.exportToUri(uri, items).getOrThrow()
         }.onSuccess {
-            _eventChannel.trySend(BaseRuleEvent.ShowSnackbar("导出成功"))
+            _effects.tryEmit(BookshelfEffect.ShowSnackbar("导出成功"))
         }.onError {
-            _eventChannel.trySend(BaseRuleEvent.ShowSnackbar("导出失败\n${it.localizedMessage}"))
+            _effects.tryEmit(BookshelfEffect.ShowSnackbar("导出失败\n${it.localizedMessage}"))
         }
     }
 
@@ -998,16 +1079,16 @@ class BookshelfViewModel(
                 contentType = "application/json"
             )
         }.onSuccess { url ->
-            _eventChannel.trySend(
-                BaseRuleEvent.ShowSnackbar(
+            _effects.tryEmit(
+                BookshelfEffect.ShowSnackbar(
                     message = "上传成功: $url",
                     actionLabel = "复制链接",
                     url = url
                 )
             )
         }.onError {
-            _eventChannel.trySend(
-                BaseRuleEvent.ShowSnackbar(
+            _effects.tryEmit(
+                BookshelfEffect.ShowSnackbar(
                     message = "上传失败: ${it.localizedMessage}"
                 )
             )