Selaa lähdekoodia

@
feat: 顶栏液态玻璃胶囊布局与合并按钮

- 合并换源/刷新/更多按钮为一个药丸形液态玻璃按钮(仅液态玻璃开启时)
- 新增标题胶囊布局开关,将书名和章节名放入导航按钮旁的液态玻璃胶囊内
- 去掉切换到 TextTitle 菜单 tab 时隐藏顶栏的逻辑
- 玻璃效果统一为 40dp,与返回按钮一致,镜头扭曲不受裁切
@

HapeLee 4 kuukautta sitten
vanhempi
commit
5a587222eb

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

@@ -302,6 +302,7 @@ object PreferKey {
     const val readMenuTopBarBlurMode = "readMenuTopBarBlurMode"
     const val readMenuBottomBarBlurMode = "readMenuBottomBarBlurMode"
     const val readMenuTopBarLiquidGlassButtons = "readMenuTopBarLiquidGlassButtons"
+    const val readMenuTopBarTitleCapsule = "readMenuTopBarTitleCapsule"
     const val readMenuBottomBarLiquidGlassButtons = "readMenuBottomBarLiquidGlassButtons"
     const val readMenuTopBarBlurStyle = "readMenuTopBarBlurStyle"
     const val readMenuBottomBarBlurStyle = "readMenuBottomBarBlurStyle"

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

@@ -88,6 +88,7 @@ data class ReadPreferences(
     val readMenuTopBarBlurMode: Int = ReadMenuBlurMode.None,
     val readMenuBottomBarBlurMode: Int = ReadMenuBlurMode.None,
     val readMenuTopBarLiquidGlassButtons: Boolean = false,
+    val readMenuTopBarTitleCapsule: Boolean = false,
     val readMenuBottomBarLiquidGlassButtons: Boolean = false,
     val readMenuTopBarBlurStyle: Int = ReadMenuBlurStyle.Progressive,
     val readMenuBottomBarBlurStyle: Int = ReadMenuBlurStyle.Solid,
@@ -316,6 +317,9 @@ class ReadSettingsRepository(
     suspend fun setReadMenuTopBarLiquidGlassButtons(value: Boolean) =
         settingsRepository.putBoolean(PreferKey.readMenuTopBarLiquidGlassButtons, value)
 
+    suspend fun setReadMenuTopBarTitleCapsule(value: Boolean) =
+        settingsRepository.putBoolean(PreferKey.readMenuTopBarTitleCapsule, value)
+
     suspend fun setReadMenuBottomBarLiquidGlassButtons(value: Boolean) =
         settingsRepository.putBoolean(PreferKey.readMenuBottomBarLiquidGlassButtons, value)
 

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

@@ -204,6 +204,7 @@ object ReadBookConfig {
     var readMenuIconShowText by prefDelegate(PreferKey.readMenuIconShowText, true)
     var readMenuFloatingBottomBar by prefDelegate(PreferKey.readMenuFloatingBottomBar, false)
     var readMenuTopBarLiquidGlassButtons by prefDelegate(PreferKey.readMenuTopBarLiquidGlassButtons, false)
+    var readMenuTopBarTitleCapsule by prefDelegate(PreferKey.readMenuTopBarTitleCapsule, false)
     var readMenuBottomBarLiquidGlassButtons by prefDelegate(PreferKey.readMenuBottomBarLiquidGlassButtons, false)
     var readMenuBorderColor by prefDelegate(PreferKey.readMenuBorderColor, 0)
     var readMenuBorderColorNight by prefDelegate(PreferKey.readMenuBorderColorNight, 0)

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

@@ -187,6 +187,7 @@ data class ReadMenuConfig(
     val readMenuTopBarBlurMode: Int = ReadMenuBlurMode.None,
     val readMenuBottomBarBlurMode: Int = ReadMenuBlurMode.None,
     val readMenuTopBarLiquidGlassButtons: Boolean = false,
+    val readMenuTopBarTitleCapsule: Boolean = false,
     val readMenuBottomBarLiquidGlassButtons: Boolean = false,
     val readMenuTopBarBlurStyle: Int = ReadMenuBlurStyle.Progressive,
     val readMenuBottomBarBlurStyle: Int = ReadMenuBlurStyle.Solid,
@@ -1004,6 +1005,9 @@ sealed interface ConfigUpdate {
     data class MenuTopBarLiquidGlassButtons(val value: Boolean) : ConfigUpdate {
         override val actions = emptySet<ConfigUpdateAction>()
     }
+    data class MenuTopBarTitleCapsule(val value: Boolean) : ConfigUpdate {
+        override val actions = emptySet<ConfigUpdateAction>()
+    }
     data class MenuBottomBarLiquidGlassButtons(val value: Boolean) : ConfigUpdate {
         override val actions = emptySet<ConfigUpdateAction>()
     }

+ 290 - 40
app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt

@@ -195,8 +195,7 @@ fun ReadBookMenuBar(
         }
     }
     val hideTopBar = dialogLikeRoute ||
-            currentRoute == ReadBookMenuRoute.ReadStyle && readStylePage >= 1 ||
-            currentRoute == ReadBookMenuRoute.TextTitle
+            currentRoute == ReadBookMenuRoute.ReadStyle && readStylePage >= 1
     val menuColors = readMenuColors()
 
     Box(Modifier.fillMaxSize()) {
@@ -902,6 +901,8 @@ private fun MenuTitleBar(
                 )
             )
     ) {
+        val useTitleCapsule = readMenuTopBarTitleCapsuleEnabled(backdrop, state.menuConfig)
+
         // Title row: back + title + actions + overflow
         Row(
             modifier = Modifier
@@ -919,7 +920,16 @@ private fun MenuTitleBar(
                 backdrop = backdrop,
             )
 
-            if (titleBarMode != "1" && titleBarMode != "3") {
+            if (useTitleCapsule) {
+                // Title capsule layout: title + chapter in a glass pill
+                TitleCapsuleGlassLayout(
+                    state = state,
+                    colors = colors,
+                    onIntent = onIntent,
+                    backdrop = backdrop,
+                    titleTextColor = titleTextColor,
+                )
+            } else if (titleBarMode != "1" && titleBarMode != "3") {
                 AppText(
                     text = state.bookName,
                     modifier = Modifier
@@ -941,46 +951,55 @@ private fun MenuTitleBar(
                 Spacer(Modifier.weight(1f))
             }
 
-            Row(
-                horizontalArrangement = Arrangement.spacedBy(6.dp),
-                verticalAlignment = Alignment.CenterVertically,
-            ) {
-                // Source action button (non-local books only)
-                if (!state.isLocalBook) {
-                    SourceActionButton(
-                        state = state,
-                        colors = colors,
-                        onIntent = onIntent,
-                        backdrop = backdrop,
-                    )
-                    RefreshActionButton(
-                        state = state,
-                        colors = colors,
-                        onIntent = onIntent,
-                        backdrop = backdrop,
-                    )
-                }
+            if (readMenuTopBarButtonLiquidGlassEnabled(backdrop, state.menuConfig)) {
+                MenuTitleBarMergedGlassButton(
+                    state = state,
+                    colors = colors,
+                    onIntent = onIntent,
+                    backdrop = backdrop,
+                )
+            } else {
+                Row(
+                    horizontalArrangement = Arrangement.spacedBy(6.dp),
+                    verticalAlignment = Alignment.CenterVertically,
+                ) {
+                    // Source action button (non-local books only)
+                    if (!state.isLocalBook) {
+                        SourceActionButton(
+                            state = state,
+                            colors = colors,
+                            onIntent = onIntent,
+                            backdrop = backdrop,
+                        )
+                        RefreshActionButton(
+                            state = state,
+                            colors = colors,
+                            onIntent = onIntent,
+                            backdrop = backdrop,
+                        )
+                    }
 
-                Box {
-                    MenuTitleGlassButton(
-                        onClick = { expanded = true },
-                        icon = Icons.Default.MoreVert,
-                        state = state,
-                        colors = colors,
-                        backdrop = backdrop,
-                    )
-                    OverflowDropdownMenu(
-                        state = state,
-                        onIntent = onIntent,
-                        expanded = expanded,
-                        onDismiss = { expanded = false },
-                    )
+                    Box {
+                        MenuTitleGlassButton(
+                            onClick = { expanded = true },
+                            icon = Icons.Default.MoreVert,
+                            state = state,
+                            colors = colors,
+                            backdrop = backdrop,
+                        )
+                        OverflowDropdownMenu(
+                            state = state,
+                            onIntent = onIntent,
+                            expanded = expanded,
+                            onDismiss = { expanded = false },
+                        )
+                    }
                 }
             }
         }
 
-        // Book name on its own line (mode "1")
-        if (titleBarMode == "1") {
+        // Book name on its own line (mode "1") — hidden when capsule is active
+        if (titleBarMode == "1" && !useTitleCapsule) {
             AppText(
                 text = state.bookName,
                 modifier = Modifier
@@ -1000,8 +1019,8 @@ private fun MenuTitleBar(
             )
         }
 
-        // Chapter name + source action (modes "0" and "1")
-        if (titleBarMode == "0" || titleBarMode == "1") {
+        // Chapter name + source action (modes "0" and "1") — hidden when capsule is active
+        if ((titleBarMode == "0" || titleBarMode == "1") && !useTitleCapsule) {
             Row(
                 modifier = Modifier
                     .fillMaxWidth()
@@ -1219,6 +1238,228 @@ private fun ReadMenuGlassButtonSurface(
     }
 }
 
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun RowScope.TitleCapsuleGlassLayout(
+    state: ReadBookUiState,
+    colors: ReadMenuColors,
+    onIntent: (ReadBookIntent) -> Unit,
+    backdrop: Backdrop?,
+    titleTextColor: Color,
+) {
+    val pillShape = RoundedCornerShape(50)
+
+    Row(
+        modifier = Modifier
+            .weight(1f)
+            .padding(horizontal = 8.dp)
+            .height(40.dp)
+            .readMenuLiquidGlass(
+                backdrop = backdrop,
+                colors = colors,
+                shape = pillShape,
+                useTopBarStyle = true,
+                useLens = false,
+                blurRadius = 32.dp,
+                menuConfig = state.menuConfig,
+            )
+            .then(
+                if (!state.isLocalBook) {
+                    Modifier.combinedClickable(
+                        indication = null,
+                        interactionSource = remember { MutableInteractionSource() },
+                        onClick = { onIntent(ReadBookIntent.OpenBookInfo) },
+                        onLongClick = { onIntent(ReadBookIntent.OpenChapterUrl) },
+                    )
+                } else {
+                    Modifier.clickable(
+                        indication = null,
+                        interactionSource = remember { MutableInteractionSource() },
+                    ) { onIntent(ReadBookIntent.OpenBookInfo) }
+                }
+            )
+            .padding(horizontal = 12.dp),
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        Column(
+            modifier = Modifier.weight(1f),
+        ) {
+            AppText(
+                text = state.bookName,
+                style = LegadoTheme.typography.labelMediumEmphasized,
+                color = titleTextColor,
+                maxLines = 1,
+                overflow = TextOverflow.Ellipsis,
+            )
+            if (state.chapterName.isNotBlank()) {
+                AppText(
+                    text = state.chapterName,
+                    style = LegadoTheme.typography.labelSmall,
+                    color = titleTextColor.copy(alpha = 0.7f),
+                    maxLines = 1,
+                    overflow = TextOverflow.Ellipsis,
+                )
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun MenuTitleBarMergedGlassButton(
+    state: ReadBookUiState,
+    colors: ReadMenuColors,
+    onIntent: (ReadBookIntent) -> Unit,
+    backdrop: Backdrop?,
+) {
+    var sourceExpanded by remember { mutableStateOf(false) }
+    var refreshExpanded by remember { mutableStateOf(false) }
+    var overflowExpanded by remember { mutableStateOf(false) }
+
+    val pillShape = RoundedCornerShape(50)
+    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),
+                verticalAlignment = Alignment.CenterVertically,
+            ) {
+            // SwapHoriz - change source
+            if (!state.isLocalBook) {
+                Box(
+                    contentAlignment = Alignment.Center,
+                    modifier = Modifier
+                        .size(40.dp)
+                        .combinedClickable(
+                            indication = null,
+                            interactionSource = remember { MutableInteractionSource() },
+                            role = Role.Button,
+                            onClick = { onIntent(ReadBookIntent.MenuChangeSource) },
+                            onLongClick = { sourceExpanded = true },
+                        ),
+                ) {
+                    Icon(
+                        imageVector = Icons.Default.SwapHoriz,
+                        contentDescription = stringResource(R.string.change_origin),
+                        tint = tint,
+                        modifier = Modifier.size(20.dp),
+                    )
+                }
+
+                Box(
+                    modifier = Modifier
+                        .width(1.dp)
+                        .height(20.dp)
+                        .background(tint.copy(alpha = 0.15f))
+                )
+
+                // Refresh
+                Box(
+                    contentAlignment = Alignment.Center,
+                    modifier = Modifier
+                        .size(40.dp)
+                        .combinedClickable(
+                            indication = null,
+                            interactionSource = remember { MutableInteractionSource() },
+                            role = Role.Button,
+                            onClick = { onIntent(ReadBookIntent.MenuRefreshAfter) },
+                            onLongClick = { refreshExpanded = true },
+                        ),
+                ) {
+                    Icon(
+                        imageVector = Icons.Default.Refresh,
+                        contentDescription = stringResource(R.string.menu_refresh_after),
+                        tint = tint,
+                        modifier = Modifier.size(20.dp),
+                    )
+                }
+
+                Box(
+                    modifier = Modifier
+                        .width(1.dp)
+                        .height(20.dp)
+                        .background(tint.copy(alpha = 0.15f))
+                )
+            }
+
+            // MoreVert - overflow menu
+            Box(
+                contentAlignment = Alignment.Center,
+                modifier = Modifier
+                    .size(40.dp)
+                    .clickable(
+                        indication = null,
+                        interactionSource = remember { MutableInteractionSource() },
+                        role = Role.Button,
+                        onClick = { overflowExpanded = true },
+                    ),
+            ) {
+                Icon(
+                    imageVector = Icons.Default.MoreVert,
+                    contentDescription = null,
+                    tint = tint,
+                    modifier = Modifier.size(20.dp),
+                )
+            }
+        }
+        }
+
+        // Dropdown menus
+        if (!state.isLocalBook) {
+            RoundDropdownMenu(
+                expanded = sourceExpanded,
+                onDismissRequest = { sourceExpanded = false },
+            ) { dismiss ->
+                RoundDropdownMenuItem(
+                    text = stringResource(R.string.change_origin),
+                    onClick = { dismiss(); onIntent(ReadBookIntent.MenuChangeSource) },
+                )
+                RoundDropdownMenuItem(
+                    text = stringResource(R.string.chapter_change_source),
+                    onClick = { dismiss(); onIntent(ReadBookIntent.MenuChapterChangeSource) },
+                )
+            }
+
+            RoundDropdownMenu(
+                expanded = refreshExpanded,
+                onDismissRequest = { refreshExpanded = false },
+            ) { dismiss ->
+                RoundDropdownMenuItem(
+                    text = stringResource(R.string.menu_refresh_dur),
+                    onClick = { dismiss(); onIntent(ReadBookIntent.MenuRefreshDur) },
+                )
+                RoundDropdownMenuItem(
+                    text = stringResource(R.string.menu_refresh_after),
+                    onClick = { dismiss(); onIntent(ReadBookIntent.MenuRefreshAfter) },
+                )
+            }
+        }
+
+        OverflowDropdownMenu(
+            state = state,
+            onIntent = onIntent,
+            expanded = overflowExpanded,
+            onDismiss = { overflowExpanded = false },
+        )
+    }
+}
+
 @Composable
 private fun SourceActionButton(
     state: ReadBookUiState,
@@ -2406,6 +2647,15 @@ private fun readMenuTopBarButtonLiquidGlassEnabled(
             readMenuLiquidGlassAvailable(backdrop)
 }
 
+private fun readMenuTopBarTitleCapsuleEnabled(
+    backdrop: Backdrop?,
+    menuConfig: ReadMenuConfig,
+): Boolean {
+    return menuConfig.readMenuTopBarBlurMode != ReadMenuBlurMode.None &&
+            menuConfig.readMenuTopBarTitleCapsule &&
+            readMenuLiquidGlassAvailable(backdrop)
+}
+
 private fun readMenuBottomBarButtonLiquidGlassEnabled(
     backdrop: Backdrop?,
     menuConfig: ReadMenuConfig,

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

@@ -1604,6 +1604,7 @@ class ReadBookViewModel(
                 readMenuTopBarBlurMode = ReadBookConfig.readMenuTopBarBlurMode,
                 readMenuBottomBarBlurMode = ReadBookConfig.readMenuBottomBarBlurMode,
                 readMenuTopBarLiquidGlassButtons = ReadBookConfig.readMenuTopBarLiquidGlassButtons,
+                readMenuTopBarTitleCapsule = ReadBookConfig.readMenuTopBarTitleCapsule,
                 readMenuBottomBarLiquidGlassButtons = ReadBookConfig.readMenuBottomBarLiquidGlassButtons,
                 readMenuTopBarBlurStyle = ReadBookConfig.readMenuTopBarBlurStyle,
                 readMenuBottomBarBlurStyle = ReadBookConfig.readMenuBottomBarBlurStyle,
@@ -2763,6 +2764,16 @@ class ReadBookViewModel(
                 }
             }
 
+            is ConfigUpdate.MenuTopBarTitleCapsule -> {
+                ReadBookConfig.readMenuTopBarTitleCapsule = update.value
+                viewModelScope.launch {
+                    readSettingsRepository.setReadMenuTopBarTitleCapsule(update.value)
+                }
+                _uiState.update {
+                    it.copy(menuConfig = it.menuConfig.copy(readMenuTopBarTitleCapsule = update.value))
+                }
+            }
+
             is ConfigUpdate.MenuBottomBarLiquidGlassButtons -> {
                 ReadBookConfig.readMenuBottomBarLiquidGlassButtons = update.value
                 viewModelScope.launch {

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

@@ -746,6 +746,21 @@ private fun TopBarTab(
                 },
             )
         }
+        AnimatedVisibility(visible = topBarBlurEnabled) {
+            TinySwitchSettingItem(
+                title = stringResource(R.string.read_menu_top_bar_title_capsule),
+                checked = preferences.readMenuTopBarTitleCapsule,
+                onCheckedChange = {
+                    onIntent(
+                        ReadBookIntent.UpdateConfig(
+                            ConfigUpdate.MenuTopBarTitleCapsule(
+                                it
+                            )
+                        )
+                    )
+                },
+            )
+        }
     }
 }
 

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

@@ -1908,6 +1908,7 @@
     <string name="read_menu_border_color">颜色</string>
     <string name="read_menu_blur_style_solid">实心模糊</string>
     <string name="read_menu_bar_liquid_glass_buttons">液态玻璃按钮</string>
+    <string name="read_menu_top_bar_title_capsule">标题胶囊布局</string>
     <string name="read_menu_bar_blur">模糊效果</string>
     <string name="read_menu_bar_liquid_glass">液态玻璃背景</string>
 

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

@@ -1903,6 +1903,7 @@
     <string name="read_menu_blur_mode_none">None</string>
     <string name="read_menu_bar_liquid_glass_summary">Use liquid glass for the floating bottom bar. Requires Android 13+</string>
     <string name="read_menu_bar_liquid_glass_buttons">Liquid glass bottom buttons</string>
+    <string name="read_menu_top_bar_title_capsule">Title capsule layout</string>
     <string name="read_menu_bottom_bar_liquid_glass_buttons_summary">Apply liquid glass to bottom bar buttons and the slider thumb. Requires Android 13+</string>
     <string name="read_menu_bar_blur_style">Blur style</string>
     <string name="read_menu_blur_style_solid">Solid blur</string>