فهرست منبع

[修复] 修复Compose界面的背景图片显示,添加了一些界面设置选项进Compose设置页中

HapeLee 7 ماه پیش
والد
کامیت
6a99e1023f
38فایلهای تغییر یافته به همراه745 افزوده شده و 182 حذف شده
  1. 2 5
      app/src/main/java/io/legado/app/base/BaseComposeActivity.kt
  2. 2 0
      app/src/main/java/io/legado/app/di/appModule.kt
  3. 3 3
      app/src/main/java/io/legado/app/ui/about/AboutScreen.kt
  4. 3 3
      app/src/main/java/io/legado/app/ui/book/bookmark/AllBookmarkScreen.kt
  5. 3 3
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt
  6. 3 3
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt
  7. 3 3
      app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt
  8. 3 3
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt
  9. 3 3
      app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt
  10. 33 22
      app/src/main/java/io/legado/app/ui/book/toc/rule/TxtRuleScreen.kt
  11. 10 5
      app/src/main/java/io/legado/app/ui/config/PrefDelegate.kt
  12. 17 10
      app/src/main/java/io/legado/app/ui/config/TestConfigActivity.kt
  13. 3 3
      app/src/main/java/io/legado/app/ui/config/coverConfig/CoverConfigScreen.kt
  14. 3 3
      app/src/main/java/io/legado/app/ui/config/otherConfig/OtherConfigScreen.kt
  15. 3 3
      app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt
  16. 153 0
      app/src/main/java/io/legado/app/ui/config/themeConfig/BackgroundImageManageSheet.kt
  17. 26 5
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt
  18. 170 0
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt
  19. 80 0
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigViewModel.kt
  20. 33 20
      app/src/main/java/io/legado/app/ui/dict/rule/DictRuleScreen.kt
  21. 5 5
      app/src/main/java/io/legado/app/ui/main/MainScreen.kt
  22. 4 5
      app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt
  23. 28 17
      app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt
  24. 3 3
      app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditScreen.kt
  25. 42 0
      app/src/main/java/io/legado/app/ui/theme/AppBackground.kt
  26. 7 6
      app/src/main/java/io/legado/app/ui/theme/AppTheme.kt
  27. 0 1
      app/src/main/java/io/legado/app/ui/theme/OpaqueColorScheme.kt
  28. 1 2
      app/src/main/java/io/legado/app/ui/theme/ThemeManager.kt
  29. 39 0
      app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt
  30. 6 5
      app/src/main/java/io/legado/app/ui/widget/components/GlassMediumFlexibleTopAppBar.kt
  31. 3 6
      app/src/main/java/io/legado/app/ui/widget/components/SectionHeader.kt
  32. 5 1
      app/src/main/java/io/legado/app/ui/widget/components/SplicedColumnGroup.kt
  33. 17 24
      app/src/main/java/io/legado/app/ui/widget/components/card/GlassCard.kt
  34. 1 1
      app/src/main/java/io/legado/app/ui/widget/components/filePicker/FilePickerSheet.kt
  35. 2 2
      app/src/main/java/io/legado/app/ui/widget/components/importComponents/ImportComponents.kt
  36. 10 4
      app/src/main/java/io/legado/app/ui/widget/components/list/ListScaffold.kt
  37. 14 2
      app/src/main/java/io/legado/app/ui/widget/components/settingItem/CompactSettingItems.kt
  38. 2 1
      app/src/main/java/io/legado/app/ui/widget/components/swipe/SwipeActionContainer.kt

+ 2 - 5
app/src/main/java/io/legado/app/base/BaseComposeActivity.kt

@@ -3,15 +3,14 @@ package io.legado.app.base
 import android.os.Bundle
 import androidx.activity.compose.setContent
 import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.material3.Surface
 import androidx.compose.runtime.Composable
 import androidx.core.graphics.drawable.toDrawable
 import androidx.core.view.WindowCompat
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.Theme
+import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.OldThemeConfig
 import io.legado.app.ui.theme.AppTheme
-import io.legado.app.help.config.AppConfig
 import io.legado.app.utils.disableAutoFill
 import io.legado.app.utils.fullScreen
 import io.legado.app.utils.observeEvent
@@ -40,9 +39,7 @@ abstract class BaseComposeActivity(
         // Compose 入口
         setContent {
             AppTheme {
-                Surface() {
-                    Content()
-                }
+                Content()
             }
         }
 

+ 2 - 0
app/src/main/java/io/legado/app/di/appModule.kt

@@ -31,6 +31,7 @@ import io.legado.app.ui.book.toc.rule.TxtTocRuleViewModel
 import io.legado.app.ui.config.coverConfig.CoverConfigViewModel
 import io.legado.app.ui.config.otherConfig.OtherConfigViewModel
 import io.legado.app.ui.config.readConfig.ReadConfigViewModel
+import io.legado.app.ui.config.themeConfig.ThemeConfigViewModel
 import io.legado.app.ui.dict.rule.DictRuleViewModel
 import io.legado.app.ui.main.MainViewModel
 import io.legado.app.ui.main.bookshelf.BookshelfViewModel
@@ -92,6 +93,7 @@ val appModule = module {
     viewModelOf(::OtherConfigViewModel)
     viewModelOf(::ReadConfigViewModel)
     viewModelOf(::CoverConfigViewModel)
+    viewModelOf(::ThemeConfigViewModel)
     viewModelOf(::TocViewModel)
     viewModelOf(::RemoteBookViewModel)
     viewModelOf(::BookInfoViewModel)

+ 3 - 3
app/src/main/java/io/legado/app/ui/about/AboutScreen.kt

@@ -17,7 +17,6 @@ import androidx.compose.material3.FilledTonalIconButton
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.MaterialTheme.colorScheme
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
@@ -32,6 +31,7 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import io.legado.app.R
 import io.legado.app.constant.AppConst.appInfo
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.SplicedColumnGroup
@@ -55,7 +55,7 @@ fun AboutScreen(
 
     val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
 
-    Scaffold(
+    AppScaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             GlassMediumFlexibleTopAppBar(
@@ -188,4 +188,4 @@ fun AboutScreen(
             }
         }
     }
-}
+}

+ 3 - 3
app/src/main/java/io/legado/app/ui/book/bookmark/AllBookmarkScreen.kt

@@ -26,7 +26,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
@@ -40,6 +39,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalContext
 import io.legado.app.data.entities.Bookmark
 import io.legado.app.ui.widget.CollapsibleHeader
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.EmptyMessageView
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
@@ -93,7 +93,7 @@ fun AllBookmarkScreen(
         }
     }
 
-    Scaffold(
+    AppScaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             Column {
@@ -260,4 +260,4 @@ fun AllBookmarkScreen(
             )
         }
     }
-}
+}

+ 3 - 3
app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt

@@ -50,7 +50,6 @@ import androidx.compose.material3.FilterChip
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Slider
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
@@ -83,6 +82,7 @@ import io.legado.app.model.BookShelfState
 import io.legado.app.ui.theme.responsiveHazeEffect
 import io.legado.app.ui.theme.responsiveHazeSource
 import io.legado.app.ui.widget.components.AnimatedTextLine
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.SearchBarSection
@@ -286,7 +286,7 @@ fun ExploreShowScreen(
         }
     }
 
-    Scaffold(
+    AppScaffold(
         modifier = Modifier
             .nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
@@ -762,4 +762,4 @@ fun LoadMoreFooter(
             )
         }
     }
