Quellcode durchsuchen

feat: 顶栏按钮液态玻璃效果

HapeLee vor 3 Monaten
Ursprung
Commit
983d65970c
20 geänderte Dateien mit 516 neuen und 194 gelöschten Zeilen
  1. 14 4
      app/src/main/java/io/legado/app/ui/book/info/BookInfoScreen.kt
  2. 4 3
      app/src/main/java/io/legado/app/ui/book/read/sheet/ReaderBookSheet.kt
  3. 1 0
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigContract.kt
  4. 25 153
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt
  5. 177 0
      app/src/main/java/io/legado/app/ui/config/themeConfig/TopBottomBarSettingsSheet.kt
  6. 4 0
      app/src/main/java/io/legado/app/ui/theme/LegadoTheme.kt
  7. 57 3
      app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt
  8. 2 1
      app/src/main/java/io/legado/app/ui/widget/components/button/series/SeriesIconButton.kt
  9. 41 2
      app/src/main/java/io/legado/app/ui/widget/components/lazylist/VerticalFastScroller.kt
  10. 7 1
      app/src/main/java/io/legado/app/ui/widget/components/topbar/GlassMediumFlexibleTopAppBar.kt
  11. 9 2
      app/src/main/java/io/legado/app/ui/widget/components/topbar/GlassSmallTopAppBar.kt
  12. 8 2
      app/src/main/java/io/legado/app/ui/widget/components/topbar/GlassTopAppBar.kt
  13. 68 21
      app/src/main/java/io/legado/app/ui/widget/components/topbar/TopBarButton.kt
  14. 81 0
      app/src/main/java/io/legado/app/ui/widget/components/topbar/TopBarLiquidGlass.kt
  15. 3 1
      app/src/main/res/values-zh-rCN/arrays.xml
  16. 3 0
      app/src/main/res/values-zh-rCN/strings.xml
  17. 3 0
      app/src/main/res/values-zh-rHK/strings.xml
  18. 3 0
      app/src/main/res/values-zh-rTW/strings.xml
  19. 3 1
      app/src/main/res/values/arrays.xml
  20. 3 0
      app/src/main/res/values/strings.xml

+ 14 - 4
app/src/main/java/io/legado/app/ui/book/info/BookInfoScreen.kt

@@ -122,6 +122,8 @@ import io.legado.app.ui.widget.components.topbar.MiuixGlassScrollBehavior
 import io.legado.app.ui.widget.components.topbar.TopBarActionButton
 import io.legado.app.ui.widget.components.topbar.TopBarActionsRow
 import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
+import io.legado.app.ui.widget.components.topbar.miuixTopBarActionsEndPadding
+import io.legado.app.ui.widget.components.topbar.miuixTopBarSlotPadding
 import io.legado.app.ui.widget.components.variable.VariableEditorSheet
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.toImmutableList
@@ -522,12 +524,20 @@ private fun BookInfoTransparentTopAppBar(
                 TopBarNavigationButton(onClick = onBackPressed)
             },
             actions = {
-                BookInfoTopBarActions(
-                    state = state,
-                    onMenuAction = onMenuAction,
-                )
+                TopBarActionsRow(
+                    modifier = Modifier.padding(
+                        end = miuixTopBarActionsEndPadding()
+                    )
+                ) {
+                    BookInfoTopBarActions(
+                        state = state,
+                        onMenuAction = onMenuAction,
+                    )
+                }
             },
             color = resolvedColor,
