diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index 5a8e52088..60257c268 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/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" diff --git a/app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt b/app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt index 352304646..82c1012df 100644 --- a/app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt +++ b/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) diff --git a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt index 5527adeab..1f760048b 100644 --- a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt @@ -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) diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt index 579608c2a..9bdadd3e5 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt @@ -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() } + data class MenuTopBarTitleCapsule(val value: Boolean) : ConfigUpdate { + override val actions = emptySet() + } data class MenuBottomBarLiquidGlassButtons(val value: Boolean) : ConfigUpdate { override val actions = emptySet() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt index dfb45706c..b714fd08e 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt +++ b/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, diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 2f720bd3e..cbe67333a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -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 { diff --git a/app/src/main/java/io/legado/app/ui/book/read/sheet/SystemMenuPage.kt b/app/src/main/java/io/legado/app/ui/book/read/sheet/SystemMenuPage.kt index 34677d596..1ea982678 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/sheet/SystemMenuPage.kt +++ b/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 + ) + ) + ) + }, + ) + } } } diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 073d0ae09..d31876f37 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1908,6 +1908,7 @@ 颜色 实心模糊 液态玻璃按钮 + 标题胶囊布局 模糊效果 液态玻璃背景 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bd7822aa7..dd219fe71 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1903,6 +1903,7 @@ None Use liquid glass for the floating bottom bar. Requires Android 13+ Liquid glass bottom buttons + Title capsule layout Apply liquid glass to bottom bar buttons and the slider thumb. Requires Android 13+ Blur style Solid blur