-}
+}

+ 3 - 3
app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt

@@ -30,7 +30,6 @@ import androidx.compose.material3.ExposedDropdownMenuDefaults
 import androidx.compose.material3.Icon
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -47,6 +46,7 @@ import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
 import io.legado.app.ui.book.changecover.ChangeCoverDialog
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.button.TopBarButtonVariant
@@ -67,7 +67,7 @@ fun BookInfoEditScreen(
     val uiState by viewModel.uiState.collectAsStateWithLifecycle()
     val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
 
-    Scaffold(
+    AppScaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             GlassMediumFlexibleTopAppBar(
@@ -278,4 +278,4 @@ fun BookTypeDropdown(
             }
         }
     }
-}
+}

+ 3 - 3
app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt

@@ -40,7 +40,6 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.SnackbarHost
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.Surface
@@ -80,6 +79,7 @@ import cn.hutool.core.date.DateUtil
 import io.legado.app.data.entities.readRecord.ReadRecord
 import io.legado.app.data.entities.readRecord.ReadRecordDetail
 import io.legado.app.ui.widget.components.AnimatedTextLine
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.EmptyMessageView
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
@@ -142,7 +142,7 @@ fun ReadRecordScreen(
         }
     }
 
-    Scaffold(
+    AppScaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         snackbarHost = { SnackbarHost(snackbarHostState) },
         topBar = {
@@ -1019,4 +1019,4 @@ fun formatDuring(mss: Long): String {
     val m = if (minutes > 0) "${minutes}分钟" else ""
     val s = if (seconds > 0) "${seconds}秒" else ""
     return if ("$d$h$m$s".isBlank()) "0秒" else "$d$h$m$s"
-}
+}

+ 3 - 3
app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt

@@ -44,7 +44,6 @@ import androidx.compose.material3.ListItem
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.PlainTooltip
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
 import androidx.compose.material3.TooltipAnchorPosition
 import androidx.compose.material3.TooltipBox
@@ -66,6 +65,7 @@ import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import io.legado.app.data.entities.SearchContentHistory
 import io.legado.app.ui.widget.components.AnimatedText
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.EmptyMessageView
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
@@ -135,7 +135,7 @@ fun SearchContentScreen(
         else -> null
     }
 
-    Scaffold(
+    AppScaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             Column {
@@ -465,4 +465,4 @@ fun SearchResultItem(
             }
         }
     }
-}
+}

+ 3 - 3
app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt

@@ -61,7 +61,6 @@ import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.PrimaryScrollableTabRow
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Tab
 import androidx.compose.material3.Text
@@ -98,6 +97,7 @@ import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
 import io.legado.app.ui.replace.ReplaceEditRoute
 import io.legado.app.ui.widget.CollapsibleHeader
 import io.legado.app.ui.widget.components.ActionItem
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.EmptyMessageView
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.SelectionBottomBar
@@ -295,7 +295,7 @@ fun TocScreen(
         viewModel.clearSelection()
     }
 
-    Scaffold(
+    AppScaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             DynamicTopAppBar(
@@ -948,4 +948,4 @@ private fun StatusIcon(
             }
         }
     }
-}
+}

+ 33 - 22
app/src/main/java/io/legado/app/ui/book/toc/rule/TxtRuleScreen.kt

@@ -14,10 +14,11 @@ import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.FileOpen
 import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.SnackbarHostState
@@ -50,11 +51,11 @@ import io.legado.app.ui.widget.components.DraggableSelectionHandler
 import io.legado.app.ui.widget.components.button.SmallIconButton
 import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
 import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
-import io.legado.app.ui.widget.components.filePicker.FilePickerSheetMode
 import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
 import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
 import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
 import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
+import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 import io.legado.app.ui.widget.components.rules.RuleEditFields
 import io.legado.app.ui.widget.components.rules.RuleEditSheet
 import io.legado.app.ui.widget.components.rules.RuleListScaffold
@@ -84,9 +85,10 @@ fun TxtRuleScreen(
     var showEditSheet by remember { mutableStateOf(false) }
     var editingRule by remember { mutableStateOf<TxtTocRule?>(null) }
     var showDeleteRuleDialog by remember { mutableStateOf<TxtTocRule?>(null) }
+
     var showUrlInput by remember { mutableStateOf(false) }
-    var showFilePickerSheet by remember { mutableStateOf(false) }
-    var filePickerMode by remember { mutableStateOf(FilePickerSheetMode.EXPORT) }
+    var showImportSheet by remember { mutableStateOf(false) }
+    var showExportSheet by remember { mutableStateOf(false) }
 
     val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
         viewModel.moveItemInList(from.index, to.index)
@@ -152,21 +154,38 @@ fun TxtRuleScreen(
         )
     }
 