+            navigationIconPadding = miuixTopBarSlotPadding(),
+            actionIconPadding = miuixTopBarSlotPadding(),
             scrollBehavior = (scrollBehavior as? MiuixGlassScrollBehavior)?.miuixBehavior,
         )
     } else {

+ 4 - 3
app/src/main/java/io/legado/app/ui/book/read/sheet/ReaderBookSheet.kt

@@ -100,6 +100,7 @@ import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet
 import io.legado.app.ui.widget.components.card.NormalCard
 import io.legado.app.ui.widget.components.card.TextCard
 import io.legado.app.ui.widget.components.image.cover.CoilBookCover
+import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
 import io.legado.app.ui.widget.components.menuItem.MenuItemIcon
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
@@ -793,11 +794,11 @@ private fun ReaderSheetChapterList(
     onIntent: (TocIntent) -> Unit,
     onChapterClick: (Int) -> Unit,
 ) {
-    LazyColumn(
+    FastScrollLazyColumn(
         state = listState,
         modifier = Modifier.fillMaxSize(),
         contentPadding = PaddingValues(bottom = 88.dp),
-        verticalArrangement = Arrangement.spacedBy(6.dp),
+        verticalArrangement = Arrangement.spacedBy(4.dp),
     ) {
         items(
             items = state.action.items,
@@ -906,7 +907,7 @@ private fun ReaderSheetChapterItem(
         ) {
             Column(modifier = Modifier.weight(1f)) {
                 Row(
-                    modifier = Modifier.padding(vertical = 8.dp),
+                    modifier = Modifier.padding(bottom = 8.dp),
                     verticalAlignment = Alignment.CenterVertically,
                 ) {
                     if (item.isVip && !item.isPay) {

+ 1 - 0
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigContract.kt

@@ -18,6 +18,7 @@ data class ThemeConfigUiState(
 sealed interface ThemeConfigSheet {
     data class BackgroundImage(val target: BackgroundImageTarget) : ThemeConfigSheet
     data object MainNavigation : ThemeConfigSheet
+    data object TopBottomBar : ThemeConfigSheet
     data object LauncherIcon : ThemeConfigSheet
     data object DividerColor : ThemeConfigSheet
     data class BaseCardBorderColor(val dark: Boolean) : ThemeConfigSheet

+ 25 - 153
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt

@@ -251,6 +251,15 @@ fun ThemeConfigScreen(
                 }
 
                 SplicedColumnGroup {
+                    if (!isMiuixEngine) {
+                        SwitchSettingItem(
+                            title = stringResource(R.string.pure_black),
+                            checked = theme.isPureBlack,
+                            onCheckedChange = { value ->
+                                updateTheme { it.copy(isPureBlack = value) }
+                            }
+                        )
+                    }
                     ClickableSettingItem(
                         title = stringResource(R.string.font_setting),
                         onClick = { onIntent(ThemeConfigIntent.ShowSheet(ThemeConfigSheet.Font)) }
@@ -351,53 +360,13 @@ fun ThemeConfigScreen(
                             )
                         }
                     )
-                    SwitchSettingItem(
-                        title = stringResource(R.string.show_bottom_nav),
-                        description = stringResource(R.string.be_swiped),
-                        checked = appShell.showBottomView,
-                        onCheckedChange = {
-                            onIntent(
-                                ThemeConfigIntent.SetShowBottomView(it)
-                            )
-                        }
-                    )
-                    SwitchSettingItem(
-                        title = stringResource(R.string.floating_bottom_bar),
-                        description = stringResource(R.string.floating_bottom_bar_summary),
-                        checked = appShell.useFloatingBottomBar,
-                        onCheckedChange = {
-                            onIntent(
-                                ThemeConfigIntent.SetUseFloatingBottomBar(it)
-                            )
-                        }
+                    ClickableSettingItem(
+                        title = stringResource(R.string.top_bottom_bar_settings),
+                        description = stringResource(R.string.top_bottom_bar_settings_summary),
+                        onClick = {
+                            onIntent(ThemeConfigIntent.ShowSheet(ThemeConfigSheet.TopBottomBar))
+                        },
                     )
-                    AnimatedVisibility(visible = appShell.useFloatingBottomBar) {
-                        Column(
-                            verticalArrangement = Arrangement.spacedBy(2.dp)
-                        ) {
-                            SwitchSettingItem(
-                                title = stringResource(R.string.floating_bottom_bar_liquid_glass),
-                                description = stringResource(R.string.floating_bottom_bar_liquid_glass_summary),
-                                checked = appShell.useFloatingBottomBarLiquidGlass,
-                                onCheckedChange = {
-                                    onIntent(
-                                        ThemeConfigIntent.SetUseFloatingBottomBarLiquidGlass(it)
-                                    )
-                                }
-                            )
-                            SliderSettingItem(
-                                title = stringResource(R.string.theme_config_bottom_bar_lens_radius),
-                                description = stringResource(R.string.theme_config_bottom_bar_lens_radius_summary),
-                                value = theme.bottomBarLensRadius,
-                                defaultValue = 24f,
-                                valueRange = 0f..50f,
-                                onValueChange = { value ->
-                                    updateTheme { it.copy(bottomBarLensRadius = value) }
-                                }
-                            )
-                        }
-
-                    }
                     DropdownListSettingItem(
                         title = stringResource(R.string.tabletInterface),
                         selectedValue = appShell.tabletInterface,
@@ -516,41 +485,7 @@ fun ThemeConfigScreen(
                     }
                 }
 
-                SplicedColumnGroup(title = stringResource(R.string.compose_related)) {
-                    if (!isMiuixEngine) {
-                        SwitchSettingItem(
-                            title = stringResource(R.string.pure_black),
-                            checked = theme.isPureBlack,
-                            onCheckedChange = { value ->
-                                updateTheme { it.copy(isPureBlack = value) }
-                            }
-                        )
-                        SwitchSettingItem(
-                            title = stringResource(R.string.use_flexible_top_bar),
-                            checked = theme.useFlexibleTopAppBar,
-                            onCheckedChange = { value ->
-                                updateTheme { it.copy(useFlexibleTopAppBar = value) }
-                            }
-                        )
-                        DropdownListSettingItem(
-                            title = stringResource(R.string.top_bar_button_style),
-                            selectedValue = theme.topBarButtonStyle,
-                            displayEntries = stringArrayResource(R.array.top_bar_button_style),
-                            entryValues = stringArrayResource(R.array.top_bar_button_style_value),
-                            onValueChange = { value ->
-                                updateTheme { it.copy(topBarButtonStyle = value) }
-                            }
-                        )
-                        AnimatedVisibility(visible = theme.topBarButtonStyle != "plain") {
-                            SwitchSettingItem(
-                                title = stringResource(R.string.merge_top_bar_actions),
-                                checked = theme.mergeTopBarActions,
-                                onCheckedChange = { value ->
-                                    updateTheme { it.copy(mergeTopBarActions = value) }
-                                }
-                            )
-                        }
-                    }
+                SplicedColumnGroup(title = stringResource(R.string.blur_effects)) {
                     SwitchSettingItem(
                         title = stringResource(R.string.is_blur_enable),
                         checked = theme.enableBlur,
@@ -565,78 +500,6 @@ fun ThemeConfigScreen(
                             }
                         )
                     }
-                    if (theme.enableBlur) {
-                        SliderSettingItem(
-                            title = stringResource(R.string.theme_manage_top_bar_blur_radius),
-                            description = stringResource(R.string.theme_config_blur_radius_performance_summary),
-                            value = theme.topBarBlurRadius.toFloat(),
-                            defaultValue = 24f,
-                            valueRange = 0f..30f,
-                            onValueChange = { value ->
-                                updateTheme { it.copy(topBarBlurRadius = value.toInt()) }
-                            }
-                        )
-                        SliderSettingItem(
-                            title = stringResource(R.string.theme_manage_bottom_bar_blur_radius),
-                            description = stringResource(R.string.theme_config_blur_radius_performance_summary),
-                            value = theme.bottomBarBlurRadius.toFloat(),
-                            defaultValue = 8f,
-                            valueRange = 0f..10f,
-                            onValueChange = { value ->
-                                updateTheme { it.copy(bottomBarBlurRadius = value.toInt()) }
-                            }
-                        )
-                        SliderSettingItem(
-                            title = stringResource(R.string.theme_manage_top_bar_blur_opacity),
-                            value = theme.topBarBlurAlpha.toFloat(),
-                            defaultValue = 73f,
-                            valueRange = 0f..100f,
-                            onValueChange = { value ->
-                                updateTheme { it.copy(topBarBlurAlpha = value.toInt()) }
-                            }
-                        )
-                        SliderSettingItem(
-                            title = stringResource(R.string.theme_manage_bottom_bar_blur_opacity),
-                            value = theme.bottomBarBlurAlpha.toFloat(),
-                            defaultValue = 40f,
-                            valueRange = 0f..100f,
-                            onValueChange = { value ->
-                                updateTheme { it.copy(bottomBarBlurAlpha = value.toInt()) }
-                            }
-                        )
-                    }
-                    AnimatedVisibility(visible = !isMiuixEngine && !theme.enableBlur) {
-                        Column(
-                            verticalArrangement = Arrangement.spacedBy(2.dp)
-                        ) {
-                            SliderSettingItem(
-                                title = stringResource(R.string.top_bar_opacity),
-                                description = stringResource(
-                                    R.string.top_bar_opacity_summary,
-                                    theme.topBarOpacity
-                                ),
-                                value = theme.topBarOpacity.toFloat(),
-                                defaultValue = 100f,
-                                valueRange = 0f..100f,
-                                onValueChange = { value ->
-                                    updateTheme { it.copy(topBarOpacity = value.toInt()) }
-                                }
-                            )
-                            SliderSettingItem(
-                                title = stringResource(R.string.bottom_bar_opacity),
-                                description = stringResource(
-                                    R.string.bottom_bar_opacity_summary,
-                                    theme.bottomBarOpacity
-                                ),
-                                value = theme.bottomBarOpacity.toFloat(),
-                                defaultValue = 100f,
-                                valueRange = 0f..100f,
-                                onValueChange = { value ->
-                                    updateTheme { it.copy(bottomBarOpacity = value.toInt()) }
-                                }
-                            )
-                        }
-                    }
                 }
 
             }
@@ -1011,6 +874,15 @@ fun ThemeConfigScreen(
         },
     )
 
+    TopBottomBarSettingsSheet(
+        show = state.activeSheet == ThemeConfigSheet.TopBottomBar,
+        appShell = appShell,
+        theme = theme,
+        isMiuixEngine = ThemeResolver.isMiuixEngine(appShell.composeEngine),
+        onDismissRequest = { onIntent(ThemeConfigIntent.DismissSheet) },
+        onIntent = onIntent,
+    )
+
 
     LauncherIconPickerSheet(
         show = state.activeSheet == ThemeConfigSheet.LauncherIcon,

+ 177 - 0
app/src/main/java/io/legado/app/ui/config/themeConfig/TopBottomBarSettingsSheet.kt

@@ -0,0 +1,177 @@
+package io.legado.app.ui.config.themeConfig
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringArrayResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import io.legado.app.R
+import io.legado.app.domain.model.settings.AppShellSettings
+import io.legado.app.domain.model.settings.ThemeSettings
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem
+import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
+import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
+
+@Composable
+fun TopBottomBarSettingsSheet(
+    show: Boolean,
+    appShell: AppShellSettings,
+    theme: ThemeSettings,
+    isMiuixEngine: Boolean,
+    onDismissRequest: () -> Unit,
+    onIntent: (ThemeConfigIntent) -> Unit,
+) {
+    fun updateTheme(transform: (ThemeSettings) -> ThemeSettings) =
+        onIntent(ThemeConfigIntent.UpdateTheme(transform))
+
+    AppModalBottomSheet(
+        show = show,
+        onDismissRequest = onDismissRequest,
+        title = stringResource(R.string.top_bottom_bar_settings),
+    ) {
+        Column(
+            modifier = Modifier
+                .fillMaxWidth()
+                .heightIn(max = 560.dp)
+                .verticalScroll(rememberScrollState())
+                .padding(bottom = 24.dp),
+            verticalArrangement = Arrangement.spacedBy(4.dp)
+        ) {
+            if (!isMiuixEngine) {
+                CompactSwitchSettingItem(
+                    title = stringResource(R.string.use_flexible_top_bar),
+                    checked = theme.useFlexibleTopAppBar,
+                    onCheckedChange = { checked ->
+                        updateTheme { current -> current.copy(useFlexibleTopAppBar = checked) }
+                    },
+                )
+            }
+            CompactDropdownSettingItem(
+                title = stringResource(R.string.top_bar_button_style),
+                selectedValue = theme.topBarButtonStyle,
+                displayEntries = stringArrayResource(R.array.top_bar_button_style),
+                entryValues = stringArrayResource(R.array.top_bar_button_style_value),
+                onValueChange = { value -> updateTheme { it.copy(topBarButtonStyle = value) } },
+            )
+            AnimatedVisibility(visible = theme.topBarButtonStyle != "plain") {
+                CompactSwitchSettingItem(
+                    title = stringResource(R.string.merge_top_bar_actions),
+                    checked = theme.mergeTopBarActions,
+                    onCheckedChange = { checked ->
+                        updateTheme { current -> current.copy(mergeTopBarActions = checked) }
+                    },
+                )
+            }
+            CompactSwitchSettingItem(
+                title = stringResource(R.string.show_bottom_nav),
+                description = stringResource(R.string.be_swiped),
+                checked = appShell.showBottomView,
+                onCheckedChange = { onIntent(ThemeConfigIntent.SetShowBottomView(it)) },
+            )
+            CompactSwitchSettingItem(
+                title = stringResource(R.string.floating_bottom_bar),
+                description = stringResource(R.string.floating_bottom_bar_summary),
+                checked = appShell.useFloatingBottomBar,
+                onCheckedChange = { onIntent(ThemeConfigIntent.SetUseFloatingBottomBar(it)) },
+            )
+            AnimatedVisibility(visible = appShell.useFloatingBottomBar) {
+                Column {
+                    CompactSwitchSettingItem(
+                        title = stringResource(R.string.floating_bottom_bar_liquid_glass),
+                        description = stringResource(R.string.floating_bottom_bar_liquid_glass_summary),
+                        checked = appShell.useFloatingBottomBarLiquidGlass,
+                        onCheckedChange = {
+                            onIntent(ThemeConfigIntent.SetUseFloatingBottomBarLiquidGlass(it))
+                        },
+                    )
+                    SliderSettingItem(
+                        title = stringResource(R.string.theme_config_bottom_bar_lens_radius),
+                        description = stringResource(R.string.theme_config_bottom_bar_lens_radius_summary),
+                        value = theme.bottomBarLensRadius,
+                        defaultValue = 24f,
+                        valueRange = 0f..50f,
+                        onValueChange = { value ->
+                            updateTheme { current -> current.copy(bottomBarLensRadius = value) }
+                        },
+                    )
+                }
+            }
+            if (theme.enableBlur) {
+                SliderSettingItem(
+                    title = stringResource(R.string.theme_manage_top_bar_blur_radius),
+                    description = stringResource(R.string.theme_config_blur_radius_performance_summary),
+                    value = theme.topBarBlurRadius.toFloat(),
+                    defaultValue = 24f,
+                    valueRange = 0f..30f,
+                    onValueChange = { value ->
+                        updateTheme { current -> current.copy(topBarBlurRadius = value.toInt()) }
+                    },
+                )
+                SliderSettingItem(
+                    title = stringResource(R.string.theme_manage_bottom_bar_blur_radius),
+                    description = stringResource(R.string.theme_config_blur_radius_performance_summary),
+                    value = theme.bottomBarBlurRadius.toFloat(),
+                    defaultValue = 8f,
+                    valueRange = 0f..10f,
+                    onValueChange = { value ->
+                        updateTheme { current -> current.copy(bottomBarBlurRadius = value.toInt()) }
+                    },
+                )
+                SliderSettingItem(
+                    title = stringResource(R.string.theme_manage_top_bar_blur_opacity),
+                    value = theme.topBarBlurAlpha.toFloat(),
+                    defaultValue = 73f,
+                    valueRange = 0f..100f,
+                    onValueChange = { value ->
+                        updateTheme { current -> current.copy(topBarBlurAlpha = value.toInt()) }
+                    },
+                )
+                SliderSettingItem(
+                    title = stringResource(R.string.theme_manage_bottom_bar_blur_opacity),
+                    value = theme.bottomBarBlurAlpha.toFloat(),
+                    defaultValue = 40f,
+                    valueRange = 0f..100f,
+                    onValueChange = { value ->
+                        updateTheme { current -> current.copy(bottomBarBlurAlpha = value.toInt()) }
+                    },
+                )
+            } else if (!isMiuixEngine) {
+                SliderSettingItem(
+                    title = stringResource(R.string.top_bar_opacity),
+                    description = stringResource(
+                        R.string.top_bar_opacity_summary,
+                        theme.topBarOpacity
+                    ),
+                    value = theme.topBarOpacity.toFloat(),
+                    defaultValue = 100f,
+                    valueRange = 0f..100f,
+                    onValueChange = { value ->
+                        updateTheme { current -> current.copy(topBarOpacity = value.toInt()) }
+                    },
+                )
+                SliderSettingItem(
+                    title = stringResource(R.string.bottom_bar_opacity),
+                    description = stringResource(
+                        R.string.bottom_bar_opacity_summary,
+                        theme.bottomBarOpacity
+                    ),
+                    value = theme.bottomBarOpacity.toFloat(),
+                    defaultValue = 100f,
+                    valueRange = 0f..100f,
+                    onValueChange = { value ->
+                        updateTheme { current -> current.copy(bottomBarOpacity = value.toInt()) }
+                    },
+                )
+            }
+        }
+    }
+}

+ 4 - 0
app/src/main/java/io/legado/app/ui/theme/LegadoTheme.kt

@@ -8,6 +8,7 @@ import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.TextStyle
+import com.kyant.backdrop.Backdrop
 import com.materialkolor.PaletteStyle
 import dev.chrisbanes.haze.HazeState
 import top.yukonga.miuix.kmp.theme.ColorSchemeMode
@@ -135,6 +136,9 @@ val LocalLegadoThemeColors = staticCompositionLocalOf {
 
 val LocalHazeState = compositionLocalOf<HazeState?> { null }
 
+/** AppScaffold supplies the content-only source; bars must never write to it. */
+val LocalTopBarBackdrop = compositionLocalOf<Backdrop?> { null }
+
 object LegadoTheme {
 
     val colorScheme: LegadoColorScheme

+ 57 - 3
app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt

@@ -20,12 +20,16 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.unit.dp
 import coil3.compose.AsyncImage
+import com.kyant.backdrop.backdrops.layerBackdrop
+import com.kyant.backdrop.backdrops.rememberCombinedBackdrop
+import com.kyant.backdrop.backdrops.rememberLayerBackdrop
 import dev.chrisbanes.haze.HazeState
 import dev.chrisbanes.haze.hazeSource
 import io.legado.app.domain.model.settings.hasBackgroundImage
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.LocalAppUiConfiguration
 import io.legado.app.ui.theme.LocalHazeState
+import io.legado.app.ui.theme.LocalTopBarBackdrop
 import io.legado.app.ui.theme.ThemeResolver
 import io.legado.app.ui.theme.responsiveHazeSource
 import top.yukonga.miuix.kmp.theme.MiuixTheme
@@ -52,6 +56,7 @@ fun AppScaffold(
     val themeSettings = configuration.theme
     val hasImageBg = themeSettings.hasBackgroundImage(isDark)
     val hazeState = remember { HazeState() }
+    val liquidGlassEnabled = configuration.theme.topBarButtonStyle == "liquid"
     val composeEngine = LegadoTheme.composeEngine
     val contentDrawsBehindBars =
         alwaysDrawBehindBars || themeSettings.enableBlur || themeSettings.enableProgressiveBlur
@@ -67,9 +72,20 @@ fun AppScaffold(
     } else {
         MiuixTheme.colorScheme.surface
     }
+    val topBarBackdropBaseColor = LegadoTheme.colorScheme.background
+    val topBarBackgroundBackdrop = rememberLayerBackdrop {
+        drawRect(topBarBackdropBaseColor)
+        drawContent()
+    }
+    val topBarContentBackdrop = rememberLayerBackdrop { drawContent() }
+    val topBarBackdrop = rememberCombinedBackdrop(
+        topBarBackgroundBackdrop,
+        topBarContentBackdrop
+    )
 
     CompositionLocalProvider(
-        LocalHazeState provides if (themeSettings.enableBlur) hazeState else null
+        LocalHazeState provides if (themeSettings.enableBlur) hazeState else null,
+        LocalTopBarBackdrop provides if (liquidGlassEnabled) topBarBackdrop else null,
     ) {
         when {
             ThemeResolver.isMiuixEngine(composeEngine) -> {
@@ -79,7 +95,19 @@ fun AppScaffold(
                     else -> MiuixFabPosition.End
                 }
                 Box(modifier = modifier.fillMaxSize()) {
-                    BackgroundImageContent(isDark = isDark, hazeState = hazeState)
+                    Box(
+                        modifier = Modifier
+                            .fillMaxSize()
+                            .then(
+                                if (liquidGlassEnabled) {
+                                    Modifier.layerBackdrop(topBarBackgroundBackdrop)
+                                } else {
+                                    Modifier
+                                }
+                            )
+                    ) {
+                        BackgroundImageContent(isDark = isDark, hazeState = hazeState)
+                    }
                     MiuixScaffold(
                         modifier = Modifier.fillMaxSize(),
                         topBar = {
@@ -100,6 +128,13 @@ fun AppScaffold(
                         Box(
                             modifier = Modifier
                                 .fillMaxSize()
+                                .then(
+                                    if (liquidGlassEnabled) {
+                                        Modifier.layerBackdrop(topBarContentBackdrop)
+                                    } else {
+                                        Modifier
+                                    }
+                                )
                                 .then(
                                     if (!disableHazeSource) Modifier.responsiveHazeSource(hazeState)
                                     else Modifier
@@ -122,7 +157,19 @@ fun AppScaffold(
 
             else -> {
                 Box(modifier = modifier.fillMaxSize()) {
-                    BackgroundImageContent(isDark = isDark, hazeState = hazeState)
+                    Box(
+                        modifier = Modifier
+                            .fillMaxSize()
+                            .then(
+                                if (liquidGlassEnabled) {
+                                    Modifier.layerBackdrop(topBarBackgroundBackdrop)
+                                } else {
+                                    Modifier
+                                }
+                            )
+                    ) {
+                        BackgroundImageContent(isDark = isDark, hazeState = hazeState)
+                    }
                     Scaffold(
                         modifier = Modifier.fillMaxSize(),
                         topBar = {
@@ -144,6 +191,13 @@ fun AppScaffold(
                         Box(
                             modifier = Modifier
                                 .fillMaxSize()
+                                .then(
+                                    if (liquidGlassEnabled) {
+                                        Modifier.layerBackdrop(topBarContentBackdrop)
+                                    } else {
+                                        Modifier
+                                    }
+                                )
                                 .then(
                                     if (!disableHazeSource) Modifier.responsiveHazeSource(hazeState)
                                     else Modifier

+ 2 - 1
app/src/main/java/io/legado/app/ui/widget/components/button/series/SeriesIconButton.kt

@@ -69,6 +69,7 @@ internal fun SeriesButton(
     onLongClick: (() -> Unit)? = null,
     size: DpSize? = null,
     enforceMinimumInteractiveSize: Boolean = true,
+    clipToShape: Boolean = true,
     shape: Shape = IconButtonDefaults.extraSmallRoundShape,
     style: SeriesIconButtonStyle = SeriesIconButtonStyle.Plain,
     contentColor: Color = LegadoTheme.colorScheme.onSurfaceVariant,
@@ -114,7 +115,7 @@ internal fun SeriesButton(
             .then(if (enforceMinimumInteractiveSize) Modifier.minimumInteractiveComponentSize() else Modifier)
             .then(modifier)
             .then(if (size != null) Modifier.size(size) else Modifier)
-            .clip(shape)
+            .then(if (clipToShape) Modifier.clip(shape) else Modifier)
             .background(containerColor, shape)
             .then(if (border != null) Modifier.border(border, shape) else Modifier)
             .combinedClickable(

+ 41 - 2
app/src/main/java/io/legado/app/ui/widget/components/lazylist/VerticalFastScroller.kt

@@ -34,6 +34,7 @@ import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.SubcomposeLayout
 import androidx.compose.ui.platform.LocalDensity
@@ -100,6 +101,18 @@ fun VerticalFastScroller(
             }
             val thumbActive = isThumbDragged || recentlyTouched
 
+            // The capsule fades out a moment after it has shrunk back to its small form,
+            // and stays/reappears as long as the list is being used (scrolled or thumb-dragged)
+            var capsuleVisible by remember { mutableStateOf(true) }
+            LaunchedEffect(isThumbDragged, listState.isScrollInProgress, recentlyTouched) {
+                if (isThumbDragged || listState.isScrollInProgress || recentlyTouched) {
+                    capsuleVisible = true
+                } else {
+                    delay(ThumbHideDelayMillis.milliseconds)
+                    capsuleVisible = false
+                }
+            }
+
             // listState.isScrollInProgress occasionally flickers
             val scrollStateTracker = remember { MutableData(listState.isScrollInProgress) }
             val stableScrollInProgress = scrollStateTracker.value || listState.isScrollInProgress
@@ -184,7 +197,12 @@ fun VerticalFastScroller(
                 animationSpec = tween(durationMillis = ThumbFormDurationMillis),
                 label = "thumbColor",
             )
-            val thumbDraggable = !listState.isScrollInProgress
+            val thumbVisibilityAlpha by animateFloatAsState(
+                targetValue = if (capsuleVisible) 1f else 0f,
+                animationSpec = tween(durationMillis = ThumbHideFadeMillis),
+                label = "thumbVisibility",
+            )
+            val thumbDraggable = !listState.isScrollInProgress && capsuleVisible
             val thumbWidth =
                 IdleThumbThickness + (ThumbThickness - IdleThumbThickness) * thumbFormProgress
             val thumbHeight = IdleThumbLength + (ThumbLength - IdleThumbLength) * thumbFormProgress
@@ -228,6 +246,7 @@ fun VerticalFastScroller(
                 Box(
                     modifier = Modifier
                         .size(thumbWidth, thumbHeight)
+                        .alpha(thumbVisibilityAlpha)
                         .background(
                             color = animatedThumbColor,
                             shape = RoundedCornerShape(thumbWidth / 2)
@@ -332,6 +351,18 @@ fun VerticalGridFastScroller(
             }
             val thumbActive = isThumbDragged || recentlyTouched
 
+            // The capsule fades out a moment after it has shrunk back to its small form,
+            // and stays/reappears as long as the list is being used (scrolled or thumb-dragged)
+            var capsuleVisible by remember { mutableStateOf(true) }
+            LaunchedEffect(isThumbDragged, state.isScrollInProgress, recentlyTouched) {
+                if (isThumbDragged || state.isScrollInProgress || recentlyTouched) {
+                    capsuleVisible = true
+                } else {
+                    delay(ThumbHideDelayMillis)
+                    capsuleVisible = false
+                }
+            }
+
             val thumbBottomPadding = with(LocalDensity.current) { bottomContentPadding.toPx() }
             val heightPx = contentHeight.toFloat() -
                     thumbTopPadding -
@@ -387,7 +418,12 @@ fun VerticalGridFastScroller(
                 animationSpec = tween(durationMillis = ThumbFormDurationMillis),
                 label = "thumbColor",
             )
-            val thumbDraggable = !state.isScrollInProgress
+            val thumbVisibilityAlpha by animateFloatAsState(
+                targetValue = if (capsuleVisible) 1f else 0f,
+                animationSpec = tween(durationMillis = ThumbHideFadeMillis),
+                label = "thumbVisibility",
+            )
+            val thumbDraggable = !state.isScrollInProgress && capsuleVisible
             val thumbWidth =
                 IdleThumbThickness + (ThumbThickness - IdleThumbThickness) * thumbFormProgress
             val thumbHeight = IdleThumbLength + (ThumbLength - IdleThumbLength) * thumbFormProgress
@@ -431,6 +467,7 @@ fun VerticalGridFastScroller(
                 Box(
                     modifier = Modifier
                         .size(thumbWidth, thumbHeight)
+                        .alpha(thumbVisibilityAlpha)
                         .background(
                             color = animatedThumbColor,
                             shape = RoundedCornerShape(thumbWidth / 2)
@@ -493,6 +530,8 @@ private val IdleThumbThickness = 4.dp
 private const val IdleThumbAlpha = 0.8f
 private const val ThumbFormDurationMillis = 250
 private const val ThumbActiveDurationMillis = 3000L
+private const val ThumbHideDelayMillis = 3000L
+private const val ThumbHideFadeMillis = 250
 
 private val LazyListItemInfo.top: Int
     get() = offset

+ 7 - 1
app/src/main/java/io/legado/app/ui/widget/components/topbar/GlassMediumFlexibleTopAppBar.kt

@@ -106,9 +106,15 @@ fun GlassMediumFlexibleTopAppBar(
                     title = title,
                     subtitle = subtitleText.orEmpty(),
                     navigationIcon = navigationIcon,
-                    actions = actions,
+                    actions = {
+                        TopBarActionsRow(
+                            modifier = Modifier.padding(end = miuixTopBarActionsEndPadding())
+                        ) { actions() }
+                    },
                     color = Color.Transparent,
                     defaultWindowInsetsPadding = false,
+                    navigationIconPadding = miuixTopBarSlotPadding(),
+                    actionIconPadding = miuixTopBarSlotPadding(),
                     scrollBehavior = (scrollBehavior as? MiuixGlassScrollBehavior)?.miuixBehavior
                 )
             }

+ 9 - 2
app/src/main/java/io/legado/app/ui/widget/components/topbar/GlassSmallTopAppBar.kt

@@ -3,6 +3,7 @@ package io.legado.app.ui.widget.components.topbar
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -48,8 +49,14 @@ fun GlassSmallTopAppBar(
         MiuixSmallTopAppBar(
             title = title,
             navigationIcon = navigationIcon,
-            actions = actions,
-            color = Color.Transparent
+            actions = {
+                TopBarActionsRow(
+                    modifier = Modifier.padding(end = miuixTopBarActionsEndPadding())
+                ) { actions() }
+            },
+            color = Color.Transparent,
+            navigationIconPadding = miuixTopBarSlotPadding(),
+            actionIconPadding = miuixTopBarSlotPadding(),
         )
     }
 }

+ 8 - 2
app/src/main/java/io/legado/app/ui/widget/components/topbar/GlassTopAppBar.kt

@@ -60,9 +60,15 @@ fun GlassTopAppBar(
                 modifier = Modifier.windowInsetsPadding(WindowInsets.statusBarsIgnoringVisibility),
                 title = title,
                 navigationIcon = navigationIcon,
-                actions = actions,
+                actions = {
+                    TopBarActionsRow(
+                        modifier = Modifier.padding(end = miuixTopBarActionsEndPadding())
+                    ) { actions() }
+                },
                 color = Color.Transparent,
-                defaultWindowInsetsPadding = false
+                defaultWindowInsetsPadding = false,
+                navigationIconPadding = miuixTopBarSlotPadding(),
+                actionIconPadding = miuixTopBarSlotPadding(),
             )
         } else {
             TopAppBar(

+ 68 - 21
app/src/main/java/io/legado/app/ui/widget/components/topbar/TopBarButton.kt

@@ -26,7 +26,6 @@ import androidx.compose.runtime.remember
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
@@ -55,12 +54,13 @@ import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
 import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
 import top.yukonga.miuix.kmp.basic.Text as MiuixText
 
-/** 顶栏按钮样式配置,与 M3 / 渐进模糊解耦。Glass 预留液态玻璃外观。 */
+/** 顶栏按钮样式配置。 */
 enum class TopBarButtonStyle(val storageValue: String) {
     Plain("plain"),
     Tonal("tonal"),
     Outlined("outlined"),
-    Glass("glass");
+    SemiTransparent("glass"),
+    LiquidGlass("liquid");
 
     companion object {
         fun fromStorage(value: String?): TopBarButtonStyle =
@@ -83,7 +83,8 @@ private fun currentTopBarButtonStyle(): TopBarButtonStyle =
 private val TopBarButtonStyle.seriesStyle: SeriesIconButtonStyle
     get() = when (this) {
         TopBarButtonStyle.Plain -> SeriesIconButtonStyle.Plain
-        TopBarButtonStyle.Tonal, TopBarButtonStyle.Glass -> SeriesIconButtonStyle.Tonal
+        TopBarButtonStyle.Tonal, TopBarButtonStyle.SemiTransparent,
+        TopBarButtonStyle.LiquidGlass -> SeriesIconButtonStyle.Tonal
         TopBarButtonStyle.Outlined -> SeriesIconButtonStyle.Outlined
     }
 
@@ -91,14 +92,16 @@ private val TopBarButtonStyle.seriesStyle: SeriesIconButtonStyle
 private val TopBarButtonStyle.buttonSize: DpSize
     get() = when (this) {
         TopBarButtonStyle.Plain -> MediumSeriesIconButtonSize
-        TopBarButtonStyle.Tonal, TopBarButtonStyle.Outlined, TopBarButtonStyle.Glass ->
+        TopBarButtonStyle.Tonal, TopBarButtonStyle.Outlined, TopBarButtonStyle.SemiTransparent,
+        TopBarButtonStyle.LiquidGlass ->
             TopBarSeriesIconButtonSize
     }
 
 private val TopBarButtonStyle.iconSize: Dp
     get() = when (this) {
         TopBarButtonStyle.Plain -> MediumSeriesIconSize
-        TopBarButtonStyle.Tonal, TopBarButtonStyle.Outlined, TopBarButtonStyle.Glass ->
+        TopBarButtonStyle.Tonal, TopBarButtonStyle.Outlined, TopBarButtonStyle.SemiTransparent,
+        TopBarButtonStyle.LiquidGlass ->
             TopBarSeriesIconSize
     }
 
@@ -112,6 +115,14 @@ internal fun topBarActionSpacing(): Dp {
     return if (style == TopBarButtonStyle.Plain) 4.dp else 8.dp
 }
 
+@Composable
+internal fun miuixTopBarSlotPadding(): Dp =
+    if (currentTopBarButtonStyle() == TopBarButtonStyle.Plain) 16.dp else 0.dp
+
+@Composable
+internal fun miuixTopBarActionsEndPadding(): Dp =
+    if (currentTopBarButtonStyle() == TopBarButtonStyle.Plain) 0.dp else 12.dp
+
 /** 合并模式下按钮左侧的竖向分隔线(首个按钮不画)。 */
 @Composable
 private fun Modifier.mergedDivider(): Modifier {
@@ -129,7 +140,7 @@ private fun Modifier.mergedDivider(): Modifier {
 /**
  * 顶栏 actions 的统一 Row。
  *
- * 开启「合并顶栏按钮」且样式为 Tonal/Outlined/Glass 时,把多个按钮的容器/边框
+ * 开启「合并顶栏按钮」且样式为 Tonal/Outlined/半透明/液态玻璃时,把多个按钮的容器/边框
  * 融合成一个胶囊,按钮间用竖向分隔线隔开(复用 [TopBarMergeCounter] 自动分配索引)。
  * 单个按钮时胶囊自然退化为普通按钮。Plain 无容器,始终走普通间距 Row。
  */
@@ -140,6 +151,8 @@ internal fun TopBarActionsRow(
 ) {
     val style = currentTopBarButtonStyle()
     val mergeEnabled = LocalAppUiConfiguration.current.theme.mergeTopBarActions
+    val liquidGlassEnabled = style == TopBarButtonStyle.LiquidGlass &&
+            topBarLiquidGlassEnabled()
     if (!mergeEnabled || style == TopBarButtonStyle.Plain) {
         Row(
             modifier = modifier,
@@ -155,14 +168,20 @@ internal fun TopBarActionsRow(
     val capsuleShape = RoundedCornerShape(50)
     val capsuleBg = when (style) {
         TopBarButtonStyle.Tonal -> LegadoTheme.colorScheme.surfaceContainerLow
-        TopBarButtonStyle.Glass -> GlassTopAppBarDefaults.controlContainerColor()
+        TopBarButtonStyle.SemiTransparent, TopBarButtonStyle.LiquidGlass ->
+            GlassTopAppBarDefaults.controlContainerColor()
         else -> Color.Transparent // Outlined
     }
     Box(
         modifier = modifier
             .height(TopBarSeriesIconButtonSize.height)
-            .clip(capsuleShape)
-            .background(capsuleBg, capsuleShape)
+            .then(
+                if (liquidGlassEnabled) {
+                    Modifier.topBarLiquidGlass(capsuleShape)
+                } else {
+                    Modifier.background(capsuleBg, capsuleShape)
+                }
+            )
             .then(
                 if (style == TopBarButtonStyle.Outlined) {
                     Modifier.border(1.dp, LegadoTheme.colorScheme.outlineVariant, capsuleShape)
@@ -189,6 +208,8 @@ private fun TopBarButton(
     style: TopBarButtonStyle = currentTopBarButtonStyle()
 ) {
     val mergeState = LocalTopBarMergeState.current
+    val liquidGlassEnabled = style == TopBarButtonStyle.LiquidGlass &&
+            topBarLiquidGlassEnabled()
     if (mergeState != null) {
         val index = mergeState.index
         mergeState.index = index + 1
@@ -212,15 +233,23 @@ private fun TopBarButton(
             )
         }
     } else {
-        val containerColor = if (style == TopBarButtonStyle.Glass) {
-            GlassTopAppBarDefaults.controlContainerColor()
-        } else {
-            null
+        val containerColor = when {
+            liquidGlassEnabled -> Color.Transparent
+            style == TopBarButtonStyle.SemiTransparent ||
+                    style == TopBarButtonStyle.LiquidGlass ->
+                GlassTopAppBarDefaults.controlContainerColor()
+
+            else -> null
         }
         SeriesButton(
             onClick = onClick,
-            modifier = modifier,
+            modifier = if (liquidGlassEnabled) {
+                modifier.topBarLiquidGlass(RoundedCornerShape(50))
+            } else {
+                modifier
+            },
             enforceMinimumInteractiveSize = false,
+            clipToShape = !liquidGlassEnabled,
             size = style.buttonSize,
             style = style.seriesStyle,
             contentColor = LegadoTheme.colorScheme.onSurface,
@@ -243,7 +272,9 @@ fun TopBarNavigationButton(
     imageVector: ImageVector = AppIcons.Back,
     contentDescription: String? = stringResource(id = R.string.back)
 ) {
-    if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
+    if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine) &&
+        currentTopBarButtonStyle() == TopBarButtonStyle.Plain
+    ) {
         MiuixIconButton(
             onClick = onClick,
             modifier = modifier
@@ -270,7 +301,9 @@ fun TopBarActionButton(
     contentDescription: String?,
     modifier: Modifier = Modifier
 ) {
-    if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
+    if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine) &&
+        currentTopBarButtonStyle() == TopBarButtonStyle.Plain
+    ) {
         MiuixIconButton(
             onClick = onClick,
             modifier = modifier,
@@ -300,7 +333,9 @@ fun TopBarAnimatedActionButton(
     inactiveText: String,
     modifier: Modifier = Modifier
 ) {
-    if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
+    if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine) &&
+        currentTopBarButtonStyle() == TopBarButtonStyle.Plain
+    ) {
         val contentColor by animateColorAsState(
             targetValue = if (checked) MiuixTheme.colorScheme.primary else MiuixTheme.colorScheme.onSurface,
             animationSpec = tween(150),
@@ -361,8 +396,14 @@ fun TopBarAnimatedActionButton(
         } else {
             -1
         }
-        val containerColor = if (mergeState == null && topBarStyle == TopBarButtonStyle.Glass) {
-            GlassTopAppBarDefaults.controlContainerColor()
+        val containerColor = if (
+            mergeState == null &&
+            (topBarStyle == TopBarButtonStyle.SemiTransparent ||
+                    topBarStyle == TopBarButtonStyle.LiquidGlass)
+        ) {
+            if (topBarStyle == TopBarButtonStyle.LiquidGlass && topBarLiquidGlassEnabled()) {
+                Color.Transparent
+            } else GlassTopAppBarDefaults.controlContainerColor()
         } else {
             null
         }
@@ -400,8 +441,14 @@ fun TopBarAnimatedActionButton(
                 } else {
                     SeriesButton(
                         onClick = { onToggle(!checked) },
-                        modifier = dividerModifier,
+                        modifier = if (topBarStyle == TopBarButtonStyle.LiquidGlass &&
+                            topBarLiquidGlassEnabled()
+                        ) {
+                            dividerModifier.topBarLiquidGlass(RoundedCornerShape(50))
+                        } else dividerModifier,
                         enforceMinimumInteractiveSize = false,
+                        clipToShape = !(topBarStyle == TopBarButtonStyle.LiquidGlass &&
+                                topBarLiquidGlassEnabled()),
                         selected = checked,
                         style = topBarStyle.seriesStyle,
                         contentColor = LegadoTheme.colorScheme.onSurface,

+ 81 - 0
app/src/main/java/io/legado/app/ui/widget/components/topbar/TopBarLiquidGlass.kt

@@ -0,0 +1,81 @@
+package io.legado.app.ui.widget.components.topbar
+
+import android.os.Build
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.dp
+import com.kyant.backdrop.drawBackdrop
+import com.kyant.backdrop.effects.blur
+import com.kyant.backdrop.effects.lens
+import com.kyant.backdrop.effects.vibrancy
+import com.kyant.backdrop.highlight.Highlight
+import com.kyant.backdrop.shadow.Shadow
+import io.legado.app.ui.animation.InteractiveHighlight
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.LocalTopBarBackdrop
+import kotlin.math.abs
+import kotlin.math.atan2
+import kotlin.math.cos
+import kotlin.math.sin
+import kotlin.math.tanh
+
+@Composable
+internal fun Modifier.topBarLiquidGlass(shape: Shape): Modifier {
+    val backdrop = LocalTopBarBackdrop.current ?: return this
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return this
+    val containerColor = LegadoTheme.colorScheme.surface.copy(
+        alpha = 0.5f
+    )
+    val shadowColor = Color.Black.copy(alpha = 0.04f)
+    val animationScope = rememberCoroutineScope()
+    val interactiveHighlight = remember(animationScope) { InteractiveHighlight(animationScope) }
+    return drawBackdrop(
+        backdrop = backdrop,
+        shape = { shape },
+        effects = {
+            vibrancy()
+            blur(12.dp.toPx())
+            lens(24.dp.toPx(), 24.dp.toPx())
+        },
+        highlight = { Highlight.Default },
+        shadow = {
+            Shadow(
+                radius = 12.dp,
+                color = shadowColor
+            )
+        },
+        layerBlock = {
+            val width = size.width
+            val height = size.height
+            if (width > 0f && height > 0f) {
+                val progress = interactiveHighlight.pressProgress
+                val scale = 1f + 4.dp.toPx() / height * progress
+                val maxOffset = size.minDimension
+                val dragOffset = interactiveHighlight.dragOffset
+                translationX = maxOffset * tanh(0.05f * dragOffset.x / maxOffset) * progress
+                translationY = maxOffset * tanh(0.05f * dragOffset.y / maxOffset) * progress
+                val maxDragScale = 4.dp.toPx() / height
+                val offsetAngle = atan2(dragOffset.y, dragOffset.x)
+                scaleX = scale + maxDragScale *
+                        abs(cos(offsetAngle) * dragOffset.x / size.maxDimension) *
+                        (width / height).coerceAtMost(1f) * progress
+                scaleY = scale + maxDragScale *
+                        abs(sin(offsetAngle) * dragOffset.y / size.maxDimension) *
+                        (height / width).coerceAtMost(1f) * progress
+            }
+        },
+        onDrawSurface = {
+            drawRect(containerColor)
+        },
+    )
+        .then(interactiveHighlight.modifier)
+        .then(interactiveHighlight.gestureModifier)
+}
+
+@Composable
+internal fun topBarLiquidGlassEnabled(): Boolean =
+    LocalTopBarBackdrop.current != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU

+ 3 - 1
app/src/main/res/values-zh-rCN/arrays.xml

@@ -227,7 +227,8 @@
         <item>普通</item>
         <item>填充</item>
         <item>描边</item>
-        <item>玻璃</item>
+        <item>半透明</item>
+        <item>液态玻璃</item>
     </string-array>
 
     <string-array name="top_bar_button_style_value">
@@ -235,6 +236,7 @@
         <item>tonal</item>
         <item>outlined</item>
         <item>glass</item>
+        <item>liquid</item>
     </string-array>
 
     <string-array name="title_bar_mode">

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

@@ -41,6 +41,9 @@
     <string name="appearance_setting">外观设置</string>
     <string name="click_to_select">点击选择</string>
     <string name="compose_related">Compose 相关</string>
+    <string name="blur_effects">模糊效果</string>
+    <string name="top_bottom_bar_settings">顶栏与底栏</string>
+    <string name="top_bottom_bar_settings_summary">配置顶栏按钮与底部导航栏</string>
     <string name="use_flexible_top_bar">使用折叠应用栏</string>
     <string name="slider">滑块</string>
     <string name="input">输入</string>

+ 3 - 0
app/src/main/res/values-zh-rHK/strings.xml

@@ -1694,6 +1694,9 @@
     <string name="complete_required_information">填寫所有必填資訊</string>
     <string name="compose_engine">主題樣式</string>
     <string name="compose_related">Compose 相關</string>
+    <string name="blur_effects">模糊效果</string>
+    <string name="top_bottom_bar_settings">頂欄與底欄</string>
+    <string name="top_bottom_bar_settings_summary">設定頂欄按鈕與底部導航欄</string>
     <string name="config_btn">配置底部按鈕</string>
     <string name="more_actions_config">設定更多選單</string>
     <string name="confirm_skip_to_chapter">跳轉到此章節?</string>

+ 3 - 0
app/src/main/res/values-zh-rTW/strings.xml

@@ -1497,6 +1497,9 @@
     <string name="custom_theme_colors">自訂顏色</string>
     <string name="click_to_select">點擊選擇</string>
     <string name="compose_related">Compose 相關</string>
+    <string name="blur_effects">模糊效果</string>
+    <string name="top_bottom_bar_settings">頂欄與底欄</string>
+    <string name="top_bottom_bar_settings_summary">設定頂欄按鈕與底部導覽列</string>
     <string name="use_flexible_top_bar">使用彈性頂部工具列</string>
     <string name="slider">滑桿</string>
     <string name="input">輸入</string>

+ 3 - 1
app/src/main/res/values/arrays.xml

@@ -230,7 +230,8 @@
         <item>Plain</item>
         <item>Tonal</item>
         <item>Outlined</item>
-        <item>Glass</item>
+        <item>Semi-transparent</item>
+        <item>Liquid Glass</item>
     </string-array>
 
     <string-array name="top_bar_button_style_value">
@@ -238,6 +239,7 @@
         <item>tonal</item>
         <item>outlined</item>
         <item>glass</item>
+        <item>liquid</item>
     </string-array>
 
     <string-array name="paletteStyle">

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

@@ -42,6 +42,9 @@
     <string name="appearance_setting">Appearance Settings</string>
     <string name="click_to_select">Click to select</string>
     <string name="compose_related">Compose Related</string>
+    <string name="blur_effects">Blur Effects</string>
+    <string name="top_bottom_bar_settings">Top &amp; Bottom Bars</string>
+    <string name="top_bottom_bar_settings_summary">Configure top-bar buttons and bottom navigation</string>
     <string name="use_flexible_top_bar">Use Flexible App Top Bar</string>
     <string name="slider">Slider</string>
     <string name="input">Input</string>