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

fix: 阅读菜单模糊着色、调色板样式、标题胶囊布局及主题覆盖修复

HapeLee 4 месяцев назад
Родитель
Сommit
9bb8571cc7
21 измененных файлов с 448 добавлено и 90 удалено
  1. 2 0
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  2. 15 0
      app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt
  3. 7 9
      app/src/main/java/io/legado/app/domain/usecase/ChangeSourceSearchUseCase.kt
  4. 2 0
      app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt
  5. 74 5
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceComposeViewModel.kt
  6. 21 4
      app/src/main/java/io/legado/app/ui/book/read/ReadBookColorTheme.kt
  7. 23 0
      app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt
  8. 63 59
      app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt
  9. 11 0
      app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt
  10. 36 0
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  11. 70 0
      app/src/main/java/io/legado/app/ui/book/read/sheet/SystemMenuPage.kt
  12. 90 1
      app/src/main/java/io/legado/app/ui/book/read/sheet/TextTitleSheet.kt
  13. 1 1
      app/src/main/java/io/legado/app/ui/book/search/ScopeSelectSheet.kt
  14. 4 1
      app/src/main/java/io/legado/app/ui/theme/ThemeColorSchemeOverride.kt
  15. 4 1
      app/src/main/java/io/legado/app/ui/theme/ThemeOverride.kt
  16. 2 2
      app/src/main/java/io/legado/app/ui/widget/components/changeSource/ChangeSourceSheet.kt
  17. 5 0
      app/src/main/java/io/legado/app/ui/widget/components/icon/AppIcons.kt
  18. 4 2
      app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenu.kt
  19. 4 5
      app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenuItem.kt
  20. 5 0
      app/src/main/res/values-zh-rCN/strings.xml
  21. 5 0
      app/src/main/res/values/strings.xml

+ 2 - 0
app/src/main/java/io/legado/app/constant/PreferKey.kt

