Sfoglia il codice sorgente

refactor: 迁移 AppShell 设置网关

5151561 3 mesi fa
parent
commit
ed2f064452

+ 32 - 46
app/src/main/java/io/legado/app/data/repository/FeatureSettingsRepositories.kt

@@ -3,10 +3,7 @@ package io.legado.app.data.repository
 import androidx.datastore.preferences.core.Preferences
 import io.legado.app.BuildConfig
 import io.legado.app.constant.PreferKey
-import io.legado.app.domain.gateway.AppShellBooleanSetting
 import io.legado.app.domain.gateway.AppShellSettingsGateway
-import io.legado.app.domain.gateway.AppShellSettingsUpdate
-import io.legado.app.domain.gateway.AppShellStringSetting
 import io.legado.app.domain.gateway.BackupSettingsGateway
 import io.legado.app.domain.gateway.CoverSettingsGateway
 import io.legado.app.domain.gateway.DownloadCacheSettingsGateway
@@ -46,49 +43,12 @@ class AppShellSettingsRepository : AppShellSettingsGateway {
         .map(Preferences::toAppShellSettings)
         .distinctUntilChanged()
 
-    override suspend fun update(update: AppShellSettingsUpdate) = updateAll(listOf(update))
-
-    override suspend fun updateAll(updates: List<AppShellSettingsUpdate>) {
-        val values = updates.associate { update ->
-            val (key, value) = when (update) {
-                is AppShellSettingsUpdate.ThemeMode -> PreferKey.themeMode to update.value
-                is AppShellSettingsUpdate.FontScale -> PreferKey.fontScale to update.value
-                is AppShellSettingsUpdate.ComposeEngine -> PreferKey.composeEngine to update.value
-                is AppShellSettingsUpdate.MainNavigationOrder ->
-                    PreferKey.mainNavigationOrder to update.value
-                is AppShellSettingsUpdate.BooleanValue -> when (update.setting) {
-                    AppShellBooleanSetting.ShowHome -> PreferKey.showHome to update.value
-                    AppShellBooleanSetting.ShowDiscovery -> PreferKey.showDiscovery to update.value
-                    AppShellBooleanSetting.ShowRss -> PreferKey.showRss to update.value
-                    AppShellBooleanSetting.ShowStatusBar -> PreferKey.showStatusBar to update.value
-                    AppShellBooleanSetting.SwipeAnimation -> PreferKey.swipeAnimation to update.value
-                    AppShellBooleanSetting.PredictiveBack ->
-                        PreferKey.isPredictiveBackEnabled to update.value
-                    AppShellBooleanSetting.ShowBottomView -> PreferKey.showBottomView to update.value
-                    AppShellBooleanSetting.UseFloatingBottomBar ->
-                        PreferKey.useFloatingBottomBar to update.value
-                    AppShellBooleanSetting.UseFloatingBottomBarLiquidGlass ->
-                        PreferKey.useFloatingBottomBarLiquidGlass to update.value
-                    AppShellBooleanSetting.NavExtended -> PreferKey.navExtended to update.value
-                }
-                is AppShellSettingsUpdate.StringValue -> when (update.setting) {
-                    AppShellStringSetting.TabletInterface ->
-                        PreferKey.tabletInterface to update.value
-                    AppShellStringSetting.LabelVisibilityMode ->
-                        PreferKey.labelVisibilityMode to update.value
-                    AppShellStringSetting.DefaultHomePage ->
-                        PreferKey.defaultHomePage to update.value
-                    AppShellStringSetting.NavIconHome -> PreferKey.navIconHome to update.value
-                    AppShellStringSetting.NavIconBookshelf -> PreferKey.navIconBookshelf to update.value
-                    AppShellStringSetting.NavIconExplore -> PreferKey.navIconExplore to update.value
-                    AppShellStringSetting.NavIconRss -> PreferKey.navIconRss to update.value
-                    AppShellStringSetting.NavIconMy -> PreferKey.navIconMy to update.value
-                    AppShellStringSetting.LauncherIcon -> PreferKey.launcherIcon to update.value
-                }
-            }
-            key to value
-        }
-        AppConfigStore.putAll(values)
+    override suspend fun update(transform: (AppShellSettings) -> AppShellSettings) {
+        AppConfigStore.atomicUpdate(
+            read = Preferences::toAppShellSettings,
+            toPrefMap = AppShellSettings::toPrefMap,
+            transform = transform,
+        )
     }
 }
 
@@ -433,6 +393,32 @@ internal fun Preferences.toAppShellSettings(): AppShellSettings = AppShellSettin
     launcherIcon = compatDsString(PreferKey.launcherIcon) ?: "ic_launcher",
 )
 
