@
feat: 顶栏液态玻璃胶囊布局与合并按钮 - 合并换源/刷新/更多按钮为一个药丸形液态玻璃按钮(仅液态玻璃开启时) - 新增标题胶囊布局开关,将书名和章节名放入导航按钮旁的液态玻璃胶囊内 - 去掉切换到 TextTitle 菜单 tab 时隐藏顶栏的逻辑 - 玻璃效果统一为 40dp,与返回按钮一致,镜头扭曲不受裁切 @
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>()
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user