HapeLee 3 месяцев назад
Родитель
Сommit
46c91fbe8c

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

@@ -220,6 +220,8 @@ object PreferKey {
     const val bottomBarBlurAlpha = "bottomBarBlurAlpha"
     const val bottomBarLensRadius = "bottomBarLensRadius"
     const val useFlexibleTopAppBar = "useFlexibleTopAppBar"
+    const val topBarButtonStyle = "topBarButtonStyle"
+    const val mergeTopBarActions = "mergeTopBarActions"
     const val bookInfoFollowCoverColor = "bookInfoFollowCoverColor"
     const val bookInfoBackgroundBlur = "bookInfoBackgroundBlur"
     const val bookInfoNetworkCoverBackground = "bookInfoNetworkCoverBackground"

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

@@ -385,6 +385,8 @@ internal fun Preferences.toThemeSettings(): ThemeSettings = ThemeSettings(
     bottomBarBlurAlpha = compatDsInt(PreferKey.bottomBarBlurAlpha) ?: 40,
     bottomBarLensRadius = compatDsFloat(PreferKey.bottomBarLensRadius) ?: 24f,
     useFlexibleTopAppBar = compatDsBoolean(PreferKey.useFlexibleTopAppBar) ?: true,
+    topBarButtonStyle = compatDsString(PreferKey.topBarButtonStyle) ?: "tonal",
+    mergeTopBarActions = compatDsBoolean(PreferKey.mergeTopBarActions) ?: false,
     bookInfoFollowCoverColor = compatDsBoolean(PreferKey.bookInfoFollowCoverColor) ?: true,
     bookInfoNetworkCoverBackground =
         compatDsString(PreferKey.bookInfoNetworkCoverBackground) ?: "on",
@@ -464,6 +466,8 @@ internal fun ThemeSettings.toGatewayPrefMap(): Map<String, Any?> = mapOf(
     PreferKey.bottomBarBlurAlpha to bottomBarBlurAlpha,
     PreferKey.bottomBarLensRadius to bottomBarLensRadius,
     PreferKey.useFlexibleTopAppBar to useFlexibleTopAppBar,
+    PreferKey.topBarButtonStyle to topBarButtonStyle,
+    PreferKey.mergeTopBarActions to mergeTopBarActions,
     PreferKey.bookInfoFollowCoverColor to bookInfoFollowCoverColor,
     PreferKey.bookInfoNetworkCoverBackground to bookInfoNetworkCoverBackground,
     PreferKey.bookInfoDefaultCoverBackground to bookInfoDefaultCoverBackground,

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

@@ -96,6 +96,8 @@ class ThemePackageSettingsRepository : ThemePackageSettingsGateway {
         navIconMy = string(PreferKey.navIconMy, ""),
         useMiuixMonet = boolean(PreferKey.useMiuixMonet, false),
         useFlexibleTopAppBar = boolean(PreferKey.useFlexibleTopAppBar, true),
+        topBarButtonStyle = string(PreferKey.topBarButtonStyle, "tonal"),
+        mergeTopBarActions = boolean(PreferKey.mergeTopBarActions, false),
         bgImageLight = AppConfigStore.getString(PreferKey.bgImage),
         bgImageDark = AppConfigStore.getString(PreferKey.bgImageN),
         bgImageBlurring = int(PreferKey.bgImageBlurring, 0),
@@ -225,6 +227,8 @@ internal fun ThemeExportData.toPreferenceValues(): Map<String, Any?> {
                 PreferKey.navIconMy to data.navIconMy,
                 PreferKey.useMiuixMonet to data.useMiuixMonet,
                 PreferKey.useFlexibleTopAppBar to data.useFlexibleTopAppBar,
+        PreferKey.topBarButtonStyle to data.topBarButtonStyle,
+        PreferKey.mergeTopBarActions to data.mergeTopBarActions,
                 PreferKey.bgImage to data.bgImageLight,
                 PreferKey.bgImageN to data.bgImageDark,
                 PreferKey.bgImageBlurring to data.bgImageBlurring,

+ 2 - 0
app/src/main/java/io/legado/app/domain/model/settings/ThemeExportData.kt

@@ -79,6 +79,8 @@ data class ThemeExportData(
     val navIconMy: String = "",
     val useMiuixMonet: Boolean = false,
     val useFlexibleTopAppBar: Boolean = true,
+    val topBarButtonStyle: String = "tonal",
+    val mergeTopBarActions: Boolean = false,
     val bgImageLight: String? = null,
     val bgImageDark: String? = null,
     val bgImageBlurring: Int = 0,

+ 2 - 0
app/src/main/java/io/legado/app/domain/model/settings/ThemeSettings.kt

@@ -42,6 +42,8 @@ data class ThemeSettings(
     val bottomBarBlurAlpha: Int = 40,
     val bottomBarLensRadius: Float = 24f,
     val useFlexibleTopAppBar: Boolean = true,
+    val topBarButtonStyle: String = "tonal",
+    val mergeTopBarActions: Boolean = false,
     val bookInfoFollowCoverColor: Boolean = true,
     val bookInfoNetworkCoverBackground: String = "on",
     val bookInfoDefaultCoverBackground: String = "on",

+ 2 - 0
app/src/main/java/io/legado/app/help/storage/BackupConfig.kt

@@ -276,6 +276,8 @@ object BackupConfig {
         PreferKey.bottomBarBlurAlpha,
         PreferKey.bottomBarLensRadius,
         PreferKey.useFlexibleTopAppBar,
+        PreferKey.topBarButtonStyle,
+        PreferKey.mergeTopBarActions,
         PreferKey.bookInfoFollowCoverColor,
         PreferKey.bookInfoBackgroundBlur,
         PreferKey.bookInfoNetworkCoverBackground,

+ 18 - 0
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt

@@ -532,6 +532,24 @@ fun ThemeConfigScreen(
                                 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) }
+                                }
+                            )
+                        }
                     }
                     SwitchSettingItem(
                         title = stringResource(R.string.is_blur_enable),

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

@@ -49,6 +49,8 @@ internal val SeriesIconSize: Dp
     get() = IconButtonDefaults.mediumIconSize
 internal val MediumSeriesIconButtonSize = DpSize(40.dp, 40.dp)
 internal val MediumSeriesIconSize = SeriesIconSize
+internal val TopBarSeriesIconButtonSize = DpSize(36.dp, 36.dp)
+internal val TopBarSeriesIconSize = 20.dp
 internal val SmallButtonShape = RoundedCornerShape(50)
 
 internal enum class SeriesIconButtonStyle {

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

@@ -1,11 +1,9 @@
 package io.legado.app.ui.widget.components.topbar
 
 import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.WindowInsets
@@ -135,9 +133,7 @@ fun GlassMediumFlexibleTopAppBar(
                         navigationIcon = navigationIcon,
                         actions = {
                             Box(modifier = Modifier.padding(end = 12.dp)) {
-                                Row(
-                                    horizontalArrangement = Arrangement.spacedBy(8.dp)
-                                ) { actions() }
+                                TopBarActionsRow { actions() }
                             }
                         },
                         scrollBehavior = (scrollBehavior as? M3GlassScrollBehavior)?.m3Behavior,
@@ -168,11 +164,7 @@ fun GlassMediumFlexibleTopAppBar(
                         navigationIcon = navigationIcon,
                         actions = {
                             Box(modifier = Modifier.padding(end = 12.dp)) {
-                                Row(
-                                    horizontalArrangement = Arrangement.spacedBy(8.dp)
-                                ) {
-                                    actions()
-                                }
+                                TopBarActionsRow { actions() }
                             }
                         },
                         scrollBehavior = (scrollBehavior as? M3GlassScrollBehavior)?.m3Behavior,

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

@@ -72,7 +72,7 @@ fun GlassTopAppBar(
                     )
                 },
                 navigationIcon = navigationIcon,
-                actions = actions,
+                actions = { TopBarActionsRow { actions() } },
                 colors = TopAppBarDefaults.topAppBarColors(
                     containerColor = Color.Transparent,
                     scrolledContainerColor = Color.Transparent

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

@@ -3,60 +3,236 @@ package io.legado.app.ui.widget.components.topbar
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.filled.ArrowBack
-import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.FilledTonalIconButton
-import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.material3.Text
-import androidx.compose.material3.ToggleButton
+import androidx.compose.material3.ripple
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
+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
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
-import io.legado.app.ui.theme.LocalAppUiConfiguration
 import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.LocalAppUiConfiguration
 import io.legado.app.ui.theme.ThemeResolver
 import io.legado.app.ui.widget.components.button.series.AnimatedActionButtonCore
 import io.legado.app.ui.widget.components.button.series.AnimatedIcon
-import io.legado.app.ui.widget.components.button.series.MediumPlainButton
+import io.legado.app.ui.widget.components.button.series.MediumSeriesIconButtonSize
+import io.legado.app.ui.widget.components.button.series.MediumSeriesIconSize
+import io.legado.app.ui.widget.components.button.series.SeriesButton
+import io.legado.app.ui.widget.components.button.series.SeriesIconButtonStyle
+import io.legado.app.ui.widget.components.button.series.TopBarSeriesIconButtonSize
+import io.legado.app.ui.widget.components.button.series.TopBarSeriesIconSize
+import io.legado.app.ui.widget.components.icon.AppIcon
 import io.legado.app.ui.widget.components.icon.AppIcons
 import top.yukonga.miuix.kmp.theme.MiuixTheme
 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");
+
+    companion object {
+        fun fromStorage(value: String?): TopBarButtonStyle =
+            entries.firstOrNull { it.storageValue == value } ?: Tonal
+    }
+}
+
+/** 合并模式下的共享计数器,记录下一个按钮的索引。 */
+internal class TopBarMergeCounter {
+    var index = 0
+}
+
+/** 合并模式的共享状态;null 表示未处于合并模式。 */
+internal val LocalTopBarMergeState = staticCompositionLocalOf<TopBarMergeCounter?> { null }
+
+@Composable
+private fun currentTopBarButtonStyle(): TopBarButtonStyle =
+    TopBarButtonStyle.fromStorage(LocalAppUiConfiguration.current.theme.topBarButtonStyle)
+
+private val TopBarButtonStyle.seriesStyle: SeriesIconButtonStyle
+    get() = when (this) {
+        TopBarButtonStyle.Plain -> SeriesIconButtonStyle.Plain
+        TopBarButtonStyle.Tonal, TopBarButtonStyle.Glass -> SeriesIconButtonStyle.Tonal
+        TopBarButtonStyle.Outlined -> SeriesIconButtonStyle.Outlined
+    }
+
+/** Plain 无容器背景,图标可以更大(40dp/24dp);带容器/边框的样式用紧凑的 36dp/20dp。 */
+private val TopBarButtonStyle.buttonSize: DpSize
+    get() = when (this) {
+        TopBarButtonStyle.Plain -> MediumSeriesIconButtonSize
+        TopBarButtonStyle.Tonal, TopBarButtonStyle.Outlined, TopBarButtonStyle.Glass ->
+            TopBarSeriesIconButtonSize
+    }
+
+private val TopBarButtonStyle.iconSize: Dp
+    get() = when (this) {
+        TopBarButtonStyle.Plain -> MediumSeriesIconSize
+        TopBarButtonStyle.Tonal, TopBarButtonStyle.Outlined, TopBarButtonStyle.Glass ->
+            TopBarSeriesIconSize
+    }
+
+/**
+ * 顶栏 actions 槽按钮间距:Plain 无边框/容器,比带容器/边框的样式更紧凑,
+ * 否则同样的间距会让裸图标显得更分散。
+ */
+@Composable
+internal fun topBarActionSpacing(): Dp {
+    val style = currentTopBarButtonStyle()
+    return if (style == TopBarButtonStyle.Plain) 4.dp else 8.dp
+}
+
+/** 合并模式下按钮左侧的竖向分隔线(首个按钮不画)。 */
+@Composable
+private fun Modifier.mergedDivider(): Modifier {
+    val dividerColor = LegadoTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.15f)
+    return drawBehind {
+        drawLine(
+            color = dividerColor,
+            start = Offset(0f, size.height * 0.3f),
+            end = Offset(0f, size.height * 0.7f),
+            strokeWidth = 1.dp.toPx()
+        )
+    }
+}
+
+/**
+ * 顶栏 actions 的统一 Row。
+ *
+ * 开启「合并顶栏按钮」且样式为 Tonal/Outlined/Glass 时,把多个按钮的容器/边框
+ * 融合成一个胶囊,按钮间用竖向分隔线隔开(复用 [TopBarMergeCounter] 自动分配索引)。
+ * 单个按钮时胶囊自然退化为普通按钮。Plain 无容器,始终走普通间距 Row。
+ */
+@Composable
+internal fun TopBarActionsRow(
+    modifier: Modifier = Modifier,
+    content: @Composable RowScope.() -> Unit
+) {
+    val style = currentTopBarButtonStyle()
+    val mergeEnabled = LocalAppUiConfiguration.current.theme.mergeTopBarActions
+    if (!mergeEnabled || style == TopBarButtonStyle.Plain) {
+        Row(
+            modifier = modifier,
+            horizontalArrangement = Arrangement.spacedBy(topBarActionSpacing()),
+            verticalAlignment = Alignment.CenterVertically,
+            content = content
+        )
+        return
+    }
+
+    val counter = remember { TopBarMergeCounter() }
+    counter.index = 0
+    val capsuleShape = RoundedCornerShape(50)
+    val capsuleBg = when (style) {
+        TopBarButtonStyle.Tonal -> LegadoTheme.colorScheme.surfaceContainerLow
+        TopBarButtonStyle.Glass -> GlassTopAppBarDefaults.controlContainerColor()
+        else -> Color.Transparent // Outlined
+    }
+    Box(
+        modifier = modifier
+            .height(TopBarSeriesIconButtonSize.height)
+            .clip(capsuleShape)
+            .background(capsuleBg, capsuleShape)
+            .then(
+                if (style == TopBarButtonStyle.Outlined) {
+                    Modifier.border(1.dp, LegadoTheme.colorScheme.outlineVariant, capsuleShape)
+                } else {
+                    Modifier
+                }
+            )
+    ) {
+        CompositionLocalProvider(LocalTopBarMergeState provides counter) {
+            Row(
+                verticalAlignment = Alignment.CenterVertically,
+                content = content
+            )
+        }
+    }
+}
+
 @Composable
 private fun TopBarButton(
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
     imageVector: ImageVector = Icons.AutoMirrored.Filled.ArrowBack,
-    contentDescription: String? = null
+    contentDescription: String? = null,
+    style: TopBarButtonStyle = currentTopBarButtonStyle()
 ) {
-    FilledTonalIconButton(
-        onClick = onClick,
-        modifier = modifier.size(36.dp),
-        colors = IconButtonDefaults.filledTonalIconButtonColors(
-            containerColor = GlassTopAppBarDefaults.controlContainerColor(),
-            contentColor = LegadoTheme.colorScheme.onSurface
-        )
-    ) {
-        AnimatedIcon(
-            modifier = Modifier.size(20.dp),
-            imageVector = imageVector,
-            contentDescription = contentDescription
-        )
+    val mergeState = LocalTopBarMergeState.current
+    if (mergeState != null) {
+        val index = mergeState.index
+        mergeState.index = index + 1
+        Box(
+            contentAlignment = Alignment.Center,
+            modifier = modifier
+                .size(style.buttonSize)
+                .then(if (index > 0) Modifier.mergedDivider() else Modifier)
+                .clickable(
+                    interactionSource = remember { MutableInteractionSource() },
+                    indication = ripple(bounded = true),
+                    role = Role.Button,
+                    onClick = onClick
+                )
+        ) {
+            AppIcon(
+                imageVector = imageVector,
+                contentDescription = contentDescription,
+                tint = LegadoTheme.colorScheme.onSurface,
+                modifier = Modifier.size(style.iconSize)
+            )
+        }
+    } else {
+        val containerColor = if (style == TopBarButtonStyle.Glass) {
+            GlassTopAppBarDefaults.controlContainerColor()
+        } else {
+            null
+        }
+        SeriesButton(
+            onClick = onClick,
+            modifier = modifier,
+            enforceMinimumInteractiveSize = false,
+            size = style.buttonSize,
+            style = style.seriesStyle,
+            contentColor = LegadoTheme.colorScheme.onSurface,
+            containerColor = containerColor
+        ) { resolvedContentColor ->
+            AppIcon(
+                imageVector = imageVector,
+                contentDescription = contentDescription,
+                tint = resolvedContentColor,
+                modifier = Modifier.size(style.iconSize)
+            )
+        }
     }
 }
 
@@ -94,8 +270,6 @@ fun TopBarActionButton(
     contentDescription: String?,
     modifier: Modifier = Modifier
 ) {
-    val enableProgressive =
-        LocalAppUiConfiguration.current.theme.enableProgressiveBlur
     if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
         MiuixIconButton(
             onClick = onClick,
@@ -107,25 +281,15 @@ fun TopBarActionButton(
             )
         }
     } else {
-        if (enableProgressive) {
-            TopBarButton(
-                onClick = onClick,
-                imageVector = imageVector,
-                contentDescription = contentDescription,
-                modifier = modifier
-            )
-        } else {
-            MediumPlainButton(
-                onClick = onClick,
-                modifier = modifier,
-                icon = imageVector,
-                contentDescription = contentDescription,
-            )
-        }
+        TopBarButton(
+            onClick = onClick,
+            imageVector = imageVector,
+            contentDescription = contentDescription,
+            modifier = modifier
+        )
     }
 }
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun TopBarAnimatedActionButton(
     checked: Boolean,
@@ -188,6 +352,20 @@ fun TopBarAnimatedActionButton(
             }
         )
     } else {
+        val topBarStyle = currentTopBarButtonStyle()
+        val mergeState = LocalTopBarMergeState.current
+        val mergeIndex = if (mergeState != null) {
+            val index = mergeState.index
+            mergeState.index = index + 1
+            index
+        } else {
+            -1
+        }
+        val containerColor = if (mergeState == null && topBarStyle == TopBarButtonStyle.Glass) {
+            GlassTopAppBarDefaults.controlContainerColor()
+        } else {
+            null
+        }
         AnimatedActionButtonCore(
             checked = checked,
             onCheckedChange = onCheckedChange,
@@ -200,17 +378,42 @@ fun TopBarAnimatedActionButton(
             textStyle = LegadoTheme.typography.labelMedium,
             textStartPadding = 8.dp,
             button = { buttonModifier, onToggle, content ->
-                ToggleButton(
-                    modifier = buttonModifier,
-                    contentPadding = PaddingValues(horizontal = 8.dp),
-                    checked = checked,
-                    onCheckedChange = onToggle
-                ) {
-                    Row(
-                        verticalAlignment = Alignment.CenterVertically,
-                        horizontalArrangement = Arrangement.Center,
-                        content = content
-                    )
+                val dividerModifier = buttonModifier
+                    .then(if (mergeIndex > 0) Modifier.mergedDivider() else Modifier)
+                if (mergeIndex >= 0) {
+                    Box(
+                        contentAlignment = Alignment.Center,
+                        modifier = dividerModifier.clickable(
+                            interactionSource = remember { MutableInteractionSource() },
+                            indication = ripple(bounded = true),
+                            role = Role.Button,
+                            onClick = { onToggle(!checked) }
+                        )
+                    ) {
+                        Row(
+                            verticalAlignment = Alignment.CenterVertically,
+                            horizontalArrangement = Arrangement.Center,
+                            modifier = Modifier.padding(horizontal = 8.dp),
+                            content = content
+                        )
+                    }
+                } else {
+                    SeriesButton(
+                        onClick = { onToggle(!checked) },
+                        modifier = dividerModifier,
+                        enforceMinimumInteractiveSize = false,
+                        selected = checked,
+                        style = topBarStyle.seriesStyle,
+                        contentColor = LegadoTheme.colorScheme.onSurface,
+                        containerColor = containerColor
+                    ) {
+                        Row(
+                            verticalAlignment = Alignment.CenterVertically,
+                            horizontalArrangement = Arrangement.Center,
+                            modifier = Modifier.padding(horizontal = 8.dp),
+                            content = content
+                        )
+                    }
                 }
             },
             icon = { imageVector, iconModifier, _ ->

+ 14 - 0
app/src/main/res/values-zh-rCN/arrays.xml

@@ -223,6 +223,20 @@
         <item>双击</item>
     </string-array>
 
+    <string-array name="top_bar_button_style">
+        <item>普通</item>
+        <item>填充</item>
+        <item>描边</item>
+        <item>玻璃</item>
+    </string-array>
+
+    <string-array name="top_bar_button_style_value">
+        <item>plain</item>
+        <item>tonal</item>
+        <item>outlined</item>
+        <item>glass</item>
+    </string-array>
+
     <string-array name="title_bar_mode">
         <item>在应用栏上显示</item>
         <item>在独立行上显示</item>

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

@@ -1807,6 +1807,8 @@
     <string name="click_image_way">点击图片行为</string>
     <string name="show_web_log">输出日志</string>
     <string name="is_blur_progressive_enable">启用渐变模糊</string>
+    <string name="top_bar_button_style">顶栏按钮样式</string>
+    <string name="merge_top_bar_actions">合并顶栏按钮</string>
     <string name="enable_select_vibrator">选择文本时震动</string>
     <string name="re_sync_webdav">重新同步远程书籍</string>
     <string name="underline_padding">离字间距</string>

+ 14 - 0
app/src/main/res/values/arrays.xml

@@ -226,6 +226,20 @@
 		<item>Off</item>
 	</string-array>
 
+    <string-array name="top_bar_button_style">
+        <item>Plain</item>
+        <item>Tonal</item>
+        <item>Outlined</item>
+        <item>Glass</item>
+    </string-array>
+
+    <string-array name="top_bar_button_style_value">
+        <item>plain</item>
+        <item>tonal</item>
+        <item>outlined</item>
+        <item>glass</item>
+    </string-array>
+
     <string-array name="paletteStyle">
         <item>TonalSpot</item>
         <item>Neutral</item>

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

@@ -1864,6 +1864,8 @@
     <string name="click_image_way">Image Tap Action</string>
     <string name="show_web_log">Output Logs</string>
     <string name="is_blur_progressive_enable">Enable Progressive Blur</string>
+    <string name="top_bar_button_style">Top Bar Button Style</string>
+    <string name="merge_top_bar_actions">Merge Top Bar Buttons</string>
     <string name="enable_select_vibrator">Vibrate When Selecting Text</string>
     <string name="re_sync_webdav">Resync Remote Books</string>
     <string name="underline_padding">Underline Gap</string>

+ 7 - 3
app/src/test/java/io/legado/app/data/repository/ThemeSettingsMappingTest.kt

@@ -27,18 +27,18 @@ class ThemeSettingsMappingTest {
     }
 
     @Test
-    fun `Theme gateway 持久化边界固定为 67 键`() {
+    fun `Theme gateway 持久化边界固定为 69 键`() {
         val actualKeys = ThemeSettings().toGatewayPrefMap().keys
         val expectedKeys = ThemeSettings().expectedGatewayPrefMap().keys
 
-        assertEquals(67, actualKeys.size)
+        assertEquals(69, actualKeys.size)
         assertEquals(expectedKeys, actualKeys)
         assertFalse(PreferKey.customMode in actualKeys)
         assertFalse(PreferKey.bookInfoInputColor in actualKeys)
     }
 
     @Test
-    fun `Theme gateway 67 键写读映射逐字段对应`() {
+    fun `Theme gateway 69 键写读映射逐字段对应`() {
         themeMappingSamples().forEach { expected ->
             assertEquals(expected.expectedGatewayPrefMap(), expected.toGatewayPrefMap())
             assertEquals(
@@ -186,6 +186,8 @@ private fun themeMappingSamples(): List<ThemeSettings> {
         bottomBarBlurAlpha = 123,
         bottomBarLensRadius = 3.75f,
         useFlexibleTopAppBar = false,
+        topBarButtonStyle = "glass",
+        mergeTopBarActions = true,
         bookInfoFollowCoverColor = false,
         bookInfoNetworkCoverBackground = "network-background",
         bookInfoDefaultCoverBackground = "default-background",
@@ -277,6 +279,8 @@ private fun ThemeSettings.expectedGatewayPrefMap(): Map<String, Any?> = mapOf(
     PreferKey.bottomBarBlurAlpha to bottomBarBlurAlpha,
     PreferKey.bottomBarLensRadius to bottomBarLensRadius,
     PreferKey.useFlexibleTopAppBar to useFlexibleTopAppBar,
+    PreferKey.topBarButtonStyle to topBarButtonStyle,
+    PreferKey.mergeTopBarActions to mergeTopBarActions,
     PreferKey.bookInfoFollowCoverColor to bookInfoFollowCoverColor,
     PreferKey.bookInfoNetworkCoverBackground to bookInfoNetworkCoverBackground,
     PreferKey.bookInfoDefaultCoverBackground to bookInfoDefaultCoverBackground,