+internal fun AppShellSettings.toPrefMap(): Map<String, Any?> = mapOf(
+    PreferKey.themeMode to themeMode,
+    PreferKey.fontScale to fontScale,
+    PreferKey.composeEngine to composeEngine,
+    PreferKey.showHome to showHome,
+    PreferKey.showDiscovery to showDiscovery,
+    PreferKey.showRss to showRss,
+    PreferKey.showStatusBar to showStatusBar,
+    PreferKey.swipeAnimation to swipeAnimation,
+    PreferKey.isPredictiveBackEnabled to predictiveBackEnabled,
+    PreferKey.showBottomView to showBottomView,
+    PreferKey.useFloatingBottomBar to useFloatingBottomBar,
+    PreferKey.useFloatingBottomBarLiquidGlass to useFloatingBottomBarLiquidGlass,
+    PreferKey.tabletInterface to tabletInterface,
+    PreferKey.labelVisibilityMode to labelVisibilityMode,
+    PreferKey.defaultHomePage to defaultHomePage,
+    PreferKey.mainNavigationOrder to mainNavigationOrder,
+    PreferKey.navExtended to navExtended,
+    PreferKey.navIconHome to navIconHome,
+    PreferKey.navIconBookshelf to navIconBookshelf,
+    PreferKey.navIconExplore to navIconExplore,
+    PreferKey.navIconRss to navIconRss,
+    PreferKey.navIconMy to navIconMy,
+    PreferKey.launcherIcon to launcherIcon,
+)
+
 internal fun Preferences.toThemeSettings(): ThemeSettings = ThemeSettings(
     appTheme = compatDsString(PreferKey.appTheme) ?: "0",
     useMiuixMonet = compatDsBoolean(PreferKey.useMiuixMonet) ?: false,

+ 1 - 42
app/src/main/java/io/legado/app/domain/gateway/AppShellSettingsGateway.kt

@@ -6,46 +6,5 @@ import kotlinx.coroutines.flow.Flow
 interface AppShellSettingsGateway {
     val currentSettings: AppShellSettings
     val settings: Flow<AppShellSettings>
-    suspend fun update(update: AppShellSettingsUpdate)
-    suspend fun updateAll(updates: List<AppShellSettingsUpdate>)
-}
-
-sealed interface AppShellSettingsUpdate {
-    data class ThemeMode(val value: String) : AppShellSettingsUpdate
-    data class FontScale(val value: Int) : AppShellSettingsUpdate
-    data class ComposeEngine(val value: String) : AppShellSettingsUpdate
-    data class MainNavigationOrder(val value: String) : AppShellSettingsUpdate
-    data class BooleanValue(
-        val setting: AppShellBooleanSetting,
-        val value: Boolean,
-    ) : AppShellSettingsUpdate
-    data class StringValue(
-        val setting: AppShellStringSetting,
-        val value: String,
-    ) : AppShellSettingsUpdate
-}
-
-enum class AppShellBooleanSetting {
-    ShowHome,
-    ShowDiscovery,
-    ShowRss,
-    ShowStatusBar,
-    SwipeAnimation,
-    PredictiveBack,
-    ShowBottomView,
-    UseFloatingBottomBar,
-    UseFloatingBottomBarLiquidGlass,
-    NavExtended,
-}
-
-enum class AppShellStringSetting {
-    TabletInterface,
-    LabelVisibilityMode,
-    DefaultHomePage,
-    NavIconHome,
-    NavIconBookshelf,
-    NavIconExplore,
-    NavIconRss,
-    NavIconMy,
-    LauncherIcon,
+    suspend fun update(transform: (AppShellSettings) -> AppShellSettings)
 }

+ 1 - 2
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

@@ -40,7 +40,6 @@ import io.legado.app.domain.gateway.AiArtifactGateway
 import io.legado.app.domain.gateway.AiProfileGateway
 import io.legado.app.domain.gateway.AiPromptPresetGateway
 import io.legado.app.domain.gateway.AppShellSettingsGateway
-import io.legado.app.domain.gateway.AppShellSettingsUpdate
 import io.legado.app.domain.gateway.AppUiConfigurationGateway
 import io.legado.app.domain.gateway.BackupSettingsGateway
 import io.legado.app.domain.gateway.BookContentProcessGateway
@@ -5935,7 +5934,7 @@ class ReadBookViewModel(
         hasDismissedLightReminder = false
         val nextMode = if (isNightTheme()) "1" else "2"
         viewModelScope.launch {
-            appShellSettingsGateway.update(AppShellSettingsUpdate.ThemeMode(nextMode))
+            appShellSettingsGateway.update { it.copy(themeMode = nextMode) }
         }
         _uiState.update {
             val newActiveReminder = if (it.activeReminder?.type is ReminderType.DayNightReminder) {

+ 11 - 2
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigContract.kt

@@ -1,7 +1,6 @@
 package io.legado.app.ui.config.themeConfig
 
 import androidx.compose.runtime.Stable
-import io.legado.app.domain.gateway.AppShellSettingsUpdate
 import io.legado.app.domain.gateway.ThemeSettingsUpdate
 import io.legado.app.domain.model.settings.AppShellSettings
 import io.legado.app.domain.model.settings.ThemeSettings
@@ -32,11 +31,21 @@ enum class ThemeTimeField {
 }
 
 sealed interface ThemeConfigIntent {
-    data class UpdateAppShell(val update: AppShellSettingsUpdate) : ThemeConfigIntent
     data class UpdateTheme(val update: ThemeSettingsUpdate) : ThemeConfigIntent
     data class ShowSheet(val sheet: ThemeConfigSheet) : ThemeConfigIntent
     data object DismissSheet : ThemeConfigIntent
     data class SelectTheme(val value: String) : ThemeConfigIntent
+    data class SetThemeMode(val value: String) : ThemeConfigIntent
+    data class SetComposeEngine(val value: String) : ThemeConfigIntent
+    data class SetPredictiveBackEnabled(val enabled: Boolean) : ThemeConfigIntent
+    data class SetFontScale(val value: Int) : ThemeConfigIntent
+    data class SetShowStatusBar(val visible: Boolean) : ThemeConfigIntent
+    data class SetSwipeAnimation(val enabled: Boolean) : ThemeConfigIntent
+    data class SetShowBottomView(val visible: Boolean) : ThemeConfigIntent
+    data class SetUseFloatingBottomBar(val enabled: Boolean) : ThemeConfigIntent
+    data class SetUseFloatingBottomBarLiquidGlass(val enabled: Boolean) : ThemeConfigIntent
+    data class SetTabletInterface(val value: String) : ThemeConfigIntent
+    data class SetLabelVisibilityMode(val value: String) : ThemeConfigIntent
     data class SetMiuixMonet(val enabled: Boolean) : ThemeConfigIntent
     data class SetDynamicColors(val enabled: Boolean) : ThemeConfigIntent
     data class SetBlurEnabled(val enabled: Boolean) : ThemeConfigIntent

+ 12 - 63
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt

@@ -60,9 +60,6 @@ import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.constraintlayout.compose.ConstraintLayout
 import io.legado.app.R
-import io.legado.app.domain.gateway.AppShellBooleanSetting
-import io.legado.app.domain.gateway.AppShellSettingsUpdate
-import io.legado.app.domain.gateway.AppShellStringSetting
 import io.legado.app.domain.gateway.ThemeBooleanSetting
 import io.legado.app.domain.gateway.ThemeFloatSetting
 import io.legado.app.domain.gateway.ThemeIntSetting
@@ -201,9 +198,7 @@ fun ThemeConfigScreen(
                             entryValues = stringArrayResource(R.array.theme_mode_v),
                             onValueChange = { mode ->
                                 onIntent(
-                                    ThemeConfigIntent.UpdateAppShell(
-                                        AppShellSettingsUpdate.ThemeMode(mode)
-                                    )
+                                    ThemeConfigIntent.SetThemeMode(mode)
                                 )
                             }
                         )
@@ -232,9 +227,7 @@ fun ThemeConfigScreen(
                             selectedMode = appShell.themeMode,
                             onModeSelected = { mode ->
                                 onIntent(
-                                    ThemeConfigIntent.UpdateAppShell(
-                                        AppShellSettingsUpdate.ThemeMode(mode)
-                                    )
+                                    ThemeConfigIntent.SetThemeMode(mode)
                                 )
                             }
                         )
@@ -280,9 +273,7 @@ fun ThemeConfigScreen(
                         entryValues = stringArrayResource(R.array.composeEngine_value),
                         onValueChange = {
                             onIntent(
-                                ThemeConfigIntent.UpdateAppShell(
-                                    AppShellSettingsUpdate.ComposeEngine(it)
-                                )
+                                ThemeConfigIntent.SetComposeEngine(it)
                             )
                         }
                     )
@@ -299,12 +290,7 @@ fun ThemeConfigScreen(
                         checked = appShell.predictiveBackEnabled,
                         onCheckedChange = {
                             onIntent(
-                                ThemeConfigIntent.UpdateAppShell(
-                                    AppShellSettingsUpdate.BooleanValue(
-                                        AppShellBooleanSetting.PredictiveBack,
-                                        it,
-                                    )
-                                )
+                                ThemeConfigIntent.SetPredictiveBackEnabled(it)
                             )
                         }
                     )
@@ -319,9 +305,7 @@ fun ThemeConfigScreen(
                         steps = 7,
                         onValueChange = { value ->
                             onIntent(
-                                ThemeConfigIntent.UpdateAppShell(
-                                    AppShellSettingsUpdate.FontScale(value.toInt())
-                                )
+                                ThemeConfigIntent.SetFontScale(value.toInt())
                             )
                         }
                     )
@@ -345,12 +329,7 @@ fun ThemeConfigScreen(
                         checked = appShell.showStatusBar,
                         onCheckedChange = {
                             onIntent(
-                                ThemeConfigIntent.UpdateAppShell(
-                                    AppShellSettingsUpdate.BooleanValue(
-                                        AppShellBooleanSetting.ShowStatusBar,
-                                        it,
-                                    )
-                                )
+                                ThemeConfigIntent.SetShowStatusBar(it)
                             )
                         }
                     )
@@ -360,12 +339,7 @@ fun ThemeConfigScreen(
                         checked = appShell.swipeAnimation,
                         onCheckedChange = {
                             onIntent(
-                                ThemeConfigIntent.UpdateAppShell(
-                                    AppShellSettingsUpdate.BooleanValue(
-                                        AppShellBooleanSetting.SwipeAnimation,
-                                        it,
-                                    )
-                                )
+                                ThemeConfigIntent.SetSwipeAnimation(it)
                             )
                         }
                     )
@@ -375,12 +349,7 @@ fun ThemeConfigScreen(
                         checked = appShell.showBottomView,
                         onCheckedChange = {
                             onIntent(
-                                ThemeConfigIntent.UpdateAppShell(
-                                    AppShellSettingsUpdate.BooleanValue(
-                                        AppShellBooleanSetting.ShowBottomView,
-                                        it,
-                                    )
-                                )
+                                ThemeConfigIntent.SetShowBottomView(it)
                             )
                         }
                     )
@@ -390,12 +359,7 @@ fun ThemeConfigScreen(
                         checked = appShell.useFloatingBottomBar,
                         onCheckedChange = {
                             onIntent(
-                                ThemeConfigIntent.UpdateAppShell(
-                                    AppShellSettingsUpdate.BooleanValue(
-                                        AppShellBooleanSetting.UseFloatingBottomBar,
-                                        it,
-                                    )
-                                )
+                                ThemeConfigIntent.SetUseFloatingBottomBar(it)
                             )
                         }
                     )
@@ -409,12 +373,7 @@ fun ThemeConfigScreen(
                                 checked = appShell.useFloatingBottomBarLiquidGlass,
                                 onCheckedChange = {
                                     onIntent(
-                                        ThemeConfigIntent.UpdateAppShell(
-                                            AppShellSettingsUpdate.BooleanValue(
-                                                AppShellBooleanSetting.UseFloatingBottomBarLiquidGlass,
-                                                it,
-                                            )
-                                        )
+                                        ThemeConfigIntent.SetUseFloatingBottomBarLiquidGlass(it)
                                     )
                                 }
                             )
@@ -445,12 +404,7 @@ fun ThemeConfigScreen(
                         entryValues = stringArrayResource(R.array.tabletInterface_value),
                         onValueChange = {
                             onIntent(
-                                ThemeConfigIntent.UpdateAppShell(
-                                    AppShellSettingsUpdate.StringValue(
-                                        AppShellStringSetting.TabletInterface,
-                                        it,
-                                    )
-                                )
+                                ThemeConfigIntent.SetTabletInterface(it)
                             )
                         }
                     )
@@ -461,12 +415,7 @@ fun ThemeConfigScreen(
                         entryValues = stringArrayResource(R.array.label_vis_mode_value),
                         onValueChange = {
                             onIntent(
-                                ThemeConfigIntent.UpdateAppShell(
-                                    AppShellSettingsUpdate.StringValue(
-                                        AppShellStringSetting.LabelVisibilityMode,
-                                        it,
-                                    )
-                                )
+                                ThemeConfigIntent.SetLabelVisibilityMode(it)
                             )
                         }
                     )

+ 94 - 54
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigViewModel.kt

@@ -5,10 +5,7 @@ import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import io.legado.app.R
 import io.legado.app.constant.PreferKey
-import io.legado.app.domain.gateway.AppShellBooleanSetting
 import io.legado.app.domain.gateway.AppShellSettingsGateway
-import io.legado.app.domain.gateway.AppShellSettingsUpdate
-import io.legado.app.domain.gateway.AppShellStringSetting
 import io.legado.app.domain.gateway.ReadSettingsGateway
 import io.legado.app.domain.gateway.ThemeBooleanSetting
 import io.legado.app.domain.gateway.ThemeIntSetting
@@ -16,6 +13,7 @@ import io.legado.app.domain.gateway.ThemeSettingsGateway
 import io.legado.app.domain.gateway.ThemeSettingsUpdate
 import io.legado.app.domain.gateway.ThemeStringSetting
 import io.legado.app.domain.gateway.LabSettingsGateway
+import io.legado.app.domain.model.settings.AppShellSettings
 import io.legado.app.ui.main.MainDestination
 import io.legado.app.utils.FileDoc
 import io.legado.app.utils.FileUtils
@@ -79,26 +77,57 @@ class ThemeConfigViewModel(
 
     fun onIntent(intent: ThemeConfigIntent) {
         when (intent) {
-            is ThemeConfigIntent.UpdateAppShell -> updateAppShell(intent.update)
             is ThemeConfigIntent.UpdateTheme -> updateTheme(intent.update)
             is ThemeConfigIntent.ShowSheet ->
                 _uiState.update { it.copy(activeSheet = intent.sheet) }
             ThemeConfigIntent.DismissSheet ->
                 _uiState.update { it.copy(activeSheet = null) }
             is ThemeConfigIntent.SelectTheme -> selectTheme(intent.value)
+            is ThemeConfigIntent.SetThemeMode -> updateAppShell(
+                transform = { it.copy(themeMode = intent.value) },
+                effect = ThemeConfigEffect.ApplyDayNight,
+            )
+            is ThemeConfigIntent.SetComposeEngine -> updateAppShell {
+                it.copy(composeEngine = intent.value)
+            }
+            is ThemeConfigIntent.SetPredictiveBackEnabled -> updateAppShell {
+                it.copy(predictiveBackEnabled = intent.enabled)
+            }
+            is ThemeConfigIntent.SetFontScale -> updateAppShell {
+                it.copy(fontScale = intent.value)
+            }
+            is ThemeConfigIntent.SetShowStatusBar -> updateAppShell(
+                transform = { it.copy(showStatusBar = intent.visible) },
+                effect = ThemeConfigEffect.NotifyMain,
+            )
+            is ThemeConfigIntent.SetSwipeAnimation -> updateAppShell {
+                it.copy(swipeAnimation = intent.enabled)
+            }
+            is ThemeConfigIntent.SetShowBottomView -> updateAppShell {
+                it.copy(showBottomView = intent.visible)
+            }
+            is ThemeConfigIntent.SetUseFloatingBottomBar -> updateAppShell {
+                it.copy(useFloatingBottomBar = intent.enabled)
+            }
+            is ThemeConfigIntent.SetUseFloatingBottomBarLiquidGlass -> updateAppShell {
+                it.copy(useFloatingBottomBarLiquidGlass = intent.enabled)
+            }
+            is ThemeConfigIntent.SetTabletInterface -> updateAppShell {
+                it.copy(tabletInterface = intent.value)
+            }
+            is ThemeConfigIntent.SetLabelVisibilityMode -> updateAppShell {
+                it.copy(labelVisibilityMode = intent.value)
+            }
             is ThemeConfigIntent.SetMiuixMonet -> setMiuixMonet(intent.enabled)
             is ThemeConfigIntent.SetDynamicColors -> setDynamicColors(intent.enabled)
             is ThemeConfigIntent.SetBlurEnabled -> setBlurEnabled(intent.enabled)
             is ThemeConfigIntent.SetMainDestinationVisible -> setMainDestinationVisible(intent)
-            is ThemeConfigIntent.SetMainNavigationOrder -> updateAppShell(
-                AppShellSettingsUpdate.MainNavigationOrder(intent.routes)
-            )
-            is ThemeConfigIntent.SetDefaultHomePage -> updateAppShell(
-                AppShellSettingsUpdate.StringValue(
-                    AppShellStringSetting.DefaultHomePage,
-                    intent.route,
-                )
-            )
+            is ThemeConfigIntent.SetMainNavigationOrder -> updateAppShell {
+                it.copy(mainNavigationOrder = intent.routes)
+            }
+            is ThemeConfigIntent.SetDefaultHomePage -> updateAppShell {
+                it.copy(defaultHomePage = intent.route)
+            }
             is ThemeConfigIntent.SelectLauncherIcon -> selectLauncherIcon(intent.value)
             is ThemeConfigIntent.SelectNavigationIcon -> selectNavigationIcon(intent)
             is ThemeConfigIntent.RequestNavigationIcon -> _effects.tryEmit(
@@ -125,19 +154,13 @@ class ThemeConfigViewModel(
         }
     }
 
-    private fun updateAppShell(update: AppShellSettingsUpdate) {
+    private fun updateAppShell(
+        effect: ThemeConfigEffect? = null,
+        transform: (AppShellSettings) -> AppShellSettings,
+    ) {
         viewModelScope.launch {
-            appShellSettingsGateway.update(update)
-            when (update) {
-                is AppShellSettingsUpdate.ThemeMode ->
-                    _effects.tryEmit(ThemeConfigEffect.ApplyDayNight)
-                is AppShellSettingsUpdate.BooleanValue -> if (
-                    update.setting == AppShellBooleanSetting.ShowStatusBar
-                ) {
-                    _effects.tryEmit(ThemeConfigEffect.NotifyMain)
-                }
-                else -> Unit
-            }
+            appShellSettingsGateway.update(transform)
+            effect?.let(_effects::tryEmit)
         }
     }
 
@@ -212,48 +235,65 @@ class ThemeConfigViewModel(
     }
 
     private fun setMainDestinationVisible(intent: ThemeConfigIntent.SetMainDestinationVisible) {
-        val setting = when (intent.route) {
-            MainDestination.Home.route -> AppShellBooleanSetting.ShowHome
-            MainDestination.Explore.route -> AppShellBooleanSetting.ShowDiscovery
-            MainDestination.Rss.route -> AppShellBooleanSetting.ShowRss
+        val transform: (AppShellSettings) -> AppShellSettings = when (intent.route) {
+            MainDestination.Home.route -> { current ->
+                current.copy(
+                    showHome = intent.visible,
+                    defaultHomePage = current.fallbackHomePage(intent),
+                )
+            }
+            MainDestination.Explore.route -> { current ->
+                current.copy(
+                    showDiscovery = intent.visible,
+                    defaultHomePage = current.fallbackHomePage(intent),
+                )
+            }
+            MainDestination.Rss.route -> { current ->
+                current.copy(
+                    showRss = intent.visible,
+                    defaultHomePage = current.fallbackHomePage(intent),
+                )
+            }
             else -> return
         }
-        viewModelScope.launch {
-            appShellSettingsGateway.updateAll(
-                buildList {
-                    add(AppShellSettingsUpdate.BooleanValue(setting, intent.visible))
-                    if (!intent.visible && _uiState.value.appShell.defaultHomePage == intent.route) {
-                        add(
-                            AppShellSettingsUpdate.StringValue(
-                                AppShellStringSetting.DefaultHomePage,
-                                MainDestination.Bookshelf.route,
-                            )
-                        )
-                    }
-                }
-            )
-        }
+        updateAppShell(transform = transform)
+    }
+
+    private fun AppShellSettings.fallbackHomePage(
+        intent: ThemeConfigIntent.SetMainDestinationVisible,
+    ): String = if (!intent.visible && defaultHomePage == intent.route) {
+        MainDestination.Bookshelf.route
+    } else {
+        defaultHomePage
     }
 
     private fun selectLauncherIcon(value: String) {
         viewModelScope.launch {
-            appShellSettingsGateway.update(
-                AppShellSettingsUpdate.StringValue(AppShellStringSetting.LauncherIcon, value)
-            )
+            appShellSettingsGateway.update { it.copy(launcherIcon = value) }
             _effects.tryEmit(ThemeConfigEffect.ChangeLauncherIcon(value))
         }
     }
 
     private fun selectNavigationIcon(intent: ThemeConfigIntent.SelectNavigationIcon) {
-        val setting = when (intent.destination) {
-            MainDestination.Home.route -> AppShellStringSetting.NavIconHome
-            MainDestination.Bookshelf.route -> AppShellStringSetting.NavIconBookshelf
-            MainDestination.Explore.route -> AppShellStringSetting.NavIconExplore
-            MainDestination.Rss.route -> AppShellStringSetting.NavIconRss
-            MainDestination.My.route -> AppShellStringSetting.NavIconMy
+        val transform: (AppShellSettings) -> AppShellSettings = when (intent.destination) {
+            MainDestination.Home.route -> { settings ->
+                settings.copy(navIconHome = intent.path)
+            }
+            MainDestination.Bookshelf.route -> { settings ->
+                settings.copy(navIconBookshelf = intent.path)
+            }
+            MainDestination.Explore.route -> { settings ->
+                settings.copy(navIconExplore = intent.path)
+            }
+            MainDestination.Rss.route -> { settings ->
+                settings.copy(navIconRss = intent.path)
+            }
+            MainDestination.My.route -> { settings ->
+                settings.copy(navIconMy = intent.path)
+            }
             else -> return
         }
-        updateAppShell(AppShellSettingsUpdate.StringValue(setting, intent.path))
+        updateAppShell(transform = transform)
     }
 
     private fun setTime(field: ThemeTimeField, value: String) {

+ 1 - 8
app/src/main/java/io/legado/app/ui/main/MainViewModel.kt

@@ -3,9 +3,7 @@ package io.legado.app.ui.main
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import io.legado.app.constant.EventBus
-import io.legado.app.domain.gateway.AppShellBooleanSetting
 import io.legado.app.domain.gateway.AppShellSettingsGateway
-import io.legado.app.domain.gateway.AppShellSettingsUpdate
 import io.legado.app.domain.gateway.ThemeSettingsGateway
 import io.legado.app.domain.model.settings.AppShellSettings
 import io.legado.app.domain.model.settings.ThemeSettings
@@ -74,12 +72,7 @@ class MainViewModel(
     private fun setNavExtended(expanded: Boolean) {
         if (_uiState.value.navExtended == expanded) return
         viewModelScope.launch {
-            appShellSettingsGateway.update(
-                AppShellSettingsUpdate.BooleanValue(
-                    AppShellBooleanSetting.NavExtended,
-                    expanded,
-                )
-            )
+            appShellSettingsGateway.update { it.copy(navExtended = expanded) }
         }
     }
 

+ 100 - 0
app/src/test/java/io/legado/app/data/repository/AppShellSettingsMappingTest.kt

@@ -0,0 +1,100 @@
+package io.legado.app.data.repository
+
+import io.legado.app.constant.PreferKey
+import io.legado.app.domain.model.settings.AppShellSettings
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class AppShellSettingsMappingTest {
+
+    @Test
+    fun `AppShell 23 键写读映射逐字段对应`() {
+        appShellMappingSamples().forEach { expected ->
+            assertEquals(expected.expectedPrefMap(), expected.toPrefMap())
+            assertEquals(
+                expected,
+                expected.expectedPrefMap().toTestPreferences().toAppShellSettings(),
+            )
+        }
+    }
+
+    @Test
+    fun `导航可见性与默认首页通过真实原子路径单批写入`() {
+        val values = captureAtomicUpdateValues(
+            current = AppShellSettings(showHome = true, defaultHomePage = "home"),
+            read = { it.toAppShellSettings() },
+            toPrefMap = AppShellSettings::toPrefMap,
+            transform = {
+                it.copy(
+                    showHome = false,
+                    defaultHomePage = "bookshelf",
+                )
+            },
+        )
+
+        assertEquals(
+            mapOf(
+                PreferKey.showHome to false,
+                PreferKey.defaultHomePage to "bookshelf",
+            ),
+            values,
+        )
+    }
+}
+
+private fun appShellMappingSamples(): List<AppShellSettings> {
+    val base = AppShellSettings(
+        themeMode = "theme-mode",
+        fontScale = 13,
+        composeEngine = "compose-engine",
+        tabletInterface = "tablet-interface",
+        labelVisibilityMode = "label-mode",
+        defaultHomePage = "default-page",
+        mainNavigationOrder = "navigation-order",
+        navIconHome = "icon-home",
+        navIconBookshelf = "icon-bookshelf",
+        navIconExplore = "icon-explore",
+        navIconRss = "icon-rss",
+        navIconMy = "icon-my",
+        launcherIcon = "launcher-icon",
+    )
+    return listOf(
+        base,
+        base.copy(showHome = false),
+        base.copy(showDiscovery = false),
+        base.copy(showRss = false),
+        base.copy(showStatusBar = false),
+        base.copy(swipeAnimation = false),
+        base.copy(predictiveBackEnabled = false),
+        base.copy(showBottomView = false),
+        base.copy(useFloatingBottomBar = true),
+        base.copy(useFloatingBottomBarLiquidGlass = true),
+        base.copy(navExtended = true),
+    )
+}
+
+private fun AppShellSettings.expectedPrefMap(): Map<String, Any?> = mapOf(
+    PreferKey.themeMode to themeMode,
+    PreferKey.fontScale to fontScale,
+    PreferKey.composeEngine to composeEngine,
+    PreferKey.showHome to showHome,
+    PreferKey.showDiscovery to showDiscovery,
+    PreferKey.showRss to showRss,
+    PreferKey.showStatusBar to showStatusBar,
+    PreferKey.swipeAnimation to swipeAnimation,
+    PreferKey.isPredictiveBackEnabled to predictiveBackEnabled,
+    PreferKey.showBottomView to showBottomView,
+    PreferKey.useFloatingBottomBar to useFloatingBottomBar,
+    PreferKey.useFloatingBottomBarLiquidGlass to useFloatingBottomBarLiquidGlass,
+    PreferKey.tabletInterface to tabletInterface,
+    PreferKey.labelVisibilityMode to labelVisibilityMode,
+    PreferKey.defaultHomePage to defaultHomePage,
+    PreferKey.mainNavigationOrder to mainNavigationOrder,
+    PreferKey.navExtended to navExtended,
+    PreferKey.navIconHome to navIconHome,
+    PreferKey.navIconBookshelf to navIconBookshelf,
+    PreferKey.navIconExplore to navIconExplore,
+    PreferKey.navIconRss to navIconRss,
+    PreferKey.navIconMy to navIconMy,
+    PreferKey.launcherIcon to launcherIcon,
+)