@@ -308,6 +308,8 @@ object PreferKey {
     const val readMenuBottomBarBlurStyle = "readMenuBottomBarBlurStyle"
     const val readMenuBlurRadius = "readMenuBlurRadius"
     const val readMenuBlurAlpha = "readMenuBlurAlpha"
+    const val readMenuBlurColor = "readMenuBlurColor"
+    const val readMenuPaletteStyle = "readMenuPaletteStyle"
     const val readMenuLensRadius = "readMenuLensRadius"
     const val readMenuBorderWidth = "readMenuBorderWidth"
     const val readMenuBorderColor = "readMenuBorderColor"

+ 15 - 0
app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt

@@ -94,6 +94,8 @@ data class ReadPreferences(
     val readMenuBottomBarBlurStyle: Int = ReadMenuBlurStyle.Solid,
     val readMenuBlurRadius: Int = 24,
     val readMenuBlurAlpha: Int = 60,
+    val readMenuBlurColor: Int = 0,
+    val readMenuPaletteStyle: String = "",
     val readMenuLensRadius: Float = 24f,
     val readMenuBorderWidth: Int = 0,
     val readMenuBorderColor: Int = 0,
@@ -335,6 +337,12 @@ class ReadSettingsRepository(
     suspend fun setReadMenuBlurAlpha(value: Int) =
         settingsRepository.putInt(PreferKey.readMenuBlurAlpha, value.coerceIn(0, 100))
 
+    suspend fun setReadMenuBlurColor(value: Int) =
+        settingsRepository.putInt(PreferKey.readMenuBlurColor, value)
+
+    suspend fun setReadMenuPaletteStyle(value: String) =
+        settingsRepository.putString(PreferKey.readMenuPaletteStyle, value)
+
     suspend fun setReadMenuLensRadius(value: Float) =
         settingsRepository.putFloat(PreferKey.readMenuLensRadius, value.coerceIn(0f, 48f))
 
@@ -446,6 +454,7 @@ class ReadSettingsRepository(
             readMenuBottomBarBlurMode = this[Keys.ReadMenuBottomBarBlurMode]
                 ?: ReadMenuBlurMode.None,
             readMenuTopBarLiquidGlassButtons = this[Keys.ReadMenuTopBarLiquidGlassButtons] ?: false,
+            readMenuTopBarTitleCapsule = this[Keys.ReadMenuTopBarTitleCapsule] ?: false,
             readMenuBottomBarLiquidGlassButtons = this[Keys.ReadMenuBottomBarLiquidGlassButtons]
                 ?: false,
             readMenuTopBarBlurStyle = this[Keys.ReadMenuTopBarBlurStyle]
@@ -454,6 +463,8 @@ class ReadSettingsRepository(
                 ?: ReadMenuBlurStyle.Solid,
             readMenuBlurRadius = this[Keys.ReadMenuBlurRadius] ?: 24,
             readMenuBlurAlpha = this[Keys.ReadMenuBlurAlpha] ?: 60,
+            readMenuBlurColor = this[Keys.ReadMenuBlurColor] ?: 0,
+            readMenuPaletteStyle = this[Keys.ReadMenuPaletteStyle] ?: "",
             readMenuLensRadius = this[Keys.ReadMenuLensRadius] ?: 24f,
             readMenuBorderWidth = this[Keys.ReadMenuBorderWidth] ?: 0,
             readMenuBorderColor = this[Keys.ReadMenuBorderColor] ?: 0,
@@ -540,12 +551,16 @@ class ReadSettingsRepository(
         val ReadMenuBottomBarBlurMode = intPreferencesKey(PreferKey.readMenuBottomBarBlurMode)
         val ReadMenuTopBarLiquidGlassButtons =
             booleanPreferencesKey(PreferKey.readMenuTopBarLiquidGlassButtons)
+        val ReadMenuTopBarTitleCapsule =
+            booleanPreferencesKey(PreferKey.readMenuTopBarTitleCapsule)
         val ReadMenuBottomBarLiquidGlassButtons =
             booleanPreferencesKey(PreferKey.readMenuBottomBarLiquidGlassButtons)
         val ReadMenuTopBarBlurStyle = intPreferencesKey(PreferKey.readMenuTopBarBlurStyle)
         val ReadMenuBottomBarBlurStyle = intPreferencesKey(PreferKey.readMenuBottomBarBlurStyle)
         val ReadMenuBlurRadius = intPreferencesKey(PreferKey.readMenuBlurRadius)
         val ReadMenuBlurAlpha = intPreferencesKey(PreferKey.readMenuBlurAlpha)
+        val ReadMenuBlurColor = intPreferencesKey(PreferKey.readMenuBlurColor)
+        val ReadMenuPaletteStyle = stringPreferencesKey(PreferKey.readMenuPaletteStyle)
         val ReadMenuLensRadius = floatPreferencesKey(PreferKey.readMenuLensRadius)
         val ReadMenuBorderWidth = intPreferencesKey(PreferKey.readMenuBorderWidth)
         val ReadMenuBorderColor = intPreferencesKey(PreferKey.readMenuBorderColor)

+ 7 - 9
app/src/main/java/io/legado/app/domain/usecase/ChangeSourceSearchUseCase.kt

@@ -25,12 +25,13 @@ import kotlinx.coroutines.flow.asFlow
 import kotlinx.coroutines.flow.flatMapMerge
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.withTimeout
 import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.atomic.AtomicInteger
 
 sealed interface ChangeSourceSearchEvent {
-    data object Started : ChangeSourceSearchEvent
+    data class Started(val totalSources: Int) : ChangeSourceSearchEvent
     data class Progress(
         val processedSources: Int,
         val totalSources: Int,
@@ -69,23 +70,20 @@ class ChangeSourceSearchUseCase(
         if (bookSourceParts.isEmpty()) {
             throw io.legado.app.exception.NoStackTraceException("启用书源为空")
         }
-        val sources = bookSourceParts.mapNotNull { it.getBookSource() }
-        if (sources.isEmpty()) {
-            throw io.legado.app.exception.NoStackTraceException("启用书源为空")
-        }
 
         tocMap.clear()
         bookMap.clear()
         tocMapChapterCount.set(0)
 
-        emit(ChangeSourceSearchEvent.Started)
+        val totalSources = bookSourceParts.size
+        emit(ChangeSourceSearchEvent.Started(totalSources))
 
         var processedSources = 0
         var resultCount = 0
-        val totalSources = sources.size
         val concurrency = threadCount.coerceAtLeast(1)
 
-        sources.asFlow()
+        bookSourceParts.asFlow()
+            .mapNotNull { it.getBookSource() }
             .flatMapMerge(concurrency) { source ->
                 flow {
                     val books = try {
@@ -231,7 +229,7 @@ class ChangeSourceSearchUseCase(
                 contentProcessor
             )
         }
-        return null
+        return book.toSearchBook()
     }
 
     private suspend fun loadBookWordCount(

+ 2 - 0
app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt

@@ -236,6 +236,8 @@ object ReadBookConfig {
     var readMenuBottomBarBlurStyle by clampedPrefDelegate(PreferKey.readMenuBottomBarBlurStyle, ReadMenuBlurStyle.Solid, 0..1)
     var readMenuBlurRadius by clampedPrefDelegate(PreferKey.readMenuBlurRadius, 24, 0..32)
     var readMenuBlurAlpha by clampedPrefDelegate(PreferKey.readMenuBlurAlpha, 60, 0..100)
+    var readMenuBlurColor by prefDelegate(PreferKey.readMenuBlurColor, 0)
+    var readMenuPaletteStyle: String = ""
     var readMenuLensRadius by clampedPrefDelegate(PreferKey.readMenuLensRadius, 24f, 0f..48f)
     var readMenuBorderWidth by clampedPrefDelegate(PreferKey.readMenuBorderWidth, 0, 0..4)
     var titleBarIconPosition by clampedPrefDelegate(PreferKey.titleBarIconPosition, 0, 0..3)

+ 74 - 5
app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceComposeViewModel.kt

@@ -70,8 +70,11 @@ class ChangeBookSourceComposeViewModel(
     val loadWordCount: Boolean get() = ChangeSourceConfig.loadWordCount
 
     // Internal state
+    private val chapterNumRegex = "^\\[(\\d+)]".toRegex()
     private var searchJob: Job? = null
     private var oldBook: Book? = null
+    private var bookName: String = ""
+    private var bookAuthor: String = ""
     private var fromReadBookActivity: Boolean = false
     private var screenKey: String = ""
     private val searchResults = mutableListOf<SearchBook>()
@@ -86,15 +89,42 @@ class ChangeBookSourceComposeViewModel(
 
     fun initData(name: String, author: String, book: Book, fromReadBookActivity: Boolean) {
         this.oldBook = book
+        this.bookName = name
+        this.bookAuthor = author
         this.fromReadBookActivity = fromReadBookActivity
         if (searchJob?.isActive != true) {
-            startSearch()
+            viewModelScope.launch {
+                val dbBooks = getDbSearchBooks()
+                if (dbBooks.isNotEmpty()) {
+                    searchResults.clear()
+                    searchResults.addAll(dbBooks)
+                    searchResults.forEach { bookMap[it.primaryStr()] = it }
+                    filterResults()
+                } else {
+                    startSearch()
+                }
+            }
+        }
+    }
+
+    private fun getDbSearchBooks(): List<SearchBook> {
+        val scopeRaw = ChangeSourceConfig.searchScope
+        return if (screenKey.isEmpty()) {
+            io.legado.app.data.appDb.searchBookDao.changeSourceByGroup(
+                bookName, bookAuthor, scopeRaw
+            )
+        } else {
+            io.legado.app.data.appDb.searchBookDao.changeSourceSearch(
+                bookName, bookAuthor, screenKey, scopeRaw
+            )
         }
     }
 
     fun startSearch() {
         val book = oldBook ?: return
         stopSearch()
+        // 清除 DB 中的旧记录
+        io.legado.app.data.appDb.searchBookDao.clear(bookName, bookAuthor)
         searchResults.clear()
         bookMap.clear()
         tocMap.clear()
@@ -113,6 +143,7 @@ class ChangeBookSourceComposeViewModel(
                 when (event) {
                     is ChangeSourceSearchEvent.Started -> {
                         _isSearching.value = true
+                        totalSourceCount = event.totalSources
                     }
 
                     is ChangeSourceSearchEvent.Progress -> {
@@ -123,6 +154,8 @@ class ChangeBookSourceComposeViewModel(
                     is ChangeSourceSearchEvent.Result -> {
                         searchResults.add(event.searchBook)
                         bookMap[event.searchBook.primaryStr()] = event.searchBook
+                        // 持久化到 DB
+                        io.legado.app.data.appDb.searchBookDao.insert(event.searchBook)
                         filterResults()
                     }
 
@@ -179,12 +212,24 @@ class ChangeBookSourceComposeViewModel(
                 it.name.contains(screenKey) || it.originName.contains(screenKey)
             }
         }
-        val sorted = filtered.sortedWith(
+        val comparator = if (ChangeSourceConfig.loadWordCount) {
             compareByDescending<SearchBook> { ObservableSourceConfig.getBookScore(it) }
                 .thenByDescending { io.legado.app.help.config.SourceConfig.getSourceScore(it.origin) }
+                .thenByDescending { it.chapterWordCount > 1000 }
+                .thenByDescending { getChapterNum(it.chapterWordCountText) }
+                .thenByDescending { it.chapterWordCount }
                 .thenBy { it.originOrder }
-        )
-        _searchDataFlow.value = sorted
+        } else {
+            compareByDescending<SearchBook> { ObservableSourceConfig.getBookScore(it) }
+                .thenByDescending { io.legado.app.help.config.SourceConfig.getSourceScore(it.origin) }
+                .thenBy { it.originOrder }
+        }
+        _searchDataFlow.value = filtered.sortedWith(comparator)
+    }
+
+    private fun getChapterNum(text: String?): Int {
+        if (text.isNullOrBlank()) return 0
+        return chapterNumRegex.find(text)?.groupValues?.get(1)?.toIntOrNull() ?: 0
     }
 
     fun getToc(
@@ -247,7 +292,14 @@ class ChangeBookSourceComposeViewModel(
     fun refresh() {
         searchResults.clear()
         bookMap.clear()
-        startSearch()
+        val dbBooks = getDbSearchBooks()
+        if (dbBooks.isNotEmpty()) {
+            searchResults.addAll(dbBooks)
+            searchResults.forEach { bookMap[it.primaryStr()] = it }
+            filterResults()
+        } else {
+            startSearch()
+        }
     }
 
     // Source actions
@@ -340,6 +392,23 @@ class ChangeBookSourceComposeViewModel(
         saveScope()
     }
 
+    fun applyScopeSelection(selection: io.legado.app.ui.book.search.ScopeSelection) {
+        if (selection.isSourceScope) {
+            if (selection.sources.isEmpty()) {
+                searchScope.update("")
+            } else {
+                searchScope.updateSources(selection.sources)
+            }
+        } else {
+            if (selection.groupNames.isEmpty()) {
+                searchScope.update("")
+            } else {
+                searchScope.update(selection.groupNames)
+            }
+        }
+        saveScope()
+    }
+
     private fun saveScope() {
         ChangeSourceConfig.searchScope = searchScope.toString()
         _scopeUiState.value = ScopeUiState(

+ 21 - 4
app/src/main/java/io/legado/app/ui/book/read/ReadBookColorTheme.kt

@@ -46,12 +46,14 @@ private fun rememberReadBookColorTheme(
     preferences: ReadPreferences,
 ): ThemeOverrideState? {
     val isAppDark = LegadoTheme.isDark
+    val paletteStyle = preferences.readMenuPaletteStyle
     return when (preferences.readBarStyle) {
-        1 -> rememberReadBackgroundTheme(styleConfig, isAppDark)
+        1 -> rememberReadBackgroundTheme(styleConfig, isAppDark, paletteStyle)
         2 -> rememberCustomReadMenuTheme(
             styleConfig = styleConfig,
             preferences = preferences,
             isAppDark = isAppDark,
+            paletteStyle = paletteStyle,
         )
         else -> null
     }
@@ -61,6 +63,7 @@ private fun rememberReadBookColorTheme(
 private fun rememberReadBackgroundTheme(
     styleConfig: ReadBookStyleConfig,
     isAppDark: Boolean,
+    paletteStyle: String,
 ): ThemeOverrideState? {
     val background = remember(styleConfig, isAppDark) {
         runCatching { ReadStyleResolver.currentBackground(ReadBookConfig.durConfig) }.getOrNull()
@@ -85,6 +88,7 @@ private fun rememberReadBackgroundTheme(
         backgroundColor = surfaceColor,
         containerColor = null,
         fallbackDark = isAppDark,
+        paletteStyle = paletteStyle,
     )
 }
 
@@ -93,6 +97,7 @@ private fun rememberCustomReadMenuTheme(
     styleConfig: ReadBookStyleConfig,
     preferences: ReadPreferences,
     isAppDark: Boolean,
+    paletteStyle: String,
 ): ThemeOverrideState {
     val menuBackgroundColor = remember(
         styleConfig,
@@ -127,6 +132,7 @@ private fun rememberCustomReadMenuTheme(
             menuBackgroundColor = menuBackgroundColor,
             menuContainerColor = menuContainerColor,
             isDark = isAppDark,
+            paletteStyle = paletteStyle,
         )
     }
 
@@ -136,11 +142,13 @@ private fun rememberCustomReadMenuTheme(
         containerColor = null,
         fallbackDark = isAppDark,
         deriveDarkFromColor = false,
+        paletteStyle = paletteStyle,
     ) ?: buildReadThemeOverride(
         seedColor = accentColor,
         backgroundColor = null,
         containerColor = null,
         isDark = isAppDark,
+        paletteStyle = paletteStyle,
     )
 }
 
@@ -150,13 +158,15 @@ private fun rememberCustomReadMenuThemeOverride(
     menuBackgroundColor: Color,
     menuContainerColor: Color,
     isDark: Boolean,
+    paletteStyle: String,
 ): ThemeOverrideState {
-    return remember(accentColor, menuBackgroundColor, menuContainerColor, isDark) {
+    return remember(accentColor, menuBackgroundColor, menuContainerColor, isDark, paletteStyle) {
         buildReadThemeOverride(
             seedColor = accentColor,
             backgroundColor = null,
             containerColor = null,
             isDark = isDark,
+            paletteStyle = paletteStyle,
         ).let { base ->
             base.copy(
                 colorScheme = base.colorScheme.withCustomReadMenuColors(
@@ -176,6 +186,7 @@ private fun rememberReadThemeOverride(
     containerColor: Color?,
     fallbackDark: Boolean = LegadoTheme.isDark,
     deriveDarkFromColor: Boolean = true,
+    paletteStyle: String = "",
 ): ThemeOverrideState? {
     val isDark = remember(backgroundColor, containerColor, fallbackDark, deriveDarkFromColor) {
         if (deriveDarkFromColor) {
@@ -184,12 +195,13 @@ private fun rememberReadThemeOverride(
             fallbackDark
         }
     }
-    return remember(seedColor, backgroundColor, containerColor, isDark) {
+    return remember(seedColor, backgroundColor, containerColor, isDark, paletteStyle) {
         buildReadThemeOverride(
             seedColor = seedColor,
             backgroundColor = backgroundColor,
             containerColor = containerColor,
             isDark = isDark,
+            paletteStyle = paletteStyle,
         )
     }
 }
@@ -199,12 +211,17 @@ private fun buildReadThemeOverride(
     backgroundColor: Color?,
     containerColor: Color?,
     isDark: Boolean,
+    paletteStyle: String = "",
 ): ThemeOverrideState {
     val colorSpec = ThemeResolver.resolveColorSpecFromMaterialVersion(ThemeConfig.materialVersion)
+    val resolvedPaletteStyle = paletteStyle
+        .takeIf { it.isNotBlank() }
+        ?.let { ThemeResolver.resolvePaletteStyle(it) }
+        ?: ThemeResolver.resolvePaletteStyle(ThemeConfig.paletteStyle)
     val base = buildThemeOverrideState(
         seedColor = seedColor,
         isDark = isDark,
-        paletteStyle = ThemeResolver.resolvePaletteStyle(ThemeConfig.paletteStyle),
+        paletteStyle = resolvedPaletteStyle,
         colorSpec = colorSpec,
         usePureBlack = false,
     )

+ 23 - 0
app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt

@@ -182,6 +182,8 @@ data class ReadMenuConfig(
     val readMenuBorderColor: Int = 0,
     val readMenuBorderColorNight: Int = 0,
     val readMenuBlurAlpha: Int = 60,
+    val readMenuBlurColor: Int = 0,
+    val readMenuPaletteStyle: String = "",
     val readMenuBlurRadius: Int = 24,
     val readMenuLensRadius: Float = 24f,
     val readMenuTopBarBlurMode: Int = ReadMenuBlurMode.None,
@@ -482,6 +484,8 @@ sealed interface ReadBookIntent {
     data object OpenSystemTtsSettings : ReadBookIntent
     data object ClearTtsCache : ReadBookIntent
     data class SelectFont(val path: String) : ReadBookIntent
+    data class SelectTitleFont(val path: String) : ReadBookIntent
+    data class SelectTitleSystemTypeface(val index: Int) : ReadBookIntent
     data class SelectSystemTypeface(val index: Int) : ReadBookIntent
     data class ColorSelected(val dialogId: Int, val color: Int) : ReadBookIntent
 
@@ -653,6 +657,7 @@ sealed interface ReadBookSheet {
     data object ShadowSet : ReadBookSheet
     data object UnderlineConfig : ReadBookSheet
     data object FontSelect : ReadBookSheet
+    data object TitleFontSelect : ReadBookSheet
     data object HighlightRuleConfig : ReadBookSheet
     data object MoreConfig : ReadBookSheet
     data object BgTextConfig : ReadBookSheet
@@ -769,6 +774,18 @@ sealed interface ConfigUpdate {
     data class TitleColor(val color: Int) : ConfigUpdate {
         override val actions = setOf(ConfigUpdateAction.UpdateStyle, ConfigUpdateAction.ReloadContent, ConfigUpdateAction.InvalidateTextPage)
     }
+    data class TitleFont(val path: String) : ConfigUpdate {
+        override val actions = setOf(ConfigUpdateAction.UpdateChapterStyle, ConfigUpdateAction.ReloadContent)
+    }
+    data class TitleSegType(val value: Int) : ConfigUpdate {
+        override val actions = setOf(ConfigUpdateAction.UpdateChapterStyle, ConfigUpdateAction.ReloadContent)
+    }
+    data class TitleSegDistance(val value: Int) : ConfigUpdate {
+        override val actions = setOf(ConfigUpdateAction.UpdateChapterStyle, ConfigUpdateAction.ReloadContent)
+    }
+    data class TitleSegFlag(val value: String) : ConfigUpdate {
+        override val actions = setOf(ConfigUpdateAction.UpdateChapterStyle, ConfigUpdateAction.ReloadContent)
+    }
 
     // --- Header / footer tips ---
     data class HeaderMode(val value: Int) : ConfigUpdate {
@@ -1023,6 +1040,12 @@ sealed interface ConfigUpdate {
     data class MenuBlurAlpha(val value: Int) : ConfigUpdate {
         override val actions = emptySet<ConfigUpdateAction>()
     }
+    data class MenuBlurColor(val color: Int) : ConfigUpdate {
+        override val actions = emptySet<ConfigUpdateAction>()
+    }
+    data class MenuPaletteStyle(val value: String) : ConfigUpdate {
+        override val actions = emptySet<ConfigUpdateAction>()
+    }
     data class MenuLensRadius(val value: Float) : ConfigUpdate {
         override val actions = emptySet<ConfigUpdateAction>()
     }

+ 63 - 59
app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt

@@ -195,7 +195,7 @@ fun ReadBookMenuBar(
         }
     }
     val hideTopBar = dialogLikeRoute ||
-            currentRoute == ReadBookMenuRoute.ReadStyle && readStylePage >= 1
+            currentRoute == ReadBookMenuRoute.TextTitle
     val menuColors = readMenuColors()
 
     Box(Modifier.fillMaxSize()) {
@@ -683,6 +683,9 @@ private fun ReadBookMenuSurface(
                                 onOpenFontSelect = {
                                     onIntent(ReadBookIntent.ShowSheet(ReadBookSheet.FontSelect))
                                 },
+                                onOpenTitleFontSelect = {
+                                    onIntent(ReadBookIntent.ShowSheet(ReadBookSheet.TitleFontSelect))
+                                },
                                 modifier = Modifier.padding(horizontal = 16.dp),
                                 onIntent = onIntent,
                             )
@@ -902,55 +905,61 @@ private fun MenuTitleBar(
             )
     ) {
         val useTitleCapsule = readMenuTopBarTitleCapsuleEnabled(backdrop, state.menuConfig)
+                && progressiveBlurActive
+        val capsuleIconColor = LegadoTheme.colorScheme.onSurfaceVariant
 
-        // Title row: back + title + actions + overflow
+        // Title row: left group (back + capsule/title) + right group (actions)
         Row(
             modifier = Modifier
                 .fillMaxWidth()
                 .padding(horizontal = 16.dp, vertical = 4.dp),
             verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.spacedBy(16.dp),
         ) {
-            // Back button
-            MenuTitleGlassButton(
-                onClick = { onIntent(ReadBookIntent.CloseReadBook) },
-                icon = Icons.AutoMirrored.Filled.ArrowBack,
-                contentDescription = "Back",
-                state = state,
-                colors = colors,
-                backdrop = backdrop,
-            )
-
-            if (useTitleCapsule) {
-                // Title capsule layout: title + chapter in a glass pill
-                TitleCapsuleGlassLayout(
+            // Left group: back + capsule/title — fills remaining space
+            Row(
+                modifier = Modifier.weight(1f),
+                verticalAlignment = Alignment.CenterVertically,
+            ) {
+                MenuTitleGlassButton(
+                    onClick = { onIntent(ReadBookIntent.CloseReadBook) },
+                    icon = Icons.AutoMirrored.Filled.ArrowBack,
+                    contentDescription = "Back",
                     state = state,
                     colors = colors,
-                    onIntent = onIntent,
                     backdrop = backdrop,
-                    titleTextColor = titleTextColor,
                 )
-            } else if (titleBarMode != "1" && titleBarMode != "3") {
-                AppText(
-                    text = state.bookName,
-                    modifier = Modifier
-                        .weight(1f)
-                        .clickable { onIntent(ReadBookIntent.OpenBookInfo) }
-                        .padding(horizontal = 8.dp, vertical = 4.dp),
-                    style = LegadoTheme.typography.titleMedium.copy(
-                        shadow = androidx.compose.ui.graphics.Shadow(
-                            color = Color.Black.copy(alpha = 0.12f),
-                            offset = Offset.Zero,
-                            blurRadius = 12f
-                        )
-                    ),
-                    color = titleTextColor,
-                    maxLines = 2,
-                    overflow = TextOverflow.Ellipsis,
-                )
-            } else {
-                Spacer(Modifier.weight(1f))
+
+                if (useTitleCapsule) {
+                    TitleCapsuleGlassLayout(
+                        state = state,
+                        colors = colors,
+                        onIntent = onIntent,
+                        backdrop = backdrop,
+                        titleTextColor = capsuleIconColor,
+                    )
+                } else if (titleBarMode != "1" && titleBarMode != "3") {
+                    AppText(
+                        text = state.bookName,
+                        modifier = Modifier
+                            .weight(1f)
+                            .clickable { onIntent(ReadBookIntent.OpenBookInfo) }
+                            .padding(horizontal = 8.dp, vertical = 4.dp),
+                        style = LegadoTheme.typography.titleMedium.copy(
+                            shadow = androidx.compose.ui.graphics.Shadow(
+                                color = Color.Black.copy(alpha = 0.12f),
+                                offset = Offset.Zero,
+                                blurRadius = 12f
+                            )
+                        ),
+                        color = titleTextColor,
+                        maxLines = 2,
+                        overflow = TextOverflow.Ellipsis,
+                    )
+                }
             }
 
+            // Right group: actions
             if (readMenuTopBarButtonLiquidGlassEnabled(backdrop, state.menuConfig)) {
                 MenuTitleBarMergedGlassButton(
                     state = state,
@@ -963,7 +972,6 @@ private fun MenuTitleBar(
                     horizontalArrangement = Arrangement.spacedBy(6.dp),
                     verticalAlignment = Alignment.CenterVertically,
                 ) {
-                    // Source action button (non-local books only)
                     if (!state.isLocalBook) {
                         SourceActionButton(
                             state = state,
@@ -1252,7 +1260,7 @@ private fun RowScope.TitleCapsuleGlassLayout(
     Row(
         modifier = Modifier
             .weight(1f)
-            .padding(horizontal = 8.dp)
+            .padding(start = 12.dp)
             .height(40.dp)
             .readMenuLiquidGlass(
                 backdrop = backdrop,
@@ -1320,24 +1328,20 @@ private fun MenuTitleBarMergedGlassButton(
     val tint = LegadoTheme.colorScheme.onSurfaceVariant
 
     Box {
-        Box(
-            contentAlignment = Alignment.Center,
-            modifier = Modifier.size(48.dp),
-        ) {
-            Row(
-                modifier = Modifier
-                    .height(40.dp)
-                    .readMenuLiquidGlass(
-                        backdrop = backdrop,
-                        colors = colors,
-                        shape = pillShape,
-                        useTopBarStyle = true,
-                        useLens = true,
-                        blurRadius = 32.dp,
-                        interactive = true,
-                        menuConfig = state.menuConfig,
-                    )
-                    .padding(horizontal = 4.dp),
+        Row(
+            modifier = Modifier
+                .height(40.dp)
+                .readMenuLiquidGlass(
+                    backdrop = backdrop,
+                    colors = colors,
+                    shape = pillShape,
+                    useTopBarStyle = true,
+                    useLens = true,
+                    blurRadius = 32.dp,
+                    interactive = true,
+                    menuConfig = state.menuConfig,
+                )
+                .padding(horizontal = 4.dp),
                 verticalAlignment = Alignment.CenterVertically,
             ) {
             // SwapHoriz - change source
@@ -1418,7 +1422,6 @@ private fun MenuTitleBarMergedGlassButton(
                 )
             }
         }
-        }
 
         // Dropdown menus
         if (!state.isLocalBook) {
@@ -2827,8 +2830,9 @@ private fun Modifier.readMenuHazeEffect(
     progressiveBottomToTop: Boolean = false,
 ): Modifier {
     val surfaceAlpha = menuConfig.readMenuBlurAlpha.coerceIn(0, 100) / 100f
+    val blurTintColor = menuConfig.readMenuBlurColor.takeIf { it != 0 }?.let { Color(it) }
     val hazeContainerColor = if (progressive) {
-        Color.Black.copy(alpha = surfaceAlpha)
+        (blurTintColor ?: Color.Black).copy(alpha = surfaceAlpha)
     } else {
         colors.background.copy(alpha = surfaceAlpha)
     }

+ 11 - 0
app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt

@@ -174,6 +174,17 @@ fun ReadBookScreen(
         onOpenFolderPicker = { onIntent(ReadBookIntent.OpenFontFolderPicker) },
         systemTypefaces = fontSelectSystemTypefaces,
     )
+    FontSelectSheet(
+        show = state.activeSheet is ReadBookSheet.TitleFontSelect,
+        title = stringResource(R.string.read_config_title_settings),
+        fontFolderUri = fontSelectFolderUri,
+        selectedFontPath = ReadBookConfig.titleFont,
+        onDismissRequest = dismissSheet,
+        onSelectFont = { onIntent(ReadBookIntent.SelectTitleFont(it.uri.toString())) },
+        onSelectSystemTypeface = { onIntent(ReadBookIntent.SelectTitleSystemTypeface(it)) },
+        onOpenFolderPicker = { onIntent(ReadBookIntent.OpenFontFolderPicker) },
+        systemTypefaces = fontSelectSystemTypefaces,
+    )
     ToolButtonConfigSheet(
         show = state.activeSheet is ReadBookSheet.ToolButtonConfig,
         items = state.menuConfig.bottomBarButtons,

+ 36 - 0
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

@@ -896,6 +896,14 @@ class ReadBookViewModel(
             }
 
             is ReadBookIntent.SelectFont -> selectFont(intent.path)
+            is ReadBookIntent.SelectTitleFont -> selectTitleFont(intent.path)
+            is ReadBookIntent.SelectTitleSystemTypeface -> {
+                ReadConfig.systemTypefaces = intent.index
+                ReadBookConfig.titleFont = ""
+                _effects.tryEmit(ReadBookEffect.UpdateReadViewConfig(
+                    setOf(ConfigUpdateAction.UpdateStyle, ConfigUpdateAction.ReloadContent)
+                ))
+            }
             is ReadBookIntent.SelectSystemTypeface -> {
                 ReadConfig.systemTypefaces = intent.index
                 ReadBookConfig.textFont = ""
@@ -1448,6 +1456,7 @@ class ReadBookViewModel(
                 val old = previous
                 previous = preferences
                 ReadConfig.syncReadPreferences(preferences)
+                ReadBookConfig.readMenuPaletteStyle = preferences.readMenuPaletteStyle
                 _readPreferences.value = preferences
                 if (!preferences.hasMenuClickArea()) {
                     ReadConfig.detectClickArea()
@@ -1599,6 +1608,8 @@ class ReadBookViewModel(
                 readMenuBorderColor = ReadBookConfig.readMenuBorderColor,
                 readMenuBorderColorNight = ReadBookConfig.readMenuBorderColorNight,
                 readMenuBlurAlpha = ReadBookConfig.readMenuBlurAlpha,
+                readMenuBlurColor = ReadBookConfig.readMenuBlurColor,
+                readMenuPaletteStyle = ReadBookConfig.readMenuPaletteStyle,
                 readMenuBlurRadius = ReadBookConfig.readMenuBlurRadius,
                 readMenuLensRadius = ReadBookConfig.readMenuLensRadius,
                 readMenuTopBarBlurMode = ReadBookConfig.readMenuTopBarBlurMode,
@@ -2507,6 +2518,10 @@ class ReadBookViewModel(
             is ConfigUpdate.TitleTopSpacing -> ReadBookConfig.titleTopSpacing = update.value
             is ConfigUpdate.TitleBottomSpacing -> ReadBookConfig.titleBottomSpacing = update.value
             is ConfigUpdate.TitleColor -> ReadBookConfig.titleColor = update.color
+            is ConfigUpdate.TitleFont -> ReadBookConfig.titleFont = update.path
+            is ConfigUpdate.TitleSegType -> ReadBookConfig.titleSegType = update.value
+            is ConfigUpdate.TitleSegDistance -> ReadBookConfig.titleSegDistance = update.value
+            is ConfigUpdate.TitleSegFlag -> ReadBookConfig.titleSegFlag = update.value
 
             // --- Header / footer tips ---
             is ConfigUpdate.HeaderMode -> ReadBookConfig.headerMode = update.value
@@ -2829,6 +2844,20 @@ class ReadBookViewModel(
                 }
                 _uiState.update { it.copy(menuConfig = it.menuConfig.copy(readMenuBlurAlpha = update.value)) }
             }
+            is ConfigUpdate.MenuBlurColor -> {
+                ReadBookConfig.readMenuBlurColor = update.color
+                viewModelScope.launch {
+                    readSettingsRepository.setReadMenuBlurColor(update.color)
+                }
+                _uiState.update { it.copy(menuConfig = it.menuConfig.copy(readMenuBlurColor = update.color)) }
+            }
+            is ConfigUpdate.MenuPaletteStyle -> {
+                ReadBookConfig.readMenuPaletteStyle = update.value
+                viewModelScope.launch {
+                    readSettingsRepository.setReadMenuPaletteStyle(update.value)
+                }
+                _uiState.update { it.copy(menuConfig = it.menuConfig.copy(readMenuPaletteStyle = update.value)) }
+            }
             is ConfigUpdate.MenuLensRadius -> {
                 ReadBookConfig.readMenuLensRadius = update.value
                 viewModelScope.launch {
@@ -3207,6 +3236,13 @@ class ReadBookViewModel(
         ))
     }
 
+    private fun selectTitleFont(path: String) {
+        ReadBookConfig.titleFont = path
+        _effects.tryEmit(ReadBookEffect.UpdateReadViewConfig(
+            setOf(ConfigUpdateAction.UpdateChapterStyle, ConfigUpdateAction.ReloadContent, ConfigUpdateAction.UpdateStyle)
+        ))
+    }
+
     private fun toggleDayNight() {
         val nextMode = if (ReadConfig.isNightTheme) "1" else "2"
         ThemeConfig.themeMode = nextMode

+ 70 - 0
app/src/main/java/io/legado/app/ui/book/read/sheet/SystemMenuPage.kt

@@ -26,7 +26,11 @@ import androidx.compose.material.icons.filled.Settings
 import androidx.compose.material.icons.filled.SkipNext
 import androidx.compose.material.icons.filled.SkipPrevious
 import androidx.compose.material.icons.filled.Translate
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.derivedStateOf
@@ -40,13 +44,16 @@ import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
 import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringArrayResource
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -60,9 +67,11 @@ import io.legado.app.ui.book.read.ConfigUpdate
 import io.legado.app.ui.book.read.ReadBookButtonConfigItem
 import io.legado.app.ui.book.read.ReadBookIntent
 import io.legado.app.ui.book.read.ReadBookSheet
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.SectionTitle
 import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
 import io.legado.app.ui.widget.components.settingItem.TinyClearColorModeSettingItem
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.ui.widget.components.settingItem.TinyClickableSettingItem
 import io.legado.app.ui.widget.components.settingItem.TinyColorModeSettingItem
 import io.legado.app.ui.widget.components.settingItem.TinyDropdownSettingItem
@@ -82,6 +91,7 @@ private const val COLOR_MENU_ACCENT_NIGHT = 9
 private const val COLOR_MENU_CONTAINER_NIGHT = 10
 private const val COLOR_BORDER = 11
 private const val COLOR_BORDER_NIGHT = 12
+private const val COLOR_BLUR_TINT = 13
 
 @Composable
 internal fun SystemMenuPage(
@@ -196,6 +206,7 @@ internal fun SystemMenuPage(
                 COLOR_MENU_CONTAINER_NIGHT -> onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.MenuContainerColorNight(color)))
                 COLOR_BORDER -> onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.BorderColor(color)))
                 COLOR_BORDER_NIGHT -> onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.BorderColorNight(color)))
+                COLOR_BLUR_TINT -> onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.MenuBlurColor(color)))
             }
             showColorPicker = false
         },
@@ -264,6 +275,23 @@ private fun GlobalMenuTab(
             },
         )
 
+        val showPaletteStyle = bottomMode == 1 || (bottomMode == 2 && colorMode == 0)
+        AnimatedVisibility(visible = showPaletteStyle) {
+            val paletteEntries = stringArrayResource(R.array.paletteStyle)
+            val paletteValues = stringArrayResource(R.array.paletteStyle_value)
+            val allEntries = arrayOf(stringResource(R.string.flow_sys)) + paletteEntries
+            val allValues = arrayOf("") + paletteValues
+            TinyDropdownSettingItem(
+                title = stringResource(R.string.palette_style),
+                selectedValue = preferences.readMenuPaletteStyle,
+                displayEntries = allEntries,
+                entryValues = allValues,
+                onValueChange = {
+                    onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.MenuPaletteStyle(it)))
+                },
+            )
+        }
+
         AnimatedVisibility(visible = bottomMode == 2) {
             Column {
                 TinyDropdownSettingItem(
@@ -461,6 +489,18 @@ private fun GlobalMenuTab(
                 onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.MenuBlurAlpha(it.toInt())))
             },
         )
+        TinyClickableSettingItem(
+            title = stringResource(R.string.read_menu_blur_color),
+            onClick = {
+                onShowColorPicker(COLOR_BLUR_TINT, preferences.readMenuBlurColor)
+            },
+            onLongClick = {
+                onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.MenuBlurColor(0)))
+            },
+            trailingContent = {
+                TinyColorSwatch(color = preferences.readMenuBlurColor)
+            },
+        )
     }
 }
 
@@ -794,3 +834,33 @@ internal fun readMenuButtonInfos(context: Context): List<ReadMenuButtonInfo> = l
     ReadMenuButtonInfo("replace_badge", Icons.Default.AutoAwesome, context.getString(R.string.replace_purify_badge)),
     ReadMenuButtonInfo("translate", Icons.Default.Translate, context.getString(R.string.translate)),
 )
+
+// ========== Color Swatch ==========
+
+@Composable
+private fun TinyColorSwatch(
+    color: Int,
+) {
+    val swatchColor = if (color != 0) Color(color) else LegadoTheme.colorScheme.onSurfaceVariant
+    val borderColor = LegadoTheme.colorScheme.outline
+    Box(
+        modifier = Modifier
+            .size(28.dp)
+            .clip(CircleShape)
+            .background(swatchColor)
+            .then(
+                if (color == 0) Modifier.border(1.5.dp, borderColor, CircleShape)
+                else Modifier
+            ),
+        contentAlignment = Alignment.Center,
+    ) {
+        if (color == 0) {
+            AppText(
+                text = "A",
+                style = LegadoTheme.typography.labelSmall,
+                color = LegadoTheme.colorScheme.surfaceContainerLow,
+                textAlign = TextAlign.Center,
+            )
+        }
+    }
+}

+ 90 - 1
app/src/main/java/io/legado/app/ui/book/read/sheet/TextTitleSheet.kt

@@ -60,6 +60,7 @@ fun ReadStyleTextTitleContent(
     onOpenUnderlineConfig: () -> Unit,
     onOpenHighlightRule: () -> Unit,
     onOpenFontSelect: () -> Unit,
+    onOpenTitleFontSelect: () -> Unit,
     modifier: Modifier = Modifier,
     onIntent: (ReadBookIntent) -> Unit,
 ) {
@@ -85,6 +86,7 @@ fun ReadStyleTextTitleContent(
         onOpenUnderlineConfig = onOpenUnderlineConfig,
         onOpenHighlightRule = onOpenHighlightRule,
         onOpenFontSelect = onOpenFontSelect,
+        onOpenTitleFontSelect = onOpenTitleFontSelect,
         animateToPage = { page -> scope.launch { pagerState.animateScrollToPage(page) } },
         modifier = modifier,
         onIntent = onIntent,
@@ -101,6 +103,7 @@ internal fun ReadStyleTextTitleContent(
     onOpenUnderlineConfig: () -> Unit,
     onOpenHighlightRule: () -> Unit,
     onOpenFontSelect: () -> Unit,
+    onOpenTitleFontSelect: () -> Unit,
     animateToPage: (Int) -> Unit,
     modifier: Modifier = Modifier,
     onIntent: (ReadBookIntent) -> Unit,
@@ -132,7 +135,10 @@ internal fun ReadStyleTextTitleContent(
                 )
 
                 1 -> LayoutSpacingPage(onIntent = onIntent)
-                2 -> TitleSettingsPage(onIntent = onIntent)
+                2 -> TitleSettingsPage(
+                    onOpenTitleFontSelect = onOpenTitleFontSelect,
+                    onIntent = onIntent,
+                )
             }
         }
     }
@@ -358,10 +364,14 @@ internal fun TextEffectsPage(
 
 @Composable
 internal fun TitleSettingsPage(
+    onOpenTitleFontSelect: () -> Unit,
     onIntent: (ReadBookIntent) -> Unit,
 ) {
     var titleMode by remember(ReadBookConfig.titleMode) { mutableIntStateOf(ReadBookConfig.titleMode) }
     var titleBold by remember(ReadBookConfig.titleBold) { mutableIntStateOf(ReadBookConfig.titleBold) }
+    var titleSegType by remember(ReadBookConfig.titleSegType) { mutableIntStateOf(ReadBookConfig.titleSegType) }
+    var titleSegDistance by remember(ReadBookConfig.titleSegDistance) { mutableIntStateOf(ReadBookConfig.titleSegDistance) }
+    var titleSegFlag by remember(ReadBookConfig.titleSegFlag) { mutableStateOf(ReadBookConfig.titleSegFlag) }
     var titleSegScaling by remember(ReadBookConfig.titleSegScaling) { mutableFloatStateOf(ReadBookConfig.titleSegScaling) }
     var titleLineSpacingExtra by remember(ReadBookConfig.titleLineSpacingExtra) { mutableIntStateOf(ReadBookConfig.titleLineSpacingExtra) }
     var titleLineSpacingSub by remember(ReadBookConfig.titleLineSpacingSub) { mutableIntStateOf(ReadBookConfig.titleLineSpacingSub) }
@@ -432,6 +442,85 @@ internal fun TitleSettingsPage(
 
         Spacer(Modifier.height(8.dp))
 
+        // Title font
+        TinyClickableSettingItem(
+            title = stringResource(R.string.read_config_title_settings),
+            imageVector = Icons.Default.TextFields,
+            onClick = onOpenTitleFontSelect,
+        )
+
+        Spacer(Modifier.height(8.dp))
+
+        // Title segmentation
+        TinyDropdownSettingItem(
+            title = stringResource(R.string.split_title_mode),
+            selectedValue = titleSegType.toString(),
+            displayEntries = arrayOf(
+                stringResource(R.string.close),
+                stringResource(R.string.split_title_by_position),
+                stringResource(R.string.split_title_by_flag),
+                stringResource(R.string.split_title_by_regex),
+            ),
+            entryValues = arrayOf("0", "1", "2", "3"),
+            onValueChange = {
+                titleSegType = it.toInt()
+                onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.TitleSegType(titleSegType)))
+            },
+        )
+        if (titleSegType == 1) {
+            TinySliderSettingItem(
+                title = stringResource(R.string.split_title_position),
+                value = titleSegDistance.toFloat(),
+                valueRange = 1f..20f,
+                steps = 18,
+                onValueChange = { value ->
+                    titleSegDistance = value.toInt()
+                    onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.TitleSegDistance(titleSegDistance)))
+                },
+            )
+        }
+        if (titleSegType == 2 || titleSegType == 3) {
+            var showFlagDialog by remember { mutableStateOf(false) }
+
+            TinyClickableSettingItem(
+                title = stringResource(R.string.rule_segment),
+                description = titleSegFlag.ifBlank { stringResource(R.string.split_title_mode) },
+                onClick = { showFlagDialog = true },
+            )
+
+            if (showFlagDialog) {
+                var flagText by remember { mutableStateOf(titleSegFlag) }
+                androidx.compose.material3.AlertDialog(
+                    onDismissRequest = { showFlagDialog = false },
+                    title = { Text(stringResource(R.string.rule_segment)) },
+                    text = {
+                        androidx.compose.material3.OutlinedTextField(
+                            value = flagText,
+                            onValueChange = { flagText = it },
+                            singleLine = false,
+                            modifier = Modifier.fillMaxWidth(),
+                        )
+                    },
+                    confirmButton = {
+                        androidx.compose.material3.TextButton(onClick = {
+                            titleSegFlag = flagText
+                            onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.TitleSegFlag(titleSegFlag)))
+                            showFlagDialog = false
+                        }) {
+                            Text(stringResource(android.R.string.ok))
+                        }
+                    },
+                    dismissButton = {
+                        androidx.compose.material3.TextButton(onClick = { showFlagDialog = false }) {
+                            Text(stringResource(android.R.string.cancel))
+                        }
+                    },
+                )
+            }
+        }
+
+        Spacer(Modifier.height(8.dp))
+
         // Title spacing sliders
         TinySliderSettingItem(
             title = stringResource(R.string.subtitle_scale),

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/search/ScopeSelectSheet.kt

@@ -104,7 +104,7 @@ fun ScopeSelectSheet(
                         applyDraftSelection()
                         onDismissRequest()
                     },
-                    text = stringResource(R.string.confirm),
+                    icon = AppIcons.Check,
                 )
             }
         },

+ 4 - 1
app/src/main/java/io/legado/app/ui/theme/ThemeColorSchemeOverride.kt

@@ -92,6 +92,7 @@ fun ColorScheme.toLegadoColorScheme(): LegadoColorScheme {
 fun ProvideColorSchemeOverride(
     colorScheme: ColorScheme,
     seedColor: Color = colorScheme.primary,
+    overrideIsDark: Boolean? = null,
     content: @Composable () -> Unit,
 ) {
     val themeAnimationSpec = tween<Color>(
@@ -106,10 +107,12 @@ fun ProvideColorSchemeOverride(
         label = "theme_seed_animation"
     ).value
     val legadoColorScheme = remember(animatedColorScheme) { animatedColorScheme.toLegadoColorScheme() }
-    val overrideThemeMode = remember(baseThemeMode, animatedColorScheme, animatedSeedColor) {
+    val resolvedIsDark = overrideIsDark ?: baseThemeMode.isDark
+    val overrideThemeMode = remember(baseThemeMode, animatedColorScheme, animatedSeedColor, resolvedIsDark) {
         baseThemeMode.copy(
             colorScheme = animatedColorScheme,
             seedColor = animatedSeedColor,
+            isDark = resolvedIsDark,
         )
     }
     val materialTypography = MaterialTheme.typography

+ 4 - 1
app/src/main/java/io/legado/app/ui/theme/ThemeOverride.kt

@@ -16,6 +16,7 @@ import io.legado.app.ui.config.themeConfig.ThemeConfig
 data class ThemeOverrideState(
     val seedColor: Color,
     val colorScheme: ColorScheme,
+    val isDark: Boolean = false,
 )
 
 fun buildThemeOverrideState(
@@ -45,7 +46,8 @@ fun buildThemeOverrideState(
 
     return ThemeOverrideState(
         seedColor = seedColor,
-        colorScheme = colorScheme
+        colorScheme = colorScheme,
+        isDark = isDark,
     )
 }
 
@@ -72,6 +74,7 @@ fun ProvideThemeOverride(
         ProvideColorSchemeOverride(
             colorScheme = currentTheme.colorScheme,
             seedColor = currentTheme.seedColor,
+            overrideIsDark = currentTheme.isDark,
             content = content
         )
     } else {

+ 2 - 2
app/src/main/java/io/legado/app/ui/widget/components/changeSource/ChangeSourceSheet.kt

@@ -438,8 +438,8 @@ fun ChangeSourceSheet(
         selectedSources = scopeState.sourceUrls,
         onToggleSource = { viewModel.toggleScopeSource(it) },
         isSourceScope = scopeState.isSource,
-        onConfirm = {
-            viewModel.startSearch()
+        onApplyScope = { selection ->
+            viewModel.applyScopeSelection(selection)
             showFilterSheet = false
         }
     )

+ 5 - 0
app/src/main/java/io/legado/app/ui/widget/components/icon/AppIcons.kt

@@ -5,6 +5,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
 import androidx.compose.material.icons.automirrored.filled.LibraryBooks
 import androidx.compose.material.icons.automirrored.outlined.LibraryBooks
 import androidx.compose.material.icons.filled.BugReport
+import androidx.compose.material.icons.filled.Check
 import androidx.compose.material.icons.filled.Clear
 import androidx.compose.material.icons.filled.Delete
 import androidx.compose.material.icons.filled.Edit
@@ -111,6 +112,10 @@ object AppIcons {
         @Composable
         get() = if (isMiuix) MiuixIcons.MoreCircle else Icons.Default.MoreHoriz
 
+    val Check: ImageVector
+        @Composable
+        get() = Icons.Default.Check
+
 
     @Composable
     fun mainDestination(destination: MainDestination, selected: Boolean): ImageVector {

+ 4 - 2
app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenu.kt

@@ -65,6 +65,7 @@ fun RoundDropdownMenu(
         }
     } else {
         val colorScheme = rememberOpaqueColorScheme()
+        val popupContainerColor = LegadoTheme.colorScheme.surfaceContainerLow
 
         DropdownMenu(
             expanded = expanded,
@@ -72,7 +73,7 @@ fun RoundDropdownMenu(
             modifier = modifier,
             shape = shape,
             shadowElevation = shadowElevation,
-            containerColor = colorScheme.surfaceContainerLow
+            containerColor = popupContainerColor
         ) {
             MaterialExpressiveTheme(
                 colorScheme = colorScheme,
@@ -129,6 +130,7 @@ fun RoundDropdownMenuLazy(
         }
     } else {
         val colorScheme = rememberOpaqueColorScheme()
+        val popupContainerColor = LegadoTheme.colorScheme.surfaceContainerLow
 
         DropdownMenu(
             expanded = expanded,
@@ -136,7 +138,7 @@ fun RoundDropdownMenuLazy(
             modifier = modifier,
             shape = shape,
             shadowElevation = shadowElevation,
-            containerColor = colorScheme.surfaceContainerLow
+            containerColor = popupContainerColor
         ) {
             MaterialExpressiveTheme(
                 colorScheme = colorScheme,

+ 4 - 5
app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenuItem.kt

@@ -35,7 +35,6 @@ import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.ThemeResolver
-import io.legado.app.ui.theme.rememberOpaqueColorScheme
 import io.legado.app.ui.widget.components.icon.AppIcon
 import top.yukonga.miuix.kmp.basic.DropdownDefaults
 import top.yukonga.miuix.kmp.icon.MiuixIcons
@@ -121,9 +120,9 @@ fun RoundDropdownMenuItem(
             }
         }
     } else {
-        val colorScheme = rememberOpaqueColorScheme()
-        val selectedContentColor = colorScheme.primary
-        val defaultContentColor = colorScheme.onSurface
+        val legadoColorScheme = LegadoTheme.colorScheme
+        val selectedContentColor = legadoColorScheme.primary
+        val defaultContentColor = legadoColorScheme.onSurface
         val contentColor = if (enabled) {
             when {
                 hasCustomContentColor -> color
@@ -137,7 +136,7 @@ fun RoundDropdownMenuItem(
                 else -> defaultContentColor.copy(alpha = 0.38f)
             }
         }
-        val containerColor = colorScheme.surface
+        val containerColor = legadoColorScheme.surface
 
         Surface(
             onClick = onClick,

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

@@ -1711,6 +1711,10 @@
 	<string name="tool_bar_style">工具栏样式</string>
 	<string name="split_title">标题分段</string>
 	<string name="split_title_mode">分段模式</string>
+	<string name="split_title_by_position">按位置分段</string>
+	<string name="split_title_by_flag">按标记分段</string>
+	<string name="split_title_by_regex">正则分段</string>
+	<string name="split_title_position">分段位置</string>
 	<string name="subtitle_scale">次行缩放</string>
 	<string name="subtitle_margin">次行距离</string>
 	<string name="heading_spacing">标题段距</string>
@@ -1903,6 +1907,7 @@
     <string name="read_menu_blur_radius">模糊半径</string>
     <string name="read_menu_blur_radius_summary">模糊半径越大,系统运行越卡顿</string>
     <string name="read_menu_blur_alpha">菜单不透明度</string>
+    <string name="read_menu_blur_color">模糊着色</string>
     <string name="read_menu_border">边框</string>
     <string name="read_menu_border_width">宽度</string>
     <string name="read_menu_border_color">颜色</string>

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

@@ -1748,6 +1748,10 @@
     <string name="rule_segment">Rule</string>
     <string name="split_title">Split title</string>
     <string name="split_title_mode">Split mode</string>
+    <string name="split_title_by_position">By position</string>
+    <string name="split_title_by_flag">By flag</string>
+    <string name="split_title_by_regex">By regex</string>
+    <string name="split_title_position">Split position</string>
     <string name="subtitle_margin">Subtitle margin</string>
     <string name="subtitle_scale">Subtitle scale</string>
     <string name="tool_bar_style">Tool bar style</string>
@@ -1912,6 +1916,7 @@
     <string name="read_menu_blur_radius">Blur radius</string>
     <string name="read_menu_blur_radius_summary">Higher radius may reduce performance</string>
     <string name="read_menu_blur_alpha">Menu opacity</string>
+    <string name="read_menu_blur_color">Blur tint color</string>
     <string name="read_menu_border">Border</string>
     <string name="read_menu_border_width">Width</string>
     <string name="read_menu_border_color">Color</string>