-    if (showFilePickerSheet) {
+    if (showExportSheet) {
         FilePickerSheet(
-            onDismissRequest = { showFilePickerSheet = false },
+            onDismissRequest = { showExportSheet = false },
+            title = stringResource(R.string.export),
             onSelectSysDir = {
-                showFilePickerSheet = false
+                showExportSheet = false
                 exportDoc.launch("exportDictRule.json")
             },
             onUpload = {
-                showFilePickerSheet = false
+                showExportSheet = false
                 viewModel.uploadSelectedRules(selectedIds, rules)
             },
             allowExtensions = arrayOf("json")
         )
     }
 
+    if (showImportSheet) {
+        FilePickerSheet(
+            onDismissRequest = { showImportSheet = false },
+            title = stringResource(R.string.import_txt_toc_rule),
+            onSelectSysFile = { types ->
+                importDoc.launch(types)
+                showImportSheet = false
+            },
+            onManualInput = {
+                showUrlInput = true
+                showImportSheet = false
+            },
+            allowExtensions = arrayOf("json", "txt")
+        )
+    }
+
     (importState as? BaseImportUiState.Success<TxtTocRule>)?.let { state ->
         BatchImportDialog(
             title = "导入词典规则",
@@ -279,7 +298,7 @@ fun TxtRuleScreen(
             }),
             ActionItem(
                 text = stringResource(R.string.export),
-                onClick = { showFilePickerSheet = true })
+                onClick = { showExportSheet = true })
         ),
         onDeleteSelected = { ids ->
             @Suppress("UNCHECKED_CAST")
@@ -292,19 +311,11 @@ fun TxtRuleScreen(
         },
         snackbarHostState = snackbarHostState,
         dropDownMenuContent = { dismiss ->
-            DropdownMenuItem(
-                text = { Text("在线导入") },
-                onClick = { dismiss(); showUrlInput = true })
-            DropdownMenuItem(
-                text = { Text("本地导入") },
-                onClick = {
-                    dismiss(); importDoc.launch(
-                    arrayOf(
-                        "text/plain",
-                        "application/json"
-                    )
-                )
-                })
+            RoundDropdownMenuItem(
+                text = { Text(stringResource(R.string.import_str)) },
+                onClick = { showImportSheet = true; dismiss() },
+                leadingIcon = { Icon(Icons.Default.FileOpen, null) }
+            )
         }
     ) { paddingValues ->
         Box(

+ 10 - 5
app/src/main/java/io/legado/app/ui/config/PrefDelegate.kt

@@ -53,11 +53,16 @@ fun <T> prefDelegate(
 
         @Suppress("UNCHECKED_CAST")
         private fun readInitialValue(): T {
-            return when (defaultValue) {
-                is String -> appCtx.getPrefString(key, defaultValue) as T
-                is Int -> appCtx.getPrefInt(key, defaultValue) as T
-                is Boolean -> appCtx.getPrefBoolean(key, defaultValue) as T
-                is Long -> appCtx.getPrefLong(key, defaultValue) as T
+            val prefs = appCtx.defaultSharedPreferences
+            return when {
+                defaultValue is String -> appCtx.getPrefString(key, defaultValue) as T
+                defaultValue == null && prefs.contains(key) -> {
+                    appCtx.getPrefString(key, null) as T
+                }
+
+                defaultValue is Int -> appCtx.getPrefInt(key, defaultValue) as T
+                defaultValue is Boolean -> appCtx.getPrefBoolean(key, defaultValue) as T
+                defaultValue is Long -> appCtx.getPrefLong(key, defaultValue) as T
                 else -> defaultValue
             }
         }

+ 17 - 10
app/src/main/java/io/legado/app/ui/config/TestConfigActivity.kt

@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
@@ -21,13 +20,13 @@ import io.legado.app.base.BaseComposeActivity
 import io.legado.app.ui.config.coverConfig.CoverConfigScreen
 import io.legado.app.ui.config.otherConfig.OtherConfigScreen
 import io.legado.app.ui.config.readConfig.ReadConfigScreen
-import io.legado.app.ui.config.themeConfig.ThemeConfig
+import io.legado.app.ui.config.themeConfig.ThemeConfigScreen
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.SplicedColumnGroup
 import io.legado.app.ui.widget.components.button.TopbarNavigationButton
 import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
-import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
 import kotlinx.serialization.Serializable
 
 class TestConfigActivity : BaseComposeActivity() {
@@ -44,6 +43,9 @@ class TestConfigActivity : BaseComposeActivity() {
     @Serializable
     object CoverConfigRoute
 
+    @Serializable
+    object ThemeConfigRoute
+
     @Composable
     override fun Content() {
         val navController = rememberNavController()
@@ -57,7 +59,8 @@ class TestConfigActivity : BaseComposeActivity() {
                     onBackClick = { finish() },
                     onNavigateToOther = { navController.navigate(OtherConfigRoute) },
                     onNavigateToRead = { navController.navigate(ReadConfigRoute) },
-                    onNavigateToCover = { navController.navigate(CoverConfigRoute) }
+                    onNavigateToCover = { navController.navigate(CoverConfigRoute) },
+                    onNavigateToTheme = { navController.navigate(ThemeConfigRoute) }
                 )
             }
 
@@ -72,6 +75,10 @@ class TestConfigActivity : BaseComposeActivity() {
             composable<CoverConfigRoute> {
                 CoverConfigScreen(onBackClick = { navController.popBackStack() })
             }
+
+            composable<ThemeConfigRoute> {
+                ThemeConfigScreen(onBackClick = { navController.popBackStack() })
+            }
         }
     }
 
@@ -81,11 +88,12 @@ class TestConfigActivity : BaseComposeActivity() {
         onBackClick: () -> Unit,
         onNavigateToOther: () -> Unit,
         onNavigateToRead: () -> Unit,
-        onNavigateToCover: () -> Unit
+        onNavigateToCover: () -> Unit,
+        onNavigateToTheme: () -> Unit
     ) {
         val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
 
-        Scaffold(
+        AppScaffold(
             modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
             topBar = {
                 GlassMediumFlexibleTopAppBar(
@@ -105,10 +113,9 @@ class TestConfigActivity : BaseComposeActivity() {
                     .padding(16.dp)
             ) {
                 SplicedColumnGroup {
-                    SwitchSettingItem(
-                        title = "使用折叠应用栏",
-                        checked = ThemeConfig.useFlexibleTopAppBar,
-                        onCheckedChange = { ThemeConfig.useFlexibleTopAppBar = it }
+                    ClickableSettingItem(
+                        title = stringResource(R.string.theme_setting),
+                        onClick = onNavigateToTheme
                     )
                     ClickableSettingItem(
                         title = stringResource(R.string.other_setting),

+ 3 - 3
app/src/main/java/io/legado/app/ui/config/coverConfig/CoverConfigScreen.kt

@@ -12,7 +12,6 @@ import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -27,6 +26,7 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.constant.PreferKey
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.SplicedColumnGroup
@@ -48,7 +48,7 @@ fun CoverConfigScreen(
     var manageKey by remember { mutableStateOf<String?>(null) }
     var showColorPickerByField by remember { mutableStateOf<String?>(null) }
 
-    Scaffold(
+    AppScaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             GlassMediumFlexibleTopAppBar(
@@ -282,4 +282,4 @@ fun CoverConfigScreen(
             }
         )
     }
-}
+}

+ 3 - 3
app/src/main/java/io/legado/app/ui/config/otherConfig/OtherConfigScreen.kt

@@ -13,7 +13,6 @@ import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.TextField
@@ -30,6 +29,7 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.service.WebService
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.SplicedColumnGroup
@@ -79,7 +79,7 @@ fun OtherConfigScreen(
         }
     }
 
-    Scaffold(
+    AppScaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             GlassMediumFlexibleTopAppBar(
@@ -505,4 +505,4 @@ fun ConfirmDialog(
             }
         }
     )
-}
+}

+ 3 - 3
app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt

@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -20,6 +19,7 @@ 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.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.SplicedColumnGroup
@@ -41,7 +41,7 @@ fun ReadConfigScreen(
     val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
     var showPageKeySheet by remember { mutableStateOf(false) }
 
-    Scaffold(
+    AppScaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             GlassMediumFlexibleTopAppBar(
@@ -326,4 +326,4 @@ fun ReadConfigScreen(
     if (showPageKeySheet) {
         PageKeySheet(onDismissRequest = { showPageKeySheet = false })
     }
-}
+}

+ 153 - 0
app/src/main/java/io/legado/app/ui/config/themeConfig/BackgroundImageManageSheet.kt

@@ -0,0 +1,153 @@
+package io.legado.app.ui.config.themeConfig
+
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
+import io.legado.app.R
+import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import kotlinx.coroutines.launch
+import org.koin.androidx.compose.koinViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun BackgroundImageManageSheet(
+    preferenceKey: String,
+    isDarkTheme: Boolean,
+    onDismissRequest: () -> Unit,
+    viewModel: ThemeConfigViewModel = koinViewModel()
+) {
+    val scope = rememberCoroutineScope()
+    var showFilePicker by remember { mutableStateOf(false) }
+
+    val selectImage =
+        rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
+            uri?.let {
+                scope.launch {
+                    viewModel.setBackgroundFromUri(
+                        uri = it,
+                        preferenceKey = preferenceKey
+                    )
+                }
+            }
+        }
+
+    val currentPath = if (isDarkTheme) {
+        ThemeConfig.bgImageDark
+    } else {
+        ThemeConfig.bgImageLight
+    }
+
+    GlassModalBottomSheet(
+        onDismissRequest = onDismissRequest
+    ) {
+        Column(
+            modifier = Modifier
+                .fillMaxWidth()
+                .padding(bottom = 24.dp)
+        ) {
+            Text(
+                text = stringResource(R.string.background_image),
+                style = MaterialTheme.typography.titleMedium,
+                modifier = Modifier.padding(16.dp)
+            )
+
+            if (currentPath.isNullOrBlank()) {
+                Surface(
+                    onClick = { showFilePicker = true },
+                    shape = MaterialTheme.shapes.medium,
+                    color = MaterialTheme.colorScheme.surfaceContainerHigh,
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .aspectRatio(16f / 9f)
+                        .padding(16.dp)
+                ) {
+                    Box(contentAlignment = Alignment.Center) {
+                        Icon(
+                            imageVector = Icons.Default.Add,
+                            contentDescription = "Add",
+                            modifier = Modifier.size(48.dp),
+                            tint = MaterialTheme.colorScheme.primary
+                        )
+                    }
+                }
+            } else {
+                Box(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(16.dp)
+                ) {
+                    Surface(
+                        shape = MaterialTheme.shapes.medium,
+                        tonalElevation = 2.dp
+                    ) {
+                        AsyncImage(
+                            model = currentPath,
+                            contentDescription = null,
+                            modifier = Modifier
+                                .fillMaxWidth()
+                                .aspectRatio(9f / 16f),
+                            contentScale = ContentScale.Crop
+                        )
+                    }
+                    IconButton(
+                        onClick = { viewModel.removeBackground(preferenceKey) },
+                        modifier = Modifier
+                            .align(Alignment.TopEnd)
+                            .padding(8.dp)
+                            .size(32.dp)
+                    ) {
+                        Surface(
+                            shape = CircleShape,
+                            color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.7f)
+                        ) {
+                            Icon(
+                                imageVector = Icons.Default.Close,
+                                contentDescription = "Remove",
+                                modifier = Modifier.size(20.dp)
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    if (showFilePicker) {
+        FilePickerSheet(
+            onDismissRequest = { showFilePicker = false },
+            onSelectSysFile = {
+                selectImage.launch("image/*")
+                showFilePicker = false
+            },
+            allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
+        )
+    }
+}

+ 26 - 5
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt

@@ -1,7 +1,10 @@
 package io.legado.app.ui.config.themeConfig
 
+import android.util.Log
+import io.legado.app.constant.EventBus
 import io.legado.app.constant.PreferKey
 import io.legado.app.ui.config.prefDelegate
+import io.legado.app.utils.postEvent
 
 object ThemeConfig {
 
@@ -19,16 +22,34 @@ object ThemeConfig {
 
     var isPureBlack by prefDelegate(PreferKey.pureBlack, false)
 
-    var bgImageLight by prefDelegate<String?>(PreferKey.bgImage, null)
+    var bgImageLight by prefDelegate<String?>(PreferKey.bgImage, null) {
+        postEvent(EventBus.RECREATE, false)
+    }
+
+    var bgImageDark by prefDelegate<String?>(PreferKey.bgImageN, null) {
+        postEvent(EventBus.RECREATE, false)
+    }
 
-    var bgImageDark by prefDelegate<String?>(PreferKey.bgImageN, null)
+    var bgImageBlurring by prefDelegate(PreferKey.bgImageBlurring, 0)
+
+    var bgImageNBlurring by prefDelegate(PreferKey.bgImageNBlurring, 0)
 
     fun hasImageBg(isDark: Boolean): Boolean {
-        return if (isDark) {
-            !bgImageDark.isNullOrEmpty()
+        val result = if (isDark) {
+            !bgImageDark.isNullOrBlank()
         } else {
-            !bgImageLight.isNullOrEmpty()
+            !bgImageLight.isNullOrBlank()
         }
+
+        Log.d(
+            "MainConfig",
+            "hasImageBg -> isDark=$isDark, " +
+                    "bgImageDark=$bgImageDark, " +
+                    "bgImageLight=$bgImageLight, " +
+                    "result=$result"
+        )
+
+        return result
     }
 
     /*

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

@@ -0,0 +1,170 @@
+package io.legado.app.ui.config.themeConfig
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import io.legado.app.R
+import io.legado.app.constant.PreferKey
+import io.legado.app.ui.widget.components.AppScaffold
+import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
+import io.legado.app.ui.widget.components.SplicedColumnGroup
+import io.legado.app.ui.widget.components.button.TopbarNavigationButton
+import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
+import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
+import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
+import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
+import org.koin.androidx.compose.koinViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ThemeConfigScreen(
+    onBackClick: () -> Unit,
+    viewModel: ThemeConfigViewModel = koinViewModel()
+) {
+    val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
+    var manageKey by remember { mutableStateOf<Pair<String, Boolean>?>(null) }
+
+    AppScaffold(
+        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+        topBar = {
+            GlassMediumFlexibleTopAppBar(
+                title = { Text(stringResource(R.string.theme_setting)) },
+                scrollBehavior = scrollBehavior,
+                navigationIcon = {
+                    TopbarNavigationButton(onClick = onBackClick)
+                }
+            )
+        }
+    ) { paddingValues ->
+        Column(
+            modifier = Modifier
+                .fillMaxSize()
+                .padding(paddingValues)
+                .verticalScroll(rememberScrollState())
+                .padding(16.dp)
+        ) {
+            SplicedColumnGroup {
+                SwitchSettingItem(
+                    title = stringResource(R.string.pure_black),
+                    checked = ThemeConfig.isPureBlack,
+                    onCheckedChange = { ThemeConfig.isPureBlack = it }
+                )
+                DropdownListSettingItem(
+                    title = stringResource(R.string.palette_style),
+                    selectedValue = ThemeConfig.paletteStyle,
+                    displayEntries = arrayOf(
+                        "柔和", "中性", "鲜艳", "表现力", "彩虹",
+                        "水果拼盘", "单色", "仿真", "内容取色"
+                    ),
+                    entryValues = arrayOf(
+                        "tonalSpot", "neutral", "vibrant", "expressive", "rainbow",
+                        "fruitSalad", "monochrome", "fidelity", "content"
+                    ),
+                    onValueChange = { ThemeConfig.paletteStyle = it }
+                )
+            }
+
+            SplicedColumnGroup(title = "Compose 相关") {
+                SwitchSettingItem(
+                    title = "使用折叠应用栏",
+                    checked = ThemeConfig.useFlexibleTopAppBar,
+                    onCheckedChange = { ThemeConfig.useFlexibleTopAppBar = it }
+                )
+                SliderSettingItem(
+                    title = stringResource(R.string.container_opacity),
+                    description = stringResource(
+                        R.string.container_opacity_summary,
+                        ThemeConfig.containerOpacity
+                    ),
+                    value = ThemeConfig.containerOpacity.toFloat(),
+                    defaultValue = 100f,
+                    valueRange = 0f..100f,
+                    steps = 99,
+                    onValueChange = { ThemeConfig.containerOpacity = it.toInt() }
+                )
+                SwitchSettingItem(
+                    title = stringResource(R.string.is_blur_enable),
+                    checked = ThemeConfig.enableBlur,
+                    onCheckedChange = { ThemeConfig.enableBlur = it }
+                )
+                if (ThemeConfig.enableBlur) {
+                    SwitchSettingItem(
+                        title = stringResource(R.string.is_blur_progressive_enable),
+                        checked = ThemeConfig.enableProgressiveBlur,
+                        onCheckedChange = { ThemeConfig.enableProgressiveBlur = it }
+                    )
+                }
+            }
+
+            SplicedColumnGroup(title = stringResource(R.string.day)) {
+                val hasLightBg = !ThemeConfig.bgImageLight.isNullOrBlank()
+                ClickableSettingItem(
+                    title = stringResource(R.string.background_image),
+                    description = if (hasLightBg) stringResource(R.string.click_to_delete) else stringResource(
+                        R.string.select_image
+                    ),
+                    onClick = { manageKey = Pair(PreferKey.bgImage, false) }
+                )
+
+                if (hasLightBg) {
+                    SliderSettingItem(
+                        title = stringResource(R.string.background_image_blurring),
+                        value = ThemeConfig.bgImageBlurring.toFloat(),
+                        defaultValue = 0f,
+                        valueRange = 0f..100f,
+                        steps = 99,
+                        onValueChange = {
+                            ThemeConfig.bgImageBlurring = it.toInt()
+                        }
+                    )
+                }
+            }
+
+            SplicedColumnGroup(title = stringResource(R.string.night)) {
+                val hasDarkBg = !ThemeConfig.bgImageDark.isNullOrBlank()
+                ClickableSettingItem(
+                    title = stringResource(R.string.background_image),
+                    description = if (hasDarkBg) stringResource(R.string.click_to_delete) else stringResource(
+                        R.string.select_image
+                    ),
+                    onClick = { manageKey = Pair(PreferKey.bgImageN, true) }
+                )
+
+                if (hasDarkBg) {
+                    SliderSettingItem(
+                        title = stringResource(R.string.background_image_blurring),
+                        value = ThemeConfig.bgImageNBlurring.toFloat(),
+                        defaultValue = 0f,
+                        valueRange = 0f..100f,
+                        steps = 99,
+                        onValueChange = {
+                            ThemeConfig.bgImageNBlurring = it.toInt()
+                        }
+                    )
+                }
+            }
+        }
+    }
+
+    manageKey?.let { (key, isDark) ->
+        BackgroundImageManageSheet(
+            preferenceKey = key,
+            isDarkTheme = isDark,
+            onDismissRequest = { manageKey = null }
+        )
+    }
+}

+ 80 - 0
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigViewModel.kt

@@ -0,0 +1,80 @@
+package io.legado.app.ui.config.themeConfig
+
+import android.net.Uri
+import androidx.lifecycle.ViewModel
+import io.legado.app.constant.PreferKey
+import io.legado.app.utils.FileDoc
+import io.legado.app.utils.FileUtils
+import io.legado.app.utils.MD5Utils
+import io.legado.app.utils.externalFiles
+import io.legado.app.utils.inputStream
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import splitties.init.appCtx
+import java.io.File
+import java.io.FileOutputStream
+
+class ThemeConfigViewModel : ViewModel() {
+
+    /**
+     * 设置背景图片
+     */
+    suspend fun setBackgroundFromUri(
+        uri: Uri,
+        preferenceKey: String
+    ) = withContext(Dispatchers.IO) {
+        kotlin.runCatching {
+            val fileDoc = FileDoc.fromUri(uri, false)
+            val suffix = fileDoc.name.substringAfterLast(".", "jpg")
+
+            // 计算MD5作为文件名
+            val md5 = uri.inputStream(appCtx).getOrThrow().use {
+                MD5Utils.md5Encode(it)
+            }
+            val fileName = "$md5.$suffix"
+
+            val folder = File(appCtx.externalFiles, preferenceKey)
+            val file = File(folder, fileName)
+
+            if (!file.exists()) {
+                FileUtils.createFileIfNotExist(file.absolutePath)
+                uri.inputStream(appCtx).getOrThrow().use { input ->
+                    FileOutputStream(file).use { output ->
+                        input.copyTo(output)
+                    }
+                }
+            }
+
+            updateBackgroundPath(preferenceKey, file.absolutePath)
+        }.onFailure {
+            it.printStackTrace()
+        }
+    }
+
+    fun removeBackground(preferenceKey: String) {
+        updateBackgroundPath(preferenceKey, null)
+    }
+
+    /**
+     * 更新背景路径并清理旧文件
+     */
+    private fun updateBackgroundPath(preferenceKey: String, newPath: String?) {
+        val oldPath = when (preferenceKey) {
+            PreferKey.bgImage -> ThemeConfig.bgImageLight
+            PreferKey.bgImageN -> ThemeConfig.bgImageDark
+            else -> null
+        }
+
+        if (oldPath != newPath && oldPath != null) {
+            val oldFile = File(oldPath)
+            if (oldFile.absolutePath.contains(appCtx.externalFiles.absolutePath)) {
+                oldFile.delete()
+            }
+        }
+
+        when (preferenceKey) {
+            PreferKey.bgImage -> ThemeConfig.bgImageLight = newPath
+            PreferKey.bgImageN -> ThemeConfig.bgImageDark = newPath
+        }
+    }
+}

+ 33 - 20
app/src/main/java/io/legado/app/ui/dict/rule/DictRuleScreen.kt

@@ -14,10 +14,11 @@ import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.FileOpen
 import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.SnackbarHostState
@@ -50,11 +51,11 @@ import io.legado.app.ui.widget.components.DraggableSelectionHandler
 import io.legado.app.ui.widget.components.button.SmallIconButton
 import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
 import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
-import io.legado.app.ui.widget.components.filePicker.FilePickerSheetMode
 import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
 import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
 import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
 import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
+import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 import io.legado.app.ui.widget.components.rules.RuleEditFields
 import io.legado.app.ui.widget.components.rules.RuleEditSheet
 import io.legado.app.ui.widget.components.rules.RuleListScaffold
@@ -82,8 +83,11 @@ fun DictRuleScreen(
     var editingRule by remember { mutableStateOf<DictRule?>(null) }
     var showDeleteRuleDialog by remember { mutableStateOf<DictRule?>(null) }
     var showUrlInput by remember { mutableStateOf(false) }
+
     var showFilePickerSheet by remember { mutableStateOf(false) }
-    var filePickerMode by remember { mutableStateOf(FilePickerSheetMode.EXPORT) }
+    var showImportSheet by remember { mutableStateOf(false) }
+    var showExportSheet by remember { mutableStateOf(false) }
+
 
     val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
         viewModel.moveItemInList(from.index, to.index)
@@ -149,21 +153,38 @@ fun DictRuleScreen(
         )
     }
 
-    if (showFilePickerSheet) {
+    if (showExportSheet) {
         FilePickerSheet(
-            onDismissRequest = { showFilePickerSheet = false },
+            onDismissRequest = { showExportSheet = false },
+            title = stringResource(R.string.export),
             onSelectSysDir = {
-                showFilePickerSheet = false
+                showExportSheet = false
                 exportDoc.launch("exportDictRule.json")
             },
             onUpload = {
-                showFilePickerSheet = false
+                showExportSheet = false
                 viewModel.uploadSelectedRules(selectedIds, rules)
             },
             allowExtensions = arrayOf("json")
         )
     }
 
+    if (showImportSheet) {
+        FilePickerSheet(
+            onDismissRequest = { showImportSheet = false },
+            title = stringResource(R.string.import_dict_rule),
+            onSelectSysFile = { types ->
+                importDoc.launch(types)
+                showImportSheet = false
+            },
+            onManualInput = {
+                showUrlInput = true
+                showImportSheet = false
+            },
+            allowExtensions = arrayOf("json", "txt")
+        )
+    }
+
     (importState as? BaseImportUiState.Success<DictRule>)?.let { state ->
         BatchImportDialog(
             title = "导入词典规则",
@@ -288,19 +309,11 @@ fun DictRuleScreen(
         },
         snackbarHostState = snackbarHostState,
         dropDownMenuContent = { dismiss ->
-            DropdownMenuItem(
-                text = { Text("在线导入") },
-                onClick = { dismiss(); showUrlInput = true })
-            DropdownMenuItem(
-                text = { Text("本地导入") },
-                onClick = {
-                    dismiss(); importDoc.launch(
-                    arrayOf(
-                        "text/plain",
-                        "application/json"
-                    )
-                )
-                })
+            RoundDropdownMenuItem(
+                text = { Text(stringResource(R.string.import_str)) },
+                onClick = { showImportSheet = true; dismiss() },
+                leadingIcon = { Icon(Icons.Default.FileOpen, null) }
+            )
         }
     ) { paddingValues ->
         Box(

+ 5 - 5
app/src/main/java/io/legado/app/ui/main/MainScreen.kt

@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.pager.HorizontalPager
 import androidx.compose.foundation.pager.rememberPagerState
@@ -27,7 +26,6 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.NavigationBar
 import androidx.compose.material3.NavigationBarItem
 import androidx.compose.material3.NavigationBarItemDefaults
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
 import androidx.compose.material3.WideNavigationRail
 import androidx.compose.material3.WideNavigationRailItem
@@ -54,6 +52,7 @@ import io.legado.app.ui.main.explore.ExploreScreen
 import io.legado.app.ui.main.my.MyScreen
 import io.legado.app.ui.main.rss.RssScreen
 import io.legado.app.ui.theme.regularHazeEffect
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.GlassDefaults
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.startActivityForBook
@@ -159,20 +158,21 @@ fun MainScreen(
             }
         }
 
-        Scaffold(
+        AppScaffold(
             modifier = Modifier.weight(1f),
             bottomBar = {
                 if (!useRail && MainConfig.showBottomView) {
+                    val labelVisibilityMode = MainConfig.labelVisibilityMode
+                    val isUnlabeled = labelVisibilityMode == "unlabeled"
                     NavigationBar(
                         modifier = Modifier
                             .regularHazeEffect(state = hazeState)
-                            .heightIn(max = 84.dp),
+                            .height(if (isUnlabeled) 64.dp else 80.dp),
                         containerColor = GlassDefaults.glassColor(
                             noBlurColor = BottomAppBarDefaults.containerColor,
                             blurAlpha = GlassDefaults.DefaultBlurAlpha
                         )
                     ) {
-                        val labelVisibilityMode = MainConfig.labelVisibilityMode
                         val alwaysShowLabel = when (labelVisibilityMode) {
                             "labeled" -> true
                             "selected" -> false

+ 4 - 5
app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt

@@ -24,14 +24,12 @@ import androidx.compose.material.icons.filled.Info
 import androidx.compose.material.icons.filled.Palette
 import androidx.compose.material.icons.filled.Settings
 import androidx.compose.material.icons.filled.Source
-import androidx.compose.material.icons.filled.Translate
 import androidx.compose.material.icons.filled.Web
 import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Switch
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -52,6 +50,7 @@ import io.legado.app.ui.config.ConfigTag
 import io.legado.app.ui.dict.rule.DictRuleActivity
 import io.legado.app.ui.file.FileManageActivity
 import io.legado.app.ui.replace.ReplaceRuleActivity
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.SplicedColumnGroup
@@ -68,7 +67,7 @@ fun MyScreen(
     val uiState by viewModel.uiState.collectAsState()
     val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
 
-    Scaffold(
+    AppScaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         contentWindowInsets = WindowInsets.systemBars
             .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top),
@@ -230,7 +229,7 @@ fun MyScreen(
                 )
                 SettingItem(
                     title = stringResource(R.string.dict_rule),
-                    imageVector = Icons.Default.Translate,
+                    imageVector = Icons.AutoMirrored.Filled.Rule,
                     onClick = {
                         onNavigate(
                             PrefClickEvent.StartActivity(DictRuleActivity::class.java)
@@ -281,4 +280,4 @@ fun MyScreen(
             Spacer(Modifier.height(120.dp))
         }
     }
-}
+}

+ 28 - 17
app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt

@@ -18,6 +18,7 @@ import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.FileOpen
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.ExperimentalMaterial3Api
@@ -69,7 +70,6 @@ import io.legado.app.ui.widget.components.GroupManageBottomSheet
 import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
 import io.legado.app.ui.widget.components.divider.PillDivider
 import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
-import io.legado.app.ui.widget.components.filePicker.FilePickerSheetMode
 import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
 import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
 import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
@@ -107,8 +107,9 @@ fun ReplaceRuleScreen(
     val hapticFeedback = LocalHapticFeedback.current
 
     var showUrlInput by remember { mutableStateOf(false) }
-    var showFilePickerSheet by remember { mutableStateOf(false) }
-    var filePickerMode by remember { mutableStateOf(FilePickerSheetMode.EXPORT) }
+    var showImportSheet by remember { mutableStateOf(false) }
+    var showExportSheet by remember { mutableStateOf(false) }
+
     var showDeleteRuleDialog by remember { mutableStateOf<ReplaceRule?>(null) }
     var showGroupManageSheet by remember { mutableStateOf(false) }
 
@@ -164,15 +165,31 @@ fun ReplaceRuleScreen(
         )
     }
 
-    if (showFilePickerSheet) {
+    if (showImportSheet) {
         FilePickerSheet(
-            onDismissRequest = { showFilePickerSheet = false },
+            onDismissRequest = { showImportSheet = false },
+            title = stringResource(R.string.import_replace_rule),
+            onSelectSysFile = { types ->
+                importDoc.launch(types)
+                showImportSheet = false
+            },
+            onManualInput = {
+                showUrlInput = true
+                showImportSheet = false
+            },
+            allowExtensions = arrayOf("json", "txt")
+        )
+    }
+
+    if (showExportSheet) {
+        FilePickerSheet(
+            onDismissRequest = { showExportSheet = false },
             onSelectSysDir = {
-                showFilePickerSheet = false
+                showExportSheet = false
                 exportDoc.launch("exportReplaceRule.json")
             },
             onUpload = {
-                showFilePickerSheet = false
+                showExportSheet = false
                 viewModel.uploadSelectedRules(selectedIds, rules)
             },
             allowExtensions = arrayOf("json")
@@ -320,7 +337,7 @@ fun ReplaceRuleScreen(
             ),
             ActionItem(
                 text = stringResource(R.string.export),
-                onClick = { showFilePickerSheet = true }
+                onClick = { showExportSheet = true }
             )
         ),
         onDeleteSelected = { ids ->
@@ -389,15 +406,9 @@ fun ReplaceRuleScreen(
         snackbarHostState = snackbarHostState,
         dropDownMenuContent = { dismiss ->
             RoundDropdownMenuItem(
-                text = { Text("在线导入") },
-                onClick = {
-                    dismiss()
-                    showUrlInput = true // 触发输入框
-                }
-            )
-            RoundDropdownMenuItem(
-                text = { Text("本地导入") },
-                onClick = { importDoc.launch(arrayOf("text/plain", "application/json")); dismiss() }
+                text = { Text(stringResource(R.string.import_str)) },
+                onClick = { showImportSheet = true; dismiss() },
+                leadingIcon = { Icon(Icons.Default.FileOpen, null) }
             )
             RoundDropdownMenuItem(
                 text = { Text("分组管理") },

+ 3 - 3
app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditScreen.kt

@@ -52,7 +52,6 @@ import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.PlainTooltip
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.TooltipAnchorPosition
@@ -78,6 +77,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.zIndex
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.button.AlertButton
@@ -108,7 +108,7 @@ fun ReplaceEditScreen(
     val isKeyboardVisible by keyboardAsState()
 
     with(sharedTransitionScope) {
-        Scaffold(
+        AppScaffold(
             modifier = Modifier
                 .nestedScroll(scrollBehavior.nestedScrollConnection)
                 .sharedBounds(
@@ -487,4 +487,4 @@ fun QuickInputBar(
             )
         }
     }
-}
+}

+ 42 - 0
app/src/main/java/io/legado/app/ui/theme/AppBackground.kt

@@ -0,0 +1,42 @@
+package io.legado.app.ui.theme
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.blur
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
+import io.legado.app.ui.config.themeConfig.ThemeConfig
+
+@Composable
+fun AppBackground(
+    darkTheme: Boolean,
+    content: @Composable () -> Unit
+) {
+    val hasImageBg = ThemeConfig.hasImageBg(darkTheme)
+    val bgImagePath = if (darkTheme) ThemeConfig.bgImageDark else ThemeConfig.bgImageLight
+    val blur = if (darkTheme) {
+        ThemeConfig.bgImageNBlurring
+    } else {
+        ThemeConfig.bgImageBlurring
+    }
+
+    Box(modifier = Modifier.fillMaxSize()) {
+
+        if (hasImageBg && !bgImagePath.isNullOrBlank()) {
+            AsyncImage(
+                model = bgImagePath,
+                contentDescription = null,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .blur(blur.dp),
+                //TODO:低版本安卓使用coil-transformations
+                contentScale = ContentScale.Crop
+            )
+        }
+
+        content()
+    }
+}

+ 7 - 6
app/src/main/java/io/legado/app/ui/theme/AppTheme.kt

@@ -20,17 +20,15 @@ fun AppTheme(
     val context = LocalContext.current
     val appThemeMode = ThemeResolver.resolveThemeMode(ThemeConfig.appTheme)
     val isPureBlack = ThemeConfig.isPureBlack
-    val hasImageBg = ThemeConfig.hasImageBg(darkTheme)
     val paletteStyle = ThemeConfig.paletteStyle
 
     val colorScheme =
-        remember(context, appThemeMode, darkTheme, isPureBlack, hasImageBg, paletteStyle) {
+        remember(context, appThemeMode, darkTheme, isPureBlack, paletteStyle) {
             ThemeManager.getColorScheme(
                 context = context,
                 mode = appThemeMode,
                 darkTheme = darkTheme,
                 isAmoled = isPureBlack,
-                isImageBg = hasImageBg,
                 paletteStyle = paletteStyle
             )
         }
@@ -39,7 +37,10 @@ fun AppTheme(
         colorScheme = colorScheme,
         typography = Typography(),
         motionScheme = MotionScheme.expressive(),
-        shapes = Shapes(),
-        content = content
-    )
+        shapes = Shapes()
+    ) {
+        AppBackground(darkTheme = darkTheme) {
+            content()
+        }
+    }
 }

+ 0 - 1
app/src/main/java/io/legado/app/ui/theme/OpaqueColorScheme.kt

@@ -23,7 +23,6 @@ fun rememberOpaqueColorScheme(): ColorScheme {
             mode = appThemeMode,
             darkTheme = isDark,
             isAmoled = isPureBlack,
-            isImageBg = hasImageBg,
             paletteStyle = paletteStyle,
             forceOpaque = true
         )

+ 1 - 2
app/src/main/java/io/legado/app/ui/theme/ThemeManager.kt

@@ -44,7 +44,6 @@ object ThemeManager {
         mode: AppThemeMode,
         darkTheme: Boolean,
         isAmoled: Boolean,
-        isImageBg: Boolean,
         paletteStyle: String?,
         forceOpaque: Boolean = false
     ): ColorScheme {
@@ -82,7 +81,7 @@ object ThemeManager {
             )
         }
 
-        if (!forceOpaque && (isImageBg || actualMode == AppThemeMode.Transparent)) {
+        if (!forceOpaque && actualMode == AppThemeMode.Transparent) {
             return scheme.copy(
                 surface = Color.Transparent,
                 background = Color.Transparent,

+ 39 - 0
app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt

@@ -0,0 +1,39 @@
+package io.legado.app.ui.widget.components
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.material3.FabPosition
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.ScaffoldDefaults
+import androidx.compose.material3.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+
+@Composable
+fun AppScaffold(
+    modifier: Modifier = Modifier,
+    topBar: @Composable () -> Unit = {},
+    bottomBar: @Composable () -> Unit = {},
+    snackbarHost: @Composable () -> Unit = {},
+    floatingActionButton: @Composable () -> Unit = {},
+    floatingActionButtonPosition: FabPosition = FabPosition.End,
+    containerColor: Color = Color.Transparent,
+    contentColor: Color = contentColorFor(MaterialTheme.colorScheme.background),
+    contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
+    content: @Composable (PaddingValues) -> Unit
+) {
+    Scaffold(
+        modifier = modifier,
+        topBar = topBar,
+        bottomBar = bottomBar,
+        snackbarHost = snackbarHost,
+        floatingActionButton = floatingActionButton,
+        floatingActionButtonPosition = floatingActionButtonPosition,
+        containerColor = containerColor,
+        contentColor = contentColor,
+        contentWindowInsets = contentWindowInsets,
+        content = content
+    )
+}

+ 6 - 5
app/src/main/java/io/legado/app/ui/widget/components/GlassMediumFlexibleTopAppBar.kt

@@ -61,10 +61,9 @@ object GlassTopAppBarDefaults {
 
     @Composable
     fun glassColors(): TopAppBarColors {
-        val opacity = ThemeConfig.containerOpacity / 100f
 
         val containerColor = GlassDefaults.glassColor(
-            noBlurColor = MaterialTheme.colorScheme.surface.copy(alpha = opacity),
+            noBlurColor = MaterialTheme.colorScheme.surface,
             blurAlpha = GlassDefaults.TransparentAlpha
         )
 
@@ -76,15 +75,17 @@ object GlassTopAppBarDefaults {
 
         return TopAppBarDefaults.topAppBarColors(
             containerColor = containerColor,
-            scrolledContainerColor = scrolledContainerColor
+            scrolledContainerColor = scrolledContainerColor,
+            navigationIconContentColor = MaterialTheme.colorScheme.onSurface,
+            titleContentColor = MaterialTheme.colorScheme.onSurface,
+            actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant
         )
     }
 
     @Composable
     fun containerColor(): Color {
-        val opacity = ThemeConfig.containerOpacity / 100f
         return GlassDefaults.glassColor(
-            noBlurColor = MaterialTheme.colorScheme.surface.copy(alpha = opacity),
+            noBlurColor = MaterialTheme.colorScheme.surface,
             blurAlpha = GlassDefaults.TransparentAlpha
         )
     }

+ 3 - 6
app/src/main/java/io/legado/app/ui/widget/components/SectionHeader.kt

@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Surface
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -24,10 +23,8 @@ fun SectionHeader(
     // 根容器的 Padding
     contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
 ) {
-    Surface(
-        modifier = Modifier.fillMaxWidth()
-    ) {
-        Row(
+
+    Row(
             modifier = Modifier
                 .fillMaxWidth()
                 .padding(contentPadding),
@@ -39,5 +36,5 @@ fun SectionHeader(
                 it()
             }
         }
-    }
+
 }

+ 5 - 1
app/src/main/java/io/legado/app/ui/widget/components/SplicedColumnGroup.kt

@@ -1,5 +1,6 @@
 package io.legado.app.ui.widget.components
 
+import androidx.compose.animation.animateContentSize
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
@@ -11,6 +12,7 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import io.legado.app.ui.widget.components.card.GlassCard
 
@@ -39,10 +41,12 @@ fun SplicedColumnGroup(
 
         GlassCard(
             shape = RoundedCornerShape(16.dp),
-            colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
+            colors = CardDefaults.cardColors(containerColor = Color.Transparent),
             modifier = Modifier.fillMaxWidth()
         ) {
             Column(
+                modifier = Modifier
+                    .animateContentSize(),
                 verticalArrangement = Arrangement.spacedBy(2.dp)
             ) {
                 content()

+ 17 - 24
app/src/main/java/io/legado/app/ui/widget/components/card/GlassCard.kt

@@ -17,32 +17,25 @@ fun GlassCard(
     modifier: Modifier = Modifier,
     onClick: (() -> Unit)? = null,
     shape: Shape = CardDefaults.shape,
-    colors: CardColors = CardDefaults.cardColors(
-        containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = ThemeConfig.containerOpacity / 100f),
-        contentColor = MaterialTheme.colorScheme.onSecondaryContainer
-    ),
+    colors: CardColors? = null,
     elevation: CardElevation = CardDefaults.cardElevation(),
     border: BorderStroke? = null,
     content: @Composable ColumnScope.() -> Unit
 ) {
-    if (onClick != null) {
-        Card(
-            onClick = onClick,
-            modifier = modifier,
-            shape = shape,
-            colors = colors,
-            elevation = elevation,
-            border = border,
-            content = content
-        )
-    } else {
-        Card(
-            modifier = modifier,
-            shape = shape,
-            colors = colors,
-            elevation = elevation,
-            border = border,
-            content = content
-        )
-    }
+    val containerAlpha = ThemeConfig.containerOpacity / 100f
+
+    val finalColors = colors ?: CardDefaults.cardColors(
+        containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = containerAlpha),
+        contentColor = MaterialTheme.colorScheme.onSecondaryContainer
+    )
+
+    Card(
+        onClick = onClick ?: {},
+        modifier = modifier,
+        shape = shape,
+        colors = finalColors,
+        elevation = elevation,
+        border = border,
+        content = content
+    )
 }

+ 1 - 1
app/src/main/java/io/legado/app/ui/widget/components/filePicker/FilePickerSheet.kt

@@ -88,7 +88,7 @@ fun FilePickerSheet(
                 onSelectSysFiles?.let {
                     FilePickerOptionCard(
                         icon = Icons.AutoMirrored.Filled.InsertDriveFile,
-                        text = "系统多选",
+                        text = "多选项目",
                         onClick = { it(typesOfExtensions(allowExtensions)) }
                     )
                 }

+ 2 - 2
app/src/main/java/io/legado/app/ui/widget/components/importComponents/ImportComponents.kt

@@ -40,7 +40,6 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.OutlinedToggleButton
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.SnackbarHost
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.Text
@@ -61,6 +60,7 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.ui.widget.components.AnimatedText
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
 import kotlinx.coroutines.launch
 
@@ -141,7 +141,7 @@ fun <T> BatchImportDialog(
         onDismissRequest = onDismissRequest,
         containerColor = { it.surfaceContainer }
     ) {
-        Scaffold(
+        AppScaffold(
             modifier = Modifier
                 .fillMaxWidth()
                 .heightIn(max = LocalConfiguration.current.screenHeightDp.dp * 0.8f),

+ 10 - 4
app/src/main/java/io/legado/app/ui/widget/components/list/ListScaffold.kt

@@ -19,7 +19,6 @@ import androidx.compose.material3.FloatingActionButton
 import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset
 import androidx.compose.material3.Icon
 import androidx.compose.material3.PlainTooltip
-import androidx.compose.material3.Scaffold
 import androidx.compose.material3.SnackbarHost
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.Text
@@ -39,6 +38,7 @@ import androidx.compose.ui.zIndex
 import dev.chrisbanes.haze.HazeState
 import io.legado.app.ui.theme.responsiveHazeEffect
 import io.legado.app.ui.theme.responsiveHazeSource
+import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.SelectionActions
 import io.legado.app.ui.widget.components.SelectionBottomBar
@@ -86,9 +86,15 @@ fun <T> ListScaffold(
     val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
     val hazeState = remember { HazeState() }
 
-    Scaffold(
+    AppScaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
-        snackbarHost = { SnackbarHost(snackbarHostState) },
+        snackbarHost = {
+            SnackbarHost(
+                hostState = snackbarHostState,
+                modifier = Modifier
+                    .padding(bottom = 72.dp)
+            )
+        },
         topBar = {
             Box(modifier = Modifier.responsiveHazeEffect(state = hazeState)) {
                 DynamicTopAppBar(
@@ -136,4 +142,4 @@ fun <T> ListScaffold(
             }
         }
     }
-}
+}

+ 14 - 2
app/src/main/java/io/legado/app/ui/widget/components/settingItem/CompactSettingItems.kt

@@ -15,6 +15,7 @@ import androidx.compose.material3.Slider
 import androidx.compose.material3.Switch
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
@@ -101,6 +102,7 @@ fun CompactSliderSettingItem(
     var expanded by remember { mutableStateOf(false) }
 
     var sliderValue by remember(value) { mutableFloatStateOf(value) }
+    var displayValue by remember(value) { mutableFloatStateOf(value) }
 
     SettingItem(
         title = title,
@@ -126,7 +128,7 @@ fun CompactSliderSettingItem(
                     cornerRadius = 8.dp,
                     horizontalPadding = 8.dp,
                     verticalPadding = 4.dp,
-                    text = value.toInt().toString(),
+                    text = displayValue.toInt().toString(),
                     backgroundColor = MaterialTheme.colorScheme.surfaceContainer,
                     contentColor = MaterialTheme.colorScheme.onSurface
                 )
@@ -144,7 +146,10 @@ fun CompactSliderSettingItem(
                 value = sliderValue,
                 onValueChange = {
                     sliderValue = it
-                    onValueChange(it)
+                    displayValue = it
+                },
+                onValueChangeFinished = {
+                    onValueChange(sliderValue)
                 },
                 valueRange = valueRange,
                 steps = steps,
@@ -152,6 +157,13 @@ fun CompactSliderSettingItem(
             )
         }
     )
+
+    LaunchedEffect(value) {
+        if (!expanded) {
+            sliderValue = value
+            displayValue = value
+        }
+    }
 }
 
 @Composable

+ 2 - 1
app/src/main/java/io/legado/app/ui/widget/components/swipe/SwipeActionContainer.kt

@@ -21,6 +21,7 @@ import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.hapticfeedback.HapticFeedbackType
@@ -103,7 +104,7 @@ fun SwipeActionContainer(
         content = {
             Surface(
                 modifier = Modifier.fillMaxSize(),
-                color = MaterialTheme.colorScheme.surface,
+                color = Color.Transparent,
                 shape = RectangleShape
             ) {
                 content()