Просмотр исходного кода

Merge branch 'miuix-dev'

# Conflicts:
#	app/version.properties
HapeLee 7 месяцев назад
Родитель
Сommit
2e7516245e
100 измененных файлов с 3973 добавлено и 2485 удалено
  1. 5 0
      app/build.gradle.kts
  2. 14 0
      app/src/main/java/io/legado/app/data/dao/BookDao.kt
  3. 9 12
      app/src/main/java/io/legado/app/ui/about/AboutScreen.kt
  4. 15 12
      app/src/main/java/io/legado/app/ui/about/AppLogSheet.kt
  5. 21 27
      app/src/main/java/io/legado/app/ui/book/bookmark/AllBookmarkScreen.kt
  6. 116 121
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt
  7. 8 17
      app/src/main/java/io/legado/app/ui/book/group/GroupEditSheet.kt
  8. 67 67
      app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookScreen.kt
  9. 11 11
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt
  10. 101 106
      app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt
  11. 17 20
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt
  12. 37 37
      app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt
  13. 87 96
      app/src/main/java/io/legado/app/ui/book/toc/rule/TxtRuleScreen.kt
  14. 3 4
      app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt
  15. 42 45
      app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigScreen.kt
  16. 14 14
      app/src/main/java/io/legado/app/ui/config/coverConfig/CoverConfigScreen.kt
  17. 17 15
      app/src/main/java/io/legado/app/ui/config/coverConfig/CoverManageSheet.kt
  18. 15 9
      app/src/main/java/io/legado/app/ui/config/coverConfig/CoverRuleConfigSheet.kt
  19. 10 7
      app/src/main/java/io/legado/app/ui/config/otherConfig/CheckSourceBottomSheet.kt
  20. 21 18
      app/src/main/java/io/legado/app/ui/config/otherConfig/DirectLinkUploadBottomSheet.kt
  21. 44 40
      app/src/main/java/io/legado/app/ui/config/otherConfig/OtherConfigScreen.kt
  22. 13 11
      app/src/main/java/io/legado/app/ui/config/readConfig/PageKeySheet.kt
  23. 7 9
      app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt
  24. 17 15
      app/src/main/java/io/legado/app/ui/config/themeConfig/BackgroundImageManageSheet.kt
  25. 8 5
      app/src/main/java/io/legado/app/ui/config/themeConfig/LauncherIconPickerSheet.kt
  26. 4 1
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt
  27. 145 94
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt
  28. 19 19
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeListDialog.kt
  29. 10 9
      app/src/main/java/io/legado/app/ui/dict/rule/DictRuleEditSheet.kt
  30. 86 95
      app/src/main/java/io/legado/app/ui/dict/rule/DictRuleScreen.kt
  31. 4 4
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  32. 34 19
      app/src/main/java/io/legado/app/ui/main/MainDestination.kt
  33. 25 92
      app/src/main/java/io/legado/app/ui/main/MainScreen.kt
  34. 17 16
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt
  35. 4 0
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookShelfItem.kt
  36. 11 11
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfConfigSheet.kt
  37. 66 81
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt
  38. 39 57
      app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageSheet.kt
  39. 77 47
      app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt
  40. 88 59
      app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt
  41. 17 20
      app/src/main/java/io/legado/app/ui/main/rss/RssScreen.kt
  42. 76 108
      app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt
  43. 29 29
      app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditScreen.kt
  44. 10 17
      app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt
  45. 49 57
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceScreen.kt
  46. 14 13
      app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubScreen.kt
  47. 19 0
      app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt
  48. 261 11
      app/src/main/java/io/legado/app/ui/theme/AppTheme.kt
  49. 4 4
      app/src/main/java/io/legado/app/ui/theme/HazeStyle.kt
  50. 172 0
      app/src/main/java/io/legado/app/ui/theme/LegadoTheme.kt
  51. 3 2
      app/src/main/java/io/legado/app/ui/theme/ThemeManager.kt
  52. 20 0
      app/src/main/java/io/legado/app/ui/theme/ThemeResolver.kt
  53. 44 0
      app/src/main/java/io/legado/app/ui/theme/Typography.kt
  54. 48 0
      app/src/main/java/io/legado/app/ui/theme/hazeStyle/HazeLegado.kt
  55. 20 19
      app/src/main/java/io/legado/app/ui/widget/CollapsibleHeader.kt
  56. 56 0
      app/src/main/java/io/legado/app/ui/widget/components/AppFloatingActionButton.kt
  57. 93 0
      app/src/main/java/io/legado/app/ui/widget/components/AppNavigationBar.kt
  58. 70 46
      app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt
  59. 127 0
      app/src/main/java/io/legado/app/ui/widget/components/AppTextField.kt
  60. 3 2
      app/src/main/java/io/legado/app/ui/widget/components/EmptyMessageView.kt
  61. 0 113
      app/src/main/java/io/legado/app/ui/widget/components/GlassMediumFlexibleTopAppBar.kt
  62. 15 27
      app/src/main/java/io/legado/app/ui/widget/components/GroupManageBottomSheet.kt
  63. 34 0
      app/src/main/java/io/legado/app/ui/widget/components/IconSwitch.kt
  64. 32 102
      app/src/main/java/io/legado/app/ui/widget/components/SearchBarSection.kt
  65. 3 3
      app/src/main/java/io/legado/app/ui/widget/components/SelectionBottomBar.kt
  66. 25 16
      app/src/main/java/io/legado/app/ui/widget/components/SplicedColumnGroup.kt
  67. 52 0
      app/src/main/java/io/legado/app/ui/widget/components/ValueStepper.kt
  68. 23 31
      app/src/main/java/io/legado/app/ui/widget/components/bookmark/BookmarkEditSheet.kt
  69. 12 12
      app/src/main/java/io/legado/app/ui/widget/components/bookmark/BookmarkItem.kt
  70. 2 2
      app/src/main/java/io/legado/app/ui/widget/components/button/AlertButton.kt
  71. 4 4
      app/src/main/java/io/legado/app/ui/widget/components/button/AnimatedActionButton.kt
  72. 2 2
      app/src/main/java/io/legado/app/ui/widget/components/button/AnimatedTextButton.kt
  73. 74 0
      app/src/main/java/io/legado/app/ui/widget/components/button/AppButton.kt
  74. 61 0
      app/src/main/java/io/legado/app/ui/widget/components/button/AppIconButton.kt
  75. 57 0
      app/src/main/java/io/legado/app/ui/widget/components/button/MiuixButton.kt
  76. 132 51
      app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt
  77. 35 17
      app/src/main/java/io/legado/app/ui/widget/components/button/SmallTextButton.kt
  78. 80 52
      app/src/main/java/io/legado/app/ui/widget/components/button/TopBarButton.kt
  79. 104 32
      app/src/main/java/io/legado/app/ui/widget/components/card/GlassCard.kt
  80. 48 20
      app/src/main/java/io/legado/app/ui/widget/components/card/SelectionItemCard.kt
  81. 75 0
      app/src/main/java/io/legado/app/ui/widget/components/card/SettingCard.kt
  82. 20 23
      app/src/main/java/io/legado/app/ui/widget/components/card/TextCard.kt
  83. 4 3
      app/src/main/java/io/legado/app/ui/widget/components/checkBox/CheckboxItem.kt
  84. 3 5
      app/src/main/java/io/legado/app/ui/widget/components/cover/BookshelfCover.kt
  85. 11 9
      app/src/main/java/io/legado/app/ui/widget/components/dialog/ColorPickerSheet.kt
  86. 9 9
      app/src/main/java/io/legado/app/ui/widget/components/dialog/TextListInputDialog.kt
  87. 24 12
      app/src/main/java/io/legado/app/ui/widget/components/divider/PillDivider.kt
  88. 6 6
      app/src/main/java/io/legado/app/ui/widget/components/divider/PillHeaderDivider.kt
  89. 15 19
      app/src/main/java/io/legado/app/ui/widget/components/filePicker/FilePickerSheet.kt
  90. 10 9
      app/src/main/java/io/legado/app/ui/widget/components/heatmap/HeatmapCalendarComponents.kt
  91. 88 0
      app/src/main/java/io/legado/app/ui/widget/components/icon/AppIcon.kt
  92. 54 0
      app/src/main/java/io/legado/app/ui/widget/components/icon/AppIcons.kt
  93. 82 116
      app/src/main/java/io/legado/app/ui/widget/components/importComponents/ImportComponents.kt
  94. 2 1
      app/src/main/java/io/legado/app/ui/widget/components/lazylist/VerticalFastScroller.kt
  95. 11 24
      app/src/main/java/io/legado/app/ui/widget/components/list/ListScaffold.kt
  96. 42 18
      app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenu.kt
  97. 90 24
      app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenuItem.kt
  98. 138 0
      app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt
  99. 0 64
      app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/GlassModalBottomSheet.kt
  100. 11 9
      app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt

+ 5 - 0
app/build.gradle.kts

@@ -285,4 +285,9 @@ dependencies {
     implementation(libs.material.kolor)
     implementation(libs.haze.core)
     implementation(libs.haze.materials)
+    implementation(libs.miuix)
+    implementation(libs.miuix.icons)
+    implementation(libs.capsule)
+    implementation(libs.backdrop)
+    implementation(libs.timber)
 }

+ 14 - 0
app/src/main/java/io/legado/app/data/dao/BookDao.kt

@@ -82,6 +82,7 @@ interface BookDao {
             durChapterPos,
             latestChapterTitle,
             latestChapterTime,
+            lastCheckCount,
             totalChapterNum,
             durChapterIndex,
             type,
@@ -113,6 +114,7 @@ interface BookDao {
         durChapterPos,
         latestChapterTitle,
         latestChapterTime,
+        lastCheckCount,
         totalChapterNum,
         durChapterIndex,
         type,
@@ -141,6 +143,7 @@ interface BookDao {
             durChapterPos,
             latestChapterTitle,
             latestChapterTime,
+            lastCheckCount,
             totalChapterNum,
             durChapterIndex,
             type,
@@ -169,6 +172,7 @@ interface BookDao {
             durChapterPos,
             latestChapterTitle,
             latestChapterTime,
+            lastCheckCount,
             totalChapterNum,
             durChapterIndex,
             type,
@@ -202,6 +206,7 @@ interface BookDao {
             durChapterPos,
             latestChapterTitle,
             latestChapterTime,
+            lastCheckCount,
             totalChapterNum,
             durChapterIndex,
             type,
@@ -236,6 +241,7 @@ interface BookDao {
             durChapterPos,
             latestChapterTitle,
             latestChapterTime,
+            lastCheckCount,
             totalChapterNum,
             durChapterIndex,
             type,
@@ -265,6 +271,7 @@ interface BookDao {
             durChapterPos,
             latestChapterTitle,
             latestChapterTime,
+            lastCheckCount,
             totalChapterNum,
             durChapterIndex,
             type,
@@ -293,6 +300,7 @@ interface BookDao {
             durChapterPos,
             latestChapterTitle,
             latestChapterTime,
+            lastCheckCount,
             totalChapterNum,
             durChapterIndex,
             type,
@@ -321,6 +329,7 @@ interface BookDao {
             durChapterPos,
             latestChapterTitle,
             latestChapterTime,
+            lastCheckCount,
             totalChapterNum,
             durChapterIndex,
             type,
@@ -350,6 +359,7 @@ interface BookDao {
             durChapterPos,
             latestChapterTitle,
             latestChapterTime,
+            lastCheckCount,
             totalChapterNum,
             durChapterIndex,
             type,
@@ -378,6 +388,7 @@ interface BookDao {
             durChapterPos,
             latestChapterTitle,
             latestChapterTime,
+            lastCheckCount,
             totalChapterNum,
             durChapterIndex,
             type,
@@ -406,6 +417,7 @@ interface BookDao {
             durChapterPos,
             latestChapterTitle,
             latestChapterTime,
+            lastCheckCount,
             totalChapterNum,
             durChapterIndex,
             type,
@@ -434,6 +446,7 @@ interface BookDao {
             durChapterPos,
             latestChapterTitle,
             latestChapterTime,
+            lastCheckCount,
             totalChapterNum,
             durChapterIndex,
             type,
@@ -462,6 +475,7 @@ interface BookDao {
             durChapterPos,
             latestChapterTitle,
             latestChapterTime,
+            lastCheckCount,
             totalChapterNum,
             durChapterIndex,
             type,

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

@@ -15,9 +15,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 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.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -31,13 +28,15 @@ 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.theme.LegadoTheme
 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.card.TextCard
 import io.legado.app.ui.widget.components.settingItem.SettingItem
+import io.legado.app.ui.widget.components.text.AppText
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 
 @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
 @Composable
@@ -59,7 +58,7 @@ fun AboutScreen(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             GlassMediumFlexibleTopAppBar(
-                title = { Text(stringResource(R.string.about)) },
+                title = stringResource(R.string.about),
                 navigationIcon = {
                     TopbarNavigationButton(onClick = onBack)
                 },
@@ -81,9 +80,9 @@ fun AboutScreen(
                     .width(160.dp)
                     .align(Alignment.CenterHorizontally)
             )
-            Text(
+            AppText(
                 text = stringResource(R.string.app_name),
-                style = MaterialTheme.typography.bodyLarge,
+                style = LegadoTheme.typography.bodyLarge,
                 fontSize = 18.sp,
                 fontWeight = FontWeight.Bold,
                 modifier = Modifier
@@ -91,16 +90,14 @@ fun AboutScreen(
                     .wrapContentWidth(Alignment.CenterHorizontally))
             TextCard(
                 text = versionName,
-                backgroundColor = colorScheme.tertiaryContainer,
-                contentColor = colorScheme.onTertiaryContainer,
                 cornerRadius = 8.dp,
                 modifier = Modifier
                     .fillMaxWidth()
                     .wrapContentWidth(Alignment.CenterHorizontally)
                     .padding(vertical = 4.dp))
-            Text(
+            AppText(
                 text = stringResource(R.string.about_description),
-                style = MaterialTheme.typography.bodyLarge,
+                style = LegadoTheme.typography.bodyLarge,
                 fontSize = 12.sp,
                 modifier = Modifier
                     .fillMaxWidth()

+ 15 - 12
app/src/main/java/io/legado/app/ui/about/AppLogSheet.kt

@@ -18,7 +18,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -31,19 +30,23 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.constant.AppLog
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.LogUtils
 import java.util.Date
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun AppLogSheet(
+    show: Boolean,
     onDismissRequest: () -> Unit
 ) {
     var logs by remember { mutableStateOf(AppLog.logs) }
     var showStackTrace by remember { mutableStateOf<String?>(null) }
 
-    GlassModalBottomSheet(
+    AppModalBottomSheet(
+        show = show,
         onDismissRequest = onDismissRequest
     ) {
         Row(
@@ -52,9 +55,9 @@ fun AppLogSheet(
                 .padding(horizontal = 16.dp, vertical = 8.dp),
             verticalAlignment = Alignment.CenterVertically
         ) {
-            Text(
+            AppText(
                 text = stringResource(R.string.log),
-                style = MaterialTheme.typography.titleLarge,
+                style = LegadoTheme.typography.titleLarge,
                 modifier = Modifier.weight(1f)
             )
             IconButton(onClick = {
@@ -82,17 +85,17 @@ fun AppLogSheet(
     showStackTrace?.let { stackTrace ->
         AlertDialog(
             onDismissRequest = { showStackTrace = null },
-            title = { Text("Log") },
+            title = { AppText("Log") },
             text = {
                 SelectionContainer {
                     Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
-                        Text(text = stackTrace, style = MaterialTheme.typography.bodySmall)
+                        AppText(text = stackTrace, style = LegadoTheme.typography.bodySmall)
                     }
                 }
             },
             confirmButton = {
                 TextButton(onClick = { showStackTrace = null }) {
-                    Text(stringResource(android.R.string.ok))
+                    AppText(stringResource(android.R.string.ok))
                 }
             }
         )
@@ -110,14 +113,14 @@ private fun LogItem(
             .clickable(onClick = onClick)
             .padding(16.dp)
     ) {
-        Text(
+        AppText(
             text = LogUtils.logTimeFormat.format(Date(item.first)),
-            style = MaterialTheme.typography.labelMedium,
+            style = LegadoTheme.typography.labelMedium,
             color = MaterialTheme.colorScheme.outline
         )
-        Text(
+        AppText(
             text = item.second,
-            style = MaterialTheme.typography.bodyMedium,
+            style = LegadoTheme.typography.bodyMedium,
             modifier = Modifier.padding(top = 4.dp)
         )
     }

+ 21 - 27
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.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
@@ -41,8 +40,6 @@ 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
 import io.legado.app.ui.widget.components.SearchBarSection
 import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet
 import io.legado.app.ui.widget.components.bookmark.BookmarkItem
@@ -51,6 +48,8 @@ import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
 import io.legado.app.ui.widget.components.lazylist.Scroller
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import org.koin.androidx.compose.koinViewModel
 
 @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class,
@@ -98,11 +97,7 @@ fun AllBookmarkScreen(
         topBar = {
             Column {
                 GlassMediumFlexibleTopAppBar(
-                    title = {
-                        Text(
-                            text = "所有书签"
-                        )
-                    },
+                    title = "所有书签",
                     scrollBehavior = scrollBehavior,
                     navigationIcon = {
                         TopbarNavigationButton(onClick = onBack)
@@ -132,7 +127,7 @@ fun AllBookmarkScreen(
                             onDismissRequest = { showMenu = false }
                         ) {
                             RoundDropdownMenuItem(
-                                text = { Text("导出 JSON") },
+                                text = "导出 JSON",
                                 onClick = {
                                     showMenu = false
                                     pendingExportIsMd = false
@@ -140,7 +135,7 @@ fun AllBookmarkScreen(
                                 }
                             )
                             RoundDropdownMenuItem(
-                                text = { Text("导出 Markdown") },
+                                text = "导出 Markdown",
                                 onClick = {
                                     showMenu = false
                                     pendingExportIsMd = true
@@ -242,22 +237,21 @@ fun AllBookmarkScreen(
             }
         }
 
-        if (showBottomSheet && editingBookmark != null) {
-            BookmarkEditSheet(
-                bookmark = editingBookmark!!,
-                onDismiss = {
-                    showBottomSheet = false
-                    editingBookmark = null
-                },
-                onSave = { updatedBookmark ->
-                    viewModel.updateBookmark(updatedBookmark)
-                    showBottomSheet = false
-                },
-                onDelete = { bookmarkToDelete ->
-                    viewModel.deleteBookmark(bookmarkToDelete)
-                    showBottomSheet = false
-                }
-            )
-        }
+        BookmarkEditSheet(
+            show = showBottomSheet && editingBookmark != null,
+            bookmark = editingBookmark ?: Bookmark(),
+            onDismiss = {
+                showBottomSheet = false
+                editingBookmark = null
+            },
+            onSave = { updatedBookmark ->
+                viewModel.updateBookmark(updatedBookmark)
+                showBottomSheet = false
+            },
+            onDelete = { bookmarkToDelete ->
+                viewModel.deleteBookmark(bookmarkToDelete)
+                showBottomSheet = false
+            }
+        )
     }
 }

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

@@ -52,7 +52,6 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Slider
 import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
 import androidx.compose.material3.pulltorefresh.PullToRefreshBox
 import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
 import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
@@ -79,12 +78,10 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.data.entities.rule.ExploreKind
 import io.legado.app.model.BookShelfState
+import io.legado.app.ui.theme.LegadoTheme
 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
 import io.legado.app.ui.widget.components.button.AnimatedTextButton
 import io.legado.app.ui.widget.components.button.TopBarActionButton
@@ -93,7 +90,10 @@ import io.legado.app.ui.widget.components.card.TextCard
 import io.legado.app.ui.widget.components.cover.Cover
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import org.koin.androidx.compose.koinViewModel
@@ -180,112 +180,111 @@ fun ExploreShowScreen(
         }
     }
 
-    if (showGridCountSheet) {
 
-        GlassModalBottomSheet(
+    AppModalBottomSheet(
+        show = showGridCountSheet,
+        modifier = Modifier
+            .padding(16.dp),
+        onDismissRequest = { showGridCountSheet = false }
+    ) {
+        Row(
             modifier = Modifier
-                .padding(16.dp),
-            onDismissRequest = { showGridCountSheet = false }
+                .fillMaxWidth()
+                .padding(bottom = 16.dp),
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.Center
         ) {
-            Row(
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .padding(bottom = 16.dp),
-                verticalAlignment = Alignment.CenterVertically,
-                horizontalArrangement = Arrangement.Center
-            ) {
-                Text(
-                    text = "布局列数",
-                    style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)
-                )
-
-                Spacer(modifier = Modifier.width(12.dp))
+            AppText(
+                text = "布局列数",
+                style = LegadoTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)
+            )
 
-                TextCard(
-                    text = "$gridColumnCount 列",
-                    textStyle = MaterialTheme.typography.titleSmall,
-                    verticalPadding = 4.dp,
-                    horizontalPadding = 12.dp,
-                    cornerRadius = 12.dp
-                )
-            }
+            Spacer(modifier = Modifier.width(12.dp))
 
-            Slider(
-                value = gridColumnCount.toFloat(),
-                onValueChange = {
-                    val col = it.toInt().coerceIn(1, 10)
-                    viewModel.saveGridCount(col)
-                },
-                valueRange = 1f..10f,
-                steps = 8,
-                modifier = Modifier.padding(horizontal = 20.dp)
+            TextCard(
+                text = "$gridColumnCount 列",
+                textStyle = LegadoTheme.typography.titleSmall,
+                verticalPadding = 4.dp,
+                horizontalPadding = 12.dp,
+                cornerRadius = 12.dp
             )
+        }
 
-            Spacer(Modifier.height(24.dp))
+        Slider(
+            value = gridColumnCount.toFloat(),
+            onValueChange = {
+                val col = it.toInt().coerceIn(1, 10)
+                viewModel.saveGridCount(col)
+            },
+            valueRange = 1f..10f,
+            steps = 8,
+            modifier = Modifier.padding(horizontal = 20.dp)
+        )
 
-            OutlinedButton(
-                onClick = { showGridCountSheet = false },
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .padding(horizontal = 16.dp)
-            ) {
-                Text("完成")
-            }
+        Spacer(Modifier.height(24.dp))
+
+        OutlinedButton(
+            onClick = { showGridCountSheet = false },
+            modifier = Modifier
+                .fillMaxWidth()
+                .padding(horizontal = 16.dp)
+        ) {
+            AppText("完成")
         }
     }
 
-    if (showKindSheet) {
-        GlassModalBottomSheet(
-            onDismissRequest = { showKindSheet = false }
-        ) {
 
-            var kindQuery by remember { mutableStateOf("") }
+    AppModalBottomSheet(
+        show = showKindSheet,
+        onDismissRequest = { showKindSheet = false }
+    ) {
+
+        var kindQuery by remember { mutableStateOf("") }
 
-            SearchBarSection(
-                query = kindQuery,
-                backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh,
-                onQueryChange = { kindQuery = it },
-                placeholder = "选择或搜索分类",
-            )
+        SearchBarSection(
+            query = kindQuery,
+            backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh,
+            onQueryChange = { kindQuery = it },
+            placeholder = "选择或搜索分类",
+        )
 
-            val filteredKinds = remember(kindQuery, kinds) {
-                if (kindQuery.isBlank()) kinds
-                else kinds.filter { kind ->
-                    kind.title.contains(kindQuery, ignoreCase = true) ||
-                            (kind.url?.contains(kindQuery, ignoreCase = true) == true)
-                }
+        val filteredKinds = remember(kindQuery, kinds) {
+            if (kindQuery.isBlank()) kinds
+            else kinds.filter { kind ->
+                kind.title.contains(kindQuery, ignoreCase = true) ||
+                        (kind.url?.contains(kindQuery, ignoreCase = true) == true)
             }
+        }
 
-            LazyVerticalGrid(
-                columns = GridCells.Fixed(3),
-                contentPadding = PaddingValues(16.dp),
-                horizontalArrangement = Arrangement.spacedBy(8.dp),
-                verticalArrangement = Arrangement.spacedBy(8.dp),
-                modifier = Modifier.weight(1f, fill = false)
-            ) {
-                itemsIndexed(
-                    items = filteredKinds,
-                    key = { index, kind -> "${kind.url ?: kind.title}_$index" },
-                    span = { _, kind ->
-                        val isClickable = !kind.url.isNullOrBlank()
-                        if (isClickable) GridItemSpan(1) else GridItemSpan(3)
-                    }
-                ) { _, kind ->
-                    KindGridItem(
-                        modifier = Modifier.animateItem(),
-                        kind = kind,
-                        currentTitle = selectedTitle ?: title,
-                        onClick = {
-                            showKindSheet = false
-                            viewModel.switchExploreUrl(kind)
-                        }
-                    )
+        LazyVerticalGrid(
+            columns = GridCells.Fixed(3),
+            contentPadding = PaddingValues(16.dp),
+            horizontalArrangement = Arrangement.spacedBy(8.dp),
+            verticalArrangement = Arrangement.spacedBy(8.dp),
+            modifier = Modifier.weight(1f, fill = false)
+        ) {
+            itemsIndexed(
+                items = filteredKinds,
+                key = { index, kind -> "${kind.url ?: kind.title}_$index" },
+                span = { _, kind ->
+                    val isClickable = !kind.url.isNullOrBlank()
+                    if (isClickable) GridItemSpan(1) else GridItemSpan(3)
                 }
+            ) { _, kind ->
+                KindGridItem(
+                    modifier = Modifier.animateItem(),
+                    kind = kind,
+                    currentTitle = selectedTitle ?: title,
+                    onClick = {
+                        showKindSheet = false
+                        viewModel.switchExploreUrl(kind)
+                    }
+                )
             }
-
         }
     }
 
+
     AppScaffold(
         modifier = Modifier
             .nestedScroll(scrollBehavior.nestedScrollConnection),
@@ -294,14 +293,7 @@ fun ExploreShowScreen(
                 modifier = Modifier.responsiveHazeEffect(
                     state = hazeState
                 ),
-                title = {
-                    AnimatedTextLine(
-                        selectedTitle ?: title,
-                        maxLines = 1,
-                        overflow = TextOverflow.Ellipsis
-                    )
-                },
-                colors = GlassTopAppBarDefaults.glassColors(),
+                title = selectedTitle ?: title,
                 navigationIcon = {
                     TopbarNavigationButton(onClick = onBack)
                 },
@@ -346,7 +338,7 @@ fun ExploreShowScreen(
                         onDismissRequest = { showMenu = false }
                     ) {
                         RoundDropdownMenuItem(
-                            text = { Text("全部显示") },
+                            text = "全部显示",
                             onClick = {
                                 viewModel.setFilterState(BookFilterState.SHOW_ALL)
                                 showMenu = false
@@ -358,7 +350,7 @@ fun ExploreShowScreen(
                         )
 
                         RoundDropdownMenuItem(
-                            text = { Text("隐藏已在书架的同源书籍") },
+                            text = "隐藏已在书架的同源书籍",
                             onClick = {
                                 viewModel.setFilterState(BookFilterState.HIDE_IN_SHELF)
                                 showMenu = false
@@ -370,7 +362,7 @@ fun ExploreShowScreen(
                         )
 
                         RoundDropdownMenuItem(
-                            text = { Text("隐藏已在书架的非同源书籍") },
+                            text = "隐藏已在书架的非同源书籍",
                             onClick = {
                                 viewModel.setFilterState(BookFilterState.HIDE_SAME_NAME_AUTHOR)
                                 showMenu = false
@@ -382,7 +374,7 @@ fun ExploreShowScreen(
                         )
 
                         RoundDropdownMenuItem(
-                            text = { Text("只显示不在书架的书籍") },
+                            text = "只显示不在书架的书籍",
                             onClick = {
                                 viewModel.setFilterState(BookFilterState.SHOW_NOT_IN_SHELF_ONLY)
                                 showMenu = false
@@ -509,7 +501,7 @@ fun KindGridItem(
         enabled = isClickable,
         selected = isSelected,
         label = {
-            Text(
+            AppText(
                 text = kind.title,
                 fontSize = 12.sp,
                 modifier = Modifier.fillMaxWidth(),
@@ -576,32 +568,32 @@ fun ExploreBookItem(
             .weight(1f)
             .align(Alignment.CenterVertically)) {
 
-            Text(
+            AppText(
                 text = book.name,
-                style = MaterialTheme.typography.titleSmall,
+                style = LegadoTheme.typography.titleSmall,
                 maxLines = 1,
                 overflow = TextOverflow.Ellipsis
             )
 
             Row {
-                Text(
+                AppText(
                     text = book.author,
-                    style = MaterialTheme.typography.bodySmall,
+                    style = LegadoTheme.typography.bodySmall,
                     maxLines = 1
                 )
 
                 val latestChapter = book.latestChapterTitle
                 if (!latestChapter.isNullOrEmpty()) {
-                    Text(
+                    AppText(
                         text = " • ",
-                        style = MaterialTheme.typography.bodySmall,
+                        style = LegadoTheme.typography.bodySmall,
                         color = Color.Gray,
                         maxLines = 1
                     )
 
-                    Text(
+                    AppText(
                         text = "最新: $latestChapter",
-                        style = MaterialTheme.typography.bodySmall,
+                        style = LegadoTheme.typography.bodySmall,
                         maxLines = 1,
                         overflow = TextOverflow.Ellipsis
                     )
@@ -612,9 +604,9 @@ fun ExploreBookItem(
 
             val intro = book.intro?.replace("\\s+".toRegex(), "") ?: ""
             if (intro.isNotEmpty()) {
-                Text(
+                AppText(
                     text = intro,
-                    style = MaterialTheme.typography.labelSmall,
+                    style = LegadoTheme.typography.labelSmall,
                     color = Color.Gray,
                     maxLines = 2,
                     minLines = 2,
@@ -654,9 +646,12 @@ fun ExploreBookGridItem(
 
     val content: (@Composable RowScope.() -> Unit)? = if (!badgeText.isNullOrBlank()) {
         {
-            Text(
+            AppText(
                 text = badgeText,
-                style = MaterialTheme.typography.labelSmall.copy(fontWeight = FontWeight.Bold, fontSize = 9.sp)
+                style = LegadoTheme.typography.labelSmall.copy(
+                    fontWeight = FontWeight.Bold,
+                    fontSize = 9.sp
+                )
             )
         }
     } else {
@@ -681,9 +676,9 @@ fun ExploreBookGridItem(
 
         Spacer(modifier = Modifier.height(4.dp))
 
-        Text(
+        AppText(
             text = book.name,
-            style = MaterialTheme.typography.bodySmall,
+            style = LegadoTheme.typography.bodySmall,
             fontWeight = FontWeight.Bold,
             maxLines = 2,
             overflow = TextOverflow.Ellipsis
@@ -698,10 +693,10 @@ fun TagChip(text: String) {
         color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f),
         shape = RoundedCornerShape(4.dp)
     ) {
-        Text(
+        AppText(
             text = text,
             modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp),
-            style = MaterialTheme.typography.labelSmall,
+            style = LegadoTheme.typography.labelSmall,
             color = MaterialTheme.colorScheme.primary
         )
     }
@@ -742,13 +737,13 @@ fun LoadMoreFooter(
                 },
                 label = "FooterTextChange"
             ) { text ->
-                Text(
+                AppText(
                     text = text,
                     color = when {
                         errorMsg != null -> Color.Red
                         else -> Color.Gray
                     },
-                    style = MaterialTheme.typography.bodySmall
+                    style = LegadoTheme.typography.bodySmall
                 )
             }
 

+ 8 - 17
app/src/main/java/io/legado/app/ui/book/group/GroupEditSheet.kt

@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
@@ -18,11 +17,9 @@ import androidx.compose.material.icons.filled.Restore
 import androidx.compose.material3.Button
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.OutlinedIconButton
 import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
@@ -39,9 +36,10 @@ import io.legado.app.R
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.ui.widget.components.cover.Cover
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
 import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem
 import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.FileUtils
 import io.legado.app.utils.MD5Utils
 import io.legado.app.utils.SelectImageContract
@@ -55,11 +53,12 @@ import java.io.FileOutputStream
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun GroupEditSheet(
+    show: Boolean,
     group: BookGroup? = null,
     onDismissRequest: () -> Unit,
     viewModel: GroupViewModel = koinViewModel()
 ) {
-    GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
+    AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
         GroupEditContent(
             group = group,
             onDismissRequest = onDismissRequest,
@@ -108,17 +107,9 @@ fun GroupEditContent(
     Column(
         modifier = Modifier
             .fillMaxWidth()
-            .padding(horizontal = 16.dp)
-            .padding(bottom = 16.dp)
             .verticalScroll(rememberScrollState()),
-        horizontalAlignment = Alignment.CenterHorizontally
+        horizontalAlignment = Alignment.CenterHorizontally,
     ) {
-        Text(
-            text = stringResource(R.string.group_edit),
-            style = MaterialTheme.typography.titleLarge,
-            modifier = Modifier.padding(vertical = 16.dp)
-        )
-
         Row(
             modifier = Modifier.fillMaxWidth(),
             verticalAlignment = Alignment.Top,
@@ -138,7 +129,7 @@ fun GroupEditContent(
                 OutlinedTextField(
                     value = groupName,
                     onValueChange = { groupName = it },
-                    label = { Text(stringResource(R.string.group_name)) },
+                    label = { AppText(stringResource(R.string.group_name)) },
                     modifier = Modifier.fillMaxWidth(),
                     singleLine = true
                 )
@@ -206,7 +197,7 @@ fun GroupEditContent(
 
             Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                 OutlinedButton(onClick = onDismissRequest) {
-                    Text(stringResource(R.string.cancel))
+                    AppText(stringResource(R.string.cancel))
                 }
                 Button(onClick = {
                     if (groupName.isEmpty()) {
@@ -235,7 +226,7 @@ fun GroupEditContent(
                         }
                     }
                 }) {
-                    Text(stringResource(R.string.ok))
+                    AppText(stringResource(R.string.ok))
                 }
             }
         }

+ 67 - 67
app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookScreen.kt

@@ -51,7 +51,6 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.RadioButton
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.pulltorefresh.PullToRefreshBox
 import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
@@ -80,6 +79,7 @@ import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Server
 import io.legado.app.help.config.AppConfig
 import io.legado.app.model.remote.RemoteBook
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.ActionItem
 import io.legado.app.ui.widget.components.EmptyMessageView
 import io.legado.app.ui.widget.components.SelectionActions
@@ -88,7 +88,8 @@ import io.legado.app.ui.widget.components.button.SmallTonalIconButton
 import io.legado.app.ui.widget.components.card.TextCard
 import io.legado.app.ui.widget.components.list.ListScaffold
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.ArchiveUtils
 import io.legado.app.utils.ConvertUtils
 import io.legado.app.utils.FileDoc
@@ -157,19 +158,19 @@ fun RemoteBookScreen(
             is RemoteBookDialog.DownloadArchive -> {
                 AlertDialog(
                     onDismissRequest = { dialogState = null },
-                    title = { Text(stringResource(R.string.draw)) },
-                    text = { Text(stringResource(R.string.archive_not_found)) },
+                    title = { AppText(stringResource(R.string.draw)) },
+                    text = { AppText(stringResource(R.string.archive_not_found)) },
                     confirmButton = {
                         OutlinedButton(onClick = {
                             scope.launch { viewModel.addToBookshelf(setOf(state.remoteBook)) }
                             dialogState = null
                         }) {
-                            Text(stringResource(android.R.string.ok))
+                            AppText(stringResource(android.R.string.ok))
                         }
                     },
                     dismissButton = {
                         TextButton(onClick = { dialogState = null }) {
-                            Text(stringResource(android.R.string.cancel))
+                            AppText(stringResource(android.R.string.cancel))
                         }
                     }
                 )
@@ -178,19 +179,19 @@ fun RemoteBookScreen(
             is RemoteBookDialog.ReImport -> {
                 AlertDialog(
                     onDismissRequest = { dialogState = null },
-                    title = { Text("是否重新加入书架?") },
-                    text = { Text("将会覆盖书籍") },
+                    title = { AppText("是否重新加入书架?") },
+                    text = { AppText("将会覆盖书籍") },
                     confirmButton = {
                         OutlinedButton(onClick = {
                             scope.launch { viewModel.addToBookshelf(setOf(state.remoteBook)) }
                             dialogState = null
                         }) {
-                            Text(stringResource(android.R.string.ok))
+                            AppText(stringResource(android.R.string.ok))
                         }
                     },
                     dismissButton = {
                         TextButton(onClick = { dialogState = null }) {
-                            Text(stringResource(android.R.string.cancel))
+                            AppText(stringResource(android.R.string.cancel))
                         }
                     }
                 )
@@ -198,42 +199,41 @@ fun RemoteBookScreen(
         }
     }
 
-    if (showSheet != null) {
-        GlassModalBottomSheet(
-            onDismissRequest = { showSheet = null }
-        ) {
-            when (val state = showSheet) {
-                is RemoteBookSheet.Servers -> {
-                    ServersSheetContent(
-                        servers = uiState.servers,
-                        selectedServerId = uiState.selectedServerId,
-                        onSelect = {
-                            viewModel.selectServer(it)
-                            showSheet = null
-                        },
-                        onEdit = { showSheet = RemoteBookSheet.ServerConfig(it) },
-                        onDelete = { viewModel.deleteServer(it) },
-                        onAdd = { showSheet = RemoteBookSheet.ServerConfig(null) },
-                        onDefault = {
-                            viewModel.selectServer(AppConst.DEFAULT_WEBDAV_ID)
-                            showSheet = null
-                        }
-                    )
-                }
-
-                is RemoteBookSheet.ServerConfig -> {
-                    ServerConfigSheetContent(
-                        server = state.server,
-                        onSave = {
-                            viewModel.saveServer(it)
-                            showSheet = RemoteBookSheet.Servers
-                        },
-                        onCancel = { showSheet = RemoteBookSheet.Servers }
-                    )
-                }
+    AppModalBottomSheet(
+        show = showSheet != null,
+        onDismissRequest = { showSheet = null }
+    ) {
+        when (val state = showSheet) {
+            is RemoteBookSheet.Servers -> {
+                ServersSheetContent(
+                    servers = uiState.servers,
+                    selectedServerId = uiState.selectedServerId,
+                    onSelect = {
+                        viewModel.selectServer(it)
+                        showSheet = null
+                    },
+                    onEdit = { showSheet = RemoteBookSheet.ServerConfig(it) },
+                    onDelete = { viewModel.deleteServer(it) },
+                    onAdd = { showSheet = RemoteBookSheet.ServerConfig(null) },
+                    onDefault = {
+                        viewModel.selectServer(AppConst.DEFAULT_WEBDAV_ID)
+                        showSheet = null
+                    }
+                )
+            }
 
-                else -> {}
+            is RemoteBookSheet.ServerConfig -> {
+                ServerConfigSheetContent(
+                    server = state.server,
+                    onSave = {
+                        viewModel.saveServer(it)
+                        showSheet = RemoteBookSheet.Servers
+                    },
+                    onCancel = { showSheet = RemoteBookSheet.Servers }
+                )
             }
+
+            else -> {}
         }
     }
 
@@ -259,7 +259,7 @@ fun RemoteBookScreen(
         },
         dropDownMenuContent = { dismiss ->
             RoundDropdownMenuItem(
-                text = { Text("按名称排序") },
+                text = "按名称排序",
                 onClick = {
                     viewModel.toggleSort(RemoteBookSort.Name)
                     dismiss()
@@ -271,7 +271,7 @@ fun RemoteBookScreen(
                 }
             )
             RoundDropdownMenuItem(
-                text = { Text("按时间排序") },
+                text = "按时间排序",
                 onClick = {
                     viewModel.toggleSort(RemoteBookSort.Default)
                     dismiss()
@@ -381,9 +381,9 @@ private fun ServersSheetContent(
             verticalAlignment = Alignment.CenterVertically,
             horizontalArrangement = Arrangement.SpaceBetween
         ) {
-            Text(
+            AppText(
                 text = stringResource(R.string.server_config),
-                style = MaterialTheme.typography.titleLarge
+                style = LegadoTheme.typography.titleLarge
             )
             IconButton(onClick = onAdd) {
                 Icon(Icons.Default.Add, contentDescription = "添加")
@@ -447,9 +447,9 @@ private fun ServerItem(
         ListItem(
             modifier = Modifier.animateContentSize(),
             headlineContent = {
-                Text(
+                AppText(
                     text = name,
-                    style = MaterialTheme.typography.titleMedium,
+                    style = LegadoTheme.typography.titleMedium,
                     maxLines = 1,
                     overflow = TextOverflow.Ellipsis
                 )
@@ -462,9 +462,9 @@ private fun ServerItem(
             },
             supportingContent = url?.let {
                 {
-                    Text(
+                    AppText(
                         text = it,
-                        style = MaterialTheme.typography.bodySmall,
+                        style = LegadoTheme.typography.bodySmall,
                         color = MaterialTheme.colorScheme.onSurfaceVariant,
                         maxLines = 1,
                         overflow = TextOverflow.Ellipsis
@@ -514,16 +514,16 @@ private fun ServerConfigSheetContent(
             .padding(16.dp)
             .verticalScroll(rememberScrollState())
     ) {
-        Text(
+        AppText(
             text = if (server == null) "添加服务器" else "编辑服务器",
-            style = MaterialTheme.typography.titleLarge,
+            style = LegadoTheme.typography.titleLarge,
             modifier = Modifier.padding(bottom = 16.dp)
         )
 
         OutlinedTextField(
             value = name,
             onValueChange = { name = it },
-            label = { Text("名称") },
+            label = { AppText("名称") },
             modifier = Modifier.fillMaxWidth(),
             singleLine = true
         )
@@ -531,7 +531,7 @@ private fun ServerConfigSheetContent(
         OutlinedTextField(
             value = url,
             onValueChange = { url = it },
-            label = { Text("URL") },
+            label = { AppText("URL") },
             modifier = Modifier.fillMaxWidth(),
             singleLine = true
         )
@@ -539,7 +539,7 @@ private fun ServerConfigSheetContent(
         OutlinedTextField(
             value = username,
             onValueChange = { username = it },
-            label = { Text("用户名") },
+            label = { AppText("用户名") },
             modifier = Modifier.fillMaxWidth(),
             singleLine = true
         )
@@ -547,7 +547,7 @@ private fun ServerConfigSheetContent(
         OutlinedTextField(
             value = password,
             onValueChange = { password = it },
-            label = { Text("密码") },
+            label = { AppText("密码") },
             modifier = Modifier.fillMaxWidth(),
             singleLine = true,
             visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
@@ -571,7 +571,7 @@ private fun ServerConfigSheetContent(
                 onClick = onCancel,
                 modifier = Modifier.weight(1f)
             ) {
-                Text(stringResource(android.R.string.cancel))
+                AppText(stringResource(android.R.string.cancel))
             }
             Button(
                 onClick = {
@@ -587,7 +587,7 @@ private fun ServerConfigSheetContent(
                 modifier = Modifier.weight(1f),
                 enabled = name.isNotBlank() && url.isNotBlank()
             ) {
-                Text("保存")
+                AppText("保存")
             }
         }
     }
@@ -625,9 +625,9 @@ private fun PathNavigationBar(
                 itemsIndexed(pathNames) { index, name ->
                     val isLast = index == pathNames.lastIndex
 
-                    Text(
+                    AppText(
                         text = name,
-                        style = MaterialTheme.typography.labelSmall,
+                        style = LegadoTheme.typography.labelSmall,
                         fontWeight = if (isLast) FontWeight.SemiBold else FontWeight.Medium,
                         color = if (isLast)
                             MaterialTheme.colorScheme.primary
@@ -716,9 +716,9 @@ private fun RemoteBookItem(
                 modifier = Modifier.weight(1f)
             ) {
 
-                Text(
+                AppText(
                     text = book.filename.substringBeforeLast("."),
-                    style = MaterialTheme.typography.bodyMedium,
+                    style = LegadoTheme.typography.bodyMedium,
                     maxLines = 1,
                     overflow = TextOverflow.Ellipsis
                 )
@@ -732,7 +732,7 @@ private fun RemoteBookItem(
                         if (book.contentType.isNotEmpty()) {
                             TextCard(
                                 text = book.contentType.uppercase(),
-                                textStyle = MaterialTheme.typography.labelSmall,
+                                textStyle = LegadoTheme.typography.labelSmall,
                                 horizontalPadding = 4.dp,
                                 verticalPadding = 2.dp,
                                 cornerRadius = 4.dp,
@@ -743,11 +743,11 @@ private fun RemoteBookItem(
                             Spacer(modifier = Modifier.width(6.dp))
                         }
 
-                        Text(
+                        AppText(
                             text = "${ConvertUtils.formatFileSize(book.size)} • ${
                                 AppConst.dateFormat.format(book.lastModify)
                             }",
-                            style = MaterialTheme.typography.labelMedium,
+                            style = LegadoTheme.typography.labelMedium,
                             color = MaterialTheme.colorScheme.onSurfaceVariant
                         )
                     }

+ 11 - 11
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.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
@@ -47,12 +46,13 @@ 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
 import io.legado.app.ui.widget.components.button.TopbarNavigationButton
 import io.legado.app.ui.widget.components.cover.Cover
 import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
+import io.legado.app.ui.widget.components.text.AppText
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.utils.SelectImageContract
 import io.legado.app.utils.launch
 import io.legado.app.utils.showDialogFragment
@@ -71,7 +71,7 @@ fun BookInfoEditScreen(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             GlassMediumFlexibleTopAppBar(
-                title = { Text(text = stringResource(id = R.string.book_info_edit)) },
+                title = stringResource(id = R.string.book_info_edit),
                 navigationIcon = {
                     TopbarNavigationButton(
                         onClick = onBack,
@@ -191,35 +191,35 @@ fun BookInfoEditContent(
         OutlinedTextField(
             value = uiState.name,
             onValueChange = { viewModel.onNameChange(it) },
-            label = { Text("书名") },
+            label = { AppText("书名") },
             modifier = Modifier.fillMaxWidth()
         )
         Spacer(modifier = Modifier.height(8.dp))
         OutlinedTextField(
             value = uiState.author,
             onValueChange = { viewModel.onAuthorChange(it) },
-            label = { Text("作者") },
+            label = { AppText("作者") },
             modifier = Modifier.fillMaxWidth()
         )
         Spacer(modifier = Modifier.height(8.dp))
         OutlinedTextField(
             value = uiState.coverUrl ?: "",
             onValueChange = { viewModel.onCoverUrlChange(it) },
-            label = { Text("封面链接") },
+            label = { AppText("封面链接") },
             modifier = Modifier.fillMaxWidth()
         )
         Spacer(modifier = Modifier.height(8.dp))
         OutlinedTextField(
             value = uiState.intro ?: "",
             onValueChange = { viewModel.onIntroChange(it) },
-            label = { Text("简介") },
+            label = { AppText("简介") },
             modifier = Modifier.fillMaxWidth()
         )
         Spacer(modifier = Modifier.height(8.dp))
         OutlinedTextField(
             value = uiState.remark ?: "",
             onValueChange = { viewModel.onRemarkChange(it) },
-            label = { Text("备注") },
+            label = { AppText("备注") },
             modifier = Modifier.fillMaxWidth()
         )
     }
@@ -251,7 +251,7 @@ fun BookTypeDropdown(
             state = textFieldState,
             readOnly = true,
             lineLimits = TextFieldLineLimits.SingleLine,
-            label = { Text("书籍类型") },
+            label = { AppText("书籍类型") },
             trailingIcon = {
                 ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
             },
@@ -269,7 +269,7 @@ fun BookTypeDropdown(
         ) {
             bookTypes.forEach { option ->
                 DropdownMenuItem(
-                    text = { Text(option) },
+                    text = { AppText(option) },
                     onClick = {
                         onTypeSelected(option)
                         expanded = false

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

@@ -32,18 +32,13 @@ import androidx.compose.material.icons.filled.Schedule
 import androidx.compose.material.icons.filled.Search
 import androidx.compose.material.icons.filled.Timeline
 import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Card
-import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.Checkbox
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.SnackbarHost
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -78,15 +73,15 @@ import androidx.compose.ui.zIndex
 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.theme.LegadoTheme
+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
 import io.legado.app.ui.widget.components.SearchBarSection
-import io.legado.app.ui.widget.components.SectionHeader
 import io.legado.app.ui.widget.components.button.AlertButton
+import io.legado.app.ui.widget.components.button.AppIconButton
 import io.legado.app.ui.widget.components.button.TopbarNavigationButton
+import io.legado.app.ui.widget.components.card.GlassCard
 import io.legado.app.ui.widget.components.cover.Cover
 import io.legado.app.ui.widget.components.heatmap.HeatmapCalendarTopBar
 import io.legado.app.ui.widget.components.heatmap.HeatmapConfig
@@ -98,9 +93,14 @@ import io.legado.app.ui.widget.components.heatmap.WeekdayLabelsColumn
 import io.legado.app.ui.widget.components.heatmap.rememberDateRange
 import io.legado.app.ui.widget.components.heatmap.rememberDaysInRange
 import io.legado.app.ui.widget.components.heatmap.rememberWeeks
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.icon.AppIcon
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
 import io.legado.app.ui.widget.components.swipe.SwipeAction
 import io.legado.app.ui.widget.components.swipe.SwipeActionContainer
+import io.legado.app.ui.widget.components.text.AnimatedTextLine
+import io.legado.app.ui.widget.components.text.AppText
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.utils.StringUtils.formatFriendlyDate
 import kotlinx.coroutines.launch
 import org.koin.androidx.compose.koinViewModel
@@ -148,11 +148,7 @@ fun ReadRecordScreen(
         topBar = {
             Column {
                 GlassMediumFlexibleTopAppBar(
-                    title = {
-                        Text(
-                            text = "阅读记录"
-                        )
-                    },
+                    title = "阅读记录",
                     subtitle = {
                         val subTitle = when (displayMode) {
                             DisplayMode.AGGREGATE -> "汇总视图"
@@ -167,7 +163,7 @@ fun ReadRecordScreen(
                         TopbarNavigationButton(onClick = onBackClick)
                     },
                     actions = {
-                        IconButton(onClick = {
+                        AppIconButton(onClick = {
                             val newMode = when (displayMode) {
                                 DisplayMode.AGGREGATE -> DisplayMode.TIMELINE
                                 DisplayMode.TIMELINE -> DisplayMode.LATEST
@@ -181,13 +177,16 @@ fun ReadRecordScreen(
                                 DisplayMode.LATEST -> Icons.AutoMirrored.Filled.List
                             }
                             val description = if (displayMode == DisplayMode.AGGREGATE) "Switch to Timeline" else "Switch to Aggregate"
-                            Icon(icon, description)
+                            AppIcon(icon, description)
                         }
-                        IconButton(onClick = { showCalendar = !showCalendar }) {
-                            Icon(Icons.Default.CalendarMonth, contentDescription = "Toggle Calendar")
+                        AppIconButton(onClick = { showCalendar = !showCalendar }) {
+                            AppIcon(
+                                Icons.Default.CalendarMonth,
+                                contentDescription = "Toggle Calendar"
+                            )
                         }
-                        IconButton(onClick = { showSearch = !showSearch }) {
-                            Icon(Icons.Default.Search, contentDescription = null)
+                        AppIconButton(onClick = { showSearch = !showSearch }) {
+                            AppIcon(Icons.Default.Search, contentDescription = null)
                         }
                     },
                     scrollBehavior = scrollBehavior
@@ -271,10 +270,10 @@ fun ReadRecordScreen(
             onDismissRequest = {
                 pendingDeleteAction = null
             },
-            title = { Text("确认删除") },
+            title = { AppText("确认删除") },
             text = {
                 Column {
-                    Text("确定要删除这条记录吗?")
+                    AppText("确定要删除这条记录吗?")
                     Spacer(modifier = Modifier.height(8.dp))
                     Row(
                         modifier = Modifier
@@ -289,10 +288,10 @@ fun ReadRecordScreen(
                         verticalAlignment = Alignment.CenterVertically
                     ) {
                         Checkbox(checked = skipDeleteConfirmTemp, onCheckedChange = null)
-                        Text(
+                        AppText(
                             text = "不再提示",
                             modifier = Modifier.padding(start = 8.dp),
-                            style = MaterialTheme.typography.bodyMedium
+                            style = LegadoTheme.typography.bodyMedium
                         )
                     }
                 }
@@ -311,30 +310,29 @@ fun ReadRecordScreen(
                 TextButton(onClick = {
                     pendingDeleteAction = null
                 }) {
-                    Text("取消")
+                    AppText("取消")
                 }
             }
         )
     }
 
-    if (showCalendar) {
-        GlassModalBottomSheet(
-            onDismissRequest = { showCalendar = false }
-        ) {
-            HeatmapCalendarSection(
-                dailyReadCounts = state.dailyReadCounts,
-                dailyReadTimes = state.dailyReadTimes,
-                selectedDate = state.selectedDate,
-                onDateSelected = { date ->
-                    viewModel.setSelectedDate(date)
-                    showCalendar = false
-                },
-                onClearDate = {
-                    viewModel.setSelectedDate(null)
-                    showCalendar = false
-                }
-            )
-        }
+    AppModalBottomSheet(
+        show = showCalendar,
+        onDismissRequest = { showCalendar = false }
+    ) {
+        HeatmapCalendarSection(
+            dailyReadCounts = state.dailyReadCounts,
+            dailyReadTimes = state.dailyReadTimes,
+            selectedDate = state.selectedDate,
+            onDateSelected = { date ->
+                viewModel.setSelectedDate(date)
+                showCalendar = false
+            },
+            onClearDate = {
+                viewModel.setSelectedDate(null)
+                showCalendar = false
+            }
+        )
     }
 
 
@@ -346,10 +344,10 @@ fun ReadRecordScreen(
 
         AlertDialog(
             onDismissRequest = { mergeDialogData = null },
-            title = { Text("合并阅读记录") },
+            title = { AppText("合并阅读记录") },
             text = {
                 Column {
-                    Text("将以下作者的“${targetRecord.bookName}”合并到 ${targetRecord.bookAuthor.ifBlank { "未知作者" }}")
+                    AppText("将以下作者的“${targetRecord.bookName}”合并到 ${targetRecord.bookAuthor.ifBlank { "未知作者" }}")
                     Spacer(modifier = Modifier.height(8.dp))
 
                     candidates.forEach { candidate ->
@@ -367,10 +365,10 @@ fun ReadRecordScreen(
                             verticalAlignment = Alignment.CenterVertically
                         ) {
                             Checkbox(checked = isChecked, onCheckedChange = null)
-                            Text(
+                            AppText(
                                 text = "$author(${formatDuring(candidate.readTime)})",
                                 modifier = Modifier.padding(start = 8.dp),
-                                style = MaterialTheme.typography.bodyMedium
+                                style = LegadoTheme.typography.bodyMedium
                             )
                         }
                     }
@@ -386,12 +384,12 @@ fun ReadRecordScreen(
                         mergeDialogData = null
                     }
                 ) {
-                    Text("合并")
+                    AppText("合并")
                 }
             },
             dismissButton = {
                 TextButton(onClick = { mergeDialogData = null }) {
-                    Text("取消")
+                    AppText("取消")
                 }
             }
         )
@@ -686,21 +684,21 @@ fun LatestReadItem(
         Spacer(modifier = Modifier.width(16.dp))
 
         Column(modifier = Modifier.weight(1f)) {
-            Text(
+            AppText(
                 text = record.bookName,
-                style = MaterialTheme.typography.titleMedium,
+                style = LegadoTheme.typography.titleMedium,
                 maxLines = 1,
                 overflow = TextOverflow.Ellipsis
             )
-            Text(
+            AppText(
                 text = record.bookAuthor.ifBlank { "未知作者" },
-                style = MaterialTheme.typography.bodySmall,
+                style = LegadoTheme.typography.bodySmall,
                 color = MaterialTheme.colorScheme.outline,
                 maxLines = 1,
                 overflow = TextOverflow.Ellipsis
             )
             Spacer(modifier = Modifier.height(8.dp))
-            Text(
+            AppText(
                 modifier = Modifier
                     .basicMarquee(
                         iterations = Int.MAX_VALUE,
@@ -716,7 +714,7 @@ fun LatestReadItem(
                         append("${DateUtil.format(Date(record.lastRead), "yyyy-MM-dd HH:mm")}")
                     }
                 },
-                style = MaterialTheme.typography.labelSmall,
+                style = LegadoTheme.typography.labelSmall,
                 maxLines = 1,
                 overflow = TextOverflow.Ellipsis
             )
@@ -783,9 +781,9 @@ fun TimelineSessionItem(
                 modifier = Modifier.width(48.dp),
                 verticalArrangement = Arrangement.Center
             ) {
-                Text(
+                AppText(
                     text = endTimeText,
-                    style = MaterialTheme.typography.bodySmall
+                    style = LegadoTheme.typography.bodySmall
                 )
             }
 
@@ -795,23 +793,23 @@ fun TimelineSessionItem(
                 Cover(coverPath)
                 Spacer(modifier = Modifier.width(8.dp))
                 Column {
-                    Text(
+                    AppText(
                         text = session.bookName,
-                        style = MaterialTheme.typography.titleMedium,
+                        style = LegadoTheme.typography.titleMedium,
                         maxLines = 1,
                         overflow = TextOverflow.Ellipsis
                     )
-                    Text(
+                    AppText(
                         text = session.bookAuthor.ifBlank { "未知作者" },
-                        style = MaterialTheme.typography.bodySmall,
+                        style = LegadoTheme.typography.bodySmall,
                         color = MaterialTheme.colorScheme.outline,
                         maxLines = 1,
                         overflow = TextOverflow.Ellipsis
                     )
                     Spacer(modifier = Modifier.height(8.dp))
-                    Text(
+                    AppText(
                         text = chapterTitle.orEmpty(),
-                        style = MaterialTheme.typography.labelSmall,
+                        style = LegadoTheme.typography.labelSmall,
                         color = MaterialTheme.colorScheme.outline,
                         maxLines = 1,
                         overflow = TextOverflow.Ellipsis
@@ -847,21 +845,21 @@ fun ReadRecordItem(
         Spacer(modifier = Modifier.width(16.dp))
 
         Column(modifier = Modifier.weight(1f)) {
-            Text(
+            AppText(
                 text = detail.bookName,
-                style = MaterialTheme.typography.titleMedium,
+                style = LegadoTheme.typography.titleMedium,
                 maxLines = 1
             )
-            Text(
+            AppText(
                 text = detail.bookAuthor.ifBlank { "未知作者" },
-                style = MaterialTheme.typography.bodySmall,
+                style = LegadoTheme.typography.bodySmall,
                 color = MaterialTheme.colorScheme.outline,
             )
             Spacer(modifier = Modifier.height(8.dp))
-            Text(
+            AppText(
                 text = "阅读时长: ${formatDuring(detail.readTime)}",
                 color = MaterialTheme.colorScheme.outline,
-                style = MaterialTheme.typography.labelSmall
+                style = LegadoTheme.typography.labelSmall
             )
         }
     }
@@ -872,28 +870,27 @@ fun DateHeader(
     date: String,
     dailyTotalTime: Long? = null
 ) {
-    val dateText = formatFriendlyDate(date)
-    SectionHeader(
+    CollapsibleHeader(
+        showIcon = false,
+        isCollapsed = false,
+        onToggle = { },
+        title = "",
         titleContent = {
-            Column {
-                Text(
-                    text = dateText,
-                    style = MaterialTheme.typography.bodyMedium,
-                    fontWeight = FontWeight.Bold,
-                    color = MaterialTheme.colorScheme.secondary
+            val dateText = formatFriendlyDate(date)
+            AppText(
+                text = dateText,
+                style = LegadoTheme.typography.bodyMedium,
+                fontWeight = FontWeight.Bold,
+                color = LegadoTheme.colorScheme.primary
+            )
+            dailyTotalTime?.let { total ->
+                AppText(
+                    text = "已读 ${formatDuring(total)}",
+                    style = LegadoTheme.typography.bodySmall,
+                    color = LegadoTheme.colorScheme.onSurface
                 )
-
-                dailyTotalTime?.let { total ->
-                    Text(
-                        text = "已读 ${formatDuring(total)}",
-                        style = MaterialTheme.typography.bodySmall,
-                        color = MaterialTheme.colorScheme.onSurface
-                    )
-                }
             }
-        },
-        detailContent = null,
-        horizontalArrangement = Arrangement.Start
+        }
     )
 }
 
@@ -915,14 +912,12 @@ fun ReadingSummaryCard(
 
     val totalDurationMinutes = totalTimeMillis / 60000
 
-    Card(
+    GlassCard(
         onClick = onClick,
         modifier = Modifier
             .fillMaxWidth()
             .padding(horizontal = 16.dp, vertical = 8.dp),
-        colors = CardDefaults.cardColors(
-            containerColor = MaterialTheme.colorScheme.surfaceContainer
-        )
+        containerColor = LegadoTheme.colorScheme.surfaceContainer
     ) {
         Row(
             modifier = Modifier
@@ -934,28 +929,28 @@ fun ReadingSummaryCard(
 
             Column(modifier = Modifier.weight(1f)) {
 
-                Text(
+                AppText(
                     text = title,
-                    style = MaterialTheme.typography.labelLarge,
-                    color = MaterialTheme.colorScheme.primary
+                    style = LegadoTheme.typography.labelLarge,
+                    color = LegadoTheme.colorScheme.primary
                 )
 
                 Spacer(modifier = Modifier.height(4.dp))
 
                 Row(verticalAlignment = Alignment.Bottom) {
-                    Text(
+                    AppText(
                         text = "已读 ",
-                        style = MaterialTheme.typography.titleMedium
+                        style = LegadoTheme.typography.titleMedium
                     )
-                    Text(
+                    AppText(
                         text = "$bookCount",
-                        style = MaterialTheme.typography.headlineMedium,
-                        color = MaterialTheme.colorScheme.primary,
+                        style = LegadoTheme.typography.headlineMedium,
+                        color = LegadoTheme.colorScheme.primary,
                         fontWeight = FontWeight.Bold,
                     )
-                    Text(
+                    AppText(
                         text = " 本书",
-                        style = MaterialTheme.typography.titleMedium
+                        style = LegadoTheme.typography.titleMedium
                     )
                 }
 
@@ -965,10 +960,10 @@ fun ReadingSummaryCard(
                 val minutes = totalDurationMinutes % 60
                 val timeString = if (hours > 0) "${hours}小时${minutes}分钟" else "${minutes}分钟"
 
-                Text(
+                AppText(
                     text = "共阅读 $timeString",
-                    style = MaterialTheme.typography.bodySmall,
-                    color = MaterialTheme.colorScheme.onSurfaceVariant
+                    style = LegadoTheme.typography.bodySmall,
+                    color = LegadoTheme.colorScheme.onSurfaceVariant
                 )
             }
 

+ 17 - 20
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.Text
 import androidx.compose.material3.TooltipAnchorPosition
 import androidx.compose.material3.TooltipBox
 import androidx.compose.material3.TooltipDefaults
@@ -64,11 +63,9 @@ import androidx.compose.ui.text.buildAnnotatedString
 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.theme.LegadoTheme
 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
 import io.legado.app.ui.widget.components.SearchBarSection
 import io.legado.app.ui.widget.components.button.SmallAnimatedActionButton
 import io.legado.app.ui.widget.components.button.SmallIconButton
@@ -76,6 +73,9 @@ import io.legado.app.ui.widget.components.button.TopBarAnimatedActionButton
 import io.legado.app.ui.widget.components.button.TopbarNavigationButton
 import io.legado.app.ui.widget.components.card.TextCard
 import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
+import io.legado.app.ui.widget.components.text.AppText
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import kotlinx.coroutines.launch
 import org.koin.androidx.compose.koinViewModel
 
@@ -140,12 +140,9 @@ fun SearchContentScreen(
         topBar = {
             Column {
                 GlassMediumFlexibleTopAppBar(
-                    title = {
-                        val title = if (searchQuery.isNotBlank() && searchResults.isNotEmpty()) {
-                            "共 ${searchResults.size} 条结果"
-                        } else "搜索内容"
-                        AnimatedText(text = title)
-                    },
+                    title = if (searchQuery.isNotBlank() && searchResults.isNotEmpty()) {
+                        "共 ${searchResults.size} 条结果"
+                    } else "搜索内容",
                     navigationIcon = { TopbarNavigationButton(onClick = onBack) },
                     actions = {
                         Row(
@@ -191,7 +188,7 @@ fun SearchContentScreen(
                 ),
                 tooltip = {
                     PlainTooltip {
-                        Text(if (isSearching) "停止搜索" else "跳转到当前章节")
+                        AppText(if (isSearching) "停止搜索" else "跳转到当前章节")
                     }
                 },
                 state = rememberTooltipState(),
@@ -310,10 +307,10 @@ fun SearchHistoryList(
                 .fillMaxWidth()
                 .padding(horizontal = 16.dp, vertical = 8.dp)
         ) {
-            Text(
+            AppText(
                 text = "搜索历史",
-                style = MaterialTheme.typography.titleSmallEmphasized,
-                color = MaterialTheme.colorScheme.primary,
+                style = LegadoTheme.typography.titleSmallEmphasized,
+                color = LegadoTheme.colorScheme.primary,
                 modifier = Modifier.align(Alignment.Center)
             )
             SmallAnimatedActionButton(
@@ -342,7 +339,7 @@ fun SearchHistoryList(
                             .clickable { onHistoryClick(item) }
                             .animateItem(),
                         headlineContent = {
-                            Text(
+                            AppText(
                                 text = item.query,
                                 maxLines = 1,
                                 overflow = TextOverflow.Ellipsis
@@ -378,7 +375,7 @@ fun SearchHistoryList(
                                 modifier = Modifier.size(18.dp)
                             )
                             Spacer(Modifier.width(8.dp))
-                            Text("清除搜索历史")
+                            AppText("清除搜索历史")
                         }
                     }
                 }
@@ -407,7 +404,7 @@ fun SearchResultItem(
         Box(modifier = Modifier.padding(16.dp)) {
 
             Column {
-                Text(
+                AppText(
                     text = buildAnnotatedString {
                         append(
                             result.getTitleSpannable(
@@ -415,14 +412,14 @@ fun SearchResultItem(
                             )
                         )
                     },
-                    style = MaterialTheme.typography.titleSmall
+                    style = LegadoTheme.typography.titleSmall
                 )
 
                 Spacer(modifier = Modifier.height(8.dp))
                 HorizontalDivider()
                 Spacer(modifier = Modifier.height(8.dp))
 
-                Text(
+                AppText(
                     text = buildAnnotatedString {
                         append(
                             result.getContentSpannable(
@@ -432,7 +429,7 @@ fun SearchResultItem(
                             )
                         )
                     },
-                    style = MaterialTheme.typography.bodyMedium
+                    style = LegadoTheme.typography.bodyMedium
                 )
             }
 

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

@@ -63,7 +63,6 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.PrimaryScrollableTabRow
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Tab
-import androidx.compose.material3.Text
 import androidx.compose.material3.ToggleFloatingActionButton
 import androidx.compose.material3.ToggleFloatingActionButtonDefaults.animateIcon
 import androidx.compose.material3.animateFloatingActionButton
@@ -95,11 +94,11 @@ import io.legado.app.data.entities.Bookmark
 import io.legado.app.help.book.isLocal
 import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
 import io.legado.app.ui.replace.ReplaceEditRoute
+import io.legado.app.ui.theme.LegadoTheme
 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
 import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet
 import io.legado.app.ui.widget.components.bookmark.BookmarkItem
@@ -109,7 +108,9 @@ import io.legado.app.ui.widget.components.divider.PillHeaderDivider
 import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import org.koin.androidx.compose.koinViewModel
@@ -312,33 +313,33 @@ fun TocScreen(
                     when (pagerState.currentPage) {
                         0 -> {
                             RoundDropdownMenuItem(
-                                text = { Text("使用替换规则") },
+                                text = "使用替换规则",
                                 trailingIcon = {
                                     Checkbox(checked = useReplace, onCheckedChange = null)
                                 },
                                 onClick = { viewModel.toggleUseReplace() }
                             )
                             RoundDropdownMenuItem(
-                                text = { Text("显示字数") },
+                                text = "显示字数",
                                 trailingIcon = {
                                     Checkbox(checked = showWordCount, onCheckedChange = null)
                                 },
                                 onClick = { viewModel.toggleShowWordCount() }
                             )
                             RoundDropdownMenuItem(
-                                text = { Text("反转目录") },
+                                text = "反转目录",
                                 onClick = { viewModel.reverseToc() }
                             )
                             PillDivider()
                             RoundDropdownMenuItem(
-                                text = { Text("替换规则") },
+                                text = "替换规则",
                                 onClick = {
                                     onOpenReplaceRule(null)
                                     dismiss()
                                 }
                             )
                             RoundDropdownMenuItem(
-                                text = { Text("新建替换规则") },
+                                text = "新建替换规则",
                                 onClick = {
                                     val scopes = mutableListOf<String>()
                                     book?.name?.let { scopes.add(it) }
@@ -358,7 +359,7 @@ fun TocScreen(
                             if (book?.isLocal == true) {
                                 PillHeaderDivider(title = "本地书籍选项")
                                 RoundDropdownMenuItem(
-                                    text = { Text("本地书籍目录规则") },
+                                    text = "本地书籍目录规则",
                                     onClick = {
                                         val intent =
                                             Intent(context, TxtTocRuleActivity::class.java).apply {
@@ -369,7 +370,7 @@ fun TocScreen(
                                     }
                                 )
                                 RoundDropdownMenuItem(
-                                    text = { Text("拆分超长章节") },
+                                    text = "拆分超长章节",
                                     trailingIcon = {
                                         Checkbox(
                                             checked = viewModel.isSplitLongChapter,
@@ -386,7 +387,7 @@ fun TocScreen(
 
                         else -> {
                             RoundDropdownMenuItem(
-                                text = { Text("导出书签为JSON") },
+                                text = "导出书签为JSON",
                                 onClick = {
                                     val dateFormat = SimpleDateFormat(
                                         "yyyyMMdd_HHmm",
@@ -398,7 +399,7 @@ fun TocScreen(
                                 }
                             )
                             RoundDropdownMenuItem(
-                                text = { Text("导出书签为MarkDown") },
+                                text = "导出书签为MarkDown",
                                 onClick = {
                                     val dateFormat = SimpleDateFormat(
                                         "yyyyMMdd_HHmm",
@@ -428,7 +429,7 @@ fun TocScreen(
                                 selected = pagerState.currentPage == 0,
                                 onClick = { scope.launch { pagerState.animateScrollToPage(0) } },
                                 text = {
-                                    Text(
+                                    AppText(
                                         text = "目录",
                                         modifier = Modifier.padding(horizontal = 16.dp),
                                         color = MaterialTheme.colorScheme.onSurfaceVariant
@@ -439,7 +440,7 @@ fun TocScreen(
                                 selected = pagerState.currentPage == 1,
                                 onClick = { scope.launch { pagerState.animateScrollToPage(1) } },
                                 text = {
-                                    Text(
+                                    AppText(
                                         text = "书签",
                                         modifier = Modifier.padding(horizontal = 16.dp),
                                         color = MaterialTheme.colorScheme.onSurfaceVariant
@@ -464,13 +465,13 @@ fun TocScreen(
                                     onDismissRequest = { showVolumeMenu = false }
                                 ) {
                                     RoundDropdownMenuItem(
-                                        text = { Text("展开所有卷") },
+                                        text = "展开所有卷",
                                         onClick = {
                                             viewModel.expandAllVolumes(); showVolumeMenu = false
                                         }
                                     )
                                     RoundDropdownMenuItem(
-                                        text = { Text("收起所有卷") },
+                                        text = "收起所有卷",
                                         onClick = {
                                             viewModel.collapseAllVolumes(); showVolumeMenu = false
                                         }
@@ -482,7 +483,7 @@ fun TocScreen(
                                         PillHeaderDivider(title = "快速跳转")
                                         volumeItems.forEach { uiItem ->
                                             RoundDropdownMenuItem(
-                                                text = { Text(uiItem.title) },
+                                                text = uiItem.title,
                                                 onClick = {
                                                     scope.launch {
                                                         val targetIndex =
@@ -541,7 +542,7 @@ fun TocScreen(
                             fabMenuExpanded = false
                         },
                         icon = { Icon(icon, contentDescription = null) },
-                        text = { Text(text = label) }
+                        text = { AppText(text = label) }
                     )
                 }
             }
@@ -593,20 +594,19 @@ fun TocScreen(
             }
         }
 
-        if (editingBookmark != null) {
-            BookmarkEditSheet(
-                bookmark = editingBookmark!!,
-                onDismiss = { editingBookmark = null },
-                onSave = { updatedBookmark ->
-                    viewModel.updateBookmark(updatedBookmark)
-                    editingBookmark = null
-                },
-                onDelete = { bookmarkToDelete ->
-                    viewModel.deleteBookmark(bookmarkToDelete)
-                    editingBookmark = null
-                }
-            )
-        }
+        BookmarkEditSheet(
+            show = editingBookmark != null,
+            bookmark = editingBookmark!!,
+            onDismiss = { editingBookmark = null },
+            onSave = { updatedBookmark ->
+                viewModel.updateBookmark(updatedBookmark)
+                editingBookmark = null
+            },
+            onDelete = { bookmarkToDelete ->
+                viewModel.deleteBookmark(bookmarkToDelete)
+                editingBookmark = null
+            }
+        )
     }
 }
 
@@ -729,9 +729,9 @@ fun ChapterItem(
                         )
                     }
 
-                    Text(
+                    AppText(
                         text = item.title,
-                        style = MaterialTheme.typography.bodyMediumEmphasized.copy(fontWeight = FontWeight.Medium),
+                        style = LegadoTheme.typography.bodyMediumEmphasized.copy(fontWeight = FontWeight.Medium),
                         color = textColor,
                         maxLines = 1,
                         overflow = TextOverflow.Ellipsis
@@ -739,9 +739,9 @@ fun ChapterItem(
                 }
 
                 if (!item.tag.isNullOrEmpty()) {
-                    Text(
+                    AppText(
                         text = item.tag,
-                        style = MaterialTheme.typography.labelSmallEmphasized,
+                        style = LegadoTheme.typography.labelSmallEmphasized,
                         color = detailColor.copy(alpha = 0.8f),
                         maxLines = 1,
                         overflow = TextOverflow.Ellipsis
@@ -910,11 +910,11 @@ private fun StatusIcon(
                     color = Color.Transparent
                 ) {
                     if (wordCount != null) {
-                        Text(
+                        AppText(
                             modifier = Modifier
                                 .padding(horizontal = 8.dp, vertical = 4.dp),
                             text = wordCount,
-                            style = MaterialTheme.typography.labelSmall.copy(fontSize = 9.sp),
+                            style = LegadoTheme.typography.labelSmall.copy(fontSize = 9.sp),
                             color = MaterialTheme.colorScheme.onSurfaceVariant
                         )
                     }

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

@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
@@ -19,11 +18,9 @@ import androidx.compose.material3.AlertDialog
 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
 import androidx.compose.material3.SnackbarResult
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.rememberModalBottomSheetState
 import androidx.compose.runtime.Composable
@@ -51,7 +48,6 @@ 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.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
@@ -59,6 +55,7 @@ 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
+import io.legado.app.ui.widget.components.text.AppText
 import org.koin.androidx.compose.koinViewModel
 import sh.calvin.reorderable.rememberReorderableLazyListState
 
@@ -154,54 +151,49 @@ fun TxtRuleScreen(
         )
     }
 
-    if (showExportSheet) {
-        FilePickerSheet(
-            onDismissRequest = { showExportSheet = false },
-            title = stringResource(R.string.export),
-            onSelectSysDir = {
-                showExportSheet = false
-                exportDoc.launch("exportDictRule.json")
-            },
-            onUpload = {
-                showExportSheet = false
-                viewModel.uploadSelectedRules(selectedIds, rules)
-            },
-            allowExtensions = arrayOf("json")
-        )
-    }
+    FilePickerSheet(
+        show = showExportSheet,
+        onDismissRequest = { showExportSheet = false },
+        title = stringResource(R.string.export),
+        onSelectSysDir = {
+            showExportSheet = false
+            exportDoc.launch("exportDictRule.json")
+        },
+        onUpload = {
+            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 = "导入词典规则",
-            importState = state,
-            onDismissRequest = { viewModel.cancelImport() },
-            onToggleItem = { viewModel.toggleImportSelection(it) },
-            onToggleAll = { viewModel.toggleImportAll(it) },
-            onConfirm = { viewModel.saveImportedRules() },
-            itemContent = { rule, _ ->
-                Column {
-                    Text(rule.name, style = MaterialTheme.typography.titleMedium)
-                    Text(rule.rule, style = MaterialTheme.typography.bodySmall, maxLines = 1)
-                }
-            }
-        )
-    }
+    FilePickerSheet(
+        show = showImportSheet,
+        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")
+    )
+
+    BatchImportDialog(
+        title = "导入词典规则",
+        importState = importState,
+        onDismissRequest = { viewModel.cancelImport() },
+        onToggleItem = { viewModel.toggleImportSelection(it) },
+        onToggleAll = { viewModel.toggleImportAll(it) },
+        onConfirm = { viewModel.saveImportedRules() },
+        itemTitle = { rule -> rule.name },
+        itemSubtitle = { rule ->
+            rule.rule.takeIf { it.isNotBlank() }
+        }
+    )
 
     LaunchedEffect(reorderableState.isAnyItemDragging) {
         if (!reorderableState.isAnyItemDragging) {
@@ -212,16 +204,16 @@ fun TxtRuleScreen(
     showDeleteRuleDialog?.let { rule ->
         AlertDialog(
             onDismissRequest = { showDeleteRuleDialog = null },
-            title = { Text(stringResource(R.string.delete)) },
-            text = { Text(stringResource(R.string.del_msg)) },
+            title = { AppText(stringResource(R.string.delete)) },
+            text = { AppText(stringResource(R.string.del_msg)) },
             confirmButton = {
                 OutlinedButton(onClick = {
                     viewModel.delete(rule); showDeleteRuleDialog = null
-                }) { Text(stringResource(R.string.ok)) }
+                }) { AppText(stringResource(R.string.ok)) }
             },
             dismissButton = {
                 TextButton(onClick = { showDeleteRuleDialog = null }) {
-                    Text(
+                    AppText(
                         stringResource(R.string.cancel)
                     )
                 }
@@ -229,48 +221,47 @@ fun TxtRuleScreen(
         )
     }
 
-    if (showEditSheet) {
-        RuleEditSheet(
-            rule = editingRule,
-            title = stringResource(R.string.txt_toc_rule),
-            label1 = stringResource(R.string.regex),
-            label2 = stringResource(R.string.example),
-            onDismissRequest = {
-                showEditSheet = false
-                editingRule = null
-            },
-            onSave = { updatedRule ->
-                //TODO:我很想把他改为自增主键,但为了兼容性日后再说
-                if (editingRule == null) {
-                    viewModel.insert(updatedRule)
-                } else {
-                    viewModel.update(updatedRule)
-                }
-                showEditSheet = false
-                editingRule = null
-            },
-            onCopy = { viewModel.copyRule(it) },
-            onPaste = { viewModel.pasteRule() },
-            toFields = { r ->
-                RuleEditFields(
-                    name = r?.name ?: "",
-                    rule1 = r?.rule ?: "",
-                    rule2 = r?.example ?: ""
-                )
-            },
-            fromFields = { fields, old ->
-                old?.copy(
-                    name = fields.name,
-                    rule = fields.rule1,
-                    example = fields.rule2
-                ) ?: TxtTocRule(
-                    name = fields.name,
-                    rule = fields.rule1,
-                    example = fields.rule2
-                )
+    RuleEditSheet(
+        show = showEditSheet,
+        rule = editingRule,
+        title = stringResource(R.string.txt_toc_rule),
+        label1 = stringResource(R.string.regex),
+        label2 = stringResource(R.string.example),
+        onDismissRequest = {
+            showEditSheet = false
+            editingRule = null
+        },
+        onSave = { updatedRule ->
+            //TODO:我很想把他改为自增主键,但为了兼容性日后再说
+            if (editingRule == null) {
+                viewModel.insert(updatedRule)
+            } else {
+                viewModel.update(updatedRule)
             }
-        )
-    }
+            showEditSheet = false
+            editingRule = null
+        },
+        onCopy = { viewModel.copyRule(it) },
+        onPaste = { viewModel.pasteRule() },
+        toFields = { r ->
+            RuleEditFields(
+                name = r?.name ?: "",
+                rule1 = r?.rule ?: "",
+                rule2 = r?.example ?: ""
+            )
+        },
+        fromFields = { fields, old ->
+            old?.copy(
+                name = fields.name,
+                rule = fields.rule1,
+                example = fields.rule2
+            ) ?: TxtTocRule(
+                name = fields.name,
+                rule = fields.rule1,
+                example = fields.rule2
+            )
+        }
+    )
 
     RuleListScaffold(
         title = if (isPickMode) "选择目录规则" else "目录规则",
@@ -312,7 +303,7 @@ fun TxtRuleScreen(
         snackbarHostState = snackbarHostState,
         dropDownMenuContent = { dismiss ->
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.import_str)) },
+                text = stringResource(R.string.import_str),
                 onClick = { showImportSheet = true; dismiss() },
                 leadingIcon = { Icon(Icons.Default.FileOpen, null) }
             )

+ 3 - 4
app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.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.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.nestedscroll.nestedScroll
@@ -14,11 +13,11 @@ 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
 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.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
@@ -36,7 +35,7 @@ fun ConfigNavScreen(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             GlassMediumFlexibleTopAppBar(
-                title = { Text(stringResource(R.string.setting)) },
+                title = stringResource(R.string.setting),
                 scrollBehavior = scrollBehavior,
                 navigationIcon = {
                     TopbarNavigationButton(onClick = onBackClick)

+ 42 - 45
app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigScreen.kt

@@ -19,7 +19,6 @@ import androidx.compose.material3.IconButton
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.SnackbarHost
 import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.TextField
 import androidx.compose.runtime.Composable
@@ -42,14 +41,15 @@ import io.legado.app.help.storage.Restore
 import io.legado.app.lib.permission.Permissions
 import io.legado.app.lib.permission.PermissionsCompat
 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.filePicker.FilePickerSheet
 import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
 import io.legado.app.ui.widget.components.settingItem.InputSettingItem
 import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
+import io.legado.app.ui.widget.components.text.AppText
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.utils.isContentScheme
 import io.legado.app.utils.takePersistablePermissionSafely
 import kotlinx.coroutines.launch
@@ -166,9 +166,7 @@ fun BackupConfigScreen(
         },
         topBar = {
             GlassMediumFlexibleTopAppBar(
-                title = {
-                    Text(stringResource(R.string.backup_restore))
-                },
+                title = stringResource(R.string.backup_restore),
                 scrollBehavior = scrollBehavior,
                 navigationIcon = {
                     TopbarNavigationButton(onClick = onBackClick)
@@ -387,19 +385,19 @@ fun BackupConfigScreen(
     if (showWebDavAuthDialog) {
         AlertDialog(
             onDismissRequest = { showWebDavAuthDialog = false },
-            title = { Text(stringResource(R.string.web_dav_account)) },
+            title = { AppText(stringResource(R.string.web_dav_account)) },
             text = {
                 Column {
                     TextField(
                         value = tempAccount,
                         onValueChange = { tempAccount = it },
-                        label = { Text("账号") }
+                        label = { AppText("账号") }
                     )
                     Spacer(modifier = Modifier.padding(8.dp))
                     TextField(
                         value = tempPassword,
                         onValueChange = { tempPassword = it },
-                        label = { Text("密码") },
+                        label = { AppText("密码") },
                         visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
                         keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
                         trailingIcon = {
@@ -432,12 +430,12 @@ fun BackupConfigScreen(
                         }
                     }
                 }) {
-                    Text(stringResource(R.string.ok))
+                    AppText(stringResource(R.string.ok))
                 }
             },
             dismissButton = {
                 TextButton(onClick = { showWebDavAuthDialog = false }) {
-                    Text(stringResource(R.string.cancel))
+                    AppText(stringResource(R.string.cancel))
                 }
             }
         )
@@ -455,36 +453,35 @@ fun BackupConfigScreen(
         )
     }
 
-    if (showBackupFilePicker) {
-        FilePickerSheet(
-            onDismissRequest = { showBackupFilePicker = false },
-            onSelectSysDir = {
-                showBackupFilePicker = false
-                try {
-                    selectBackupPathLauncher.launch(null)
-                } catch (e: Exception) {
-                }
+    FilePickerSheet(
+        show = showBackupFilePicker,
+        onDismissRequest = { showBackupFilePicker = false },
+        onSelectSysDir = {
+            showBackupFilePicker = false
+            try {
+                selectBackupPathLauncher.launch(null)
+            } catch (e: Exception) {
             }
-        )
-    }
+        }
+    )
 
-    if (showRestoreFilePicker) {
-        FilePickerSheet(
-            onDismissRequest = { showRestoreFilePicker = false },
-            onSelectSysDir = {
-                showRestoreFilePicker = false
-                try {
-                    backupAndSelectLauncher.launch(null)
-                } catch (e: Exception) {
-                }
+
+    FilePickerSheet(
+        show = showRestoreFilePicker,
+        onDismissRequest = { showRestoreFilePicker = false },
+        onSelectSysDir = {
+            showRestoreFilePicker = false
+            try {
+                backupAndSelectLauncher.launch(null)
+            } catch (e: Exception) {
             }
-        )
-    }
+        }
+    )
 
     if (showRestoreSheet && backupNames.isNotEmpty()) {
         AlertDialog(
             onDismissRequest = { showRestoreSheet = false },
-            title = { Text(stringResource(R.string.select_restore_file)) },
+            title = { AppText(stringResource(R.string.select_restore_file)) },
             text = {
                 Column {
                     backupNames.forEach { name ->
@@ -508,7 +505,7 @@ fun BackupConfigScreen(
                                 }
                             )
                         }) {
-                            Text(name)
+                            AppText(name)
                         }
                     }
                 }
@@ -516,7 +513,7 @@ fun BackupConfigScreen(
             confirmButton = {},
             dismissButton = {
                 TextButton(onClick = { showRestoreSheet = false }) {
-                    Text(stringResource(R.string.cancel))
+                    AppText(stringResource(R.string.cancel))
                 }
             }
         )
@@ -535,7 +532,7 @@ fun BackupConfigScreen(
                 io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
                 showBackupIgnoreDialog = false
             },
-            title = { Text(stringResource(R.string.restore_ignore)) },
+            title = { AppText(stringResource(R.string.restore_ignore)) },
             text = {
                 Column {
                     io.legado.app.help.storage.BackupConfig.ignoreTitle.forEachIndexed { index, title ->
@@ -546,7 +543,7 @@ fun BackupConfigScreen(
                             ] = checkedItems[index]
                         }) {
                             val isChecked = checkedItems[index]
-                            Text(if (isChecked) "✓ $title" else title)
+                            AppText(if (isChecked) "✓ $title" else title)
                         }
                     }
                 }
@@ -556,7 +553,7 @@ fun BackupConfigScreen(
                     io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
                     showBackupIgnoreDialog = false
                 }) {
-                    Text(stringResource(R.string.ok))
+                    AppText(stringResource(R.string.ok))
                 }
             },
             dismissButton = {
@@ -564,7 +561,7 @@ fun BackupConfigScreen(
                     io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
                     showBackupIgnoreDialog = false
                 }) {
-                    Text(stringResource(R.string.cancel))
+                    AppText(stringResource(R.string.cancel))
                 }
             }
         )
@@ -573,7 +570,7 @@ fun BackupConfigScreen(
     if (showLoadingDialog) {
         AlertDialog(
             onDismissRequest = {},
-            title = { Text(loadingText) },
+            title = { AppText(loadingText) },
             confirmButton = {},
             dismissButton = {}
         )
@@ -589,16 +586,16 @@ fun ConfirmDialog(
 ) {
     AlertDialog(
         onDismissRequest = onDismiss,
-        title = { Text(title) },
-        text = { Text(text) },
+        title = { AppText(title) },
+        text = { AppText(text) },
         confirmButton = {
             OutlinedButton(onClick = onConfirm) {
-                Text(stringResource(R.string.ok))
+                AppText(stringResource(R.string.ok))
             }
         },
         dismissButton = {
             TextButton(onClick = onDismiss) {
-                Text(stringResource(R.string.cancel))
+                AppText(stringResource(R.string.cancel))
             }
         }
     )

+ 14 - 14
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.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -27,14 +26,14 @@ 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.dialog.ColorPickerSheet
 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.SwitchSettingItem
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import org.koin.androidx.compose.koinViewModel
 
 @OptIn(ExperimentalMaterial3Api::class)
@@ -52,7 +51,7 @@ fun CoverConfigScreen(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             GlassMediumFlexibleTopAppBar(
-                title = { Text(stringResource(R.string.cover_config)) },
+                title = stringResource(R.string.cover_config),
                 scrollBehavior = scrollBehavior,
                 navigationIcon = {
                     TopbarNavigationButton(onClick = onBackClick)
@@ -247,17 +246,17 @@ fun CoverConfigScreen(
         }
     }
 
-    if (showCoverRuleSheet) {
-        CoverRuleConfigSheet(onDismissRequest = { showCoverRuleSheet = false })
-    }
+    CoverRuleConfigSheet(
+        show = showCoverRuleSheet,
+        onDismissRequest = { showCoverRuleSheet = false }
+    )
 
-    manageKey?.let { key ->
-        CoverManageSheet(
-            preferenceKey = key,
-            onDismissRequest = { manageKey = null },
-            viewModel = viewModel
-        )
-    }
+    CoverManageSheet(
+        show = manageKey != null,
+        preferenceKey = manageKey!!,
+        onDismissRequest = { manageKey = null },
+        viewModel = viewModel
+    )
 
     showColorPickerByField?.let { field ->
         val initialColor = when (field) {
@@ -269,6 +268,7 @@ fun CoverConfigScreen(
         }
 
         ColorPickerSheet(
+            show = showColorPickerByField != null,
             initialColor = initialColor,
             onDismissRequest = { showColorPickerByField = null },
             onColorSelected = { color ->

+ 17 - 15
app/src/main/java/io/legado/app/ui/config/coverConfig/CoverManageSheet.kt

@@ -22,7 +22,6 @@ 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
@@ -36,12 +35,15 @@ import androidx.compose.ui.unit.dp
 import coil.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.constant.PreferKey
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun CoverManageSheet(
+    show: Boolean,
     preferenceKey: String,
     onDismissRequest: () -> Unit,
     viewModel: CoverConfigViewModel
@@ -61,7 +63,8 @@ fun CoverManageSheet(
     }
     val coverList = currentCovers.split(",").filter { it.isNotBlank() }
 
-    GlassModalBottomSheet(
+    AppModalBottomSheet(
+        show = show,
         onDismissRequest = onDismissRequest
     ) {
         Column(
@@ -69,9 +72,9 @@ fun CoverManageSheet(
                 .fillMaxWidth()
                 .padding(bottom = 24.dp)
         ) {
-            Text(
+            AppText(
                 text = stringResource(R.string.default_cover),
-                style = MaterialTheme.typography.titleMedium,
+                style = LegadoTheme.typography.titleMedium,
                 modifier = Modifier.padding(16.dp)
             )
 
@@ -141,14 +144,13 @@ fun CoverManageSheet(
         }
     }
 
-    if (showFilePicker) {
-        FilePickerSheet(
-            onDismissRequest = { showFilePicker = false },
-            onSelectSysFiles = {
-                selectImages.launch("image/*")
-                showFilePicker = false
-            },
-            allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
-        )
-    }
+    FilePickerSheet(
+        show = showFilePicker,
+        onDismissRequest = { showFilePicker = false },
+        onSelectSysFiles = {
+            selectImages.launch("image/*")
+            showFilePicker = false
+        },
+        allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
+    )
 }

+ 15 - 9
app/src/main/java/io/legado/app/ui/config/coverConfig/CoverRuleConfigSheet.kt

@@ -13,7 +13,6 @@ import androidx.compose.material3.Checkbox
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -27,8 +26,10 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.model.BookCover
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
 import io.legado.app.ui.widget.components.settingItem.InputSettingItem
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.toastOnUi
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
@@ -37,6 +38,7 @@ import splitties.init.appCtx
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun CoverRuleConfigSheet(
+    show: Boolean,
     onDismissRequest: () -> Unit
 ) {
     var enable by remember { mutableStateOf(false) }
@@ -52,7 +54,8 @@ fun CoverRuleConfigSheet(
         coverRule = rule.coverRule
     }
 
-    GlassModalBottomSheet(
+    AppModalBottomSheet(
+        show = show,
         onDismissRequest = onDismissRequest
     ) {
         Column(
@@ -61,9 +64,9 @@ fun CoverRuleConfigSheet(
                 .padding(16.dp)
                 .verticalScroll(rememberScrollState())
         ) {
-            Text(
+            AppText(
                 text = stringResource(R.string.cover_rule),
-                style = MaterialTheme.typography.titleLarge,
+                style = LegadoTheme.typography.titleLarge,
                 modifier = Modifier.padding(bottom = 16.dp)
             )
 
@@ -75,7 +78,7 @@ fun CoverRuleConfigSheet(
                     checked = enable,
                     onCheckedChange = { enable = it }
                 )
-                Text(text = stringResource(R.string.enable))
+                AppText(text = stringResource(R.string.enable))
             }
 
             InputSettingItem(
@@ -100,13 +103,16 @@ fun CoverRuleConfigSheet(
                     BookCover.delCoverRule()
                     onDismissRequest()
                 }) {
-                    Text(stringResource(R.string.delete), color = MaterialTheme.colorScheme.error)
+                    AppText(
+                        stringResource(R.string.delete),
+                        color = MaterialTheme.colorScheme.error
+                    )
                 }
 
                 Spacer(modifier = Modifier.weight(1f))
 
                 OutlinedButton(onClick = onDismissRequest) {
-                    Text(stringResource(R.string.cancel))
+                    AppText(stringResource(R.string.cancel))
                 }
 
                 Spacer(modifier = Modifier.padding(horizontal = 4.dp))
@@ -121,7 +127,7 @@ fun CoverRuleConfigSheet(
                         onDismissRequest()
                     }
                 }) {
-                    Text(stringResource(R.string.ok))
+                    AppText(stringResource(R.string.ok))
                 }
             }
         }

+ 10 - 7
app/src/main/java/io/legado/app/ui/config/otherConfig/CheckSourceBottomSheet.kt

@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.Button
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
@@ -18,21 +17,25 @@ import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.checkBox.CheckboxGroupContainer
 import io.legado.app.ui.widget.components.checkBox.CheckboxItem
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
 import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
+import io.legado.app.ui.widget.components.text.AppText
 import org.koin.androidx.compose.koinViewModel
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun CheckSourceBottomSheet(
+    show: Boolean,
     viewModel: OtherConfigViewModel = koinViewModel(),
     onDismiss: () -> Unit
 ) {
     val context = LocalContext.current
 
-    GlassModalBottomSheet(
+    AppModalBottomSheet(
+        show = show,
         onDismissRequest = onDismiss
     ) {
         Column(
@@ -41,9 +44,9 @@ fun CheckSourceBottomSheet(
                 .padding(horizontal = 16.dp)
                 .padding(bottom = 32.dp)
         ) {
-            Text(
+            AppText(
                 text = stringResource(R.string.check_source_config),
-                style = MaterialTheme.typography.titleLarge,
+                style = LegadoTheme.typography.titleLarge,
                 modifier = Modifier.padding(vertical = 16.dp)
             )
 
@@ -131,7 +134,7 @@ fun CheckSourceBottomSheet(
                 horizontalArrangement = Arrangement.End
             ) {
                 TextButton(onClick = onDismiss) {
-                    Text(stringResource(R.string.cancel))
+                    AppText(stringResource(R.string.cancel))
                 }
                 Button(onClick = {
                     if (viewModel.saveCheckSourceConfig()) {
@@ -140,7 +143,7 @@ fun CheckSourceBottomSheet(
                         Toast.makeText(context, R.string.error, Toast.LENGTH_SHORT).show()
                     }
                 }) {
-                    Text(stringResource(R.string.ok))
+                    AppText(stringResource(R.string.ok))
                 }
             }
         }

+ 21 - 18
app/src/main/java/io/legado/app/ui/config/otherConfig/DirectLinkUploadBottomSheet.kt

@@ -24,7 +24,6 @@ import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.runtime.Composable
@@ -41,8 +40,10 @@ import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.help.DirectLinkUpload
 import io.legado.app.lib.dialogs.selector
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.checkBox.CheckboxItem
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.GSON
 import io.legado.app.utils.fromJsonObject
 import io.legado.app.utils.getClipText
@@ -52,6 +53,7 @@ import io.legado.app.utils.toastOnUi
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun DirectLinkUploadBottomSheet(
+    show: Boolean,
     viewModel: OtherConfigViewModel,
     onDismiss: () -> Unit
 ) {
@@ -63,7 +65,8 @@ fun DirectLinkUploadBottomSheet(
         viewModel.initDirectLinkRule()
     }
 
-    GlassModalBottomSheet(
+    AppModalBottomSheet(
+        show = show,
         onDismissRequest = onDismiss
     ) {
         Column(
@@ -75,9 +78,9 @@ fun DirectLinkUploadBottomSheet(
         ) {
             CenterAlignedTopAppBar(
                 title = {
-                    Text(
+                    AppText(
                         text = stringResource(R.string.direct_link_upload_config),
-                        style = MaterialTheme.typography.titleLarge
+                        style = LegadoTheme.typography.titleLarge
                     )
                 },
                 actions = {
@@ -89,7 +92,7 @@ fun DirectLinkUploadBottomSheet(
                         onDismissRequest = { showMenu = false }
                     ) {
                         DropdownMenuItem(
-                            text = { Text("导入默认") },
+                            text = { AppText("导入默认") },
                             leadingIcon = { Icon(Icons.Default.Download, null) },
                             onClick = {
                                 showMenu = false
@@ -99,7 +102,7 @@ fun DirectLinkUploadBottomSheet(
                             }
                         )
                         DropdownMenuItem(
-                            text = { Text(stringResource(R.string.copy_rule)) },
+                            text = { AppText(stringResource(R.string.copy_rule)) },
                             leadingIcon = { Icon(Icons.Default.ContentCopy, null) },
                             onClick = {
                                 showMenu = false
@@ -113,7 +116,7 @@ fun DirectLinkUploadBottomSheet(
                             }
                         )
                         DropdownMenuItem(
-                            text = { Text(stringResource(R.string.paste_rule)) },
+                            text = { AppText(stringResource(R.string.paste_rule)) },
                             leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
                             onClick = {
                                 showMenu = false
@@ -141,7 +144,7 @@ fun DirectLinkUploadBottomSheet(
             OutlinedTextField(
                 value = viewModel.uploadUrl,
                 onValueChange = { viewModel.uploadUrl = it },
-                label = { Text(stringResource(R.string.upload_url)) },
+                label = { AppText(stringResource(R.string.upload_url)) },
                 modifier = Modifier.fillMaxWidth(),
             )
 
@@ -150,7 +153,7 @@ fun DirectLinkUploadBottomSheet(
             OutlinedTextField(
                 value = viewModel.downloadUrlRule,
                 onValueChange = { viewModel.downloadUrlRule = it },
-                label = { Text(stringResource(R.string.download_url_rule)) },
+                label = { AppText(stringResource(R.string.download_url_rule)) },
                 modifier = Modifier.fillMaxWidth(),
             )
 
@@ -159,7 +162,7 @@ fun DirectLinkUploadBottomSheet(
             OutlinedTextField(
                 value = viewModel.summary,
                 onValueChange = { viewModel.summary = it },
-                label = { Text(stringResource(R.string.summary)) },
+                label = { AppText(stringResource(R.string.summary)) },
                 modifier = Modifier.fillMaxWidth(),
             )
 
@@ -178,11 +181,11 @@ fun DirectLinkUploadBottomSheet(
                 TextButton(onClick = {
                     viewModel.testRule { result -> showTestResult = result }
                 }) {
-                    Text("测试")
+                    AppText("测试")
                 }
                 Spacer(Modifier.weight(1f))
                 OutlinedButton(onClick = onDismiss) {
-                    Text(stringResource(R.string.cancel))
+                    AppText(stringResource(R.string.cancel))
                 }
                 Button(onClick = {
                     if (viewModel.saveDirectLinkRule()) {
@@ -191,7 +194,7 @@ fun DirectLinkUploadBottomSheet(
                         context.toastOnUi("请填写完整信息")
                     }
                 }) {
-                    Text(stringResource(R.string.ok))
+                    AppText(stringResource(R.string.ok))
                 }
             }
         }
@@ -200,15 +203,15 @@ fun DirectLinkUploadBottomSheet(
     showTestResult?.let { result ->
         AlertDialog(
             onDismissRequest = { showTestResult = null },
-            title = { Text("Result") },
-            text = { Text(result) },
+            title = { AppText("Result") },
+            text = { AppText(result) },
             confirmButton = {
                 TextButton(onClick = {
                     showTestResult = null
-                }) { Text(stringResource(R.string.ok)) }
+                }) { AppText(stringResource(R.string.ok)) }
             },
             dismissButton = {
-                TextButton(onClick = { context.sendToClip(result) }) { Text(stringResource(R.string.copy_text)) }
+                TextButton(onClick = { context.sendToClip(result) }) { AppText(stringResource(R.string.copy_text)) }
             }
         )
     }

+ 44 - 40
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.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.TextField
 import androidx.compose.runtime.Composable
@@ -30,8 +29,6 @@ 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
 import io.legado.app.ui.widget.components.button.TopbarNavigationButton
 import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
@@ -40,6 +37,9 @@ import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
 import io.legado.app.ui.widget.components.settingItem.InputSettingItem
 import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
 import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
+import io.legado.app.ui.widget.components.text.AppText
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.utils.restart
 import io.legado.app.utils.takePersistablePermissionSafely
 import org.koin.androidx.compose.koinViewModel
@@ -83,9 +83,7 @@ fun OtherConfigScreen(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             GlassMediumFlexibleTopAppBar(
-                title = {
-                    Text(stringResource(R.string.other_setting))
-                },
+                title = stringResource(R.string.other_setting),
                 scrollBehavior = scrollBehavior,
                 navigationIcon = {
                     TopbarNavigationButton(onClick = onBackClick)
@@ -200,7 +198,10 @@ fun OtherConfigScreen(
 
                 SliderSettingItem(
                     title = stringResource(R.string.bitmap_cache_size),
-                    description = stringResource(R.string.bitmap_cache_size_summary),
+                    description = stringResource(
+                        R.string.bitmap_cache_size_summary,
+                        OtherConfig.bitmapCacheSize
+                    ),
                     value = OtherConfig.bitmapCacheSize.toFloat(),
                     defaultValue = 32f,
                     valueRange = 1f..2047f,
@@ -211,7 +212,10 @@ fun OtherConfigScreen(
 
                 SliderSettingItem(
                     title = stringResource(R.string.image_retain_number),
-                    description = stringResource(R.string.image_retain_number_summary),
+                    description = stringResource(
+                        R.string.image_retain_number_summary,
+                        OtherConfig.imageRetainNum
+                    ),
                     value = OtherConfig.imageRetainNum.toFloat(),
                     defaultValue = 10f,
                     valueRange = 0f..100f,
@@ -220,7 +224,10 @@ fun OtherConfigScreen(
 
                 SliderSettingItem(
                     title = stringResource(R.string.pre_download),
-                    description = stringResource(R.string.pre_download_s),
+                    description = stringResource(
+                        R.string.pre_download_s,
+                        OtherConfig.preDownloadNum
+                    ),
                     value = OtherConfig.preDownloadNum.toFloat(),
                     defaultValue = 10f,
                     valueRange = 0f..100f,
@@ -391,33 +398,30 @@ fun OtherConfigScreen(
             }
         }
 
-        if (showFilePicker) {
-            FilePickerSheet(
-                onDismissRequest = { showFilePicker = false },
-                onSelectSysDir = {
-                    showFilePicker = false
-                    try {
-                        selectDocTree.launch(null)
-                    } catch (e: Exception) {
+        FilePickerSheet(
+            show = showFilePicker,
+            onDismissRequest = { showFilePicker = false },
+            onSelectSysDir = {
+                showFilePicker = false
+                try {
+                    selectDocTree.launch(null)
+                } catch (e: Exception) {
 
-                    }
                 }
-            )
-        }
+            }
+        )
 
-        if (showCheckSourceSheet) {
-            CheckSourceBottomSheet(
-                viewModel = viewModel,
-                onDismiss = { showCheckSourceSheet = false }
-            )
-        }
+        CheckSourceBottomSheet(
+            show = showCheckSourceSheet,
+            viewModel = viewModel,
+            onDismiss = { showCheckSourceSheet = false }
+        )
 
-        if (showDirectLinkUploadSheet) {
-            DirectLinkUploadBottomSheet(
-                viewModel = viewModel,
-                onDismiss = { showDirectLinkUploadSheet = false }
-            )
-        }
+        DirectLinkUploadBottomSheet(
+            show = showDirectLinkUploadSheet,
+            viewModel = viewModel,
+            onDismiss = { showDirectLinkUploadSheet = false }
+        )
 
         if (showClearCacheDialog) {
             ConfirmDialog(
@@ -458,23 +462,23 @@ fun OtherConfigScreen(
         if (showPasswordDialog) {
             AlertDialog(
                 onDismissRequest = { showPasswordDialog = false },
-                title = { Text(stringResource(R.string.set_local_password)) },
+                title = { AppText(stringResource(R.string.set_local_password)) },
                 text = {
                     TextField(
                         value = tempPassword,
                         onValueChange = { tempPassword = it },
-                        label = { Text("Password") }
+                        label = { AppText("Password") }
                     )
                 },
                 confirmButton = {
                     TextButton(onClick = {
                         viewModel.setLocalPassword(tempPassword)
                         showPasswordDialog = false
-                    }) { Text(stringResource(R.string.ok)) }
+                    }) { AppText(stringResource(R.string.ok)) }
                 },
                 dismissButton = {
                     TextButton(onClick = { showPasswordDialog = false }) {
-                        Text(stringResource(R.string.cancel))
+                        AppText(stringResource(R.string.cancel))
                     }
                 }
             )
@@ -492,16 +496,16 @@ fun ConfirmDialog(
 ) {
     AlertDialog(
         onDismissRequest = onDismiss,
-        title = { Text(title) },
-        text = { Text(text) },
+        title = { AppText(title) },
+        text = { AppText(text) },
         confirmButton = {
             OutlinedButton(onClick = onConfirm) {
-                Text(stringResource(R.string.ok))
+                AppText(stringResource(R.string.ok))
             }
         },
         dismissButton = {
             TextButton(onClick = onDismiss) {
-                Text(stringResource(R.string.cancel))
+                AppText(stringResource(R.string.cancel))
             }
         }
     )

+ 13 - 11
app/src/main/java/io/legado/app/ui/config/readConfig/PageKeySheet.kt

@@ -11,7 +11,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -23,33 +22,36 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun PageKeySheet(
+    show: Boolean,
     onDismissRequest: () -> Unit
 ) {
     var prevKeys by remember { mutableStateOf(ReadConfig.prevKeys) }
     var nextKeys by remember { mutableStateOf(ReadConfig.nextKeys) }
 
-    GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
+    AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
         Column(
             modifier = Modifier.Companion
                 .fillMaxWidth()
                 .padding(16.dp),
             verticalArrangement = Arrangement.spacedBy(16.dp)
         ) {
-            Text(
+            AppText(
                 text = stringResource(R.string.custom_page_key),
-                style = MaterialTheme.typography.titleLarge,
+                style = LegadoTheme.typography.titleLarge,
                 fontWeight = FontWeight.Companion.Bold
             )
 
             OutlinedTextField(
                 value = prevKeys,
                 onValueChange = { prevKeys = it },
-                label = { Text(stringResource(R.string.prev_page_key)) },
+                label = { AppText(stringResource(R.string.prev_page_key)) },
                 modifier = Modifier.Companion
                     .fillMaxWidth()
                     .onPreviewKeyEvent { event ->
@@ -72,7 +74,7 @@ fun PageKeySheet(
             OutlinedTextField(
                 value = nextKeys,
                 onValueChange = { nextKeys = it },
-                label = { Text(stringResource(R.string.next_page_key)) },
+                label = { AppText(stringResource(R.string.next_page_key)) },
                 modifier = Modifier.Companion
                     .fillMaxWidth()
                     .onPreviewKeyEvent { event ->
@@ -92,9 +94,9 @@ fun PageKeySheet(
                 singleLine = true
             )
 
-            Text(
+            AppText(
                 text = stringResource(R.string.page_key_set_help),
-                style = MaterialTheme.typography.bodyMedium,
+                style = LegadoTheme.typography.bodyMedium,
                 color = MaterialTheme.colorScheme.onSurfaceVariant
             )
 
@@ -109,7 +111,7 @@ fun PageKeySheet(
                     },
                     modifier = Modifier.Companion.weight(1f)
                 ) {
-                    Text(stringResource(R.string.reset))
+                    AppText(stringResource(R.string.reset))
                 }
 
                 Button(
@@ -120,7 +122,7 @@ fun PageKeySheet(
                     },
                     modifier = Modifier.Companion.weight(1f)
                 ) {
-                    Text(stringResource(R.string.ok))
+                    AppText(stringResource(R.string.ok))
                 }
             }
         }

+ 7 - 9
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.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -20,14 +19,14 @@ 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
 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 io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
 import org.koin.androidx.compose.koinViewModel
 
@@ -45,9 +44,7 @@ fun ReadConfigScreen(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             GlassMediumFlexibleTopAppBar(
-                title = {
-                    Text(stringResource(R.string.read_config))
-                },
+                title = stringResource(R.string.read_config),
                 scrollBehavior = scrollBehavior,
                 navigationIcon = {
                     TopbarNavigationButton(onClick = onBackClick)
@@ -323,7 +320,8 @@ fun ReadConfigScreen(
         }
     }
 
-    if (showPageKeySheet) {
-        PageKeySheet(onDismissRequest = { showPageKeySheet = false })
-    }
+    PageKeySheet(
+        show = showPageKeySheet,
+        onDismissRequest = { showPageKeySheet = false }
+    )
 }

+ 17 - 15
app/src/main/java/io/legado/app/ui/config/themeConfig/BackgroundImageManageSheet.kt

@@ -17,7 +17,6 @@ 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
@@ -31,14 +30,17 @@ 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.theme.LegadoTheme
 import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
 import kotlinx.coroutines.launch
 import org.koin.androidx.compose.koinViewModel
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun BackgroundImageManageSheet(
+    show: Boolean,
     isDarkTheme: Boolean,
     onDismissRequest: () -> Unit,
     viewModel: ThemeConfigViewModel = koinViewModel()
@@ -64,7 +66,8 @@ fun BackgroundImageManageSheet(
         ThemeConfig.bgImageLight
     }
 
-    GlassModalBottomSheet(
+    AppModalBottomSheet(
+        show = show,
         onDismissRequest = onDismissRequest
     ) {
         Column(
@@ -72,9 +75,9 @@ fun BackgroundImageManageSheet(
                 .fillMaxWidth()
                 .padding(bottom = 24.dp)
         ) {
-            Text(
+            AppText(
                 text = stringResource(R.string.background_image),
-                style = MaterialTheme.typography.titleMedium,
+                style = LegadoTheme.typography.titleMedium,
                 modifier = Modifier.padding(16.dp)
             )
 
@@ -139,14 +142,13 @@ fun BackgroundImageManageSheet(
         }
     }
 
-    if (showFilePicker) {
-        FilePickerSheet(
-            onDismissRequest = { showFilePicker = false },
-            onSelectSysFile = {
-                selectImage.launch("image/*")
-                showFilePicker = false
-            },
-            allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
-        )
-    }
+    FilePickerSheet(
+        show = showFilePicker,
+        onDismissRequest = { showFilePicker = false },
+        onSelectSysFile = {
+            selectImage.launch("image/*")
+            showFilePicker = false
+        },
+        allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
+    )
 }

+ 8 - 5
app/src/main/java/io/legado/app/ui/config/themeConfig/LauncherIconPickerSheet.kt

@@ -20,7 +20,6 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
 import androidx.compose.foundation.lazy.grid.items
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
@@ -39,13 +38,16 @@ import io.legado.app.ui.main.Launcher4
 import io.legado.app.ui.main.Launcher5
 import io.legado.app.ui.main.Launcher6
 import io.legado.app.ui.main.LauncherW
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.getCompatDrawable
 import splitties.init.appCtx
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun LauncherIconPickerSheet(
+    show: Boolean,
     selectedValue: String,
     onDismissRequest: () -> Unit,
     onValueChange: (String) -> Unit
@@ -53,7 +55,8 @@ fun LauncherIconPickerSheet(
     val context = LocalContext.current
     val icons = LauncherIcons.list
 
-    GlassModalBottomSheet(
+    AppModalBottomSheet(
+        show = show,
         onDismissRequest = onDismissRequest
     ) {
         Column(
@@ -61,9 +64,9 @@ fun LauncherIconPickerSheet(
                 .fillMaxWidth()
                 .padding(bottom = 24.dp)
         ) {
-            Text(
+            AppText(
                 text = stringResource(R.string.change_icon),
-                style = MaterialTheme.typography.titleMedium,
+                style = LegadoTheme.typography.titleMedium,
                 modifier = Modifier.padding(16.dp)
             )
 

+ 4 - 1
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt

@@ -15,12 +15,15 @@ object ThemeConfig {
 
     var enableBlur by prefDelegate(PreferKey.enableBlur, false)
 
-    var enableProgressiveBlur by prefDelegate(PreferKey.enableProgressiveBlur, true)
+    var enableProgressiveBlur by prefDelegate(PreferKey.enableProgressiveBlur, false)
 
     var useFlexibleTopAppBar by prefDelegate(PreferKey.useFlexibleTopAppBar, true)
 
     var paletteStyle by prefDelegate(PreferKey.paletteStyle, "tonalSpot")
 
+    //m3 or miuix
+    var composeEngine by prefDelegate(PreferKey.composeEngine, "material")
+
     var materialVersion by prefDelegate(PreferKey.materialVersion, "material3")
 
     var appTheme by prefDelegate(PreferKey.appTheme, "0")

+ 145 - 94
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt

@@ -15,6 +15,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
@@ -78,11 +79,10 @@ import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.OldThemeConfig
 import io.legado.app.lib.theme.ThemeStore
 import io.legado.app.lib.theme.primaryColor
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.ThemeManager
 import io.legado.app.ui.theme.ThemeResolver
 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.dialog.ColorPickerSheet
@@ -90,10 +90,17 @@ 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 io.legado.app.ui.widget.components.text.AppText
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.utils.postEvent
 import io.legado.app.utils.restart
 import io.legado.app.utils.toastOnUi
 import org.koin.androidx.compose.koinViewModel
+import top.yukonga.miuix.kmp.theme.MiuixTheme
+import top.yukonga.miuix.kmp.basic.Card as MiuixCard
+import top.yukonga.miuix.kmp.basic.CardDefaults as MiuixCardDefaults
+import top.yukonga.miuix.kmp.basic.Text as MiuixText
 
 @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
 @Composable
@@ -121,7 +128,7 @@ fun ThemeConfigScreen(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             GlassMediumFlexibleTopAppBar(
-                title = { Text(stringResource(R.string.theme_setting)) },
+                title = stringResource(R.string.theme_setting),
                 scrollBehavior = scrollBehavior,
                 navigationIcon = {
                     TopbarNavigationButton(onClick = onBackClick)
@@ -136,23 +143,29 @@ fun ThemeConfigScreen(
                 .verticalScroll(rememberScrollState())
                 .padding(16.dp)
         ) {
+            val composeEngine = ThemeConfig.composeEngine
+            val isMiuixEngine = remember(composeEngine) {
+                ThemeResolver.isMiuixEngine(composeEngine)
+            }
             val isDarkTheme = when (selectedThemeMode) {
                 "1" -> false
                 "2" -> true
                 else -> isSystemInDarkTheme()
             }
 
-            Column(
-                horizontalAlignment = Alignment.CenterHorizontally,
-                modifier = Modifier.fillMaxWidth()
-            ) {
-                ThemeCard(
-                    context = context,
-                    value = selectedTheme,
-                    isDark = isDarkTheme,
-                    isAmoled = ThemeConfig.isPureBlack,
-                    paletteStyle = ThemeConfig.paletteStyle
-                )
+            if (!isMiuixEngine) {
+                Column(
+                    horizontalAlignment = Alignment.CenterHorizontally,
+                    modifier = Modifier.fillMaxWidth()
+                ) {
+                    ThemeCard(
+                        context = context,
+                        value = selectedTheme,
+                        isDark = isDarkTheme,
+                        isAmoled = ThemeConfig.isPureBlack,
+                        paletteStyle = ThemeConfig.paletteStyle
+                    )
+                }
             }
 
             Spacer(modifier = Modifier.height(16.dp))
@@ -163,47 +176,77 @@ fun ThemeConfigScreen(
                 themeItems.zip(themeValues).toList()
             }
 
+            if (isMiuixEngine) {
+                MiuixCard(
+                    cornerRadius = 16.dp,
+                    insideMargin = PaddingValues(16.dp),
+                    colors = MiuixCardDefaults.defaultColors(
+                        color = MiuixTheme.colorScheme.primaryVariant,
+                        contentColor = MiuixTheme.colorScheme.onPrimary
+                    )
+                ) {
+                    MiuixText("Miuix 目前为测试主题,且不对基于View的界面生效!")
+                }
+            }
+
+
             SplicedColumnGroup(title = stringResource(R.string.theme)) {
-                ThemeModeSelector(
-                    selectedMode = selectedThemeMode,
-                    onModeSelected = { mode ->
-                        selectedThemeMode = mode
-                        ThemeConfig.themeMode = mode
-                        OldThemeConfig.applyDayNight(context)
-                    }
-                )
+                if (isMiuixEngine) {
+                    DropdownListSettingItem(
+                        title = stringResource(R.string.theme_mode),
+                        selectedValue = selectedThemeMode,
+                        displayEntries = stringArrayResource(R.array.theme_mode),
+                        entryValues = stringArrayResource(R.array.theme_mode_v),
+                        onValueChange = { mode ->
+                            selectedThemeMode = mode
+                            ThemeConfig.themeMode = mode
+                            OldThemeConfig.applyDayNight(context)
+                        }
+                    )
+                } else {
+                    ThemeModeSelector(
+                        selectedMode = selectedThemeMode,
+                        onModeSelected = { mode ->
+                            selectedThemeMode = mode
+                            ThemeConfig.themeMode = mode
+                            OldThemeConfig.applyDayNight(context)
+                        }
+                    )
+                }
 
-                Spacer(modifier = Modifier.height(16.dp))
-
-                ThemeColorSelector(
-                    context = context,
-                    themes = themes,
-                    selectedTheme = selectedTheme,
-                    isDark = isDarkTheme,
-                    isAmoled = ThemeConfig.isPureBlack,
-                    paletteStyle = ThemeConfig.paletteStyle,
-                    onThemeSelected = { theme ->
-                        if (theme == "13") {
-                            val hasLightBg = !ThemeConfig.bgImageLight.isNullOrEmpty()
-                            val hasDarkBg = !ThemeConfig.bgImageDark.isNullOrEmpty()
-                            if (!hasLightBg || !hasDarkBg) {
-                                context.toastOnUi(R.string.transparent_theme_alarm)
-                                return@ThemeColorSelector
+                if (!isMiuixEngine) {
+                    Spacer(modifier = Modifier.height(16.dp))
+
+                    ThemeColorSelector(
+                        context = context,
+                        themes = themes,
+                        selectedTheme = selectedTheme,
+                        isDark = isDarkTheme,
+                        isAmoled = ThemeConfig.isPureBlack,
+                        paletteStyle = ThemeConfig.paletteStyle,
+                        onThemeSelected = { theme ->
+                            if (theme == "13") {
+                                val hasLightBg = !ThemeConfig.bgImageLight.isNullOrEmpty()
+                                val hasDarkBg = !ThemeConfig.bgImageDark.isNullOrEmpty()
+                                if (!hasLightBg || !hasDarkBg) {
+                                    context.toastOnUi(R.string.transparent_theme_alarm)
+                                    return@ThemeColorSelector
+                                } else {
+                                    AppConfig.containerOpacity = 0
+                                }
+                            }
+                            val oldTheme = selectedTheme
+                            selectedTheme = theme
+                            ThemeConfig.appTheme = theme
+                            val isDynamicSwitch = (oldTheme == "12" || theme == "12")
+                            if (isDynamicSwitch) {
+                                showRestartDialog = true
                             } else {
-                                AppConfig.containerOpacity = 0
+                                postEvent(EventBus.RECREATE, "")
                             }
                         }
-                        val oldTheme = selectedTheme
-                        selectedTheme = theme
-                        ThemeConfig.appTheme = theme
-                        val isDynamicSwitch = (oldTheme == "12" || theme == "12")
-                        if (isDynamicSwitch) {
-                            showRestartDialog = true
-                        } else {
-                            postEvent(EventBus.RECREATE, "")
-                        }
-                    }
-                )
+                    )
+                }
             }
 
             SplicedColumnGroup {
@@ -226,13 +269,22 @@ fun ThemeConfigScreen(
                         context.toastOnUi(R.string.restart_to_apply)
                     }
                 )
+                DropdownListSettingItem(
+                    title = stringResource(R.string.compose_engine),
+                    selectedValue = ThemeConfig.composeEngine,
+                    displayEntries = stringArrayResource(R.array.composeEngine),
+                    entryValues = stringArrayResource(R.array.composeEngine_value),
+                    onValueChange = {
+                        ThemeConfig.composeEngine = it
+                    }
+                )
                 SliderSettingItem(
                     title = stringResource(R.string.font_scale),
                     description = stringResource(
                         R.string.font_scale_summary,
                         AppContextWrapper.getFontScale(context)
                     ),
-                    value = fontScaleValue.value,
+                    value = fontScaleValue.floatValue,
                     defaultValue = 10f,
                     valueRange = 8f..16f,
                     steps = 7,
@@ -478,7 +530,7 @@ fun ThemeConfigScreen(
     if (showRestartDialog) {
         AlertDialog(
             onDismissRequest = { showRestartDialog = false },
-            title = { Text(stringResource(R.string.restart_required_message)) },
+            title = { AppText(stringResource(R.string.restart_required_message)) },
             confirmButton = {
                 OutlinedButton(onClick = {
                     showRestartDialog = false
@@ -486,7 +538,7 @@ fun ThemeConfigScreen(
                         context.restart()
                     }, 100)
                 }) {
-                    Text(stringResource(R.string.ok))
+                    AppText(stringResource(R.string.ok))
                 }
             },
             dismissButton = {
@@ -494,7 +546,7 @@ fun ThemeConfigScreen(
                     showRestartDialog = false
                     context.toastOnUi(R.string.restart_later_message)
                 }) {
-                    Text(stringResource(R.string.cancel))
+                    AppText(stringResource(R.string.cancel))
                 }
             }
         )
@@ -502,57 +554,56 @@ fun ThemeConfigScreen(
 
     manageKey?.let { isDark ->
         BackgroundImageManageSheet(
+            show = true,
             isDarkTheme = isDark,
             onDismissRequest = { manageKey = null }
         )
     }
 
-    if (showColorPicker) {
-        ColorPickerSheet(
-            initialColor = primaryColorValue.value,
-            onDismissRequest = { showColorPicker = false },
-            onColorSelected = { color ->
-                primaryColorValue.value = color
-                ThemeConfig.cPrimary = color
-                ThemeStore.editTheme(context)
-                    .primaryColor(color)
-                    .apply()
-                DynamicColors.applyToActivitiesIfAvailable(
-                    context.applicationContext as android.app.Application,
-                    DynamicColorsOptions.Builder()
-                        .setContentBasedSource(context.primaryColor)
-                        .build()
-                )
-            }
-        )
-    }
 
-    if (showLauncherIconPicker) {
-        LauncherIconPickerSheet(
-            selectedValue = ThemeConfig.launcherIcon,
-            onDismissRequest = { showLauncherIconPicker = false },
-            onValueChange = {
-                ThemeConfig.launcherIcon = it
-                LauncherIconHelp.changeIcon(it)
-            }
-        )
-    }
+    ColorPickerSheet(
+        show = showColorPicker,
+        initialColor = primaryColorValue.value,
+        onDismissRequest = { showColorPicker = false },
+        onColorSelected = { color ->
+            primaryColorValue.value = color
+            ThemeConfig.cPrimary = color
+            ThemeStore.editTheme(context)
+                .primaryColor(color)
+                .apply()
+            DynamicColors.applyToActivitiesIfAvailable(
+                context.applicationContext as android.app.Application,
+                DynamicColorsOptions.Builder()
+                    .setContentBasedSource(context.primaryColor)
+                    .build()
+            )
+        }
+    )
 
-    if (showThemeListDialog) {
-        ThemeListDialog(
-            onDismissRequest = { showThemeListDialog = false }
-        )
-    }
+    LauncherIconPickerSheet(
+        show = showLauncherIconPicker,
+        selectedValue = ThemeConfig.launcherIcon,
+        onDismissRequest = { showLauncherIconPicker = false },
+        onValueChange = {
+            ThemeConfig.launcherIcon = it
+            LauncherIconHelp.changeIcon(it)
+        }
+    )
+
+    ThemeListDialog(
+        show = showThemeListDialog,
+        onDismissRequest = { showThemeListDialog = false }
+    )
 
     saveThemeKey?.let { key ->
         AlertDialog(
             onDismissRequest = { saveThemeKey = null },
-            title = { Text(stringResource(R.string.theme_name)) },
+            title = { AppText(stringResource(R.string.theme_name)) },
             text = {
                 OutlinedTextField(
                     value = themeName,
                     onValueChange = { themeName = it },
-                    label = { Text("name") },
+                    label = { AppText("name") },
                     singleLine = true
                 )
             },
@@ -566,12 +617,12 @@ fun ThemeConfigScreen(
                         saveThemeKey = null
                     }
                 ) {
-                    Text(stringResource(android.R.string.ok))
+                    AppText(stringResource(android.R.string.ok))
                 }
             },
             dismissButton = {
                 TextButton(onClick = { saveThemeKey = null }) {
-                    Text(stringResource(android.R.string.cancel))
+                    AppText(stringResource(android.R.string.cancel))
                 }
             }
         )
@@ -745,9 +796,9 @@ fun ThemeColorButton(
 
         Spacer(modifier = Modifier.height(12.dp))
 
-        Text(
+        AppText(
             text = label,
-            style = MaterialTheme.typography.labelSmall,
+            style = LegadoTheme.typography.labelSmall,
             color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface
         )
     }

+ 19 - 19
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeListDialog.kt

@@ -15,12 +15,10 @@ import androidx.compose.material.icons.filled.Delete
 import androidx.compose.material.icons.filled.FileDownload
 import androidx.compose.material.icons.filled.Share
 import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -38,8 +36,10 @@ import androidx.core.graphics.toColorInt
 import io.legado.app.R
 import io.legado.app.help.config.OldThemeConfig
 import io.legado.app.lib.theme.primaryColor
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.card.GlassCard
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.GSON
 import io.legado.app.utils.getClipText
 import io.legado.app.utils.share
@@ -48,6 +48,7 @@ import io.legado.app.utils.toastOnUi
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun ThemeListDialog(
+    show: Boolean,
     onDismissRequest: () -> Unit
 ) {
     val context = LocalContext.current
@@ -55,7 +56,8 @@ fun ThemeListDialog(
     var deleteIndex by remember { mutableStateOf<Int?>(null) }
     val themeList = remember(listVersion) { OldThemeConfig.configList.toList() }
 
-    GlassModalBottomSheet(
+    AppModalBottomSheet(
+        show = show,
         onDismissRequest = onDismissRequest
     ) {
         Column(
@@ -69,9 +71,9 @@ fun ThemeListDialog(
                     .padding(start = 16.dp, end = 8.dp, top = 8.dp, bottom = 8.dp),
                 verticalAlignment = Alignment.CenterVertically
             ) {
-                Text(
+                AppText(
                     text = stringResource(R.string.theme_list),
-                    style = MaterialTheme.typography.titleMedium,
+                    style = LegadoTheme.typography.titleMedium,
                     modifier = Modifier.weight(1f)
                 )
                 IconButton(
@@ -104,13 +106,11 @@ fun ThemeListDialog(
                             .fillMaxWidth()
                             .padding(horizontal = 16.dp),
                         shape = RoundedCornerShape(20.dp),
-                        colors = CardDefaults.cardColors(
-                            containerColor = if (item.primaryColor.toColorInt() == context.primaryColor) {
-                                MaterialTheme.colorScheme.secondaryContainer
-                            } else {
-                                MaterialTheme.colorScheme.surfaceContainer
-                            }
-                        )
+                        containerColor = if (item.primaryColor.toColorInt() == context.primaryColor) {
+                            MaterialTheme.colorScheme.secondaryContainer
+                        } else {
+                            MaterialTheme.colorScheme.surfaceContainer
+                        }
                     ) {
                         Row(
                             modifier = Modifier
@@ -129,9 +129,9 @@ fun ThemeListDialog(
                                     )
                                     .padding(8.dp)
                             )
-                            Text(
+                            AppText(
                                 text = item.themeName,
-                                style = MaterialTheme.typography.bodyLarge,
+                                style = LegadoTheme.typography.bodyLarge,
                                 modifier = Modifier.weight(1f)
                             )
                             IconButton(
@@ -161,8 +161,8 @@ fun ThemeListDialog(
     deleteIndex?.let { index ->
         AlertDialog(
             onDismissRequest = { deleteIndex = null },
-            title = { Text(stringResource(R.string.delete)) },
-            text = { Text(stringResource(R.string.sure_del)) },
+            title = { AppText(stringResource(R.string.delete)) },
+            text = { AppText(stringResource(R.string.sure_del)) },
             confirmButton = {
                 TextButton(
                     onClick = {
@@ -171,12 +171,12 @@ fun ThemeListDialog(
                         deleteIndex = null
                     }
                 ) {
-                    Text(stringResource(android.R.string.ok))
+                    AppText(stringResource(android.R.string.ok))
                 }
             },
             dismissButton = {
                 TextButton(onClick = { deleteIndex = null }) {
-                    Text(stringResource(android.R.string.cancel))
+                    AppText(stringResource(android.R.string.cancel))
                 }
             }
         )

+ 10 - 9
app/src/main/java/io/legado/app/ui/dict/rule/DictRuleEditSheet.kt

@@ -22,7 +22,6 @@ import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Text
 import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -37,7 +36,8 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.data.entities.DictRule
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
 import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalMaterial3Api::class)
@@ -56,7 +56,8 @@ fun DictRuleEditSheet(
     var showRule by remember(rule) { mutableStateOf(rule?.showRule ?: "") }
     var showMenu by remember { mutableStateOf(false) }
 
-    GlassModalBottomSheet(
+    AppModalBottomSheet(
+        show = true,
         onDismissRequest = onDismissRequest
     ) {
         Box(
@@ -69,7 +70,7 @@ fun DictRuleEditSheet(
                     .verticalScroll(rememberScrollState())
             ) {
                 CenterAlignedTopAppBar(
-                    title = { Text(stringResource(R.string.dict_rule)) },
+                    title = { AppText(stringResource(R.string.dict_rule)) },
                     navigationIcon = {
                         IconButton(onClick = onDismissRequest) {
                             Icon(
@@ -87,7 +88,7 @@ fun DictRuleEditSheet(
                             onDismissRequest = { showMenu = false }
                         ) {
                             DropdownMenuItem(
-                                text = { Text(stringResource(R.string.copy_rule)) },
+                                text = { AppText(stringResource(R.string.copy_rule)) },
                                 leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) },
                                 onClick = {
                                     onCopy(
@@ -102,7 +103,7 @@ fun DictRuleEditSheet(
                                 }
                             )
                             DropdownMenuItem(
-                                text = { Text(stringResource(R.string.paste_rule)) },
+                                text = { AppText(stringResource(R.string.paste_rule)) },
                                 leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
                                 onClick = {
                                     scope.launch {
@@ -132,20 +133,20 @@ fun DictRuleEditSheet(
                         modifier = Modifier.fillMaxWidth(),
                         value = name,
                         onValueChange = { name = it },
-                        label = { Text(stringResource(R.string.name)) },
+                        label = { AppText(stringResource(R.string.name)) },
                         singleLine = true
                     )
                     OutlinedTextField(
                         modifier = Modifier.fillMaxWidth(),
                         value = urlRule,
                         onValueChange = { urlRule = it },
-                        label = { Text(stringResource(R.string.url_rule)) }
+                        label = { AppText(stringResource(R.string.url_rule)) }
                     )
                     OutlinedTextField(
                         modifier = Modifier.fillMaxWidth(),
                         value = showRule,
                         onValueChange = { showRule = it },
-                        label = { Text(stringResource(R.string.show_rule)) },
+                        label = { AppText(stringResource(R.string.show_rule)) },
                         minLines = 3
                     )
                 }

+ 86 - 95
app/src/main/java/io/legado/app/ui/dict/rule/DictRuleScreen.kt

@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
@@ -19,11 +18,9 @@ import androidx.compose.material3.AlertDialog
 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
 import androidx.compose.material3.SnackbarResult
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.rememberModalBottomSheetState
 import androidx.compose.runtime.Composable
@@ -51,7 +48,6 @@ 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.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
@@ -59,6 +55,7 @@ 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
+import io.legado.app.ui.widget.components.text.AppText
 import org.koin.androidx.compose.koinViewModel
 import sh.calvin.reorderable.rememberReorderableLazyListState
 
@@ -153,54 +150,49 @@ fun DictRuleScreen(
         )
     }
 
-    if (showExportSheet) {
-        FilePickerSheet(
-            onDismissRequest = { showExportSheet = false },
-            title = stringResource(R.string.export),
-            onSelectSysDir = {
-                showExportSheet = false
-                exportDoc.launch("exportDictRule.json")
-            },
-            onUpload = {
-                showExportSheet = false
-                viewModel.uploadSelectedRules(selectedIds, rules)
-            },
-            allowExtensions = arrayOf("json")
-        )
-    }
+    FilePickerSheet(
+        show = showExportSheet,
+        onDismissRequest = { showExportSheet = false },
+        title = stringResource(R.string.export),
+        onSelectSysDir = {
+            showExportSheet = false
+            exportDoc.launch("exportDictRule.json")
+        },
+        onUpload = {
+            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 = "导入词典规则",
-            importState = state,
-            onDismissRequest = { viewModel.cancelImport() },
-            onToggleItem = { viewModel.toggleImportSelection(it) },
-            onToggleAll = { viewModel.toggleImportAll(it) },
-            onConfirm = { viewModel.saveImportedRules() },
-            itemContent = { rule, _ ->
-                Column {
-                    Text(rule.name, style = MaterialTheme.typography.titleMedium)
-                    Text(rule.urlRule, style = MaterialTheme.typography.bodySmall, maxLines = 1)
-                }
-            }
-        )
-    }
+    FilePickerSheet(
+        show = showImportSheet,
+        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")
+    )
+
+    BatchImportDialog(
+        title = "导入词典规则",
+        importState = importState,
+        onDismissRequest = { viewModel.cancelImport() },
+        onToggleItem = { viewModel.toggleImportSelection(it) },
+        onToggleAll = { viewModel.toggleImportAll(it) },
+        onConfirm = { viewModel.saveImportedRules() },
+        itemTitle = { rule -> rule.name },
+        itemSubtitle = { rule ->
+            rule.urlRule.takeIf { it.isNotBlank() }
+        }
+    )
 
     LaunchedEffect(reorderableState.isAnyItemDragging) {
         if (!reorderableState.isAnyItemDragging) {
@@ -211,16 +203,16 @@ fun DictRuleScreen(
     showDeleteRuleDialog?.let { rule ->
         AlertDialog(
             onDismissRequest = { showDeleteRuleDialog = null },
-            title = { Text(stringResource(R.string.delete)) },
-            text = { Text(stringResource(R.string.del_msg)) },
+            title = { AppText(stringResource(R.string.delete)) },
+            text = { AppText(stringResource(R.string.del_msg)) },
             confirmButton = {
                 OutlinedButton(onClick = {
                     viewModel.delete(rule); showDeleteRuleDialog = null
-                }) { Text(stringResource(R.string.ok)) }
+                }) { AppText(stringResource(R.string.ok)) }
             },
             dismissButton = {
                 TextButton(onClick = { showDeleteRuleDialog = null }) {
-                    Text(
+                    AppText(
                         stringResource(R.string.cancel)
                     )
                 }
@@ -228,47 +220,46 @@ fun DictRuleScreen(
         )
     }
 
-    if (showEditSheet) {
-        RuleEditSheet(
-            rule = editingRule,
-            title = stringResource(R.string.dict_rule),
-            label1 = stringResource(R.string.url_rule),
-            label2 = stringResource(R.string.show_rule),
-            onDismissRequest = {
-                showEditSheet = false
-                editingRule = null
-            },
-            onSave = { updatedRule ->
-                if (editingRule == null) {
-                    viewModel.insert(updatedRule)
-                } else {
-                    viewModel.update(updatedRule)
-                }
-                showEditSheet = false
-                editingRule = null
-            },
-            onCopy = { viewModel.copyRule(it) },
-            onPaste = { viewModel.pasteRule() },
-            toFields = { r ->
-                RuleEditFields(
-                    name = r?.name ?: "",
-                    rule1 = r?.urlRule ?: "",
-                    rule2 = r?.showRule ?: ""
-                )
-            },
-            fromFields = { fields, old ->
-                old?.copy(
-                    name = fields.name,
-                    urlRule = fields.rule1,
-                    showRule = fields.rule2
-                ) ?: DictRule(
-                    name = fields.name,
-                    urlRule = fields.rule1,
-                    showRule = fields.rule2
-                )
+    RuleEditSheet(
+        show = showEditSheet,
+        rule = editingRule,
+        title = stringResource(R.string.dict_rule),
+        label1 = stringResource(R.string.url_rule),
+        label2 = stringResource(R.string.show_rule),
+        onDismissRequest = {
+            showEditSheet = false
+            editingRule = null
+        },
+        onSave = { updatedRule ->
+            if (editingRule == null) {
+                viewModel.insert(updatedRule)
+            } else {
+                viewModel.update(updatedRule)
             }
-        )
-    }
+            showEditSheet = false
+            editingRule = null
+        },
+        onCopy = { viewModel.copyRule(it) },
+        onPaste = { viewModel.pasteRule() },
+        toFields = { r ->
+            RuleEditFields(
+                name = r?.name ?: "",
+                rule1 = r?.urlRule ?: "",
+                rule2 = r?.showRule ?: ""
+            )
+        },
+        fromFields = { fields, old ->
+            old?.copy(
+                name = fields.name,
+                urlRule = fields.rule1,
+                showRule = fields.rule2
+            ) ?: DictRule(
+                name = fields.name,
+                urlRule = fields.rule1,
+                showRule = fields.rule2
+            )
+        }
+    )
 
     RuleListScaffold(
         title = "字典规则",
@@ -310,7 +301,7 @@ fun DictRuleScreen(
         snackbarHostState = snackbarHostState,
         dropDownMenuContent = { dismiss ->
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.import_str)) },
+                text = stringResource(R.string.import_str),
                 onClick = { showImportSheet = true; dismiss() },
                 leadingIcon = { Icon(Icons.Default.FileOpen, null) }
             )

+ 4 - 4
app/src/main/java/io/legado/app/ui/main/MainActivity.kt

@@ -181,7 +181,7 @@ open class MainActivity : BaseComposeActivity() {
                 (slideIntoContainer(
                     towards = AnimatedContentTransitionScope.SlideDirection.Start,
                     animationSpec = tween(
-                        durationMillis = 520,
+                        durationMillis = 480,
                         easing = FastOutSlowInEasing
                     ),
                     initialOffset = { fullWidth -> fullWidth }
@@ -193,7 +193,7 @@ open class MainActivity : BaseComposeActivity() {
                 )) togetherWith (slideOutOfContainer(
                     towards = AnimatedContentTransitionScope.SlideDirection.Start,
                     animationSpec = tween(
-                        durationMillis = 520,
+                        durationMillis = 480,
                         easing = FastOutSlowInEasing
                     ),
                     targetOffset = { fullWidth -> fullWidth / 4 }
@@ -208,7 +208,7 @@ open class MainActivity : BaseComposeActivity() {
                 (slideIntoContainer(
                     towards = AnimatedContentTransitionScope.SlideDirection.Start,
                     animationSpec = tween(
-                        durationMillis = 520,
+                        durationMillis = 480,
                         easing = FastOutSlowInEasing
                     ),
                     initialOffset = { fullWidth -> -fullWidth / 4 }
@@ -220,7 +220,7 @@ open class MainActivity : BaseComposeActivity() {
                 )) togetherWith (scaleOut(
                     targetScale = 0.8f,
                     animationSpec = tween(
-                        durationMillis = 520,
+                        durationMillis = 480,
                         easing = FastOutSlowInEasing
                     )
                 ) + fadeOut(

+ 34 - 19
app/src/main/java/io/legado/app/ui/main/MainDestination.kt

@@ -12,42 +12,57 @@ import androidx.compose.material.icons.outlined.Person
 import androidx.compose.material.icons.outlined.RssFeed
 import androidx.compose.ui.graphics.vector.ImageVector
 import io.legado.app.R
+import top.yukonga.miuix.kmp.icon.MiuixIcons
+import top.yukonga.miuix.kmp.icon.extended.Album
+import top.yukonga.miuix.kmp.icon.extended.ContactsBook
+import top.yukonga.miuix.kmp.icon.extended.Favorites
+import top.yukonga.miuix.kmp.icon.extended.Settings
 
 sealed class MainDestination(
     val route: String,
     @StringRes val labelId: Int,
-    val icon: ImageVector,
-    val selectedIcon: ImageVector
+    val m3Icon: ImageVector,
+    val m3SelectedIcon: ImageVector,
+    val miuixIcon: ImageVector,
+    val miuixSelectedIcon: ImageVector
 ) {
     object Bookshelf : MainDestination(
-        "bookshelf",
-        R.string.bookshelf,
-        Icons.AutoMirrored.Outlined.LibraryBooks,
-        Icons.AutoMirrored.Filled.LibraryBooks
+        route = "bookshelf",
+        labelId = R.string.bookshelf,
+        m3Icon = Icons.AutoMirrored.Outlined.LibraryBooks,
+        m3SelectedIcon = Icons.AutoMirrored.Filled.LibraryBooks,
+        miuixIcon = MiuixIcons.Regular.ContactsBook, // 替换为实际的 MIUIX 线框图标
+        miuixSelectedIcon = MiuixIcons.Heavy.ContactsBook // 替换为实际的 MIUIX 填充图标
     )
 
     object Explore : MainDestination(
-        "explore",
-        R.string.discovery,
-        Icons.Outlined.Explore,
-        Icons.Default.Explore
+        route = "explore",
+        labelId = R.string.discovery,
+        m3Icon = Icons.Outlined.Explore,
+        m3SelectedIcon = Icons.Default.Explore,
+        miuixIcon = MiuixIcons.Regular.Album,
+        miuixSelectedIcon = MiuixIcons.Heavy.Album
     )
 
     object Rss : MainDestination(
-        "rss",
-        R.string.rss,
-        Icons.Outlined.RssFeed,
-        Icons.Default.RssFeed
+        route = "rss",
+        labelId = R.string.rss,
+        m3Icon = Icons.Outlined.RssFeed,
+        m3SelectedIcon = Icons.Default.RssFeed,
+        miuixIcon = MiuixIcons.Regular.Favorites,
+        miuixSelectedIcon = MiuixIcons.Heavy.Favorites
     )
 
     object My : MainDestination(
-        "my",
-        R.string.my,
-        Icons.Outlined.Person,
-        Icons.Default.Person
+        route = "my",
+        labelId = R.string.my,
+        m3Icon = Icons.Outlined.Person,
+        m3SelectedIcon = Icons.Default.Person,
+        miuixIcon = MiuixIcons.Regular.Settings,
+        miuixSelectedIcon = MiuixIcons.Heavy.Settings
     )
 
     companion object {
         val mainDestinations = listOf(Bookshelf, Explore, Rss, My)
     }
-}
+}

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

@@ -20,16 +20,12 @@ import androidx.compose.material.icons.filled.Menu
 import androidx.compose.material.icons.filled.Search
 import androidx.compose.material3.Badge
 import androidx.compose.material3.BadgedBox
-import androidx.compose.material3.BottomAppBarDefaults
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.ExtendedFloatingActionButton
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.NavigationBar
-import androidx.compose.material3.NavigationBarItem
-import androidx.compose.material3.NavigationBarItemDefaults
 import androidx.compose.material3.Text
 import androidx.compose.material3.WideNavigationRail
 import androidx.compose.material3.WideNavigationRailItem
@@ -56,17 +52,19 @@ import io.legado.app.R
 import io.legado.app.ui.book.info.BookInfoActivity
 import io.legado.app.ui.book.search.SearchActivity
 import io.legado.app.ui.config.mainConfig.MainConfig
-import io.legado.app.ui.config.themeConfig.ThemeConfig
 import io.legado.app.ui.main.bookshelf.BookshelfScreen
 import io.legado.app.ui.main.bookshelf.BookshelfViewModel
 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.AppNavigationBar
+import io.legado.app.ui.widget.components.AppNavigationBarItem
 import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.GlassDefaults
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.startActivityForBook
 import kotlinx.coroutines.launch
@@ -151,7 +149,7 @@ fun MainScreen(
                             onClick = { context.startActivity<SearchActivity>() },
                             expanded = expanded,
                             icon = { Icon(Icons.Default.Search, contentDescription = null) },
-                            text = { Text(stringResource(R.string.search)) }
+                            text = { AppText(stringResource(R.string.search)) }
                         )
                     }
                 }
@@ -198,7 +196,7 @@ fun MainScreen(
                                     ) { dismiss ->
                                         bookshelfUiState.groups.forEachIndexed { groupIndex, group ->
                                             RoundDropdownMenuItem(
-                                                text = { Text(group.groupName) },
+                                                text = group.groupName,
                                                 onClick = {
                                                     coroutineScope.launch {
                                                         if (pagerState.currentPage != index) {
@@ -224,7 +222,7 @@ fun MainScreen(
                             }
                         },
                         label = if (labelVisibilityMode != "unlabeled") {
-                            { Text(stringResource(destination.labelId)) }
+                            { AppText(stringResource(destination.labelId)) }
                         } else null
                     )
                 }
@@ -237,103 +235,38 @@ fun MainScreen(
                 if (!useRail && MainConfig.showBottomView) {
                     val labelVisibilityMode = MainConfig.labelVisibilityMode
                     val isUnlabeled = labelVisibilityMode == "unlabeled"
-                    NavigationBar(
+
+                    AppNavigationBar(
                         modifier = Modifier
                             .regularHazeEffect(state = hazeState)
-                            .height(if (isUnlabeled) 64.dp else 80.dp),
-                        containerColor = GlassDefaults.glassColor(
-                            noBlurColor = BottomAppBarDefaults.containerColor,
-                            blurAlpha = GlassDefaults.DefaultBlurAlpha
-                        ).let { baseColor ->
-                            val opacity = (ThemeConfig.bottomBarOpacity.coerceIn(0, 100)) / 100f
-                            baseColor.copy(alpha = (baseColor.alpha * opacity).coerceIn(0f, 1f))
-                        }
+                            .height(if (isUnlabeled) 64.dp else 80.dp)
                     ) {
                         val alwaysShowLabel = when (labelVisibilityMode) {
                             "labeled" -> true
-                            "selected" -> false
-                            "unlabeled" -> false
                             else -> false
                         }
                         destinations.forEachIndexed { index, destination ->
                             val selected = pagerState.targetPage == index
-                            var showGroupMenu by remember { mutableStateOf(false) }
-                            val haptic = LocalHapticFeedback.current
-
-                            NavigationBarItem(
+                            AppNavigationBarItem(
                                 selected = selected,
                                 onClick = {
-                                    coroutineScope.launch {
-                                        pagerState.animateScrollToPage(index)
-                                    }
+                                    coroutineScope.launch { pagerState.animateScrollToPage(index) }
                                 },
-                                icon = {
-                                    Box {
-                                        NavigationIcon(
-                                            destination = destination,
-                                            selected = selected,
-                                            upBooksCount = uiState.upBooksCount,
-                                            modifier = if (destination == MainDestination.Bookshelf) {
-                                                Modifier.combinedClickable(
-                                                    onClick = {
-                                                        coroutineScope.launch {
-                                                            pagerState.animateScrollToPage(index)
-                                                        }
-                                                    },
-                                                    onLongClick = {
-                                                        haptic.performHapticFeedback(
-                                                            HapticFeedbackType.LongPress
-                                                        )
-                                                        showGroupMenu = true
-                                                    }
-                                                )
-                                            } else Modifier
-                                        )
-
-                                        if (destination == MainDestination.Bookshelf && showGroupMenu) {
-                                            RoundDropdownMenu(
-                                                expanded = showGroupMenu,
-                                                onDismissRequest = { showGroupMenu = false }
-                                            ) { dismiss ->
-                                                bookshelfUiState.groups.forEachIndexed { groupIndex, group ->
-                                                    RoundDropdownMenuItem(
-                                                        text = { Text(group.groupName) },
-                                                        onClick = {
-                                                            coroutineScope.launch {
-                                                                if (pagerState.currentPage != index) {
-                                                                    pagerState.scrollToPage(index)
-                                                                }
-                                                                bookshelfViewModel.changeGroup(
-                                                                    group.groupId
-                                                                )
-                                                                dismiss()
-                                                            }
-                                                        },
-                                                        trailingIcon = {
-                                                            if (bookshelfUiState.selectedGroupIndex == groupIndex) {
-                                                                Icon(
-                                                                    Icons.Default.Check,
-                                                                    null,
-                                                                    modifier = Modifier.size(18.dp)
-                                                                )
-                                                            }
-                                                        }
-                                                    )
-                                                }
-                                            }
-                                        }
-                                    }
+                                labelString = stringResource(destination.labelId),
+                                miuixIcon = if (selected) destination.miuixSelectedIcon else destination.miuixIcon,
+                                m3Icon = {
+                                    NavigationIcon(
+                                        destination = destination,
+                                        selected = selected,
+                                        upBooksCount = uiState.upBooksCount
+                                    )
                                 },
-                                colors = NavigationBarItemDefaults.colors(
-                                    indicatorColor = GlassDefaults.glassColor(
-                                        noBlurColor = MaterialTheme.colorScheme.secondaryContainer,
-                                        blurAlpha = GlassDefaults.ThickBlurAlpha
-                                    ),
+                                m3IndicatorColor = GlassDefaults.glassColor(
+                                    noBlurColor = MaterialTheme.colorScheme.secondaryContainer,
+                                    blurAlpha = GlassDefaults.ThickBlurAlpha
                                 ),
-                                label = if (labelVisibilityMode != "unlabeled") {
-                                    { Text(stringResource(destination.labelId)) }
-                                } else null,
-                                alwaysShowLabel = alwaysShowLabel
+                                m3ShowLabel = !isUnlabeled,
+                                m3AlwaysShowLabel = alwaysShowLabel
                             )
                         }
                     }
@@ -386,7 +319,7 @@ private fun NavigationIcon(
     upBooksCount: Int,
     modifier: Modifier = Modifier
 ) {
-    val icon = if (selected) destination.selectedIcon else destination.icon
+    val icon = if (selected) destination.m3SelectedIcon else destination.m3Icon
     Box(modifier = modifier) {
         if (destination == MainDestination.Bookshelf && upBooksCount > 0) {
             BadgedBox(badge = { Badge { Text(upBooksCount.toString()) } }) {

+ 17 - 16
app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt

@@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.HorizontalDivider
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -34,9 +33,11 @@ import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.cover.BookCover
 import io.legado.app.ui.widget.components.cover.BookshelfCover
 import io.legado.app.ui.widget.components.cover.Cover
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.toTimeAgo
 
 /**
@@ -88,9 +89,9 @@ fun BookshelfItem(
                 ) {
                     cover(Modifier.fillMaxSize())
                     if (gridStyle == 1) {
-                        Text(
+                        AppText(
                             text = title,
-                            style = (if (titleSmallFont) MaterialTheme.typography.labelSmall else MaterialTheme.typography.labelMedium).copy(
+                            style = (if (titleSmallFont) LegadoTheme.typography.labelSmall else LegadoTheme.typography.labelMedium).copy(
                                 color = Color.White,
                                 shadow = Shadow(
                                     color = Color.Black.copy(alpha = 0.5f),
@@ -117,9 +118,9 @@ fun BookshelfItem(
                 }
 
                 if (gridStyle == 0) {
-                    Text(
+                    AppText(
                         text = title,
-                        style = if (titleSmallFont) MaterialTheme.typography.labelSmall else MaterialTheme.typography.labelMedium,
+                        style = if (titleSmallFont) LegadoTheme.typography.labelSmall else LegadoTheme.typography.labelMedium,
                         maxLines = titleMaxLines,
                         overflow = TextOverflow.Ellipsis,
                         textAlign = if (titleCenter) TextAlign.Center else TextAlign.Start,
@@ -152,16 +153,16 @@ fun BookshelfItem(
                     modifier = Modifier.weight(1f),
                     verticalArrangement = Arrangement.Center
                 ) {
-                    Text(
+                    AppText(
                         text = title,
-                        style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
+                        style = LegadoTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
                         maxLines = if (!isCompact) 2 else 1,
                         overflow = TextOverflow.Ellipsis
                     )
                     subTitle?.let {
-                        Text(
+                        AppText(
                             text = it,
-                            style = MaterialTheme.typography.bodySmall,
+                            style = LegadoTheme.typography.bodySmall,
                             maxLines = 1,
                             overflow = TextOverflow.Ellipsis,
                             modifier = Modifier.padding(top = 2.dp)
@@ -169,9 +170,9 @@ fun BookshelfItem(
                     }
                     if (!isCompact) {
                         desc?.let {
-                            Text(
+                            AppText(
                                 text = it,
-                                style = MaterialTheme.typography.bodySmall,
+                                style = LegadoTheme.typography.bodySmall,
                                 maxLines = 1,
                                 overflow = TextOverflow.Ellipsis,
                                 modifier = Modifier.padding(top = 2.dp)
@@ -372,7 +373,7 @@ fun BookItem(
                 isUpdating = isUpdating,
                 modifier = modifier,
                 badgeText = if (BookshelfConfig.showUnread && unreadCount > 0) unreadCount.toString() else null,
-                showBadgeDot = !BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && unreadCount > 0
+                showBadgeDot = BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && book.isNew
             )
         },
         title = book.name,
@@ -384,16 +385,16 @@ fun BookItem(
         desc = stringResource(R.string.read_dur_progress, book.durChapterTitle ?: ""),
         extra = {
             if (BookshelfConfig.showLastUpdateTime && !book.isLocal) {
-                Text(
+                AppText(
                     text = book.latestChapterTime.toTimeAgo(),
-                    style = MaterialTheme.typography.bodySmall,
+                    style = LegadoTheme.typography.bodySmall,
                     color = if (layoutMode != 0 || !isCompact) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary,
                     modifier = Modifier.padding(end = 4.dp)
                 )
             }
-            Text(
+            AppText(
                 text = book.latestChapterTitle ?: "",
-                style = MaterialTheme.typography.bodySmall,
+                style = LegadoTheme.typography.bodySmall,
                 maxLines = 1,
                 overflow = TextOverflow.Ellipsis,
                 modifier = Modifier.weight(1f)

+ 4 - 0
app/src/main/java/io/legado/app/ui/main/bookshelf/BookShelfItem.kt

@@ -16,6 +16,7 @@ data class BookShelfItem(
     val durChapterPos: Int,
     val latestChapterTitle: String?,
     val latestChapterTime: Long,
+    val lastCheckCount: Int,
     val totalChapterNum: Int,
     val durChapterIndex: Int,
     val type: Int,
@@ -33,5 +34,8 @@ data class BookShelfItem(
 
     val isNotShelf: Boolean get() = (type and BookType.notShelf) > 0
 
+    val isNew: Boolean get() = lastCheckCount > 0
+
+
     fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0)
 }

+ 11 - 11
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfConfigSheet.kt

@@ -23,7 +23,6 @@ import androidx.compose.material.icons.filled.TextFormat
 import androidx.compose.material.icons.filled.ViewCompact
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalConfiguration
@@ -32,7 +31,8 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.card.GlassCard
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
 import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem
 import io.legado.app.ui.widget.components.settingItem.CompactSliderSettingItem
 import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
@@ -40,28 +40,28 @@ import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun BookshelfConfigSheet(
+    show: Boolean,
     onDismissRequest: () -> Unit
 ) {
     val configuration = LocalConfiguration.current
     val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
 
-    GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
+    AppModalBottomSheet(
+        title = stringResource(R.string.bookshelf_layout),
+        show = show,
+        onDismissRequest = onDismissRequest
+    ) {
+        GlassCard() {
+
+        }
         Column(
             modifier = Modifier
                 .fillMaxWidth()
                 .verticalScroll(rememberScrollState())
                 .animateContentSize()
-                .padding(horizontal = 16.dp)
                 .padding(bottom = 32.dp),
             verticalArrangement = Arrangement.spacedBy(8.dp)
         ) {
-            Text(
-                text = stringResource(R.string.bookshelf_layout),
-                style = MaterialTheme.typography.titleLarge,
-                modifier = Modifier.padding(vertical = 16.dp)
-            )
-
-            // Group Style
             CompactDropdownSettingItem(
                 title = stringResource(R.string.group_style),
                 selectedValue = BookshelfConfig.bookGroupStyle.toString(),

+ 66 - 81
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt

@@ -40,12 +40,9 @@ import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.PrimaryScrollableTabRow
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.SnackbarResult
 import androidx.compose.material3.Surface
-import androidx.compose.material3.Tab
-import androidx.compose.material3.Text
 import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
 import androidx.compose.material3.pulltorefresh.pullToRefresh
 import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
@@ -60,13 +57,11 @@ import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.ClipEntry
 import androidx.compose.ui.platform.LocalClipboard
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.Dialog
 import io.legado.app.R
@@ -78,6 +73,7 @@ import io.legado.app.ui.book.import.remote.RemoteBookActivity
 import io.legado.app.ui.book.manage.BookshelfManageActivity
 import io.legado.app.ui.book.search.SearchActivity
 import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton
 import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
 import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
@@ -85,6 +81,8 @@ import io.legado.app.ui.widget.components.lazylist.FastScrollLazyVerticalGrid
 import io.legado.app.ui.widget.components.list.ListScaffold
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
+import io.legado.app.ui.widget.components.tabRow.AppTabRow
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.readText
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.toastOnUi
@@ -237,37 +235,37 @@ fun BookshelfScreen(
         topBarActions = { },
         dropDownMenuContent = { dismiss ->
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.add_remote_book)) },
+                text = stringResource(R.string.add_remote_book),
                 onClick = { context.startActivity<RemoteBookActivity>(); dismiss() },
                 leadingIcon = { Icon(Icons.Default.Wifi, null) }
             )
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.book_local)) },
+                text = stringResource(R.string.book_local),
                 onClick = { context.startActivity<ImportBookActivity>(); dismiss() },
                 leadingIcon = { Icon(Icons.Default.Save, null) }
             )
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.update_toc)) },
+                text = stringResource(R.string.update_toc),
                 onClick = { viewModel.upToc(uiState.items); dismiss() },
                 leadingIcon = { Icon(Icons.Default.Refresh, null) }
             )
             RoundDropdownMenuItem(
-                text = { Text("布局设置") },
+                text = "布局设置",
                 onClick = { showConfigSheet = true; dismiss() },
                 leadingIcon = { Icon(Icons.Default.GridView, null) }
             )
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.group_manage)) },
+                text = stringResource(R.string.group_manage),
                 onClick = { showGroupManageSheet = true; dismiss() },
                 leadingIcon = { Icon(Icons.Default.Edit, null) }
             )
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.add_url)) },
+                text = stringResource(R.string.add_url),
                 onClick = { showAddUrlDialog = true; dismiss() },
                 leadingIcon = { Icon(Icons.Default.Link, null) }
             )
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.bookshelf_management)) },
+                text = stringResource(R.string.bookshelf_management),
                 onClick = {
                     val groupId =
                         uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
@@ -279,7 +277,7 @@ fun BookshelfScreen(
                 leadingIcon = { Icon(Icons.Default.Settings, null) }
             )
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.cache_export)) },
+                text = stringResource(R.string.cache_export),
                 onClick = {
                     val groupId =
                         uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
@@ -291,7 +289,7 @@ fun BookshelfScreen(
                 leadingIcon = { Icon(Icons.Default.Download, null) }
             )
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.export_bookshelf)) },
+                text = stringResource(R.string.export_bookshelf),
                 onClick = {
                     showExportSheet = true
                     dismiss()
@@ -299,12 +297,12 @@ fun BookshelfScreen(
                 leadingIcon = { Icon(Icons.Default.ImportExport, null) }
             )
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.import_bookshelf)) },
+                text = stringResource(R.string.import_bookshelf),
                 onClick = { showImportSheet = true; dismiss() },
                 leadingIcon = { Icon(Icons.Default.FileOpen, null) }
             )
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.log)) },
+                text = stringResource(R.string.log),
                 onClick = {
                     showLogSheet = true
                     dismiss()
@@ -322,32 +320,18 @@ fun BookshelfScreen(
                         modifier = Modifier.fillMaxWidth(),
                         verticalAlignment = Alignment.CenterVertically
                     ) {
-                        PrimaryScrollableTabRow(
+                        val tabTitles = remember(uiState.groups) {
+                            uiState.groups.map { it.groupName }
+                        }
+
+                        AppTabRow(
+                            tabTitles = tabTitles,
                             selectedTabIndex = selectedTabIndex,
-                            edgePadding = 0.dp,
-                            divider = { },
-                            containerColor = Color.Transparent,
-                            minTabWidth = 0.dp,
+                            onTabSelected = { index ->
+                                scope.launch { pagerState.animateScrollToPage(index) }
+                            },
                             modifier = Modifier.weight(1f)
-                        ) {
-                            uiState.groups.forEachIndexed { index, group ->
-                                Tab(
-                                    selected = selectedTabIndex == index,
-                                    onClick = {
-                                        scope.launch { pagerState.animateScrollToPage(index) }
-                                    },
-                                    text = {
-                                        Text(
-                                            text = group.groupName,
-                                            maxLines = 1,
-                                            overflow = TextOverflow.Ellipsis,
-                                            modifier = Modifier.padding(horizontal = 8.dp),
-                                            color = MaterialTheme.colorScheme.onSurfaceVariant
-                                        )
-                                    }
-                                )
-                            }
-                        }
+                        )
 
                         if (BookshelfConfig.shouldShowExpandButton) {
                             Box(modifier = Modifier.padding(end = 16.dp)) {
@@ -363,7 +347,7 @@ fun BookshelfScreen(
                                 ) { dismiss ->
                                     uiState.groups.forEachIndexed { index, group ->
                                         RoundDropdownMenuItem(
-                                            text = { Text(group.groupName) },
+                                            text = group.groupName,
                                             onClick = {
                                                 scope.launch { pagerState.animateScrollToPage(index) }
                                                 dismiss()
@@ -502,13 +486,15 @@ fun BookshelfScreen(
         }
     }
 
-    if (showConfigSheet) {
-        BookshelfConfigSheet(onDismissRequest = { showConfigSheet = false })
-    }
+    BookshelfConfigSheet(
+        show = showConfigSheet,
+        onDismissRequest = { showConfigSheet = false }
+    )
 
-    if (showGroupManageSheet) {
-        GroupManageSheet(onDismissRequest = { showGroupManageSheet = false })
-    }
+    GroupManageSheet(
+        show = showGroupManageSheet,
+        onDismissRequest = { showGroupManageSheet = false }
+    )
 
     if (showAddUrlDialog) {
         SourceInputDialog(
@@ -521,40 +507,39 @@ fun BookshelfScreen(
         )
     }
 
-    if (showImportSheet) {
-        FilePickerSheet(
-            onDismissRequest = { showImportSheet = false },
-            title = stringResource(R.string.import_bookshelf),
-            onSelectSysFile = { types ->
-                importLauncher.launch(types)
-                showImportSheet = false
-            },
-            onManualInput = {
-                showAddUrlDialog = true
-                showImportSheet = false
-            },
-            allowExtensions = arrayOf("json", "txt")
-        )
-    }
+    FilePickerSheet(
+        show = showImportSheet,
+        onDismissRequest = { showImportSheet = false },
+        title = stringResource(R.string.import_bookshelf),
+        onSelectSysFile = { types ->
+            importLauncher.launch(types)
+            showImportSheet = false
+        },
+        onManualInput = {
+            showAddUrlDialog = true
+            showImportSheet = false
+        },
+        allowExtensions = arrayOf("json", "txt")
+    )
 
-    if (showExportSheet) {
-        FilePickerSheet(
-            onDismissRequest = { showExportSheet = false },
-            title = stringResource(R.string.export_bookshelf),
-            onSelectSysDir = {
-                showExportSheet = false
-                exportLauncher.launch("bookshelf.json")
-            },
-            onUpload = {
-                showExportSheet = false
-                viewModel.uploadBookshelf(uiState.items)
-            }
-        )
-    }
+    FilePickerSheet(
+        show = showExportSheet,
+        onDismissRequest = { showExportSheet = false },
+        title = stringResource(R.string.export_bookshelf),
+        onSelectSysDir = {
+            showExportSheet = false
+            exportLauncher.launch("bookshelf.json")
+        },
+        onUpload = {
+            showExportSheet = false
+            viewModel.uploadBookshelf(uiState.items)
+        }
+    )
 
-    if (showLogSheet) {
-        AppLogSheet(onDismissRequest = { showLogSheet = false })
-    }
+    AppLogSheet(
+        show = showLogSheet,
+        onDismissRequest = { showLogSheet = false }
+    )
 
     if (uiState.isLoading) {
         Dialog(onDismissRequest = {}) {
@@ -568,10 +553,10 @@ fun BookshelfScreen(
                 ) {
                     CircularProgressIndicator()
                     uiState.loadingText?.let {
-                        Text(
+                        AppText(
                             text = it,
                             modifier = Modifier.padding(top = 16.dp),
-                            style = MaterialTheme.typography.bodyMedium
+                            style = LegadoTheme.typography.bodyMedium
                         )
                     }
                 }

+ 39 - 57
app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageSheet.kt

@@ -6,20 +6,14 @@ import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.togetherWith
 import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
 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.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
@@ -27,7 +21,6 @@ import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
@@ -36,8 +29,9 @@ import io.legado.app.R
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.ui.book.group.GroupEditContent
 import io.legado.app.ui.book.group.GroupViewModel
+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.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
 import io.legado.app.utils.move
 import org.koin.androidx.compose.koinViewModel
 import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -45,6 +39,7 @@ import sh.calvin.reorderable.rememberReorderableLazyListState
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun GroupManageSheet(
+    show: Boolean,
     onDismissRequest: () -> Unit,
     viewModel: GroupViewModel = koinViewModel(),
     bookshelfViewModel: BookshelfViewModel = koinViewModel()
@@ -77,7 +72,22 @@ fun GroupManageSheet(
         }
     }
 
-    GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
+    AppModalBottomSheet(
+        show = show,
+        onDismissRequest = onDismissRequest,
+        title = if (!isEditing) stringResource(R.string.group_manage) else stringResource(R.string.group_edit),
+        endAction = {
+            if (!isEditing) {
+                SmallIconButton(
+                    onClick = {
+                        editingGroup = null
+                        isEditing = true
+                    },
+                    icon = Icons.Default.Add
+                )
+            }
+        }
+    ) {
         AnimatedContent(
             targetState = isEditing,
             transitionSpec = {
@@ -92,56 +102,28 @@ fun GroupManageSheet(
                     viewModel = viewModel
                 )
             } else {
-                Column(
-                    modifier = Modifier
-                        .fillMaxWidth()
+                LazyColumn(
+                    state = listState,
+                    modifier = Modifier.fillMaxWidth(),
+                    verticalArrangement = Arrangement.spacedBy(8.dp)
                 ) {
-                    Row(
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .padding(horizontal = 16.dp)
-                            .padding(vertical = 8.dp),
-                        horizontalArrangement = Arrangement.SpaceBetween,
-                        verticalAlignment = Alignment.CenterVertically
-                    ) {
-                        Text(
-                            text = stringResource(R.string.group_manage),
-                            style = MaterialTheme.typography.titleLarge
+                    items(listData, key = { it.groupId }) { group ->
+                        val manageNameInfo = remember(group) { group.getManageName(context) }
+                        ReorderableSelectionItem(
+                            state = reorderableState,
+                            key = group.groupId,
+                            title = group.groupName.ifBlank { manageNameInfo.suffix.orEmpty() },
+                            subtitle = if (group.groupName.isNotBlank()) manageNameInfo.suffix else null,
+                            isEnabled = group.show,
+                            containerColor = MaterialTheme.colorScheme.surface,
+                            onEnabledChange = { isChecked ->
+                                viewModel.upGroup(group.copy(show = isChecked))
+                            },
+                            onClickEdit = {
+                                editingGroup = group
+                                isEditing = true
+                            }
                         )
-                        IconButton(onClick = {
-                            editingGroup = null
-                            isEditing = true
-                        }) {
-                            Icon(
-                                Icons.Default.Add,
-                                contentDescription = stringResource(R.string.add)
-                            )
-                        }
-                    }
-
-                    LazyColumn(
-                        state = listState,
-                        modifier = Modifier.fillMaxWidth(),
-                        verticalArrangement = Arrangement.spacedBy(8.dp)
-                    ) {
-                        items(listData, key = { it.groupId }) { group ->
-                            val manageNameInfo = remember(group) { group.getManageName(context) }
-                            ReorderableSelectionItem(
-                                state = reorderableState,
-                                key = group.groupId,
-                                title = group.groupName.ifBlank { manageNameInfo.suffix.orEmpty() },
-                                subtitle = if (group.groupName.isNotBlank()) manageNameInfo.suffix else null,
-                                isEnabled = group.show,
-                                containerColor = MaterialTheme.colorScheme.surface,
-                                onEnabledChange = { isChecked ->
-                                    viewModel.upGroup(group.copy(show = isChecked))
-                                },
-                                onClickEdit = {
-                                    editingGroup = group
-                                    isEditing = true
-                                }
-                            )
-                        }
                     }
                 }
             }

+ 77 - 47
app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt

@@ -42,8 +42,6 @@ import androidx.compose.material3.ListItem
 import androidx.compose.material3.ListItemDefaults
 import androidx.compose.material3.LocalMinimumInteractiveComponentSize
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedCard
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
@@ -73,6 +71,9 @@ import io.legado.app.ui.book.search.SearchActivity
 import io.legado.app.ui.book.search.SearchScope
 import io.legado.app.ui.book.source.edit.BookSourceEditActivity
 import io.legado.app.ui.login.SourceLoginActivity
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.LegadoTheme.composeEngine
+import io.legado.app.ui.theme.ThemeResolver
 import io.legado.app.ui.widget.components.card.GlassCard
 import io.legado.app.ui.widget.components.card.TextCard
 import io.legado.app.ui.widget.components.divider.PillHeaderDivider
@@ -81,9 +82,11 @@ import io.legado.app.ui.widget.components.list.ListScaffold
 import io.legado.app.ui.widget.components.menuItem.MenuItemIcon
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.startActivity
 import kotlinx.coroutines.launch
 import org.koin.androidx.compose.koinViewModel
+import top.yukonga.miuix.kmp.theme.MiuixTheme
 import kotlin.math.roundToInt
 
 @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@@ -138,6 +141,8 @@ fun ExploreScreen(
         }
     }
 
+    val composeEngine = ThemeResolver.isMiuixEngine(composeEngine)
+
     ListScaffold(
         title = stringResource(R.string.discovery),
         state = uiState,
@@ -148,13 +153,13 @@ fun ExploreScreen(
         dropDownMenuContent = { dismiss ->
             RoundDropdownMenuItem(
                 leadingIcon = { MenuItemIcon(Icons.Default.Group) },
-                text = { Text(stringResource(R.string.all)) },
+                text = stringResource(R.string.all),
                 onClick = { viewModel.setGroup(""); dismiss() }
             )
             uiState.groups.forEach { group ->
                 RoundDropdownMenuItem(
                     leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) },
-                    text = { Text(group) },
+                    text = group,
                     onClick = { viewModel.setGroup(group); dismiss() }
                 )
             }
@@ -197,7 +202,8 @@ fun ExploreScreen(
                                 }
                             },
                             onRefresh = { viewModel.refreshExploreKinds(item) },
-                            onDelete = { sourceToDelete = item }
+                            onDelete = { sourceToDelete = item },
+                            isMiuix = composeEngine
                         )
                     }
 
@@ -228,7 +234,8 @@ fun ExploreScreen(
                                                     putExtra("exploreUrl", kind.url)
                                                 }
                                             }
-                                        }
+                                        },
+                                        isMiuix = composeEngine
                                     )
                                 }
 
@@ -264,7 +271,8 @@ fun ExploreScreen(
                                     uiState.items.indexOfFirst { it.bookSourceUrl == item.bookSourceUrl }
                                 if (index >= 0) listState.animateScrollToItem(index)
                             }
-                        }
+                        },
+                        isMiuix = composeEngine
                     )
                 }
             }
@@ -274,19 +282,19 @@ fun ExploreScreen(
     sourceToDelete?.let { source ->
         AlertDialog(
             onDismissRequest = { sourceToDelete = null },
-            title = { Text(stringResource(R.string.draw)) },
-            text = { Text(stringResource(R.string.sure_del) + "\n" + source.bookSourceName) },
+            title = { AppText(stringResource(R.string.draw)) },
+            text = { AppText(stringResource(R.string.sure_del) + "\n" + source.bookSourceName) },
             confirmButton = {
                 TextButton(onClick = {
                     viewModel.deleteSource(source)
                     sourceToDelete = null
                 }) {
-                    Text(stringResource(R.string.yes))
+                    AppText(stringResource(R.string.yes))
                 }
             },
             dismissButton = {
                 TextButton(onClick = { sourceToDelete = null }) {
-                    Text(stringResource(R.string.no))
+                    AppText(stringResource(R.string.no))
                 }
             }
         )
@@ -341,14 +349,25 @@ fun ExploreSourceHeader(
     onLogin: () -> Unit,
     onRefresh: () -> Unit,
     onDelete: () -> Unit,
+    isMiuix: Boolean,
 ) {
     var showMenu by remember { mutableStateOf(false) }
     val rotation by animateFloatAsState(if (isExpanded) 90f else 0f, label = "rotation")
+
     val containerColor by animateColorAsState(
         targetValue = if (isExpanded)
-            MaterialTheme.colorScheme.secondaryContainer
+            if (isMiuix) MiuixTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.secondaryContainer
         else
-            MaterialTheme.colorScheme.surfaceContainerLow,
+            if (isMiuix) MiuixTheme.colorScheme.surfaceContainer else MaterialTheme.colorScheme.surfaceContainerLow,
+        animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
+        label = "CardColor"
+    )
+
+    val contentColor by animateColorAsState(
+        targetValue = if (isExpanded)
+            if (isMiuix) MiuixTheme.colorScheme.primary else MaterialTheme.colorScheme.primary
+        else
+            if (isMiuix) MiuixTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurface,
         animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
         label = "CardColor"
     )
@@ -358,7 +377,7 @@ fun ExploreSourceHeader(
             .fillMaxWidth()
             .padding(horizontal = 16.dp, vertical = 4.dp),
         shape = MaterialTheme.shapes.medium,
-        colors = CardDefaults.cardColors(containerColor = containerColor),
+        containerColor = containerColor
     ) {
         ListItem(
             modifier = Modifier
@@ -371,14 +390,10 @@ fun ExploreSourceHeader(
                 containerColor = Color.Transparent
             ),
             headlineContent = {
-                Text(
+                AppText(
                     text = item.bookSourceName,
-                    style = MaterialTheme.typography.titleMedium,
-                    color = if (isExpanded) {
-                        MaterialTheme.colorScheme.primary
-                    } else {
-                        MaterialTheme.colorScheme.onSurface
-                    }
+                    style = LegadoTheme.typography.titleMedium,
+                    color = contentColor
                 )
             },
             trailingContent = {
@@ -401,29 +416,29 @@ fun ExploreSourceHeader(
                     PillHeaderDivider(title = item.bookSourceName)
                     RoundDropdownMenuItem(
                         leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) },
-                        text = { Text(stringResource(R.string.to_top)) },
+                        text = stringResource(R.string.to_top),
                         onClick = { onTop(); showMenu = false }
                     )
                     RoundDropdownMenuItem(
                         leadingIcon = { MenuItemIcon(Icons.Default.Edit) },
-                        text = { Text(stringResource(R.string.edit)) },
+                        text = stringResource(R.string.edit),
                         onClick = { onEdit(); showMenu = false }
                     )
                     RoundDropdownMenuItem(
                         leadingIcon = { MenuItemIcon(Icons.Default.Search) },
-                        text = { Text(stringResource(R.string.search)) },
+                        text = stringResource(R.string.search),
                         onClick = { onSearch(); showMenu = false }
                     )
                     if (item.hasLoginUrl) {
                         RoundDropdownMenuItem(
                             leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) },
-                            text = { Text(stringResource(R.string.login)) },
+                            text = stringResource(R.string.login),
                             onClick = { onLogin(); showMenu = false }
                         )
                     }
                     RoundDropdownMenuItem(
                         leadingIcon = { MenuItemIcon(Icons.Default.Refresh) },
-                        text = { Text(stringResource(R.string.refresh)) },
+                        text = stringResource(R.string.refresh),
                         onClick = { onRefresh(); showMenu = false }
                     )
                     RoundDropdownMenuItem(
@@ -433,12 +448,8 @@ fun ExploreSourceHeader(
                                 tint = MaterialTheme.colorScheme.error
                             )
                         },
-                        text = {
-                            Text(
-                                stringResource(R.string.delete),
-                                color = MaterialTheme.colorScheme.error
-                            )
-                        },
+                        text = stringResource(R.string.delete),
+                        color = LegadoTheme.colorScheme.error,
                         onClick = { onDelete(); showMenu = false }
                     )
                 }
@@ -450,12 +461,12 @@ fun ExploreSourceHeader(
 @Composable
 fun ExploreStickyCard(
     item: BookSourcePart,
-    onClick: () -> Unit
+    onClick: () -> Unit,
+    isMiuix: Boolean,
 ) {
     TextCard(
         text = item.bookSourceName,
-        backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh,
-        textStyle = MaterialTheme.typography.labelLarge,
+        textStyle = LegadoTheme.typography.labelLarge,
         horizontalPadding = 8.dp,
         verticalPadding = 6.dp,
         onClick = onClick
@@ -467,8 +478,29 @@ fun ExploreKindItem(
     kind: ExploreKind,
     isClickable: Boolean,
     onClick: () -> Unit,
-    modifier: Modifier = Modifier
+    modifier: Modifier = Modifier,
+    isMiuix: Boolean,
 ) {
+    val color = if (isMiuix)
+        MiuixTheme.colorScheme.surfaceContainer
+    else
+        MaterialTheme.colorScheme.secondaryContainer
+
+    val contentColor = if (isMiuix)
+        MiuixTheme.colorScheme.onSurface
+    else
+        MaterialTheme.colorScheme.secondary
+
+    val unClickBackColor = if (isMiuix)
+        MiuixTheme.colorScheme.surfaceContainer
+    else
+        MaterialTheme.colorScheme.surface
+
+    val unClickColor = if (isMiuix)
+        MiuixTheme.colorScheme.disabledOnSurface
+    else
+        MaterialTheme.colorScheme.primary
+
     CompositionLocalProvider(
         LocalMinimumInteractiveComponentSize provides Dp.Unspecified
     ) {
@@ -479,21 +511,19 @@ fun ExploreKindItem(
             GlassCard(
                 onClick = onClick,
                 shape = shape,
-                colors = CardDefaults.cardColors(
-                    containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.6f),
-                    contentColor = MaterialTheme.colorScheme.onSecondaryContainer
-                ),
+                containerColor = color.copy(alpha = 0.6f),
+                contentColor = contentColor,
                 modifier = modifier
             ) {
                 KindText(kind)
             }
         } else {
-            OutlinedCard(
+            GlassCard(
                 shape = shape,
-                colors = CardDefaults.outlinedCardColors(
-                    contentColor = MaterialTheme.colorScheme.primary
-                ),
-                modifier = modifier
+                containerColor = unClickBackColor.copy(alpha = 0.6f),
+                contentColor = unClickColor,
+                modifier = modifier,
+                border = CardDefaults.outlinedCardBorder()
             ) {
                 KindText(kind)
             }
@@ -506,12 +536,12 @@ fun ExploreKindItem(
 private fun KindText(
     kind: ExploreKind
 ) {
-    Text(
+    AppText(
         text = kind.title,
         modifier = Modifier
             .fillMaxWidth()
             .padding(vertical = 8.dp),
-        style = MaterialTheme.typography.labelMediumEmphasized,
+        style = LegadoTheme.typography.labelMediumEmphasized,
         textAlign = TextAlign.Center,
         overflow = TextOverflow.Ellipsis,
         maxLines = 1

+ 88 - 59
app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt

@@ -1,13 +1,20 @@
 package io.legado.app.ui.main.my
 
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.shrinkVertically
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.only
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
@@ -16,20 +23,19 @@ import androidx.compose.material.icons.automirrored.filled.HelpOutline
 import androidx.compose.material.icons.automirrored.filled.LibraryBooks
 import androidx.compose.material.icons.automirrored.filled.Rule
 import androidx.compose.material.icons.filled.Bookmark
+import androidx.compose.material.icons.filled.ContentCopy
 import androidx.compose.material.icons.filled.FindReplace
 import androidx.compose.material.icons.filled.Folder
 import androidx.compose.material.icons.filled.History
 import androidx.compose.material.icons.filled.Info
+import androidx.compose.material.icons.filled.OpenInBrowser
 import androidx.compose.material.icons.filled.Settings
 import androidx.compose.material.icons.filled.Source
 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.Switch
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
@@ -46,11 +52,14 @@ import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
 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.theme.adaptiveHorizontalPadding
 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.settingItem.SettingItem
+import io.legado.app.ui.widget.components.button.SmallTextButton
+import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
+import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 
 
 @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@@ -70,11 +79,7 @@ fun MyScreen(
             .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top),
         topBar = {
             GlassMediumFlexibleTopAppBar(
-                title = {
-                    Text(
-                        text = stringResource(R.string.my)
-                    )
-                },
+                title = stringResource(R.string.my),
                 actions = {
                     IconButton(
                         onClick = { onNavigate(PrefClickEvent.ShowMd("appHelp", "xxx")) }
@@ -99,52 +104,21 @@ fun MyScreen(
             )
 
             SplicedColumnGroup(
-                modifier = Modifier.padding(horizontal = 16.dp),
+                modifier = Modifier.adaptiveHorizontalPadding(),
                 title = ""
             ) {
-                SettingItem(
-                    title = stringResource(R.string.web_service),
-                    description = if (uiState.isWebServiceRun)
-                        uiState.webServiceAddress
-                    else
-                        stringResource(R.string.web_service_desc),
-                    imageVector = Icons.Default.Web,
-                    trailingContent = {
-                        Switch(
-                            checked = uiState.isWebServiceRun,
-                            onCheckedChange = {
-                                viewModel.onEvent(PrefClickEvent.ToggleWebService)
-                            }
-                        )
-                    },
-                    dropdownMenu = if (uiState.isWebServiceRun) {
-                        { onDismiss ->
-                            DropdownMenuItem(
-                                text = { Text("复制地址") },
-                                onClick = {
-                                    onNavigate(PrefClickEvent.CopyUrl(uiState.webServiceAddress))
-                                    onDismiss()
-                                }
-                            )
-                            DropdownMenuItem(
-                                text = { Text("浏览器打开") },
-                                onClick = {
-                                    onNavigate(PrefClickEvent.OpenUrl(uiState.webServiceAddress))
-                                    onDismiss()
-                                }
-                            )
-                        }
-                    } else null,
-                    onClick = { }
+                WebServiceSettingBlock(
+                    uiState = uiState,
+                    viewModel = viewModel,
+                    onNavigate = onNavigate
                 )
             }
 
-
             SplicedColumnGroup(
-                modifier = Modifier.padding(horizontal = 16.dp),
+                modifier = Modifier.adaptiveHorizontalPadding(),
                 title = "规则"
             ) {
-                SettingItem(
+                ClickableSettingItem(
                     title = stringResource(R.string.book_source_manage),
                     description = stringResource(R.string.book_source_manage_desc),
                     imageVector = Icons.Default.Source,
@@ -154,7 +128,7 @@ fun MyScreen(
                         )
                     }
                 )
-                SettingItem(
+                ClickableSettingItem(
                     title = stringResource(R.string.replace_purify),
                     imageVector = Icons.Default.FindReplace,
                     onClick = {
@@ -163,7 +137,7 @@ fun MyScreen(
                         )
                     }
                 )
-                SettingItem(
+                ClickableSettingItem(
                     title = stringResource(R.string.txt_toc_rule),
                     imageVector = Icons.AutoMirrored.Filled.Rule,
                     onClick = {
@@ -172,7 +146,7 @@ fun MyScreen(
                         )
                     }
                 )
-                SettingItem(
+                ClickableSettingItem(
                     title = stringResource(R.string.dict_rule),
                     imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
                     onClick = {
@@ -184,44 +158,44 @@ fun MyScreen(
             }
 
             SplicedColumnGroup(
-                modifier = Modifier.padding(horizontal = 16.dp),
+                modifier = Modifier.adaptiveHorizontalPadding(),
                 title = stringResource(R.string.other)) {
-                SettingItem(
+                ClickableSettingItem(
                     title = stringResource(R.string.setting),
                     imageVector = Icons.Default.Settings,
                     onClick = {
                         onOpenSettings()
                     }
                 )
-                SettingItem(
+                ClickableSettingItem(
                     title = stringResource(R.string.bookmark),
                     imageVector = Icons.Default.Bookmark,
                     onClick = {
                         onNavigate(PrefClickEvent.StartActivity(AllBookmarkActivity::class.java))
                     }
                 )
-                SettingItem(
+                ClickableSettingItem(
                     title = stringResource(R.string.read_record),
                     imageVector = Icons.Default.History,
                     onClick = {
                         onNavigate(PrefClickEvent.StartActivity(ReadRecordActivity::class.java))
                     }
                 )
-                SettingItem(
+                ClickableSettingItem(
                     title = stringResource(R.string.file_manage),
                     imageVector = Icons.Default.Folder,
                     onClick = {
                         onNavigate(PrefClickEvent.StartActivity(FileManageActivity::class.java))
                     }
                 )
-                SettingItem(
+                ClickableSettingItem(
                     title = stringResource(R.string.about),
                     imageVector = Icons.Default.Info,
                     onClick = {
                         onNavigate(PrefClickEvent.StartActivity(AboutActivity::class.java))
                     }
                 )
-                SettingItem(
+                ClickableSettingItem(
                     title = stringResource(R.string.exit),
                     imageVector = Icons.AutoMirrored.Filled.ExitToApp,
                     onClick = {
@@ -234,3 +208,58 @@ fun MyScreen(
         }
     }
 }
+
+
+@Composable
+fun WebServiceSettingBlock(
+    uiState: MyUiState,
+    viewModel: MyViewModel,
+    onNavigate: (PrefClickEvent) -> Unit
+) {
+    Column(modifier = Modifier.fillMaxWidth()) {
+        SwitchSettingItem(
+            title = stringResource(R.string.web_service),
+            description = if (uiState.isWebServiceRun) {
+                uiState.webServiceAddress
+            } else {
+                stringResource(R.string.web_service_desc)
+            },
+            imageVector = Icons.Default.Web,
+            checked = uiState.isWebServiceRun,
+            onCheckedChange = {
+                viewModel.onEvent(PrefClickEvent.ToggleWebService)
+            }
+        )
+
+        AnimatedVisibility(
+            visible = uiState.isWebServiceRun,
+            enter = expandVertically(),
+            exit = shrinkVertically()
+        ) {
+            Row(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(start = 16.dp, end = 16.dp, top = 12.dp, bottom = 12.dp),
+                horizontalArrangement = Arrangement.End
+            ) {
+                SmallTextButton(
+                    text = "复制地址",
+                    icon = Icons.Default.ContentCopy,
+                    onClick = {
+                        onNavigate(PrefClickEvent.CopyUrl(uiState.webServiceAddress))
+                    }
+                )
+
+                Spacer(modifier = Modifier.width(12.dp))
+
+                SmallTextButton(
+                    text = "浏览器打开",
+                    icon = Icons.Default.OpenInBrowser,
+                    onClick = {
+                        onNavigate(PrefClickEvent.OpenUrl(uiState.webServiceAddress))
+                    }
+                )
+            }
+        }
+    }
+}

+ 17 - 20
app/src/main/java/io/legado/app/ui/main/rss/RssScreen.kt

@@ -30,7 +30,6 @@ import androidx.compose.material.icons.filled.VerticalAlignTop
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
@@ -55,6 +54,7 @@ import io.legado.app.ui.rss.read.ReadRssActivity
 import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
 import io.legado.app.ui.rss.source.manage.RssSourceActivity
 import io.legado.app.ui.rss.subscription.RuleSubActivity
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.SourceIcon
 import io.legado.app.ui.widget.components.button.TopBarActionButton
 import io.legado.app.ui.widget.components.divider.PillDivider
@@ -63,6 +63,7 @@ import io.legado.app.ui.widget.components.list.ListScaffold
 import io.legado.app.ui.widget.components.menuItem.MenuItemIcon
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.openUrl
 import io.legado.app.utils.startActivity
 import org.koin.androidx.compose.koinViewModel
@@ -135,12 +136,12 @@ fun RssScreen(
                     dismiss()
                 },
                 leadingIcon = { MenuItemIcon(Icons.Default.Settings) },
-                text = { Text("订阅源管理") }
+                text = "订阅源管理"
             )
             PillDivider()
             RoundDropdownMenuItem(
                 leadingIcon = { MenuItemIcon(Icons.Default.Group) },
-                text = { Text(stringResource(R.string.all)) },
+                text = stringResource(R.string.all),
                 onClick = {
                     viewModel.setGroup("")
                     dismiss()
@@ -149,7 +150,7 @@ fun RssScreen(
             uiState.groups.forEach { group ->
                 RoundDropdownMenuItem(
                     leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) },
-                    text = { Text(group) },
+                    text = group,
                     onClick = {
                         viewModel.setGroup(group)
                         dismiss()
@@ -188,8 +189,8 @@ fun RssScreen(
     sourceToDelete?.let { source ->
         AlertDialog(
             onDismissRequest = { sourceToDelete = null },
-            title = { Text(stringResource(R.string.draw)) },
-            text = { Text(stringResource(R.string.sure_del) + "\n" + source.sourceName) },
+            title = { AppText(stringResource(R.string.draw)) },
+            text = { AppText(stringResource(R.string.sure_del) + "\n" + source.sourceName) },
             confirmButton = {
                 TextButton(
                     onClick = {
@@ -197,12 +198,12 @@ fun RssScreen(
                         sourceToDelete = null
                     }
                 ) {
-                    Text(stringResource(R.string.yes))
+                    AppText(stringResource(R.string.yes))
                 }
             },
             dismissButton = {
                 TextButton(onClick = { sourceToDelete = null }) {
-                    Text(stringResource(R.string.no))
+                    AppText(stringResource(R.string.no))
                 }
             }
         )
@@ -246,7 +247,7 @@ fun RssSourceGridItem(
                 PillHeaderDivider(title = source.sourceName)
                 RoundDropdownMenuItem(
                     leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) },
-                    text = { Text(stringResource(R.string.to_top)) },
+                    text = stringResource(R.string.to_top),
                     onClick = {
                         onTop()
                         showMenu = false
@@ -254,7 +255,7 @@ fun RssSourceGridItem(
                 )
                 RoundDropdownMenuItem(
                     leadingIcon = { MenuItemIcon(Icons.Default.Edit) },
-                    text = { Text(stringResource(R.string.edit)) },
+                    text = stringResource(R.string.edit),
                     onClick = {
                         onEdit()
                         showMenu = false
@@ -263,7 +264,7 @@ fun RssSourceGridItem(
                 if (!source.loginUrl.isNullOrBlank()) {
                     RoundDropdownMenuItem(
                         leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) },
-                        text = { Text(stringResource(R.string.login)) },
+                        text = stringResource(R.string.login),
                         onClick = {
                             onLogin()
                             showMenu = false
@@ -272,7 +273,7 @@ fun RssSourceGridItem(
                 }
                 RoundDropdownMenuItem(
                     leadingIcon = { MenuItemIcon(Icons.Default.Close) },
-                    text = { Text(stringResource(R.string.disable_source)) },
+                    text = stringResource(R.string.disable_source),
                     onClick = {
                         onDisable()
                         showMenu = false
@@ -285,12 +286,8 @@ fun RssSourceGridItem(
                             tint = MaterialTheme.colorScheme.error
                         )
                     },
-                    text = {
-                        Text(
-                            stringResource(R.string.delete),
-                            color = MaterialTheme.colorScheme.error
-                        )
-                    },
+                    text = stringResource(R.string.delete),
+                    color = MaterialTheme.colorScheme.error,
                     onClick = {
                         onDelete()
                         showMenu = false
@@ -299,9 +296,9 @@ fun RssSourceGridItem(
             }
         }
         Spacer(modifier = Modifier.height(8.dp))
-        Text(
+        AppText(
             text = source.sourceName,
-            style = MaterialTheme.typography.labelMedium,
+            style = LegadoTheme.typography.labelMedium,
             maxLines = 2,
             overflow = TextOverflow.Ellipsis,
             textAlign = TextAlign.Center,

+ 76 - 108
app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt

@@ -8,11 +8,9 @@ import androidx.compose.animation.SharedTransitionScope
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.rememberLazyListState
@@ -20,20 +18,15 @@ 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
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.FloatingActionButton
 import androidx.compose.material3.Icon
 import androidx.compose.material3.LoadingIndicator
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.PlainTooltip
-import androidx.compose.material3.PrimaryScrollableTabRow
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.SnackbarResult
-import androidx.compose.material3.Tab
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.TooltipAnchorPosition
 import androidx.compose.material3.TooltipBox
@@ -50,14 +43,12 @@ import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.hapticfeedback.HapticFeedbackType
 import androidx.compose.ui.platform.ClipEntry
 import androidx.compose.ui.platform.LocalClipboard
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalHapticFeedback
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.Dialog
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -76,6 +67,8 @@ 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.RuleListScaffold
+import io.legado.app.ui.widget.components.tabRow.AppTabRow
+import io.legado.app.ui.widget.components.text.AppText
 import kotlinx.coroutines.launch
 import org.koin.androidx.compose.koinViewModel
 import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -165,56 +158,48 @@ fun ReplaceRuleScreen(
         )
     }
 
-    if (showImportSheet) {
-        FilePickerSheet(
-            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")
-        )
-    }
+    FilePickerSheet(
+        show = showImportSheet,
+        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 = {
-                showExportSheet = false
-                exportDoc.launch("exportReplaceRule.json")
-            },
-            onUpload = {
-                showExportSheet = false
-                viewModel.uploadSelectedRules(selectedIds, rules)
-            },
-            allowExtensions = arrayOf("json")
-        )
-    }
+    FilePickerSheet(
+        show = showExportSheet,
+        onDismissRequest = { showExportSheet = false },
+        onSelectSysDir = {
+            showExportSheet = false
+            exportDoc.launch("exportReplaceRule.json")
+        },
+        onUpload = {
+            showExportSheet = false
+            viewModel.uploadSelectedRules(selectedIds, rules)
+        },
+        allowExtensions = arrayOf("json")
+    )
 
-    (importState as? BaseImportUiState.Success<ReplaceRule>)?.let { state ->
-        BatchImportDialog(
-            title = stringResource(R.string.import_replace_rule),
-            importState = state,
-            onDismissRequest = { viewModel.cancelImport() },
-            onToggleItem = { viewModel.toggleImportSelection(it) },
-            onToggleAll = { viewModel.toggleImportAll(it) },
-            onConfirm = { viewModel.saveImportedRules() },
-            topBarActions = {},
-            itemContent = { rule, _ ->
-                Column {
-                    Text(rule.name, style = MaterialTheme.typography.titleMedium)
-                    if (!rule.group.isNullOrBlank()) {
-                        Text(rule.group!!, style = MaterialTheme.typography.bodySmall)
-                    }
-                }
-            }
-        )
-    }
+    BatchImportDialog(
+        title = stringResource(R.string.import_replace_rule),
+        importState = importState,
+        onDismissRequest = { viewModel.cancelImport() },
+        onToggleItem = { viewModel.toggleImportSelection(it) },
+        onToggleAll = { viewModel.toggleImportAll(it) },
+        onConfirm = { viewModel.saveImportedRules() },
+        topBarActions = {},
+        itemTitle = { rule -> rule.name },
+        itemSubtitle = { rule ->
+            rule.group?.takeIf { it.isNotBlank() }
+        }
+    )
 
     if (importState is BaseImportUiState.Loading) {
         Dialog(onDismissRequest = { viewModel.cancelImport() }) { LoadingIndicator() }
@@ -267,28 +252,29 @@ fun ReplaceRuleScreen(
         }
     }
 
-    if (showGroupManageSheet) {
-        GroupManageBottomSheet(
-            groups = groups,
-            onDismissRequest = { showGroupManageSheet = false },
-            onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
-            onDeleteGroup = { viewModel.delGroup(it) }
-        )
-    }
+
+    GroupManageBottomSheet(
+        show = showGroupManageSheet,
+        groups = groups,
+        onDismissRequest = { showGroupManageSheet = false },
+        onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
+        onDeleteGroup = { viewModel.delGroup(it) }
+    )
+
 
     showDeleteRuleDialog?.let { rule ->
         AlertDialog(
             onDismissRequest = { showDeleteRuleDialog = null },
-            title = { Text(stringResource(R.string.delete)) },
-            text = { Text(stringResource(R.string.del_msg)) },
+            title = { AppText(stringResource(R.string.delete)) },
+            text = { AppText(stringResource(R.string.del_msg)) },
             confirmButton = {
                 OutlinedButton(onClick = {
                     viewModel.delete(rule); showDeleteRuleDialog = null
-                }) { Text(stringResource(R.string.ok)) }
+                }) { AppText(stringResource(R.string.ok)) }
             },
             dismissButton = {
                 TextButton(onClick = { showDeleteRuleDialog = null }) {
-                    Text(
+                    AppText(
                         stringResource(R.string.cancel)
                     )
                 }
@@ -347,39 +333,21 @@ fun ReplaceRuleScreen(
         },
         bottomContent = {
             if (tabItems.size > 1) {
-                PrimaryScrollableTabRow(
-                    selectedTabIndex = selectedTabIndex.coerceAtMost(tabItems.size - 1)
-                        .coerceAtLeast(0),
-                    edgePadding = 0.dp,
-                    divider = {},
-                    containerColor = Color.Transparent,
-                ) {
-                    tabItems.forEachIndexed { index, title ->
-                        Tab(
-                            selected = selectedTabIndex == index,
-                            onClick = {
-                                selectedTabIndex = index
-                                viewModel.setGroup(title)
-                            },
-                            text = {
-                                Text(
-                                    text = title,
-                                    maxLines = 1,
-                                    overflow = TextOverflow.Ellipsis,
-                                    modifier = Modifier.padding(horizontal = 16.dp),
-                                    color = MaterialTheme.colorScheme.onSurfaceVariant
-                                )
-                            }
-                        )
+                AppTabRow(
+                    tabTitles = tabItems,
+                    selectedTabIndex = selectedTabIndex,
+                    onTabSelected = { index ->
+                        selectedTabIndex = index
+                        viewModel.setGroup(tabItems[index])
                     }
-                }
+                )
             }
         },
         floatingActionButton = {
             TooltipBox(
                 positionProvider =
                     TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
-                tooltip = { PlainTooltip { Text("Localized description") } },
+                tooltip = { PlainTooltip { AppText("Localized description") } },
                 state = rememberTooltipState(),
             ) {
                 with(sharedTransitionScope) {
@@ -406,29 +374,29 @@ fun ReplaceRuleScreen(
         snackbarHostState = snackbarHostState,
         dropDownMenuContent = { dismiss ->
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.import_str)) },
+                text = stringResource(R.string.import_str),
                 onClick = { showImportSheet = true; dismiss() },
                 leadingIcon = { Icon(Icons.Default.FileOpen, null) }
             )
             RoundDropdownMenuItem(
-                text = { Text("分组管理") },
+                text = "分组管理",
                 onClick = { showGroupManageSheet = true; dismiss() }
             )
             RoundDropdownMenuItem(
-                text = { Text("帮助") },
+                text = "帮助",
                 onClick = { /*TODO*/ dismiss() }
             )
             PillDivider()
             RoundDropdownMenuItem(
-                text = { Text("旧的在前") },
+                text = "旧的在前",
                 onClick = { viewModel.setSortMode("asc"); dismiss() }
             )
             RoundDropdownMenuItem(
-                text = { Text("新的在前") },
+                text = "新的在前",
                 onClick = { viewModel.setSortMode("desc"); dismiss() }
             )
             RoundDropdownMenuItem(
-                text = { Text("名称升序") },
+                text = "名称升序",
                 onClick = {
                     viewModel.setSortMode("name_asc")
                     dismiss()
@@ -438,7 +406,7 @@ fun ReplaceRuleScreen(
                 }
             )
             RoundDropdownMenuItem(
-                text = { Text("名称降序") },
+                text = "名称降序",
                 onClick = {
                     viewModel.setSortMode("name_desc")
                     dismiss()
@@ -495,16 +463,16 @@ fun ReplaceRuleScreen(
                                     //clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(12.dp))
                                 ),
                             dropdownContent = { dismiss ->
-                                DropdownMenuItem(
-                                    text = { Text("移至顶部") },
+                                RoundDropdownMenuItem(
+                                    text = "移至顶部",
                                     onClick = { viewModel.toTop(ui.rule); dismiss() }
                                 )
-                                DropdownMenuItem(
-                                    text = { Text("移至底部") },
+                                RoundDropdownMenuItem(
+                                    text = "移至底部",
                                     onClick = { viewModel.toBottom(ui.rule); dismiss() }
                                 )
-                                DropdownMenuItem(
-                                    text = { Text("删除") },
+                                RoundDropdownMenuItem(
+                                    text = "删除",
                                     onClick = { showDeleteRuleDialog = ui.rule; dismiss() }
                                 )
                             }

+ 29 - 29
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.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.TooltipAnchorPosition
 import androidx.compose.material3.TooltipBox
@@ -78,10 +77,11 @@ 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
 import io.legado.app.ui.widget.components.button.TopbarNavigationButton
+import io.legado.app.ui.widget.components.text.AppText
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import org.koin.androidx.compose.koinViewModel
 
 @Composable
@@ -119,7 +119,7 @@ fun ReplaceEditScreen(
                 .imePadding(),
             topBar = {
                 GlassMediumFlexibleTopAppBar(
-                    title = { Text(if (state.id > 0) "编辑替换规则" else "新增替换规则") },
+                    title = if (state.id > 0) "编辑替换规则" else "新增替换规则",
                     navigationIcon = {
                         TopbarNavigationButton(onClick = onBack)
                     },
@@ -143,14 +143,14 @@ fun ReplaceEditScreen(
                             onDismissRequest = { showMenu = false }
                         ) {
                             DropdownMenuItem(
-                                text = { Text("复制规则") },
+                                text = { AppText("复制规则") },
                                 onClick = {
                                     showMenu = false
                                     viewModel.copyRule()
                                 }
                             )
                             DropdownMenuItem(
-                                text = { Text("粘贴规则") },
+                                text = { AppText("粘贴规则") },
                                 onClick = {
                                     showMenu = false
                                     viewModel.pasteRule(onSuccess = {})
@@ -167,7 +167,7 @@ fun ReplaceEditScreen(
                     positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
                         TooltipAnchorPosition.Above
                     ),
-                    tooltip = { PlainTooltip { Text("添加") } },
+                    tooltip = { PlainTooltip { AppText("添加") } },
                     state = rememberTooltipState(),
                 ) {
                     FloatingActionButton(
@@ -214,7 +214,7 @@ fun ReplaceEditScreen(
                     OutlinedTextField(
                         value = state.name,
                         onValueChange = viewModel::onNameChange,
-                        label = { Text("规则名称") },
+                        label = { AppText("规则名称") },
                         modifier = Modifier
                             .fillMaxWidth()
                             .onFocusChanged {
@@ -234,8 +234,8 @@ fun ReplaceEditScreen(
                     OutlinedTextField(
                         value = state.pattern,
                         onValueChange = viewModel::onPatternChange,
-                        label = { Text("匹配规则") },
-                        placeholder = { Text("输入正则表达式或关键字") },
+                        label = { AppText("匹配规则") },
+                        placeholder = { AppText("输入正则表达式或关键字") },
                         modifier = Modifier
                             .fillMaxWidth()
                             .onFocusChanged {
@@ -247,8 +247,8 @@ fun ReplaceEditScreen(
                     OutlinedTextField(
                         value = state.replacement,
                         onValueChange = viewModel::onReplacementChange,
-                        label = { Text("替换为") },
-                        placeholder = { Text("输入替换内容或捕获组") },
+                        label = { AppText("替换为") },
+                        placeholder = { AppText("输入替换内容或捕获组") },
                         modifier = Modifier
                             .fillMaxWidth()
                             .onFocusChanged {
@@ -265,7 +265,7 @@ fun ReplaceEditScreen(
                         FilterChip(
                             selected = state.scopeTitle,
                             onClick = { viewModel.onScopeTitleChange(!state.scopeTitle) },
-                            label = { Text("标题") },
+                            label = { AppText("标题") },
                             leadingIcon = if (state.scopeTitle) {
                                 {
                                     Icon(
@@ -282,7 +282,7 @@ fun ReplaceEditScreen(
                         FilterChip(
                             selected = state.scopeContent,
                             onClick = { viewModel.onScopeContentChange(!state.scopeContent) },
-                            label = { Text("内容") },
+                            label = { AppText("内容") },
                             leadingIcon = if (state.scopeContent) {
                                 {
                                     Icon(
@@ -299,7 +299,7 @@ fun ReplaceEditScreen(
                         FilterChip(
                             selected = state.isRegex,
                             onClick = { viewModel.onRegexChange(!state.isRegex) },
-                            label = { Text("使用正则") },
+                            label = { AppText("使用正则") },
                             leadingIcon = if (state.isRegex) {
                                 {
                                     Icon(
@@ -316,8 +316,8 @@ fun ReplaceEditScreen(
                     OutlinedTextField(
                         value = state.scope,
                         onValueChange = viewModel::onScopeChange,
-                        label = { Text("特定范围") },
-                        placeholder = { Text("指定规则适用的范围") },
+                        label = { AppText("特定范围") },
+                        placeholder = { AppText("指定规则适用的范围") },
                         modifier = Modifier
                             .fillMaxWidth()
                             .onFocusChanged {
@@ -329,8 +329,8 @@ fun ReplaceEditScreen(
                     OutlinedTextField(
                         value = state.excludeScope,
                         onValueChange = viewModel::onExcludeScopeChange,
-                        label = { Text("排除范围") },
-                        placeholder = { Text("指定规则不适用的范围") },
+                        label = { AppText("排除范围") },
+                        placeholder = { AppText("指定规则不适用的范围") },
                         modifier = Modifier
                             .fillMaxWidth()
                             .onFocusChanged {
@@ -342,8 +342,8 @@ fun ReplaceEditScreen(
                     OutlinedTextField(
                         value = state.timeout,
                         onValueChange = viewModel::onTimeoutChange,
-                        label = { Text("超时 (ms)") },
-                        placeholder = { Text("3000") },
+                        label = { AppText("超时 (ms)") },
+                        placeholder = { AppText("3000") },
                         modifier = Modifier.fillMaxWidth()
                     )
 
@@ -383,8 +383,8 @@ fun GroupSelector(
             OutlinedTextField(
                 value = currentGroup,
                 onValueChange = onGroupChange,
-                label = { Text("分组") },
-                placeholder = { Text("默认") },
+                label = { AppText("分组") },
+                placeholder = { AppText("默认") },
                 trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
                 colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
                 modifier = Modifier
@@ -400,7 +400,7 @@ fun GroupSelector(
             ) {
                 allGroups.forEach { selectionOption ->
                     DropdownMenuItem(
-                        text = { Text(selectionOption) },
+                        text = { AppText(selectionOption) },
                         onClick = {
                             onGroupChange(selectionOption)
                             expanded = false
@@ -426,9 +426,9 @@ fun ManageGroupDialog(
 
     AlertDialog(
         onDismissRequest = onDismiss,
-        title = { Text("分组管理") },
+        title = { AppText("分组管理") },
         text = {
-            if (groups.isEmpty()) Text("暂无其他分组")
+            if (groups.isEmpty()) AppText("暂无其他分组")
             else Column(
                 modifier = Modifier.verticalScroll(rememberScrollState()),
                 verticalArrangement = Arrangement.spacedBy(8.dp)
@@ -448,7 +448,7 @@ fun ManageGroupDialog(
                         verticalAlignment = Alignment.CenterVertically
                     ) {
                         Checkbox(checked = isSelected, onCheckedChange = null)
-                        Text(group, Modifier.padding(start = 8.dp))
+                        AppText(group, Modifier.padding(start = 8.dp))
                     }
                 }
             }
@@ -462,7 +462,7 @@ fun ManageGroupDialog(
                 text = "删除选中"
             )
         },
-        dismissButton = { TextButton(onClick = onDismiss) { Text("关闭") } }
+        dismissButton = { TextButton(onClick = onDismiss) { AppText("关闭") } }
     )
 }
 
@@ -482,7 +482,7 @@ fun QuickInputBar(
         symbols.forEach { symbol ->
             AssistChip(
                 onClick = { onInsert(symbol) },
-                label = { Text(symbol) },
+                label = { AppText(symbol) },
                 modifier = Modifier.padding(end = 8.dp)
             )
         }

+ 10 - 17
app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt

@@ -3,11 +3,9 @@ package io.legado.app.ui.rss.favorites
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.material.icons.Icons
@@ -19,7 +17,6 @@ import androidx.compose.material.icons.filled.Group
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
@@ -129,11 +126,10 @@ fun RssFavoritesScreen(
         topBarActions = {},
         dropDownMenuContent = { dismiss ->
             RoundDropdownMenuItem(
-                text = {
+                text = stringResource(R.string.all),
+                leadingIcon = {
                     Row(verticalAlignment = Alignment.CenterVertically) {
                         Icon(Icons.Default.Group, null, modifier = Modifier.size(18.dp))
-                        Spacer(Modifier.width(12.dp))
-                        Text(stringResource(R.string.all))
                     }
                 },
                 onClick = {
@@ -143,15 +139,14 @@ fun RssFavoritesScreen(
             )
             groups.forEach { group ->
                 RoundDropdownMenuItem(
-                    text = {
+                    text = group,
+                    leadingIcon = {
                         Row(verticalAlignment = Alignment.CenterVertically) {
                             Icon(
                                 Icons.AutoMirrored.Outlined.Label,
                                 null,
                                 modifier = Modifier.size(18.dp)
                             )
-                            Spacer(Modifier.width(12.dp))
-                            Text(group)
                         }
                     },
                     onClick = {
@@ -162,11 +157,10 @@ fun RssFavoritesScreen(
             }
             PillDivider()
             RoundDropdownMenuItem(
-                text = {
+                text = stringResource(R.string.delete_select_group),
+                leadingIcon = {
                     Row(verticalAlignment = Alignment.CenterVertically) {
                         Icon(Icons.Default.DeleteSweep, null, modifier = Modifier.size(18.dp))
-                        Spacer(Modifier.width(12.dp))
-                        Text(stringResource(R.string.delete_select_group))
                     }
                 },
                 onClick = {
@@ -175,11 +169,10 @@ fun RssFavoritesScreen(
                 }
             )
             RoundDropdownMenuItem(
-                text = {
+                text = stringResource(R.string.all),
+                leadingIcon = {
                     Row(verticalAlignment = Alignment.CenterVertically) {
                         Icon(Icons.Default.DeleteForever, null, modifier = Modifier.size(18.dp))
-                        Spacer(Modifier.width(12.dp))
-                        Text(stringResource(R.string.all))
                     }
                 },
                 onClick = {
@@ -248,14 +241,14 @@ fun RssFavoritesScreen(
                         },
                         dropdownContent = { dismiss ->
                             RoundDropdownMenuItem(
-                                text = { Text(stringResource(R.string.change_group)) },
+                                text = stringResource(R.string.change_group),
                                 onClick = {
                                     showSetGroupDialog = rssStar
                                     dismiss()
                                 }
                             )
                             RoundDropdownMenuItem(
-                                text = { Text(stringResource(R.string.delete)) },
+                                text = stringResource(R.string.delete),
                                 onClick = {
                                     viewModel.deleteStar(rssStar)
                                     dismiss()

+ 49 - 57
app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceScreen.kt

@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
@@ -17,11 +16,9 @@ import androidx.compose.material.icons.filled.Delete
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.SnackbarResult
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -51,13 +48,13 @@ import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
 import io.legado.app.ui.widget.components.dialog.TextListInputDialog
 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.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.RoundDropdownMenu
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 import io.legado.app.ui.widget.components.rules.RuleListScaffold
+import io.legado.app.ui.widget.components.text.AppText
 import org.koin.androidx.compose.koinViewModel
 import sh.calvin.reorderable.rememberReorderableLazyListState
 
@@ -175,46 +172,41 @@ fun RssSourceScreen(
         )
     }
 
-    if (showGroupManageSheet) {
-        GroupManageBottomSheet(
-            groups = groups,
-            onDismissRequest = { showGroupManageSheet = false },
-            onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
-            onDeleteGroup = { viewModel.delGroup(it) }
-        )
-    }
+    GroupManageBottomSheet(
+        show = showGroupManageSheet,
+        groups = groups,
+        onDismissRequest = { showGroupManageSheet = false },
+        onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
+        onDeleteGroup = { viewModel.delGroup(it) }
+    )
 
-    if (showFilePickerSheet) {
-        FilePickerSheet(
-            onDismissRequest = { showFilePickerSheet = false },
-            onSelectSysDir = {
-                showFilePickerSheet = false
-                exportDoc.launch("exportRssSource.json")
-            },
-            onUpload = {
-                showFilePickerSheet = false
-                viewModel.uploadSelectedRules(selectedIds, rules)
-            },
-            allowExtensions = arrayOf("json")
-        )
-    }
 
-    (importState as? BaseImportUiState.Success<RssSource>)?.let { state ->
-        BatchImportDialog(
-            title = stringResource(R.string.import_rss_source),
-            importState = state,
-            onDismissRequest = { viewModel.cancelImport() },
-            onToggleItem = { viewModel.toggleImportSelection(it) },
-            onToggleAll = { viewModel.toggleImportAll(it) },
-            onConfirm = { viewModel.saveImportedRules() },
-            itemContent = { source, _ ->
-                Column {
-                    Text(source.sourceName, style = MaterialTheme.typography.titleMedium)
-                    Text(source.sourceUrl, style = MaterialTheme.typography.bodySmall, maxLines = 1)
-                }
-            }
-        )
-    }
+    FilePickerSheet(
+        show = showFilePickerSheet,
+        onDismissRequest = { showFilePickerSheet = false },
+        onSelectSysDir = {
+            showFilePickerSheet = false
+            exportDoc.launch("exportRssSource.json")
+        },
+        onUpload = {
+            showFilePickerSheet = false
+            viewModel.uploadSelectedRules(selectedIds, rules)
+        },
+        allowExtensions = arrayOf("json")
+    )
+
+    BatchImportDialog(
+        title = stringResource(R.string.import_rss_source),
+        importState = importState,
+        onDismissRequest = { viewModel.cancelImport() },
+        onToggleItem = { viewModel.toggleImportSelection(it) },
+        onToggleAll = { viewModel.toggleImportAll(it) },
+        onConfirm = { viewModel.saveImportedRules() },
+        itemTitle = { rule -> rule.sourceName },
+        itemSubtitle = { rule ->
+            rule.sourceUrl.takeIf { it.isNotBlank() }
+        }
+    )
 
     LaunchedEffect(reorderableState.isAnyItemDragging) {
         if (!reorderableState.isAnyItemDragging) {
@@ -225,16 +217,16 @@ fun RssSourceScreen(
     showDeleteRuleDialog?.let { source ->
         AlertDialog(
             onDismissRequest = { showDeleteRuleDialog = null },
-            title = { Text(stringResource(R.string.delete)) },
-            text = { Text(stringResource(R.string.del_msg)) },
+            title = { AppText(stringResource(R.string.delete)) },
+            text = { AppText(stringResource(R.string.del_msg)) },
             confirmButton = {
                 OutlinedButton(onClick = {
                     viewModel.del(source); showDeleteRuleDialog = null
-                }) { Text(stringResource(R.string.ok)) }
+                }) { AppText(stringResource(R.string.ok)) }
             },
             dismissButton = {
                 TextButton(onClick = { showDeleteRuleDialog = null }) {
-                    Text(stringResource(R.string.cancel))
+                    AppText(stringResource(R.string.cancel))
                 }
             }
         )
@@ -287,11 +279,11 @@ fun RssSourceScreen(
         dropDownMenuContent = { dismiss ->
             RoundDropdownMenuItem(
                 onClick = { showGroupManageSheet = true },
-                text = { Text("分组管理") },
+                text = "分组管理",
             )
             Box {
                 RoundDropdownMenuItem(
-                    text = { Text(stringResource(R.string.import_rss_source)) },
+                    text = stringResource(R.string.import_rss_source),
                     onClick = { showImportMenu = true }
                 )
                 RoundDropdownMenu(
@@ -299,7 +291,7 @@ fun RssSourceScreen(
                     onDismissRequest = { showImportMenu = false }
                 ) {
                     RoundDropdownMenuItem(
-                        text = { Text(stringResource(R.string.import_on_line)) },
+                        text = stringResource(R.string.import_on_line),
                         onClick = {
                             showImportMenu = false
                             dismiss()
@@ -307,7 +299,7 @@ fun RssSourceScreen(
                         }
                     )
                     RoundDropdownMenuItem(
-                        text = { Text(stringResource(R.string.import_local)) },
+                        text = stringResource(R.string.import_local),
                         onClick = {
                             showImportMenu = false
                             dismiss()
@@ -315,7 +307,7 @@ fun RssSourceScreen(
                         }
                     )
                     RoundDropdownMenuItem(
-                        text = { Text(stringResource(R.string.import_default_rule)) },
+                        text = stringResource(R.string.import_default_rule),
                         onClick = {
                             showImportMenu = false
                             dismiss()
@@ -326,29 +318,29 @@ fun RssSourceScreen(
             }
             PillDivider()
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.all)) },
+                text = stringResource(R.string.all),
                 onClick = { dismiss(); viewModel.setGroupFilter(null) }
             )
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.enabled)) },
+                text = stringResource(R.string.enabled),
                 onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_ENABLED) }
             )
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.disabled)) },
+                text = stringResource(R.string.disabled),
                 onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_DISABLED) }
             )
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.need_login)) },
+                text = stringResource(R.string.need_login),
                 onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_LOGIN) }
             )
             RoundDropdownMenuItem(
-                text = { Text(stringResource(R.string.no_group)) },
+                text = stringResource(R.string.no_group),
                 onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_NO_GROUP) }
             )
             PillDivider()
             groups.forEach { group ->
                 RoundDropdownMenuItem(
-                    text = { Text(group) },
+                    text = group,
                     onClick = { dismiss(); viewModel.setGroupFilter("${RssSourceViewModel.PREFIX_GROUP}$group") }
                 )
             }

+ 14 - 13
app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubScreen.kt

@@ -22,11 +22,9 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.FloatingActionButton
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.PlainTooltip
 import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.TooltipAnchorPosition
 import androidx.compose.material3.TooltipBox
@@ -54,12 +52,14 @@ import io.legado.app.data.entities.RuleSubType
 import io.legado.app.ui.association.ImportBookSourceDialog
 import io.legado.app.ui.association.ImportReplaceRuleDialog
 import io.legado.app.ui.association.ImportRssSourceDialog
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.EmptyMessageView
 import io.legado.app.ui.widget.components.card.SelectionItemCard
 import io.legado.app.ui.widget.components.checkBox.CheckboxGroupContainer
 import io.legado.app.ui.widget.components.checkBox.CheckboxItem
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 import io.legado.app.ui.widget.components.rules.RuleListScaffold
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.showDialogFragment
 import io.legado.app.utils.toastOnUi
 
@@ -89,11 +89,12 @@ fun RuleSubScreen(
         topBarActions = {},
         dropDownMenuContent = { dismiss ->
             RoundDropdownMenuItem(
-                text = {
+                text = stringResource(R.string.sort),
+                leadingIcon = {
                     Row(verticalAlignment = Alignment.CenterVertically) {
                         Icon(Icons.AutoMirrored.Filled.Sort, null, modifier = Modifier.size(18.dp))
                         Spacer(Modifier.size(12.dp))
-                        Text(stringResource(R.string.sort))
+                        AppText(stringResource(R.string.sort))
                     }
                 },
                 onClick = {
@@ -106,7 +107,7 @@ fun RuleSubScreen(
             TooltipBox(
                 positionProvider =
                     TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
-                tooltip = { PlainTooltip { Text("Localized description") } },
+                tooltip = { PlainTooltip { AppText("Localized description") } },
                 state = rememberTooltipState(),
             ) {
                 FloatingActionButton(
@@ -185,7 +186,7 @@ fun RuleSubScreen(
                         },
                         dropdownContent = { dismiss ->
                             RoundDropdownMenuItem(
-                                text = { Text(stringResource(R.string.delete)) },
+                                text = stringResource(R.string.delete),
                                 onClick = {
                                     viewModel.delete(ruleSub)
                                     dismiss()
@@ -226,24 +227,24 @@ fun RuleSubEditDialog(
 
     AlertDialog(
         onDismissRequest = onDismiss,
-        title = { Text(stringResource(R.string.rule_subscription)) },
+        title = { AppText(stringResource(R.string.rule_subscription)) },
         text = {
             Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
                 OutlinedTextField(
                     value = name,
                     onValueChange = { name = it },
-                    label = { Text(stringResource(R.string.name)) },
+                    label = { AppText(stringResource(R.string.name)) },
                     modifier = Modifier.fillMaxWidth()
                 )
                 OutlinedTextField(
                     value = url,
                     onValueChange = { url = it },
-                    label = { Text("URL") },
+                    label = { AppText("URL") },
                     modifier = Modifier.fillMaxWidth()
                 )
-                Text(
+                AppText(
                     text = "订阅类型",
-                    style = MaterialTheme.typography.titleSmall,
+                    style = LegadoTheme.typography.titleSmall,
                     modifier = Modifier.padding(top = 8.dp)
                 )
                 CheckboxGroupContainer(columns = 2) {
@@ -265,12 +266,12 @@ fun RuleSubEditDialog(
             TextButton(onClick = {
                 onConfirm(ruleSub.copy(name = name, url = url, type = type))
             }) {
-                Text(stringResource(R.string.ok))
+                AppText(stringResource(R.string.ok))
             }
         },
         dismissButton = {
             TextButton(onClick = onDismiss) {
-                Text(stringResource(R.string.cancel))
+                AppText(stringResource(R.string.cancel))
             }
         }
     )

+ 19 - 0
app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt

@@ -0,0 +1,19 @@
+package io.legado.app.ui.theme
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme.composeEngine
+
+@Composable
+fun Modifier.adaptiveHorizontalPadding(): Modifier {
+    val horizontal = if (ThemeResolver.isMiuixEngine(composeEngine)) 12.dp else 16.dp
+    return this.padding(horizontal = horizontal)
+}
+
+@Composable
+fun Modifier.adaptiveVerticalPadding(): Modifier {
+    val horizontal = if (ThemeResolver.isMiuixEngine(composeEngine)) 12.dp else 8.dp
+    return this.padding(horizontal = horizontal)
+}

+ 261 - 11
app/src/main/java/io/legado/app/ui/theme/AppTheme.kt

@@ -7,9 +7,14 @@ import androidx.compose.material3.MotionScheme
 import androidx.compose.material3.Shapes
 import androidx.compose.material3.Typography
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.font.FontWeight
 import io.legado.app.ui.config.themeConfig.ThemeConfig
+import top.yukonga.miuix.kmp.theme.MiuixTheme
+import top.yukonga.miuix.kmp.theme.ThemeController
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
@@ -20,29 +25,274 @@ fun AppTheme(
     val context = LocalContext.current
     val appThemeMode = ThemeResolver.resolveThemeMode(ThemeConfig.appTheme)
     val isPureBlack = ThemeConfig.isPureBlack
-    val paletteStyle = ThemeConfig.paletteStyle
+    val paletteStyleStr = ThemeConfig.paletteStyle
     val materialVersion = ThemeConfig.materialVersion
+    val composeEngine = ThemeConfig.composeEngine
+    val colorSchemeMode = ThemeResolver.resolveColorSchemeMode(ThemeConfig.themeMode)
+    val paletteStyle =
+        remember(paletteStyleStr) { ThemeResolver.resolvePaletteStyle(paletteStyleStr) }
+    val seedColor = remember(ThemeConfig.cPrimary) {
+        if (ThemeConfig.cPrimary != 0) Color(ThemeConfig.cPrimary) else Color(0xFF3482FF)
+    }
 
     val colorScheme =
-        remember(context, appThemeMode, darkTheme, isPureBlack, paletteStyle, materialVersion) {
+        remember(context, appThemeMode, darkTheme, isPureBlack, paletteStyleStr, materialVersion) {
             ThemeManager.getColorScheme(
                 context = context,
                 mode = appThemeMode,
                 darkTheme = darkTheme,
                 isAmoled = isPureBlack,
-                paletteStyle = paletteStyle,
+                paletteStyle = paletteStyleStr,
                 materialVersion = materialVersion
             )
         }
 
-    MaterialExpressiveTheme(
-        colorScheme = colorScheme,
-        typography = Typography(),
-        motionScheme = MotionScheme.expressive(),
-        shapes = Shapes()
+    val themeColors = remember(
+        colorScheme,
+        darkTheme,
+        seedColor,
+        paletteStyle,
+        colorSchemeMode,
+        composeEngine
+    ) {
+        LegadoThemeMode(
+            colorScheme = colorScheme,
+            isDark = darkTheme,
+            seedColor = seedColor,
+            paletteStyle = paletteStyle,
+            themeMode = colorSchemeMode,
+            useDynamicColor = appThemeMode == AppThemeMode.Dynamic,
+            composeEngine = composeEngine
+        )
+    }
+
+    CompositionLocalProvider(
+        LocalLegadoThemeColors provides themeColors
     ) {
-        AppBackground(darkTheme = darkTheme) {
-            content()
+        if (ThemeResolver.isMiuixEngine(themeColors.composeEngine)) {
+            val controller = remember(colorSchemeMode, darkTheme) {
+                ThemeController(colorSchemeMode = colorSchemeMode, isDark = darkTheme)
+            }
+
+            MiuixTheme(controller = controller) {
+                val miuixStyles = MiuixTheme.textStyles
+                val legadoTypography = remember(miuixStyles) {
+                    LegadoTypography(
+                        headlineLarge = miuixStyles.title1,
+                        headlineLargeEmphasized = miuixStyles.title1.copy(fontWeight = FontWeight.Medium),
+                        headlineMedium = miuixStyles.title2,
+                        headlineMediumEmphasized = miuixStyles.title2.copy(fontWeight = FontWeight.Medium),
+                        headlineSmall = miuixStyles.title3,
+                        headlineSmallEmphasized = miuixStyles.title3.copy(fontWeight = FontWeight.Medium),
+
+
+                        titleLarge = miuixStyles.headline1,
+                        titleLargeEmphasized = miuixStyles.headline1.copy(fontWeight = FontWeight.Medium),
+                        titleMedium = miuixStyles.headline2,
+                        titleMediumEmphasized = miuixStyles.headline2.copy(fontWeight = FontWeight.Medium),
+                        titleSmall = miuixStyles.subtitle,
+                        titleSmallEmphasized = miuixStyles.subtitle.copy(fontWeight = FontWeight.Medium),
+
+
+                        bodyLarge = miuixStyles.paragraph,
+                        bodyLargeEmphasized = miuixStyles.paragraph.copy(fontWeight = FontWeight.Medium),
+                        bodyMedium = miuixStyles.body1,
+                        bodyMediumEmphasized = miuixStyles.body1.copy(fontWeight = FontWeight.Medium),
+                        bodySmall = miuixStyles.body2,
+                        bodySmallEmphasized = miuixStyles.body2.copy(fontWeight = FontWeight.Medium),
+
+                        labelLarge = miuixStyles.button,
+                        labelLargeEmphasized = miuixStyles.button.copy(fontWeight = FontWeight.Medium),
+                        labelMedium = miuixStyles.footnote1,
+                        labelMediumEmphasized = miuixStyles.footnote1.copy(fontWeight = FontWeight.Medium),
+                        labelSmall = miuixStyles.footnote2,
+                        labelSmallEmphasized = miuixStyles.footnote2.copy(fontWeight = FontWeight.Medium)
+                    )
+                }
+
+                val miuixColorScheme = MiuixTheme.colorScheme
+
+                val mappedColorScheme = remember(miuixColorScheme) {
+                    LegadoColorScheme(
+                        primary = miuixColorScheme.primary,
+                        onPrimary = miuixColorScheme.onPrimary,
+                        primaryContainer = miuixColorScheme.primaryContainer,
+                        onPrimaryContainer = miuixColorScheme.onPrimaryContainer,
+                        inversePrimary = miuixColorScheme.primaryVariant,
+
+                        secondary = miuixColorScheme.primary.copy(alpha = 0.5f),
+                        onSecondary = miuixColorScheme.onPrimary.copy(alpha = 0.5f),
+                        secondaryContainer = miuixColorScheme.primaryContainer.copy(alpha = 0.5f),
+                        onSecondaryContainer = miuixColorScheme.onPrimaryContainer.copy(alpha = 0.5f),
+
+                        tertiary = miuixColorScheme.secondary,
+                        onTertiary = miuixColorScheme.onSecondary,
+                        tertiaryContainer = miuixColorScheme.secondaryContainer,
+                        onTertiaryContainer = miuixColorScheme.onSecondaryContainer,
+
+                        // ================= 4. 背景与表面 (Background & Surface) =================
+                        background = miuixColorScheme.background,
+                        onBackground = miuixColorScheme.onBackground,
+
+                        surface = miuixColorScheme.surface,
+                        onSurface = miuixColorScheme.onSurface,
+                        surfaceVariant = miuixColorScheme.surfaceVariant,
+                        // M3 的 onSurfaceVariant 通常是次级文字色。Miuix 的 onSurfaceSecondary 完美契合这个语义
+                        onSurfaceVariant = miuixColorScheme.onSurfaceSecondary,
+
+                        // M3 中用于给 Surface 叠加一层极淡主题色的属性,通常直接取 primary
+                        surfaceTint = miuixColorScheme.primary,
+
+                        // Inverse 系列通常用于深色模式下的反色提示(如 Snackbar)。
+                        // 简单映射法:直接用现有的 onSurface 和 surface 交叉互换。
+                        inverseSurface = miuixColorScheme.onSurface,
+                        inverseOnSurface = miuixColorScheme.surface,
+
+                        // ================= 5. 错误状态 (Error) =================
+                        error = miuixColorScheme.error,
+                        onError = miuixColorScheme.onError,
+                        errorContainer = miuixColorScheme.errorContainer,
+                        onErrorContainer = miuixColorScheme.onErrorContainer,
+
+                        // ================= 6. 边框、分割线与遮罩 (Outline & Scrim) =================
+                        outline = miuixColorScheme.outline,
+                        // outlineVariant 在 M3 中常用于分割线。Miuix 刚好有 dividerLine
+                        outlineVariant = miuixColorScheme.dividerLine,
+                        // scrim 是 M3 的遮罩层(如弹窗背后的阴影)。Miuix 刚好有 windowDimming
+                        scrim = miuixColorScheme.windowDimming,
+
+                        surfaceBright = miuixColorScheme.surface, // Miuix 缺省,用 surface 兜底
+                        surfaceDim = miuixColorScheme.background, // Miuix 缺省,用 background 兜底
+                        surfaceContainer = miuixColorScheme.surfaceContainer,
+                        surfaceContainerHigh = miuixColorScheme.surfaceContainerHigh,
+                        surfaceContainerHighest = miuixColorScheme.surfaceContainerHighest,
+                        surfaceContainerLow = miuixColorScheme.secondaryContainer,
+                        surfaceContainerLowest = miuixColorScheme.background,
+
+                        primaryFixed = miuixColorScheme.primaryContainer,
+                        primaryFixedDim = miuixColorScheme.primary,
+                        onPrimaryFixed = miuixColorScheme.onPrimaryContainer,
+                        onPrimaryFixedVariant = miuixColorScheme.onPrimary,
+                        secondaryFixed = miuixColorScheme.secondaryContainer,
+                        secondaryFixedDim = miuixColorScheme.secondary,
+                        onSecondaryFixed = miuixColorScheme.onSecondaryContainer,
+                        onSecondaryFixedVariant = miuixColorScheme.onSecondary,
+                        tertiaryFixed = miuixColorScheme.tertiaryContainer,
+                        tertiaryFixedDim = miuixColorScheme.tertiaryContainerVariant,
+                        onTertiaryFixed = miuixColorScheme.onTertiaryContainer,
+                        onTertiaryFixedVariant = miuixColorScheme.onTertiaryContainer
+                    )
+                }
+
+                CompositionLocalProvider(
+                    LocalLegadoTypography provides legadoTypography,
+                    LocalLegadoColorScheme provides mappedColorScheme
+                ) {
+                    AppBackground(darkTheme = darkTheme) { content() }
+                }
+            }
+        } else {
+            val Typography = Typography()
+            MaterialExpressiveTheme(
+                colorScheme = colorScheme,
+                typography = Typography,
+                motionScheme = MotionScheme.expressive(),
+                shapes = Shapes()
+            ) {
+                val legadoTypography = remember(Typography) {
+                    LegadoTypography(
+                        headlineLarge = Typography.headlineLarge,
+                        headlineLargeEmphasized = Typography.headlineLargeEmphasized,
+                        headlineMedium = Typography.headlineMedium,
+                        headlineMediumEmphasized = Typography.headlineMediumEmphasized,
+                        headlineSmall = Typography.headlineSmall,
+                        headlineSmallEmphasized = Typography.headlineSmallEmphasized,
+
+
+                        titleLarge = Typography.titleLarge,
+                        titleLargeEmphasized = Typography.titleLargeEmphasized,
+                        titleMedium = Typography.titleMedium,
+                        titleMediumEmphasized = Typography.titleMediumEmphasized,
+                        titleSmall = Typography.titleSmall,
+                        titleSmallEmphasized = Typography.titleSmallEmphasized,
+
+
+                        bodyLarge = Typography.bodyLarge,
+                        bodyLargeEmphasized = Typography.bodyLargeEmphasized,
+                        bodyMedium = Typography.bodyMedium,
+                        bodyMediumEmphasized = Typography.bodyMediumEmphasized,
+                        bodySmall = Typography.bodySmall,
+                        bodySmallEmphasized = Typography.bodySmallEmphasized,
+
+                        labelLarge = Typography.labelLarge,
+                        labelLargeEmphasized = Typography.labelLargeEmphasized,
+                        labelMedium = Typography.labelMedium,
+                        labelMediumEmphasized = Typography.labelMediumEmphasized,
+                        labelSmall = Typography.labelSmall,
+                        labelSmallEmphasized = Typography.labelSmallEmphasized
+                    )
+                }
+
+                val semanticColors = remember(colorScheme) {
+                    LegadoColorScheme(
+                        primary = colorScheme.primary,
+                        onPrimary = colorScheme.onPrimary,
+                        primaryContainer = colorScheme.primaryContainer,
+                        onPrimaryContainer = colorScheme.onPrimaryContainer,
+                        inversePrimary = colorScheme.inversePrimary,
+                        secondary = colorScheme.secondary,
+                        onSecondary = colorScheme.onSecondary,
+                        secondaryContainer = colorScheme.secondaryContainer,
+                        onSecondaryContainer = colorScheme.onSecondaryContainer,
+                        tertiary = colorScheme.tertiary,
+                        onTertiary = colorScheme.onTertiary,
+                        tertiaryContainer = colorScheme.tertiaryContainer,
+                        onTertiaryContainer = colorScheme.onTertiaryContainer,
+                        background = colorScheme.background,
+                        onBackground = colorScheme.onBackground,
+                        surface = colorScheme.surface,
+                        onSurface = colorScheme.onSurface,
+                        surfaceVariant = colorScheme.surfaceVariant,
+                        onSurfaceVariant = colorScheme.onSurfaceVariant,
+                        surfaceTint = colorScheme.surfaceTint,
+                        inverseSurface = colorScheme.inverseSurface,
+                        inverseOnSurface = colorScheme.inverseOnSurface,
+                        error = colorScheme.error,
+                        onError = colorScheme.onError,
+                        errorContainer = colorScheme.errorContainer,
+                        onErrorContainer = colorScheme.onErrorContainer,
+                        outline = colorScheme.outline,
+                        outlineVariant = colorScheme.outlineVariant,
+                        scrim = colorScheme.scrim,
+                        surfaceBright = colorScheme.surfaceBright,
+                        surfaceDim = colorScheme.surfaceDim,
+                        surfaceContainer = colorScheme.surfaceContainer,
+                        surfaceContainerHigh = colorScheme.surfaceContainerHigh,
+                        surfaceContainerHighest = colorScheme.surfaceContainerHighest,
+                        surfaceContainerLow = colorScheme.surfaceContainerLow,
+                        surfaceContainerLowest = colorScheme.surfaceContainerLowest,
+                        primaryFixed = colorScheme.primaryFixed,
+                        primaryFixedDim = colorScheme.primaryFixedDim,
+                        onPrimaryFixed = colorScheme.onPrimaryFixed,
+                        onPrimaryFixedVariant = colorScheme.onPrimaryFixedVariant,
+                        secondaryFixed = colorScheme.secondaryFixed,
+                        secondaryFixedDim = colorScheme.secondaryFixedDim,
+                        onSecondaryFixed = colorScheme.onSecondaryFixed,
+                        onSecondaryFixedVariant = colorScheme.onSecondaryFixedVariant,
+                        tertiaryFixed = colorScheme.tertiaryFixed,
+                        tertiaryFixedDim = colorScheme.tertiaryFixedDim,
+                        onTertiaryFixed = colorScheme.onTertiaryFixed,
+                        onTertiaryFixedVariant = colorScheme.onTertiaryFixedVariant
+                    )
+                }
+
+                CompositionLocalProvider(
+                    LocalLegadoTypography provides legadoTypography,
+                    LocalLegadoColorScheme provides semanticColors
+                ) {
+                    AppBackground(darkTheme = darkTheme) { content() }
+                }
+            }
         }
     }
-}
+}

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

@@ -7,8 +7,8 @@ import dev.chrisbanes.haze.HazeState
 import dev.chrisbanes.haze.hazeEffect
 import dev.chrisbanes.haze.hazeSource
 import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
-import dev.chrisbanes.haze.materials.HazeMaterials
 import io.legado.app.ui.config.themeConfig.ThemeConfig
+import io.legado.app.ui.theme.hazeStyle.HazeLegado
 
 /**
  * 自动感知全局配置的 HazeSource
@@ -31,9 +31,9 @@ fun Modifier.responsiveHazeEffect(
     if (!enableBlur) return this
 
     val style = if (enableProgressiveBlur) {
-        HazeMaterials.ultraThin()
+        HazeLegado.ultraThin()
     } else {
-        HazeMaterials.regular()
+        HazeLegado.regular()
     }
 
     return this.hazeEffect(
@@ -61,6 +61,6 @@ fun Modifier.regularHazeEffect(state: HazeState): Modifier {
 
     return this.hazeEffect(
         state = state,
-        style = HazeMaterials.ultraThin()
+        style = HazeLegado.ultraThin()
     )
 }

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

@@ -0,0 +1,172 @@
+package io.legado.app.ui.theme
+
+import androidx.compose.material3.ColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import com.materialkolor.PaletteStyle
+import dev.chrisbanes.haze.HazeState
+import top.yukonga.miuix.kmp.theme.ColorSchemeMode
+
+data class LegadoThemeMode(
+    val colorScheme: ColorScheme,
+    val isDark: Boolean,
+    val seedColor: Color,
+    val paletteStyle: PaletteStyle,
+    val themeMode: ColorSchemeMode,
+    val useDynamicColor: Boolean,
+    val composeEngine: String,
+)
+
+data class LegadoColorScheme(
+    val primary: Color,
+    val onPrimary: Color,
+    val primaryContainer: Color,
+    val onPrimaryContainer: Color,
+    val inversePrimary: Color,
+    val secondary: Color,
+    val onSecondary: Color,
+    val secondaryContainer: Color,
+    val onSecondaryContainer: Color,
+    val tertiary: Color,
+    val onTertiary: Color,
+    val tertiaryContainer: Color,
+    val onTertiaryContainer: Color,
+    val background: Color,
+    val onBackground: Color,
+    val surface: Color,
+    val onSurface: Color,
+    val surfaceVariant: Color,
+    val onSurfaceVariant: Color,
+    val surfaceTint: Color,
+    val inverseSurface: Color,
+    val inverseOnSurface: Color,
+    val error: Color,
+    val onError: Color,
+    val errorContainer: Color,
+    val onErrorContainer: Color,
+    val outline: Color,
+    val outlineVariant: Color,
+    val scrim: Color,
+    val surfaceBright: Color,
+    val surfaceDim: Color,
+    val surfaceContainer: Color,
+    val surfaceContainerHigh: Color,
+    val surfaceContainerHighest: Color,
+    val surfaceContainerLow: Color,
+    val surfaceContainerLowest: Color,
+    val primaryFixed: Color,
+    val primaryFixedDim: Color,
+    val onPrimaryFixed: Color,
+    val onPrimaryFixedVariant: Color,
+    val secondaryFixed: Color,
+    val secondaryFixedDim: Color,
+    val onSecondaryFixed: Color,
+    val onSecondaryFixedVariant: Color,
+    val tertiaryFixed: Color,
+    val tertiaryFixedDim: Color,
+    val onTertiaryFixed: Color,
+    val onTertiaryFixedVariant: Color,
+)
+
+data class LegadoTypography(
+    val headlineLarge: TextStyle,
+    val headlineLargeEmphasized: TextStyle,
+    val headlineMedium: TextStyle,
+    val headlineMediumEmphasized: TextStyle,
+    val headlineSmall: TextStyle,
+    val headlineSmallEmphasized: TextStyle,
+
+    val titleLarge: TextStyle,
+    val titleLargeEmphasized: TextStyle,
+    val titleMedium: TextStyle,
+    val titleMediumEmphasized: TextStyle,
+    val titleSmall: TextStyle,
+    val titleSmallEmphasized: TextStyle,
+
+
+    val bodyLarge: TextStyle,
+    val bodyLargeEmphasized: TextStyle,
+    val bodyMedium: TextStyle,
+    val bodyMediumEmphasized: TextStyle,
+    val bodySmall: TextStyle,
+    val bodySmallEmphasized: TextStyle,
+
+    val labelLarge: TextStyle,
+    val labelLargeEmphasized: TextStyle,
+    val labelMedium: TextStyle,
+    val labelMediumEmphasized: TextStyle,
+    val labelSmall: TextStyle,
+    val labelSmallEmphasized: TextStyle,
+
+    )
+
+val LocalLegadoColorScheme = staticCompositionLocalOf<LegadoColorScheme> {
+    error("No ColorScheme provided")
+}
+
+val LocalLegadoTypography = staticCompositionLocalOf<LegadoTypography> {
+    error("No Typography provided")
+}
+
+val LocalLegadoThemeColors = staticCompositionLocalOf {
+    LegadoThemeMode(
+        colorScheme = lightColorScheme(),
+        isDark = false,
+        seedColor = Color.Unspecified,
+        paletteStyle = PaletteStyle.TonalSpot,
+        themeMode = ColorSchemeMode.System,
+        useDynamicColor = true,
+        composeEngine = "material"
+    )
+}
+
+val LocalHazeState = compositionLocalOf<HazeState?> { null }
+
+object LegadoTheme {
+
+    val colorScheme: LegadoColorScheme
+        @Composable
+        @ReadOnlyComposable
+        get() = LocalLegadoColorScheme.current
+
+    val isDark: Boolean
+        @Composable
+        @ReadOnlyComposable
+        get() = LocalLegadoThemeColors.current.isDark
+
+    val seedColor: Color
+        @Composable
+        @ReadOnlyComposable
+        get() = LocalLegadoThemeColors.current.seedColor
+
+    val paletteStyle: PaletteStyle
+        @Composable
+        @ReadOnlyComposable
+        get() = LocalLegadoThemeColors.current.paletteStyle
+
+    val themeMode: ColorSchemeMode
+        @Composable
+        @ReadOnlyComposable
+        get() = LocalLegadoThemeColors.current.themeMode
+
+    val composeEngine: String
+        @Composable
+        @ReadOnlyComposable
+        get() = LocalLegadoThemeColors.current.composeEngine
+
+    val useDynamicColor: Boolean
+        @Composable
+        @ReadOnlyComposable
+        get() = LocalLegadoThemeColors.current.useDynamicColor
+
+    val typography: LegadoTypography
+        @Composable
+        @ReadOnlyComposable
+        get() = LocalLegadoTypography.current
+
+}

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

@@ -66,7 +66,8 @@ object ThemeManager {
             }
 
             AppThemeMode.CUSTOM -> {
-                CustomColorScheme(context.primaryColor, style)
+                val colorSpec = ThemeResolver.resolveColorSpecFromMaterialVersion(materialVersion)
+                CustomColorScheme(context.primaryColor, style, colorSpec)
                     .getColorScheme(darkTheme)
             }
 
@@ -93,4 +94,4 @@ object ThemeManager {
 
         return scheme
     }
-}
+}

+ 20 - 0
app/src/main/java/io/legado/app/ui/theme/ThemeResolver.kt

@@ -4,6 +4,7 @@ import com.materialkolor.Contrast
 import com.materialkolor.PaletteStyle
 import com.materialkolor.dynamiccolor.ColorSpec
 import io.legado.app.ui.config.themeConfig.ThemeConfig
+import top.yukonga.miuix.kmp.theme.ColorSchemeMode
 
 object ThemeResolver {
 
@@ -48,6 +49,17 @@ object ThemeResolver {
         }
     }
 
+    fun resolveColorSchemeMode(value: String): ColorSchemeMode = when (value) {
+        "0" -> ColorSchemeMode.System
+        "1" -> ColorSchemeMode.Light
+        "2" -> ColorSchemeMode.Dark
+        else -> ColorSchemeMode.System
+    }
+
+    fun isMiuixEngine(composeEngine: String): Boolean = composeEngine == "miuix"
+
+    fun isM3Engine(composeEngine: String): Boolean = composeEngine == "m3"
+
     fun resolveColorSpecVersion(colorSpec: ThemeColorSpec): ColorSpec.SpecVersion {
         return when (colorSpec) {
             ThemeColorSpec.SPEC_2025 -> ColorSpec.SpecVersion.SPEC_2025
@@ -55,4 +67,12 @@ object ThemeResolver {
         }
     }
 
+    fun resolveColorSpecFromMaterialVersion(value: String?): ThemeColorSpec {
+        return when (value) {
+            "material3Expressive" -> ThemeColorSpec.SPEC_2025
+            "material3" -> ThemeColorSpec.SPEC_2021
+            else -> ThemeColorSpec.SPEC_2021
+        }
+    }
+
 }

+ 44 - 0
app/src/main/java/io/legado/app/ui/theme/Typography.kt

@@ -0,0 +1,44 @@
+package io.legado.app.ui.theme
+
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.font.FontWeight
+import top.yukonga.miuix.kmp.theme.TextStyles
+
+
+/**
+ * 将 Miuix 的 TextStyles 语义化映射为 Material 3 的 Typography
+ */
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+fun miuixStylesToM3Typography(miuixStyles: TextStyles): Typography {
+    return Typography(
+        displayLarge = miuixStyles.title1,   // 32.sp
+        displayMedium = miuixStyles.title2,  // 24.sp
+        displaySmall = miuixStyles.title3,   // 20.sp
+
+        headlineLarge = miuixStyles.title2,  // 24.sp
+        headlineMedium = miuixStyles.title3, // 20.sp
+        headlineSmall = miuixStyles.title4,  // 18.sp
+
+        titleLarge = miuixStyles.headline1,  // 17.sp
+        titleMedium = miuixStyles.headline2, // 16.sp
+        titleSmall = miuixStyles.subtitle,   // 14.sp, Bold
+
+        bodyLarge = miuixStyles.paragraph,   // 17.sp, lineHeight 1.2em (如果不需要行高也可以换成 main)
+        bodyMedium = miuixStyles.body1,      // 16.sp
+        bodySmall = miuixStyles.body2,       // 14.sp
+
+        labelLarge = miuixStyles.button,     // 17.sp
+        labelMedium = miuixStyles.footnote1, // 13.sp
+        labelSmall = miuixStyles.footnote2,   // 11.sp
+
+        bodyLargeEmphasized = miuixStyles.paragraph.copy(fontWeight = FontWeight.Medium),
+        bodyMediumEmphasized = miuixStyles.body1.copy(fontWeight = FontWeight.Medium),
+        bodySmallEmphasized = miuixStyles.body2.copy(fontWeight = FontWeight.Medium),
+
+        labelLargeEmphasized = miuixStyles.button.copy(fontWeight = FontWeight.Medium),
+        labelMediumEmphasized = miuixStyles.footnote1.copy(fontWeight = FontWeight.Medium),
+        labelSmallEmphasized = miuixStyles.footnote2.copy(fontWeight = FontWeight.Medium)
+    )
+}

+ 48 - 0
app/src/main/java/io/legado/app/ui/theme/hazeStyle/HazeLegado.kt

@@ -0,0 +1,48 @@
+package io.legado.app.ui.theme.hazeStyle
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.luminance
+import androidx.compose.ui.unit.dp
+import dev.chrisbanes.haze.HazeStyle
+import dev.chrisbanes.haze.HazeTint
+import io.legado.app.ui.theme.LegadoTheme.composeEngine
+import io.legado.app.ui.theme.ThemeResolver
+import top.yukonga.miuix.kmp.theme.MiuixTheme
+
+object HazeLegado {
+
+    @Composable
+    @ReadOnlyComposable
+    fun ultraThin(
+        containerColor: Color = if (ThemeResolver.isMiuixEngine(composeEngine)) MiuixTheme.colorScheme.surface else MaterialTheme.colorScheme.surface,
+    ): HazeStyle = hazeLegado(
+        containerColor = containerColor,
+        lightAlpha = 0.35f,
+        darkAlpha = 0.55f,
+    )
+
+    @Composable
+    @ReadOnlyComposable
+    fun regular(
+        containerColor: Color = if (ThemeResolver.isMiuixEngine(composeEngine)) MiuixTheme.colorScheme.surface else MaterialTheme.colorScheme.surface,
+    ): HazeStyle = hazeLegado(
+        containerColor = containerColor,
+        lightAlpha = 0.73f,
+        darkAlpha = 0.8f,
+    )
+
+    private fun hazeLegado(
+        containerColor: Color,
+        lightAlpha: Float,
+        darkAlpha: Float,
+    ): HazeStyle = HazeStyle(
+        blurRadius = 24.dp,
+        backgroundColor = containerColor,
+        tint = HazeTint(
+            containerColor.copy(alpha = if (containerColor.luminance() >= 0.5) lightAlpha else darkAlpha),
+        ),
+    )
+}

+ 20 - 19
app/src/main/java/io/legado/app/ui/widget/CollapsibleHeader.kt

@@ -8,12 +8,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ExpandMore
-import androidx.compose.material3.Card
-import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
@@ -22,25 +19,27 @@ import androidx.compose.ui.draw.rotate
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.card.GlassCard
+import io.legado.app.ui.widget.components.text.AppText
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun CollapsibleHeader(
     modifier: Modifier = Modifier,
+    showIcon: Boolean = true,
     isCollapsed: Boolean,
     onToggle: () -> Unit,
     title: String,
     subtitle: String? = null,
     titleContent: (@Composable ColumnScope.() -> Unit)? = null
 ) {
-    Card(
+    GlassCard(
         modifier = modifier
             .fillMaxWidth()
             .padding(start = 16.dp, top = 4.dp, end = 16.dp),
         shape = MaterialTheme.shapes.medium,
-        colors = CardDefaults.cardColors(
-            containerColor = MaterialTheme.colorScheme.surfaceContainer
-        ),
+        containerColor = LegadoTheme.colorScheme.surfaceContainer,
         onClick = onToggle
     ) {
         Row(
@@ -54,20 +53,20 @@ fun CollapsibleHeader(
                 if (titleContent != null) {
                     titleContent()
                 } else {
-                    Text(
+                    AppText(
                         text = title,
-                        style = MaterialTheme.typography.bodySmallEmphasized.copy(
+                        style = LegadoTheme.typography.bodySmallEmphasized.copy(
                             fontWeight = FontWeight.Bold,
-                            color = MaterialTheme.colorScheme.primary
+                            color = LegadoTheme.colorScheme.primary
                         ),
                         maxLines = 1,
                         overflow = TextOverflow.Ellipsis
                     )
                     subtitle?.let {
-                        Text(
+                        AppText(
                             text = it,
-                            style = MaterialTheme.typography.labelSmallEmphasized.copy(
-                                color = MaterialTheme.colorScheme.onSurfaceVariant
+                            style = LegadoTheme.typography.labelSmallEmphasized.copy(
+                                color = LegadoTheme.colorScheme.onSurfaceVariant
                             ),
                             maxLines = 1,
                             overflow = TextOverflow.Ellipsis
@@ -81,12 +80,14 @@ fun CollapsibleHeader(
                 label = "arrowRotation"
             )
 
-            Icon(
-                imageVector = Icons.Default.ExpandMore,
-                contentDescription = if (isCollapsed) "展开" else "折叠",
-                modifier = Modifier.rotate(rotation),
-                tint = MaterialTheme.colorScheme.primary
-            )
+            if (showIcon) {
+                Icon(
+                    imageVector = Icons.Default.ExpandMore,
+                    contentDescription = if (isCollapsed) "展开" else "折叠",
+                    modifier = Modifier.rotate(rotation),
+                    tint = LegadoTheme.colorScheme.primary
+                )
+            }
         }
     }
 }

+ 56 - 0
app/src/main/java/io/legado/app/ui/widget/components/AppFloatingActionButton.kt

@@ -0,0 +1,56 @@
+package io.legado.app.ui.widget.components
+
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.PlainTooltip
+import androidx.compose.material3.TooltipAnchorPosition
+import androidx.compose.material3.TooltipBox
+import androidx.compose.material3.TooltipDefaults
+import androidx.compose.material3.rememberTooltipState
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import io.legado.app.ui.widget.components.text.AppText
+import top.yukonga.miuix.kmp.basic.FloatingActionButton as MiuixFloatingActionButton
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AppFloatingActionButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    tooltipText: String? = null,
+    content: @Composable () -> Unit
+) {
+    val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
+
+    if (isMiuix) {
+        MiuixFloatingActionButton(
+            onClick = onClick,
+            modifier = modifier,
+            content = content
+        )
+    } else {
+        if (tooltipText != null) {
+            TooltipBox(
+                positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
+                    TooltipAnchorPosition.Above
+                ),
+                tooltip = { PlainTooltip { AppText(tooltipText) } },
+                state = rememberTooltipState(),
+            ) {
+                FloatingActionButton(
+                    onClick = onClick,
+                    modifier = modifier,
+                    content = content
+                )
+            }
+        } else {
+            FloatingActionButton(
+                onClick = onClick,
+                modifier = modifier,
+                content = content
+            )
+        }
+    }
+}

+ 93 - 0
app/src/main/java/io/legado/app/ui/widget/components/AppNavigationBar.kt

@@ -0,0 +1,93 @@
+package io.legado.app.ui.widget.components
+
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.material3.BottomAppBarDefaults
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.NavigationBarItemDefaults
+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.graphics.vector.ImageVector
+import io.legado.app.ui.config.themeConfig.ThemeConfig
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import top.yukonga.miuix.kmp.basic.NavigationBarDisplayMode
+import top.yukonga.miuix.kmp.theme.MiuixTheme
+import top.yukonga.miuix.kmp.basic.NavigationBar as MiuixNavigationBar
+import top.yukonga.miuix.kmp.basic.NavigationBarItem as MiuixNavigationBarItem
+
+@Composable
+fun AppNavigationBar(
+    modifier: Modifier = Modifier,
+    miuixMode: NavigationBarDisplayMode = NavigationBarDisplayMode.IconAndText,
+    content: @Composable RowScope.() -> Unit
+) {
+    val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
+    val opacity = (ThemeConfig.bottomBarOpacity.coerceIn(0, 100)) / 100f
+
+    if (isMiuix) {
+        val baseColor = GlassDefaults.glassColor(
+            noBlurColor = MiuixTheme.colorScheme.surface,
+            blurAlpha = GlassDefaults.DefaultBlurAlpha
+        )
+        val finalColor = baseColor.copy(alpha = (baseColor.alpha * opacity).coerceIn(0f, 1f))
+
+        MiuixNavigationBar(
+            modifier = modifier,
+            color = finalColor,
+            mode = miuixMode,
+            content = content
+        )
+    } else {
+        val baseColor = GlassDefaults.glassColor(
+            noBlurColor = BottomAppBarDefaults.containerColor,
+            blurAlpha = GlassDefaults.DefaultBlurAlpha
+        )
+        val finalColor = baseColor.copy(alpha = (baseColor.alpha * opacity).coerceIn(0f, 1f))
+
+        NavigationBar(
+            modifier = modifier,
+            containerColor = finalColor,
+            content = content
+        )
+    }
+}
+
+@Composable
+fun RowScope.AppNavigationBarItem(
+    selected: Boolean,
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    labelString: String,
+    miuixIcon: ImageVector,
+    m3Icon: @Composable () -> Unit,
+    m3IndicatorColor: Color,
+    m3ShowLabel: Boolean,
+    m3AlwaysShowLabel: Boolean,
+) {
+    val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
+
+    if (isMiuix) {
+        MiuixNavigationBarItem(
+            selected = selected,
+            onClick = onClick,
+            icon = miuixIcon,
+            label = labelString,
+            modifier = modifier
+        )
+    } else {
+        NavigationBarItem(
+            selected = selected,
+            onClick = onClick,
+            modifier = modifier,
+            icon = m3Icon,
+            colors = NavigationBarItemDefaults.colors(indicatorColor = m3IndicatorColor),
+            label = if (m3ShowLabel) {
+                { Text(labelString) }
+            } else null,
+            alwaysShowLabel = m3AlwaysShowLabel
+        )
+    }
+}

+ 70 - 46
app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt

@@ -3,89 +3,113 @@ package io.legado.app.ui.widget.components
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.FabPosition
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Scaffold
 import androidx.compose.material3.ScaffoldDefaults
-import androidx.compose.material3.TopAppBarColors
-import androidx.compose.material3.TopAppBarScrollBehavior
 import androidx.compose.material3.contentColorFor
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import dev.chrisbanes.haze.HazeState
 import io.legado.app.ui.config.themeConfig.ThemeConfig
-import io.legado.app.ui.theme.responsiveHazeEffect
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.LocalHazeState
+import io.legado.app.ui.theme.ThemeResolver
 import io.legado.app.ui.theme.responsiveHazeSource
+import top.yukonga.miuix.kmp.theme.MiuixTheme
+import top.yukonga.miuix.kmp.basic.FabPosition as MiuixFabPosition
+import top.yukonga.miuix.kmp.basic.Scaffold as MiuixScaffold
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun AppScaffold(
     modifier: Modifier = Modifier,
-    title: (@Composable () -> Unit)? = null,
-    subtitle: (@Composable () -> Unit)? = null,
-    scrollBehavior: TopAppBarScrollBehavior? = null,
-    navigationIcon: @Composable () -> Unit = {},
-    actions: @Composable RowScope.() -> Unit = {},
-    colors: TopAppBarColors = GlassTopAppBarDefaults.glassColors(),
-    topBar: @Composable () -> Unit = {},
+    topBar: @Composable (HazeState) -> Unit = {},
     bottomBar: @Composable () -> Unit = {},
     snackbarHost: @Composable () -> Unit = {},
     floatingActionButton: @Composable () -> Unit = {},
     floatingActionButtonPosition: FabPosition = FabPosition.End,
-    contentColor: Color = contentColorFor(MaterialTheme.colorScheme.background),
+    contentColor: Color = contentColorFor(MiuixTheme.colorScheme.surface),
     contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
     content: @Composable (PaddingValues) -> Unit
 ) {
     val isDark = isSystemInDarkTheme()
     val hasImageBg = ThemeConfig.hasImageBg(isDark)
     val hazeState = remember { HazeState() }
+    val composeEngine = LegadoTheme.composeEngine
 
     val containerColor = if (hasImageBg) {
         Color.Transparent
     } else {
-        MaterialTheme.colorScheme.background
+        LegadoTheme.colorScheme.background
     }
 
-    val finalTopBar = @Composable {
-        Box(modifier = Modifier.responsiveHazeEffect(state = hazeState)) {
-            if (title != null) {
-                GlassMediumFlexibleTopAppBar(
-                    title = title,
-                    subtitle = subtitle,
-                    scrollBehavior = scrollBehavior,
-                    navigationIcon = navigationIcon,
-                    actions = actions,
-                    colors = colors
-                )
-            } else {
-                topBar()
-            }
-        }
+    val miuixContainerColor = if (hasImageBg) {
+        Color.Transparent
+    } else {
+        MiuixTheme.colorScheme.surface
     }
 
-    Scaffold(
-        modifier = modifier,
-        topBar = finalTopBar,
-        bottomBar = bottomBar,
-        snackbarHost = snackbarHost,
-        floatingActionButton = floatingActionButton,
-        floatingActionButtonPosition = floatingActionButtonPosition,
-        containerColor = containerColor,
-        contentColor = contentColor,
-        contentWindowInsets = contentWindowInsets
-    ) { paddingValues ->
-        Box(
-            modifier = Modifier
-                .fillMaxSize()
-                .responsiveHazeSource(hazeState)
-        ) {
-            content(paddingValues)
+    CompositionLocalProvider(
+        LocalHazeState provides if (ThemeConfig.enableBlur) hazeState else null
+    ) {
+        when {
+            ThemeResolver.isMiuixEngine(composeEngine) -> {
+                val miuixFabPosition = when (floatingActionButtonPosition) {
+                    FabPosition.End -> MiuixFabPosition.End
+                    FabPosition.Center -> MiuixFabPosition.Center
+                    else -> MiuixFabPosition.End
+                }
+                MiuixScaffold(
+                    modifier = modifier,
+                    topBar = {
+                        topBar(hazeState)
+                    },
+                    bottomBar = bottomBar,
+                    snackbarHost = snackbarHost,
+                    floatingActionButton = floatingActionButton,
+                    floatingActionButtonPosition = miuixFabPosition,
+                    containerColor = miuixContainerColor,
+                    contentWindowInsets = contentWindowInsets
+                ) { paddingValues ->
+                    Box(
+                        modifier = Modifier
+                            .fillMaxSize()
+                            .responsiveHazeSource(hazeState)
+                    ) {
+                        content(paddingValues)
+                    }
+                }
+            }
+
+            else -> {
+                Scaffold(
+                    modifier = modifier,
+                    topBar = {
+                        topBar(hazeState)
+                    },
+                    bottomBar = bottomBar,
+                    snackbarHost = snackbarHost,
+                    floatingActionButton = floatingActionButton,
+                    floatingActionButtonPosition = floatingActionButtonPosition,
+                    containerColor = containerColor,
+                    contentColor = contentColor,
+                    contentWindowInsets = contentWindowInsets
+                ) { paddingValues ->
+                    Box(
+                        modifier = Modifier
+                            .fillMaxSize()
+                            .responsiveHazeSource(hazeState)
+                    ) {
+                        content(paddingValues)
+                    }
+                }
+            }
         }
     }
 }

+ 127 - 0
app/src/main/java/io/legado/app/ui/widget/components/AppTextField.kt

@@ -0,0 +1,127 @@
+package io.legado.app.ui.widget.components
+
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.KeyboardActionHandler
+import androidx.compose.foundation.text.input.OutputTransformation
+import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TextFieldColors
+import androidx.compose.material3.TextFieldDefaults
+import androidx.compose.material3.TextFieldLabelPosition
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Density
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import io.legado.app.ui.widget.components.text.AppText
+import top.yukonga.miuix.kmp.theme.MiuixTheme
+import top.yukonga.miuix.kmp.basic.TextField as MiuixTextField
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AppTextField(
+    state: TextFieldState,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    readOnly: Boolean = false,
+    // 1. 核心思想:保留 AppText 的统一样式与颜色降级参数
+    textStyle: TextStyle? = null,
+    textColor: Color = Color.Unspecified,
+    // 2. 将 Label 统一定义为 String 以兼容两套引擎
+    label: String? = null,
+    // 3. 保留 M3 TextField 的所有其他参数
+    labelPosition: TextFieldLabelPosition = TextFieldLabelPosition.Attached(),
+    placeholder: @Composable (() -> Unit)? = null,
+    leadingIcon: @Composable (() -> Unit)? = null,
+    trailingIcon: @Composable (() -> Unit)? = null,
+    prefix: @Composable (() -> Unit)? = null,
+    suffix: @Composable (() -> Unit)? = null,
+    supportingText: @Composable (() -> Unit)? = null,
+    isError: Boolean = false,
+    inputTransformation: InputTransformation? = null,
+    outputTransformation: OutputTransformation? = null,
+    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
+    onKeyboardAction: KeyboardActionHandler? = null,
+    lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
+    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
+    scrollState: ScrollState = rememberScrollState(),
+    shape: Shape = TextFieldDefaults.shape,
+    colors: TextFieldColors = TextFieldDefaults.colors(),
+    contentPadding: PaddingValues? = null,
+    interactionSource: MutableInteractionSource? = null,
+) {
+    val baseStyle = textStyle ?: LegadoTheme.typography.bodyMedium
+    val defaultTextColor = LegadoTheme.colorScheme.onSurface
+    val finalTextColor = textColor.takeOrElse {
+        baseStyle.color.takeOrElse { defaultTextColor }
+    }
+    val mergedTextStyle = baseStyle.merge(color = finalTextColor)
+    val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
+
+    if (isMiuix) {
+        MiuixTextField(
+            state = state,
+            modifier = modifier,
+            enabled = enabled,
+            readOnly = readOnly,
+            textStyle = MiuixTheme.textStyles.main,
+            label = label ?: "",
+            leadingIcon = leadingIcon,
+            trailingIcon = trailingIcon,
+            inputTransformation = inputTransformation,
+            outputTransformation = outputTransformation,
+            keyboardOptions = keyboardOptions,
+            onKeyboardAction = onKeyboardAction,
+            lineLimits = lineLimits,
+            onTextLayout = onTextLayout,
+            scrollState = scrollState,
+            interactionSource = interactionSource
+        )
+    } else {
+        val resolvedContentPadding =
+            contentPadding ?: if (label == null || labelPosition is TextFieldLabelPosition.Above) {
+                TextFieldDefaults.contentPaddingWithoutLabel()
+            } else {
+                TextFieldDefaults.contentPaddingWithLabel()
+            }
+        TextField(
+            state = state,
+            modifier = modifier,
+            enabled = enabled,
+            readOnly = readOnly,
+            textStyle = mergedTextStyle,
+            labelPosition = labelPosition,
+            label = label?.let { { AppText(it) } },
+            placeholder = placeholder,
+            leadingIcon = leadingIcon,
+            trailingIcon = trailingIcon,
+            prefix = prefix,
+            suffix = suffix,
+            supportingText = supportingText,
+            isError = isError,
+            inputTransformation = inputTransformation,
+            outputTransformation = outputTransformation,
+            keyboardOptions = keyboardOptions,
+            onKeyboardAction = onKeyboardAction,
+            lineLimits = lineLimits,
+            onTextLayout = onTextLayout,
+            scrollState = scrollState,
+            shape = shape,
+            colors = colors,
+            contentPadding = resolvedContentPadding,
+            interactionSource = interactionSource
+        )
+    }
+}

+ 3 - 2
app/src/main/java/io/legado/app/ui/widget/components/EmptyMessageView.kt

@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.widthIn
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.material3.ContainedLoadingIndicator
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -24,6 +23,8 @@ import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.text.AnimatedTextLine
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
@@ -70,7 +71,7 @@ fun EmptyMessageView(
 
         AnimatedTextLine(
             text = message,
-            style = MaterialTheme.typography.labelLarge,
+            style = LegadoTheme.typography.labelLarge,
             textAlign = TextAlign.Center,
             maxLines = 2,
             softWrap = true,

+ 0 - 113
app/src/main/java/io/legado/app/ui/widget/components/GlassMediumFlexibleTopAppBar.kt

@@ -1,113 +0,0 @@
-package io.legado.app.ui.widget.components
-
-import androidx.compose.foundation.layout.RowScope
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.MediumFlexibleTopAppBar
-import androidx.compose.material3.TopAppBar
-import androidx.compose.material3.TopAppBarColors
-import androidx.compose.material3.TopAppBarDefaults
-import androidx.compose.material3.TopAppBarScrollBehavior
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import io.legado.app.ui.config.themeConfig.ThemeConfig
-
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
-@Composable
-fun GlassMediumFlexibleTopAppBar(
-    modifier: Modifier = Modifier,
-    title: @Composable () -> Unit,
-    subtitle: (@Composable () -> Unit)? = null,
-    scrollBehavior: TopAppBarScrollBehavior? = null,
-    navigationIcon: @Composable () -> Unit = {},
-    actions: @Composable RowScope.() -> Unit = {},
-    colors: TopAppBarColors = GlassTopAppBarDefaults.glassColors()
-) {
-    if (ThemeConfig.useFlexibleTopAppBar) {
-        MediumFlexibleTopAppBar(
-            modifier = modifier,
-            title = title,
-            subtitle = subtitle,
-            navigationIcon = navigationIcon,
-            actions = actions,
-            scrollBehavior = scrollBehavior,
-            colors = colors
-        )
-    } else {
-        TopAppBar(
-            modifier = modifier,
-            title = title,
-            navigationIcon = navigationIcon,
-            actions = actions,
-            scrollBehavior = scrollBehavior,
-            colors = colors
-        )
-    }
-}
-
-object GlassTopAppBarDefaults {
-
-    @OptIn(ExperimentalMaterial3Api::class)
-    @Composable
-    fun defaultScrollBehavior(): TopAppBarScrollBehavior {
-        return if (ThemeConfig.useFlexibleTopAppBar) {
-            TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
-        } else {
-            TopAppBarDefaults.pinnedScrollBehavior()
-        }
-    }
-
-    @Composable
-    fun glassColors(): TopAppBarColors {
-
-        val containerColor = GlassDefaults.glassColor(
-            noBlurColor = MaterialTheme.colorScheme.surface,
-            blurAlpha = GlassDefaults.TransparentAlpha
-        )
-
-        val scrolledContainerColor = if (ThemeConfig.enableBlur) {
-            MaterialTheme.colorScheme.surface.copy(alpha = GlassDefaults.TransparentAlpha)
-        } else {
-            MaterialTheme.colorScheme.surfaceContainer
-        }
-
-        return TopAppBarDefaults.topAppBarColors(
-            containerColor = applyTopBarOpacity(containerColor),
-            scrolledContainerColor = applyTopBarOpacity(scrolledContainerColor)
-        )
-    }
-
-    @Composable
-    fun containerColor(): Color {
-        val baseColor = GlassDefaults.glassColor(
-            noBlurColor = MaterialTheme.colorScheme.surface,
-            blurAlpha = GlassDefaults.TransparentAlpha
-        )
-        return applyTopBarOpacity(baseColor)
-    }
-
-    @Composable
-    fun scrolledContainerColor(): Color {
-        val baseColor = GlassDefaults.glassColor(
-            noBlurColor = MaterialTheme.colorScheme.surfaceContainer,
-            blurAlpha = GlassDefaults.TransparentAlpha
-        )
-        return applyTopBarOpacity(baseColor)
-    }
-
-    @Composable
-    fun controlContainerColor(): Color {
-        val baseColor = GlassDefaults.glassColor(
-            noBlurColor = MaterialTheme.colorScheme.surfaceContainerHighest,
-            blurAlpha = GlassDefaults.DefaultBlurAlpha
-        )
-        return applyTopBarOpacity(baseColor)
-    }
-
-    private fun applyTopBarOpacity(color: Color): Color {
-        val opacity = (ThemeConfig.topBarOpacity.coerceIn(0, 100)) / 100f
-        return color.copy(alpha = (color.alpha * opacity).coerceIn(0f, 1f))
-    }
-}

+ 15 - 27
app/src/main/java/io/legado/app/ui/widget/components/GroupManageBottomSheet.kt

@@ -1,12 +1,10 @@
 package io.legado.app.ui.widget.components
 
 import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.text.input.rememberTextFieldState
@@ -18,7 +16,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextField
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -26,45 +23,36 @@ import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.ui.widget.components.button.SmallTextButton
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
 import io.legado.app.ui.widget.components.settingItem.SettingItem
+import io.legado.app.ui.widget.components.text.AppText
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun GroupManageBottomSheet(
+    show: Boolean,
     groups: List<String>,
     onDismissRequest: () -> Unit,
     onUpdateGroup: (oldGroup: String, newGroup: String) -> Unit,
     onDeleteGroup: (group: String) -> Unit
 ) {
-    GlassModalBottomSheet(
-        onDismissRequest = onDismissRequest
+    AppModalBottomSheet(
+        show = show,
+        onDismissRequest = onDismissRequest,
+        title = stringResource(R.string.group_manage),
     ) {
-        Column(
-            modifier = Modifier
-                .padding(horizontal = 16.dp)
-                .padding(bottom = 16.dp),
-            horizontalAlignment = Alignment.CenterHorizontally,
-        ) {
-            Text(
-                modifier = Modifier.padding(bottom = 16.dp),
-                text = stringResource(R.string.group_manage),
-                style = MaterialTheme.typography.titleMedium
-            )
-            LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
-                items(groups, key = { it }) { group ->
-                    GroupItem(
-                        group = group,
-                        onUpdateGroup = onUpdateGroup,
-                        onDeleteGroup = onDeleteGroup
-                    )
-                }
+        LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+            items(groups, key = { it }) { group ->
+                GroupItem(
+                    group = group,
+                    onUpdateGroup = onUpdateGroup,
+                    onDeleteGroup = onDeleteGroup
+                )
             }
         }
     }
@@ -115,7 +103,7 @@ private fun GroupItem(
                 modifier = Modifier
                     .fillMaxWidth()
                     .heightIn(min = 48.dp),
-                label = { Text(stringResource(R.string.edit)) },
+                label = { AppText(stringResource(R.string.edit)) },
                 contentPadding = PaddingValues(
                     top = 4.dp,
                     bottom = 4.dp,

+ 34 - 0
app/src/main/java/io/legado/app/ui/widget/components/IconSwitch.kt

@@ -10,6 +10,40 @@ import androidx.compose.material3.SwitchDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import top.yukonga.miuix.kmp.basic.Switch as MiuixSwitch
+
+@Composable
+fun AdaptiveSwitch(
+    checked: Boolean,
+    onCheckedChange: (Boolean) -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    checkedIcon: ImageVector = Icons.Filled.Check,
+    uncheckedIcon: ImageVector? = null,
+    showIcon: Boolean = true
+) {
+    val composeEngine = LegadoTheme.composeEngine
+
+    if (ThemeResolver.isMiuixEngine(composeEngine)) {
+        MiuixSwitch(
+            checked = checked,
+            onCheckedChange = onCheckedChange,
+            modifier = modifier,
+            enabled = enabled
+        )
+    } else {
+        IconSwitch(
+            checked = checked,
+            onCheckedChange = onCheckedChange,
+            enabled = enabled,
+            checkedIcon = checkedIcon,
+            uncheckedIcon = uncheckedIcon,
+            showIcon = showIcon
+        )
+    }
+}
 
 @Composable
 fun IconSwitch(

+ 32 - 102
app/src/main/java/io/legado/app/ui/widget/components/SearchBarSection.kt

@@ -1,33 +1,14 @@
 package io.legado.app.ui.widget.components
 
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.animateContentSize
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.scaleIn
-import androidx.compose.animation.scaleOut
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.text.input.TextFieldLineLimits
-import androidx.compose.foundation.text.input.clearText
 import androidx.compose.foundation.text.input.rememberTextFieldState
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Clear
-import androidx.compose.material.icons.filled.Search
-import androidx.compose.material.icons.filled.VerticalAlignTop
-import androidx.compose.material3.DropdownMenu
 import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextField
 import androidx.compose.material3.TextFieldDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -38,13 +19,15 @@ import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
-import io.legado.app.ui.widget.components.button.SmallIconButton
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import io.legado.app.ui.widget.components.icon.AppIcon
+import io.legado.app.ui.widget.components.icon.AppIcons
+import io.legado.app.ui.widget.components.text.AppText
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
@@ -53,10 +36,10 @@ fun SearchBarSection(
     onQueryChange: (String) -> Unit,
     placeholder: String = "搜索...",
     leadingIcon: @Composable (() -> Unit)? = {
-        SmallIconButton(
-            icon = Icons.Default.Search,
-            contentDescription = null,
-            onClick = {})
+        AppIcon(
+            imageVector = AppIcons.Search,
+            contentDescription = null
+        )
     },
     backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerLow,
     scrollState: LazyListState? = null,
@@ -65,7 +48,6 @@ fun SearchBarSection(
     dropdownMenu: (@Composable (onDismiss: () -> Unit) -> Unit)? = null
 ) {
     var showMenu by remember { mutableStateOf(false) }
-
     val textFieldState = rememberTextFieldState(initialText = query)
 
     LaunchedEffect(query) {
@@ -91,79 +73,19 @@ fun SearchBarSection(
         }
     }
 
-    Surface(
-        modifier = Modifier
-            .fillMaxWidth()
-            .padding(start = 16.dp, end = 16.dp, bottom = 4.dp),
-        shape = RoundedCornerShape(32.dp),
-        color = backgroundColor
-    ) {
-        TextField(
+    val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
+
+    val searchTextField = @Composable {
+        AppTextField(
             state = textFieldState,
             modifier = Modifier
                 .fillMaxWidth()
-                .heightIn(min = 48.dp)
-                .padding(horizontal = 4.dp),
-            placeholder = { Text(placeholder) },
+                .then(
+                    if (isMiuix) Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
+                    else Modifier
+                ),
+            placeholder = { AppText(placeholder) },
             leadingIcon = leadingIcon,
-            trailingIcon = {
-                Row(
-                    verticalAlignment = Alignment.CenterVertically,
-                    modifier = Modifier
-                        .padding(end = 4.dp)
-                        .animateContentSize()
-                ) {
-
-                    AnimatedVisibility(
-                        visible = textFieldState.text.isNotEmpty(),
-                        enter = fadeIn() + scaleIn(),
-                        exit = fadeOut() + scaleOut()
-                    ) {
-                        SmallIconButton(
-                            onClick = { textFieldState.clearText() },
-                            icon = Icons.Default.Clear,
-                            contentDescription = "清空输入"
-                        )
-                    }
-
-                    AnimatedVisibility(
-                        visible = showScrollToTop,
-                        enter = fadeIn() + scaleIn(),
-                        exit = fadeOut() + scaleOut()
-                    ) {
-                        SmallIconButton(
-                            onClick = {
-                                scope.launch {
-                                    scrollState?.animateScrollToItem(0)
-                                }
-                            },
-                            icon = Icons.Default.VerticalAlignTop,
-                            contentDescription = "回到顶部"
-                        )
-                    }
-
-                    if (trailingIcon != null) {
-                        Box {
-                            IconButton(
-                                onClick = {
-                                    if (dropdownMenu != null) showMenu = true
-                                }
-                            ) {
-                                trailingIcon()
-                            }
-
-                            if (dropdownMenu != null) {
-                                DropdownMenu(
-                                    expanded = showMenu,
-                                    onDismissRequest = { showMenu = false }
-                                ) {
-                                    dropdownMenu { showMenu = false }
-                                }
-                            }
-                        }
-                    }
-                }
-            },
             lineLimits = TextFieldLineLimits.SingleLine,
             colors = TextFieldDefaults.colors(
                 focusedContainerColor = Color.Transparent,
@@ -171,13 +93,21 @@ fun SearchBarSection(
                 disabledContainerColor = Color.Transparent,
                 focusedIndicatorColor = Color.Transparent,
                 unfocusedIndicatorColor = Color.Transparent,
-            ),
-            contentPadding = PaddingValues(
-                top = 4.dp,
-                bottom = 4.dp,
-                start = 12.dp,
-                end = 12.dp
             )
         )
     }
+
+    if (isMiuix) {
+        searchTextField()
+    } else {
+        Surface(
+            modifier = Modifier
+                .fillMaxWidth()
+                .padding(start = 16.dp, end = 16.dp, bottom = 4.dp),
+            shape = RoundedCornerShape(32.dp),
+            color = backgroundColor
+        ) {
+            searchTextField()
+        }
+    }
 }

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

@@ -15,7 +15,6 @@ import androidx.compose.material3.HorizontalFloatingToolbar
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.PlainTooltip
-import androidx.compose.material3.Text
 import androidx.compose.material3.TooltipAnchorPosition
 import androidx.compose.material3.TooltipBox
 import androidx.compose.material3.TooltipDefaults
@@ -27,6 +26,7 @@ import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+import io.legado.app.ui.widget.components.text.AppText
 
 data class SelectionActions(
     val primaryAction: ActionItem,
@@ -84,7 +84,7 @@ fun SelectionBottomBar(
                     ) {
                         secondaryActions.forEach { action ->
                             DropdownMenuItem(
-                                text = { Text(action.text) },
+                                text = { AppText(action.text) },
                                 leadingIcon = action.icon,
                                 onClick = {
                                     action.onClick()
@@ -101,7 +101,7 @@ fun SelectionBottomBar(
                 positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
                     TooltipAnchorPosition.Above
                 ),
-                tooltip = { PlainTooltip { Text(primaryAction.text) } },
+                tooltip = { PlainTooltip { AppText(primaryAction.text) } },
                 state = rememberTooltipState(),
             ) {
                 FilledIconButton(

+ 25 - 16
app/src/main/java/io/legado/app/ui/widget/components/SplicedColumnGroup.kt

@@ -7,14 +7,14 @@ import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.Card
-import androidx.compose.material3.CardDefaults
-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.draw.clip
 import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import io.legado.app.ui.widget.components.title.AdaptiveTitle
+import top.yukonga.miuix.kmp.basic.Card as MiuixCard
 
 /**
  * Settings Group Container by https://github.com/wxxsfxyzm/InstallerX-Revived
@@ -29,24 +29,33 @@ fun SplicedColumnGroup(
     title: String = "",
     content: @Composable ColumnScope.() -> Unit,
 ) {
-    Column(modifier = modifier.padding(top = 8.dp, bottom = 16.dp)) {
+    val composeEngine = LegadoTheme.composeEngine
+    Column(modifier = modifier.padding(top = 8.dp, bottom = 8.dp)) {
         if (title.isNotEmpty()) {
-            Text(
+            AdaptiveTitle(
                 text = title,
-                style = MaterialTheme.typography.labelLarge,
-                color = MaterialTheme.colorScheme.primary,
                 modifier = Modifier.padding(start = 16.dp, bottom = 8.dp)
             )
         }
 
-        Card(
-            shape = RoundedCornerShape(16.dp),
-            colors = CardDefaults.cardColors(containerColor = Color.Transparent),
-            modifier = Modifier
-                .fillMaxWidth()
-                .animateContentSize()
-        ) {
+        if (ThemeResolver.isMiuixEngine(composeEngine)) {
+            MiuixCard {
+                Column(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .animateContentSize()
+                        .clip(RoundedCornerShape(16.dp))
+                ) {
+                    content()
+                }
+            }
+
+        } else {
             Column(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .animateContentSize()
+                    .clip(RoundedCornerShape(16.dp)),
                 verticalArrangement = Arrangement.spacedBy(2.dp)
             ) {
                 content()

+ 52 - 0
app/src/main/java/io/legado/app/ui/widget/components/ValueStepper.kt

@@ -0,0 +1,52 @@
+package io.legado.app.ui.widget.components
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Remove
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.button.SmallOutlinedIconButton
+import io.legado.app.ui.widget.components.card.TextCard
+
+@Composable
+fun ValueStepper(
+    value: Float,
+    displayValue: Float,
+    valueRange: ClosedFloatingPointRange<Float>,
+    onValueChange: (Float) -> Unit,
+    modifier: Modifier = Modifier
+) {
+    Row(
+        modifier = modifier,
+        verticalAlignment = Alignment.CenterVertically,
+        horizontalArrangement = Arrangement.spacedBy(8.dp)
+    ) {
+        SmallOutlinedIconButton(
+            onClick = {
+                val newValue = (value.toInt() - 1).toFloat().coerceIn(valueRange)
+                onValueChange(newValue)
+            },
+            icon = Icons.Default.Remove
+        )
+        TextCard(
+            cornerRadius = 8.dp,
+            horizontalPadding = 8.dp,
+            verticalPadding = 4.dp,
+            text = displayValue.toInt().toString(),
+            backgroundColor = LegadoTheme.colorScheme.surfaceContainer,
+            contentColor = LegadoTheme.colorScheme.onSurface
+        )
+        SmallOutlinedIconButton(
+            onClick = {
+                val newValue = (value.toInt() + 1).toFloat().coerceIn(valueRange)
+                onValueChange(newValue)
+            },
+            icon = Icons.Default.Add
+        )
+    }
+}

+ 23 - 31
app/src/main/java/io/legado/app/ui/widget/components/bookmark/BookmarkEditSheet.kt

@@ -7,15 +7,11 @@ import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.navigationBarsPadding
-import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -26,11 +22,15 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import io.legado.app.data.entities.Bookmark
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.button.PrimaryButton
+import io.legado.app.ui.widget.components.button.SecondaryButton
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun BookmarkEditSheet(
+    show: Boolean,
     bookmark: Bookmark,
     onDismiss: () -> Unit,
     onSave: (Bookmark) -> Unit,
@@ -41,25 +41,20 @@ fun BookmarkEditSheet(
     var bookText by remember { mutableStateOf(bookmark.bookText) }
     var content by remember { mutableStateOf(bookmark.content) }
 
-    GlassModalBottomSheet(
-        onDismissRequest = onDismiss
+    AppModalBottomSheet(
+        show = show,
+        onDismissRequest = onDismiss,
+        title = bookmark.chapterName,
     ) {
         Column(
             modifier = Modifier
                 .fillMaxWidth()
-                .padding(16.dp)
                 .navigationBarsPadding()
         ) {
-            Text(
-                text = bookmark.chapterName,
-                style = MaterialTheme.typography.titleMedium,
-                modifier = Modifier.padding(bottom = 16.dp)
-            )
-
             OutlinedTextField(
                 value = bookText,
                 onValueChange = { bookText = it },
-                label = { Text("原文") },
+                label = { AppText("原文") },
                 modifier = Modifier.fillMaxWidth(),
                 maxLines = 10
             )
@@ -69,7 +64,7 @@ fun BookmarkEditSheet(
             OutlinedTextField(
                 value = content,
                 onValueChange = { content = it },
-                label = { Text("摘要/笔记") },
+                label = { AppText("摘要/笔记") },
                 modifier = Modifier.fillMaxWidth(),
                 maxLines = 5
             )
@@ -81,15 +76,13 @@ fun BookmarkEditSheet(
                 horizontalArrangement = Arrangement.spacedBy(12.dp),
                 verticalAlignment = Alignment.CenterVertically
             ) {
-                OutlinedButton(
+                SecondaryButton(
                     onClick = { showDeleteConfirmDialog = true },
-                    colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.error),
-                    modifier = Modifier.weight(1f)
-                ) {
-                    Text("删除")
-                }
+                    modifier = Modifier.weight(1f),
+                    text = "删除"
+                )
 
-                Button(
+                PrimaryButton(
                     onClick = {
                         val newBookmark = bookmark.apply {
                             this.bookText = bookText
@@ -97,10 +90,9 @@ fun BookmarkEditSheet(
                         }
                         onSave(newBookmark)
                     },
-                    modifier = Modifier.weight(1f)
-                ) {
-                    Text("保存")
-                }
+                    modifier = Modifier.weight(1f),
+                    text = "保存"
+                )
             }
             Spacer(modifier = Modifier.height(16.dp))
         }
@@ -109,8 +101,8 @@ fun BookmarkEditSheet(
     if (showDeleteConfirmDialog) {
         AlertDialog(
             onDismissRequest = { showDeleteConfirmDialog = false },
-            title = { Text("确认删除") },
-            text = { Text("你确定要删除这条书签吗?") },
+            title = { AppText("确认删除") },
+            text = { AppText("你确定要删除这条书签吗?") },
             confirmButton = {
                 TextButton(
                     onClick = {
@@ -119,14 +111,14 @@ fun BookmarkEditSheet(
                     },
                     colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error)
                 ) {
-                    Text("删除")
+                    AppText("删除")
                 }
             },
             dismissButton = {
                 TextButton(
                     onClick = { showDeleteConfirmDialog = false }
                 ) {
-                    Text("取消")
+                    AppText("取消")
                 }
             }
         )

+ 12 - 12
app/src/main/java/io/legado/app/ui/widget/components/bookmark/BookmarkItem.kt

@@ -9,9 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-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.ui.Modifier
@@ -19,6 +17,8 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import io.legado.app.data.entities.Bookmark
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.text.AppText
 
 @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3ExpressiveApi::class)
 @Composable
@@ -30,7 +30,7 @@ fun BookmarkItem(
     onLongClick: () -> Unit
 ) {
     val backgroundColor by animateColorAsState(
-        targetValue = if (isDur) MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.3f)
+        targetValue = if (isDur) LegadoTheme.colorScheme.secondaryContainer.copy(alpha = 0.3f)
         else Color.Transparent,
         label = "BgColor"
     )
@@ -49,29 +49,29 @@ fun BookmarkItem(
                 .fillMaxWidth()
                 .padding(horizontal = 16.dp, vertical = 12.dp)
         ) {
-            Text(
+            AppText(
                 text = bookmark.chapterName,
-                style = MaterialTheme.typography.titleSmallEmphasized,
-                color = if (isDur) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary
+                style = LegadoTheme.typography.titleSmallEmphasized,
+                color = if (isDur) LegadoTheme.colorScheme.primary else LegadoTheme.colorScheme.secondary
             )
 
             if (bookmark.bookText.isNotEmpty()) {
                 Spacer(modifier = Modifier.height(4.dp))
-                Text(
+                AppText(
                     text = bookmark.bookText,
-                    style = MaterialTheme.typography.bodyMedium,
+                    style = LegadoTheme.typography.bodyMedium,
                     maxLines = 2,
                     overflow = TextOverflow.Ellipsis,
-                    color = MaterialTheme.colorScheme.onSurfaceVariant
+                    color = LegadoTheme.colorScheme.onSurfaceVariant
                 )
             }
 
             if (bookmark.content.isNotEmpty()) {
                 Spacer(modifier = Modifier.height(2.dp))
-                Text(
+                AppText(
                     text = bookmark.content,
-                    style = MaterialTheme.typography.bodyMedium,
-                    color = MaterialTheme.colorScheme.primary
+                    style = LegadoTheme.typography.bodyMedium,
+                    color = LegadoTheme.colorScheme.primary
                 )
             }
         }

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

@@ -3,10 +3,10 @@ package io.legado.app.ui.widget.components.button
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import io.legado.app.ui.widget.components.text.AppText
 
 @Composable
 fun AlertButton(
@@ -24,6 +24,6 @@ fun AlertButton(
             containerColor = Color.Transparent,
         )
     ) {
-        Text(text = text)
+        AppText(text = text)
     }
 }

+ 4 - 4
app/src/main/java/io/legado/app/ui/widget/components/button/AnimatedActionButton.kt

@@ -8,8 +8,6 @@ import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.material3.ToggleButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -21,6 +19,8 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.text.AppText
 import kotlinx.coroutines.delay
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -73,9 +73,9 @@ fun AnimatedActionButton(
             AnimatedVisibility(
                 visible = showText
             ) {
-                Text(
+                AppText(
                     text = if (lastCheckedState) activeText else inactiveText,
-                    style = MaterialTheme.typography.labelMedium,
+                    style = LegadoTheme.typography.labelMedium,
                     modifier = Modifier.padding(start = 8.dp),
                     maxLines = 1,
                     softWrap = false

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

@@ -8,11 +8,11 @@ import androidx.compose.animation.fadeOut
 import androidx.compose.animation.togetherWith
 import androidx.compose.material3.ContainedLoadingIndicator
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import io.legado.app.ui.widget.components.text.AppText
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
@@ -38,7 +38,7 @@ fun AnimatedTextButton(
             if (loading) {
                 ContainedLoadingIndicator()
             } else {
-                Text(text)
+                AppText(text)
             }
         }
     }

+ 74 - 0
app/src/main/java/io/legado/app/ui/widget/components/button/AppButton.kt

@@ -0,0 +1,74 @@
+package io.legado.app.ui.widget.components.button
+
+import androidx.compose.material3.Button
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import io.legado.app.ui.theme.LegadoTheme.composeEngine
+import io.legado.app.ui.theme.ThemeResolver
+import top.yukonga.miuix.kmp.basic.Button as MiuixButton
+import top.yukonga.miuix.kmp.basic.ButtonDefaults as MiuixButtonDefaults
+import top.yukonga.miuix.kmp.basic.Text as MiuixText
+
+@Composable
+fun PrimaryButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    text: String
+) {
+    if (ThemeResolver.isMiuixEngine(composeEngine)) {
+        MiuixButton(
+            onClick = onClick,
+            modifier = modifier,
+            enabled = enabled,
+            colors = MiuixButtonDefaults.buttonColorsPrimary()
+        ) {
+            MiuixText(
+                text = text
+            )
+        }
+    } else {
+        Button(
+            onClick = onClick,
+            modifier = modifier,
+            enabled = enabled,
+        ) {
+            Text(
+                text = text,
+            )
+        }
+    }
+}
+
+@Composable
+fun SecondaryButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    text: String
+) {
+    if (ThemeResolver.isMiuixEngine(composeEngine)) {
+        MiuixButton(
+            onClick = onClick,
+            modifier = modifier,
+            enabled = enabled,
+            colors = MiuixButtonDefaults.buttonColors()
+        ) {
+            MiuixText(
+                text = text
+            )
+        }
+    } else {
+        OutlinedButton(
+            onClick = onClick,
+            modifier = modifier,
+            enabled = enabled,
+        ) {
+            Text(
+                text = text
+            )
+        }
+    }
+}

+ 61 - 0
app/src/main/java/io/legado/app/ui/widget/components/button/AppIconButton.kt

@@ -0,0 +1,61 @@
+package io.legado.app.ui.widget.components.button
+
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonColors
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
+import top.yukonga.miuix.kmp.basic.IconButtonDefaults as MiuixIconButtonDefaults
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun AppIconButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    interactionSource: MutableInteractionSource? = null,
+    //M3
+    colors: IconButtonColors = IconButtonDefaults.iconButtonColors(),
+    shape: Shape = IconButtonDefaults.standardShape,
+    // MIUIX
+    holdDownState: Boolean = false,
+    miuixBackgroundColor: Color = Color.Unspecified,
+    miuixCornerRadius: Dp? = null,
+    miuixMinHeight: Dp? = null,
+    miuixMinWidth: Dp? = null,
+    content: @Composable () -> Unit
+) {
+    val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
+
+    if (isMiuix) {
+        MiuixIconButton(
+            onClick = onClick,
+            modifier = modifier,
+            enabled = enabled,
+            holdDownState = holdDownState,
+            backgroundColor = miuixBackgroundColor,
+            cornerRadius = MiuixIconButtonDefaults.CornerRadius,
+            minHeight = MiuixIconButtonDefaults.MinHeight,
+            minWidth = MiuixIconButtonDefaults.MinWidth,
+            content = content
+        )
+    } else {
+        IconButton(
+            onClick = onClick,
+            modifier = modifier,
+            enabled = enabled,
+            colors = colors,
+            interactionSource = interactionSource,
+            shape = shape,
+            content = content
+        )
+    }
+}

+ 57 - 0
app/src/main/java/io/legado/app/ui/widget/components/button/MiuixButton.kt

@@ -0,0 +1,57 @@
+package io.legado.app.ui.widget.components.button
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import io.legado.app.R
+import top.yukonga.miuix.kmp.basic.Icon
+import top.yukonga.miuix.kmp.icon.MiuixIcons
+import top.yukonga.miuix.kmp.icon.extended.Back
+import top.yukonga.miuix.kmp.theme.LocalContentColor
+import top.yukonga.miuix.kmp.theme.MiuixTheme
+import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
+
+@Composable
+fun MiuixIconButton(
+    modifier: Modifier = Modifier,
+    imageVector: ImageVector = MiuixIcons.Regular.Back,
+    tint: Color = LocalContentColor.current,
+    contentDescription: String? = stringResource(id = R.string.back),
+    onClick: () -> Unit,
+) {
+    MiuixIconButton(
+        onClick = onClick,
+        modifier = modifier
+    ) {
+        Icon(
+            imageVector = imageVector,
+            contentDescription = contentDescription,
+            tint = tint
+        )
+    }
+}
+
+@Composable
+fun MiuixToggleButton(
+    modifier: Modifier = Modifier,
+    checked: Boolean,
+    onCheckedChange: (Boolean) -> Unit,
+    iconChecked: ImageVector,
+    iconUnchecked: ImageVector = iconChecked,
+    checkedTint: Color = MiuixTheme.colorScheme.primary,
+    uncheckedTint: Color = LocalContentColor.current,
+    contentDescription: String? = null,
+) {
+    MiuixIconButton(
+        onClick = { onCheckedChange(!checked) },
+        modifier = modifier
+    ) {
+        Icon(
+            imageVector = if (checked) iconChecked else iconUnchecked,
+            contentDescription = contentDescription,
+            tint = if (checked) checkedTint else uncheckedTint
+        )
+    }
+}

+ 132 - 51
app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt

@@ -1,6 +1,8 @@
 package io.legado.app.ui.widget.components.button
 
 import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
@@ -15,11 +17,9 @@ import androidx.compose.material3.IconButton
 import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.material3.IconToggleButtonShapes
 import androidx.compose.material3.LocalMinimumInteractiveComponentSize
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedIconButton
 import androidx.compose.material3.OutlinedIconToggleButton
 import androidx.compose.material3.OutlinedToggleButton
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
@@ -31,7 +31,13 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.LegadoTheme.composeEngine
+import io.legado.app.ui.theme.ThemeResolver
+import io.legado.app.ui.widget.components.text.AppText
 import kotlinx.coroutines.delay
+import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
+import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
 
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -41,22 +47,33 @@ fun SmallIconButton(
     icon: ImageVector,
     contentDescription: String? = null
 ) {
-    CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
-        IconButton(
-            onClick = onClick,
-            modifier = Modifier.size(
-                IconButtonDefaults.extraSmallContainerSize(
-                    IconButtonDefaults.IconButtonWidthOption.Uniform
-                )
-            ),
-            shape = IconButtonDefaults.extraSmallRoundShape,
+    if (ThemeResolver.isMiuixEngine(composeEngine)) {
+        MiuixIconButton(
+            onClick = onClick
         ) {
-            Icon(
+            MiuixIcon(
                 imageVector = icon,
-                contentDescription = contentDescription,
-                modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
+                contentDescription = contentDescription
             )
         }
+    } else {
+        CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
+            IconButton(
+                onClick = onClick,
+                modifier = Modifier.size(
+                    IconButtonDefaults.extraSmallContainerSize(
+                        IconButtonDefaults.IconButtonWidthOption.Uniform
+                    )
+                ),
+                shape = IconButtonDefaults.extraSmallRoundShape,
+            ) {
+                Icon(
+                    imageVector = icon,
+                    contentDescription = contentDescription,
+                    modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
+                )
+            }
+        }
     }
 }
 
@@ -65,8 +82,24 @@ fun SmallIconButton(
 fun SmallOutlinedIconButton(
     onClick: () -> Unit,
     icon: ImageVector,
+    modifier: Modifier = Modifier,
     contentDescription: String? = null
 ) {
+
+    val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
+    if (isMiuix) {
+        MiuixIconButton(
+            onClick = onClick,
+            modifier = modifier.size(32.dp),
+            backgroundColor = LegadoTheme.colorScheme.surfaceContainer
+        ) {
+            MiuixIcon(
+                imageVector = icon,
+                contentDescription = contentDescription,
+                modifier = Modifier.size(18.dp)
+            )
+        }
+    } else {
     CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
         OutlinedIconButton(
             onClick = onClick,
@@ -87,6 +120,7 @@ fun SmallOutlinedIconButton(
             )
         }
     }
+    }
 }
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -94,27 +128,42 @@ fun SmallOutlinedIconButton(
 fun SmallTonalIconButton(
     onClick: () -> Unit,
     icon: ImageVector,
+    modifier: Modifier = Modifier,
     contentDescription: String? = null
 ) {
-    CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
-        FilledTonalIconButton(
+    val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
+
+    if (isMiuix) {
+        MiuixIconButton(
             onClick = onClick,
-            modifier = Modifier.size(
-                IconButtonDefaults.extraSmallContainerSize(
-                    IconButtonDefaults.IconButtonWidthOption.Uniform
-                )
-            ),
-            shapes = IconButtonDefaults.shapes(),
-            colors = IconButtonDefaults.filledTonalIconButtonColors()
+            modifier = modifier.size(32.dp),
+            backgroundColor = LegadoTheme.colorScheme.surfaceContainer
         ) {
-            Icon(
+            MiuixIcon(
                 imageVector = icon,
                 contentDescription = contentDescription,
-                modifier = Modifier.size(
-                    IconButtonDefaults.extraSmallIconSize
-                )
+                modifier = Modifier.size(18.dp)
             )
         }
+    } else {
+        CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
+            FilledTonalIconButton(
+                onClick = onClick,
+                modifier = modifier.size(
+                    IconButtonDefaults.extraSmallContainerSize(
+                        IconButtonDefaults.IconButtonWidthOption.Uniform
+                    )
+                ),
+                shapes = IconButtonDefaults.shapes(),
+                colors = IconButtonDefaults.filledTonalIconButtonColors()
+            ) {
+                Icon(
+                    imageVector = icon,
+                    contentDescription = contentDescription,
+                    modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize)
+                )
+            }
+        }
     }
 }
 
@@ -127,35 +176,67 @@ fun SmallOutlinedIconToggleButton(
     modifier: Modifier = Modifier,
     contentDescription: String? = null
 ) {
-    val defaultShape = IconButtonDefaults.extraSmallRoundShape
-    val pressedShape = IconButtonDefaults.extraSmallPressedShape
-    val checkedShape = IconButtonDefaults.extraSmallSelectedRoundShape
-
-    val toggleShapes = remember(defaultShape, checkedShape) {
-        IconToggleButtonShapes(
-            shape = defaultShape,
-            pressedShape = pressedShape,
-            checkedShape = checkedShape
+    val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
+
+    if (isMiuix) {
+
+        val containerColor by animateColorAsState(
+            targetValue = if (checked) LegadoTheme.colorScheme.primaryContainer else LegadoTheme.colorScheme.surfaceContainer,
+            animationSpec = tween(150),
+            label = "MiuixToggleContainerColor"
         )
-    }
 
-    CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
-        OutlinedIconToggleButton(
-            checked = checked,
-            onCheckedChange = onCheckedChange,
-            modifier = modifier.size(
-                IconButtonDefaults.extraSmallContainerSize(
-                    IconButtonDefaults.IconButtonWidthOption.Uniform
-                )
-            ),
-            shapes = toggleShapes
+        val iconTint by animateColorAsState(
+            targetValue = if (checked) LegadoTheme.colorScheme.onPrimaryContainer else LegadoTheme.colorScheme.onSurfaceVariant,
+            animationSpec = tween(150),
+            label = "MiuixToggleIconTint"
+        )
+
+        MiuixIconButton(
+            onClick = { onCheckedChange(!checked) },
+            modifier = modifier
+                .size(32.dp),
+            backgroundColor = containerColor
         ) {
-            Icon(
+            MiuixIcon(
                 imageVector = icon,
                 contentDescription = contentDescription,
-                modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
+                tint = iconTint,
+                modifier = Modifier.size(18.dp)
+            )
+        }
+
+    } else {
+        val defaultShape = IconButtonDefaults.extraSmallRoundShape
+        val pressedShape = IconButtonDefaults.extraSmallPressedShape
+        val checkedShape = IconButtonDefaults.extraSmallSelectedRoundShape
+
+        val toggleShapes = remember(defaultShape, checkedShape) {
+            IconToggleButtonShapes(
+                shape = defaultShape,
+                pressedShape = pressedShape,
+                checkedShape = checkedShape
             )
         }
+
+        CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
+            OutlinedIconToggleButton(
+                checked = checked,
+                onCheckedChange = onCheckedChange,
+                modifier = modifier.size(
+                    IconButtonDefaults.extraSmallContainerSize(
+                        IconButtonDefaults.IconButtonWidthOption.Uniform
+                    )
+                ),
+                shapes = toggleShapes
+            ) {
+                Icon(
+                    imageVector = icon,
+                    contentDescription = contentDescription,
+                    modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
+                )
+            }
+        }
     }
 }
 
@@ -204,9 +285,9 @@ fun SmallAnimatedActionButton(
                 AnimatedVisibility(
                     visible = showText
                 ) {
-                    Text(
+                    AppText(
                         text = if (lastCheckedState) activeText else inactiveText,
-                        style = MaterialTheme.typography.labelSmall,
+                        style = LegadoTheme.typography.labelSmall,
                         modifier = Modifier.padding(start = 6.dp),
                         maxLines = 1,
                         softWrap = false

+ 35 - 17
app/src/main/java/io/legado/app/ui/widget/components/button/SmallTextButton.kt

@@ -6,12 +6,18 @@ import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.LegadoTheme.composeEngine
+import io.legado.app.ui.theme.ThemeResolver
+import io.legado.app.ui.widget.components.text.AppText
+import top.yukonga.miuix.kmp.basic.Button as MiuixButton
+import top.yukonga.miuix.kmp.basic.ButtonDefaults as MiuixButtonDefaults
+import top.yukonga.miuix.kmp.basic.Text as MiuixText
 
 @Composable
 fun SmallTextButton(
@@ -20,21 +26,33 @@ fun SmallTextButton(
     modifier: Modifier = Modifier,
     onClick: () -> Unit
 ) {
-    TextButton(
-        onClick = onClick,
-        modifier = modifier,
-        contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
-        shape = MaterialTheme.shapes.small
-    ) {
-        Icon(
-            imageVector = icon,
-            contentDescription = null,
-            modifier = Modifier.size(16.dp)
-        )
-        Spacer(Modifier.width(4.dp))
-        Text(
-            text = text,
-            style = MaterialTheme.typography.labelMedium
-        )
+    if (ThemeResolver.isMiuixEngine(composeEngine)) {
+        MiuixButton(
+            onClick = onClick,
+            modifier = modifier,
+            colors = MiuixButtonDefaults.buttonColors()
+        ) {
+            MiuixText(
+                text = text
+            )
+        }
+    } else {
+        TextButton(
+            onClick = onClick,
+            modifier = modifier,
+            contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
+            shape = MaterialTheme.shapes.small
+        ) {
+            Icon(
+                imageVector = icon,
+                contentDescription = null,
+                modifier = Modifier.size(16.dp)
+            )
+            Spacer(Modifier.width(4.dp))
+            AppText(
+                text = text,
+                style = LegadoTheme.typography.labelMedium
+            )
+        }
     }
 }

+ 80 - 52
app/src/main/java/io/legado/app/ui/widget/components/button/TopBarButton.kt

@@ -22,7 +22,6 @@ import androidx.compose.material3.IconButton
 import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedIconButton
-import androidx.compose.material3.Text
 import androidx.compose.material3.ToggleButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -35,7 +34,11 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.unit.dp
 import io.legado.app.ui.config.themeConfig.ThemeConfig
-import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import io.legado.app.ui.widget.components.icon.AppIcons
+import io.legado.app.ui.widget.components.text.AppText
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import kotlinx.coroutines.delay
 
 enum class TopBarButtonVariant {
@@ -105,17 +108,26 @@ fun TopBarButton(
 fun TopbarNavigationButton(
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
-    imageVector: ImageVector = Icons.AutoMirrored.Filled.ArrowBack,
+    imageVector: ImageVector = AppIcons.Back,
     contentDescription: String? = "返回",
     style: TopBarButtonVariant = TopBarButtonVariant.Filled
 ) {
-    TopBarButton(
-        onClick = onClick,
-        imageVector = imageVector,
-        contentDescription = contentDescription,
-        modifier = modifier.padding(horizontal = 12.dp),
-        style = style
-    )
+    if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
+        MiuixIconButton(
+            onClick = onClick,
+            imageVector = imageVector,
+            contentDescription = contentDescription,
+            modifier = modifier.padding(horizontal = 16.dp)
+        )
+    } else {
+        TopBarButton(
+            onClick = onClick,
+            imageVector = imageVector,
+            contentDescription = contentDescription,
+            modifier = modifier.padding(horizontal = 12.dp),
+            style = style
+        )
+    }
 }
 
 @Composable
@@ -126,21 +138,29 @@ fun TopBarActionButton(
     modifier: Modifier = Modifier
 ) {
     val enableProgressive = ThemeConfig.enableProgressiveBlur
-
-    if (enableProgressive) {
-        TopBarButton(
+    if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
+        MiuixIconButton(
             onClick = onClick,
             imageVector = imageVector,
             contentDescription = contentDescription,
-            modifier = modifier.padding(end = 12.dp),
-            style = TopBarButtonVariant.Filled
+            modifier = modifier.padding(end = 8.dp),
         )
     } else {
-        IconButton(onClick = onClick) {
-            Icon(
+        if (enableProgressive) {
+            TopBarButton(
+                onClick = onClick,
                 imageVector = imageVector,
-                contentDescription = contentDescription
+                contentDescription = contentDescription,
+                modifier = modifier.padding(end = 12.dp),
+                style = TopBarButtonVariant.Filled
             )
+        } else {
+            IconButton(onClick = onClick) {
+                Icon(
+                    imageVector = imageVector,
+                    contentDescription = contentDescription
+                )
+            }
         }
     }
 }
@@ -165,43 +185,51 @@ fun TopBarAnimatedActionButton(
             showText = false
         }
     }
-
-    ToggleButton(
-        modifier = modifier.height(36.dp),
-        contentPadding = PaddingValues(horizontal = 8.dp),
-        checked = checked,
-        onCheckedChange = {
-            lastCheckedState = it
-            onCheckedChange(it)
-            showText = true
-        }
-    ) {
-        Row(
-            verticalAlignment = Alignment.CenterVertically,
-            horizontalArrangement = Arrangement.Center
+    if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
+        MiuixToggleButton(
+            checked = checked,
+            onCheckedChange = onCheckedChange,
+            iconChecked = iconChecked,
+            iconUnchecked = iconUnchecked
+        )
+    } else {
+        ToggleButton(
+            modifier = modifier.height(36.dp),
+            contentPadding = PaddingValues(horizontal = 8.dp),
+            checked = checked,
+            onCheckedChange = {
+                lastCheckedState = it
+                onCheckedChange(it)
+                showText = true
+            }
         ) {
+            Row(
+                verticalAlignment = Alignment.CenterVertically,
+                horizontalArrangement = Arrangement.Center
+            ) {
 
-            AnimatedContent(
-                targetState = checked,
-                label = "IconAnimation"
-            ) { targetChecked ->
-                AnimatedIcon(
-                    modifier = Modifier.size(20.dp),
-                    imageVector = if (targetChecked) iconChecked else iconUnchecked,
-                    contentDescription = null
-                )
-            }
+                AnimatedContent(
+                    targetState = checked,
+                    label = "IconAnimation"
+                ) { targetChecked ->
+                    AnimatedIcon(
+                        modifier = Modifier.size(20.dp),
+                        imageVector = if (targetChecked) iconChecked else iconUnchecked,
+                        contentDescription = null
+                    )
+                }
 
-            AnimatedVisibility(
-                visible = showText
-            ) {
-                Text(
-                    text = if (lastCheckedState) activeText else inactiveText,
-                    style = MaterialTheme.typography.labelMedium,
-                    modifier = Modifier.padding(start = 8.dp),
-                    maxLines = 1,
-                    softWrap = false
-                )
+                AnimatedVisibility(
+                    visible = showText
+                ) {
+                    AppText(
+                        text = if (lastCheckedState) activeText else inactiveText,
+                        style = LegadoTheme.typography.labelMedium,
+                        modifier = Modifier.padding(start = 8.dp),
+                        maxLines = 1,
+                        softWrap = false
+                    )
+                }
             }
         }
     }

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

@@ -4,58 +4,130 @@ import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material3.Card
-import androidx.compose.material3.CardColors
 import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.CardElevation
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
 import io.legado.app.ui.config.themeConfig.ThemeConfig
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import top.yukonga.miuix.kmp.theme.MiuixTheme
+import top.yukonga.miuix.kmp.basic.Card as MiuixCard
+import top.yukonga.miuix.kmp.basic.CardDefaults as MiuixCardDefaults
 
 @OptIn(ExperimentalMaterialApi::class)
 @Composable
-fun GlassCard(
+private fun BaseCard(
     modifier: Modifier = Modifier,
     onClick: (() -> Unit)? = null,
     shape: Shape = CardDefaults.shape,
-    colors: CardColors? = null,
+    cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
+    containerColor: Color? = null,
+    contentColor: Color? = null,
     elevation: CardElevation = CardDefaults.cardElevation(),
     border: BorderStroke? = null,
+    alpha: Float = 1f,
     content: @Composable ColumnScope.() -> Unit
 ) {
-    val containerAlpha = ThemeConfig.containerOpacity / 100f
-
-    val baseColors = colors ?: CardDefaults.cardColors(
-        containerColor = MaterialTheme.colorScheme.secondaryContainer,
-        contentColor = MaterialTheme.colorScheme.onSecondaryContainer
-    )
-
-    val finalColors = CardDefaults.cardColors(
-        containerColor = baseColors.containerColor.copy(alpha = containerAlpha),
-        contentColor = baseColors.contentColor,
-        disabledContainerColor = baseColors.disabledContainerColor.copy(alpha = containerAlpha),
-        disabledContentColor = baseColors.disabledContentColor
-    )
-
-    if (onClick != null) {
-        Card(
-            onClick = onClick,
+    if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
+        val colors = MiuixCardDefaults.defaultColors(
+            color = (containerColor
+                ?: MiuixTheme.colorScheme.secondaryContainer).copy(alpha = alpha),
+            contentColor = contentColor ?: MiuixTheme.colorScheme.onSurface
+        )
+        MiuixCard(
             modifier = modifier,
-            shape = shape,
-            colors = finalColors,
-            elevation = elevation,
-            border = border,
-            content = content
+            cornerRadius = cornerRadius,
+            onClick = onClick,
+            content = content,
+            colors = colors
         )
     } else {
-        Card(
-            modifier = modifier,
-            shape = shape,
-            colors = finalColors,
-            elevation = elevation,
-            border = border,
-            content = content
+        val colors = CardDefaults.cardColors(
+            containerColor = (containerColor ?: MaterialTheme.colorScheme.secondaryContainer).copy(
+                alpha = alpha
+            ),
+            contentColor = contentColor ?: MaterialTheme.colorScheme.onSecondaryContainer,
+            disabledContainerColor = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = alpha * 0.38f),
+            disabledContentColor = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = alpha * 0.38f)
         )
+        if (onClick != null) {
+            Card(
+                modifier = modifier,
+                shape = shape,
+                colors = colors,
+                elevation = elevation,
+                border = border,
+                onClick = onClick,
+                content = content
+            )
+        } else {
+            Card(
+                modifier = modifier,
+                shape = shape,
+                colors = colors,
+                elevation = elevation,
+                border = border,
+                content = content
+            )
+        }
     }
 }
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun GlassCard(
+    modifier: Modifier = Modifier,
+    onClick: (() -> Unit)? = null,
+    shape: Shape = CardDefaults.shape,
+    cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
+    containerColor: Color? = null,
+    contentColor: Color? = null,
+    elevation: CardElevation = CardDefaults.cardElevation(),
+    border: BorderStroke? = null,
+    content: @Composable ColumnScope.() -> Unit
+) {
+    BaseCard(
+        modifier = modifier,
+        onClick = onClick,
+        shape = shape,
+        cornerRadius = cornerRadius,
+        containerColor = containerColor,
+        contentColor = contentColor,
+        elevation = elevation,
+        border = border,
+        alpha = ThemeConfig.containerOpacity / 100f,
+        content = content
+    )
+}
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun NormalCard(
+    modifier: Modifier = Modifier,
+    onClick: (() -> Unit)? = null,
+    shape: Shape = CardDefaults.shape,
+    cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
+    containerColor: Color? = null,
+    contentColor: Color? = null,
+    elevation: CardElevation = CardDefaults.cardElevation(),
+    border: BorderStroke? = null,
+    content: @Composable ColumnScope.() -> Unit
+) {
+    BaseCard(
+        modifier = modifier,
+        onClick = onClick,
+        shape = shape,
+        cornerRadius = cornerRadius,
+        containerColor = containerColor,
+        contentColor = contentColor,
+        elevation = elevation,
+        border = border,
+        alpha = 1f,
+        content = content
+    )
+}

+ 48 - 20
app/src/main/java/io/legado/app/ui/widget/components/card/SelectionItemCard.kt

@@ -24,8 +24,6 @@ import androidx.compose.material3.Checkbox
 import androidx.compose.material3.ListItem
 import androidx.compose.material3.ListItemDefaults
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Switch
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -41,10 +39,17 @@ import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.zIndex
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.LegadoTheme.composeEngine
+import io.legado.app.ui.theme.ThemeResolver
+import io.legado.app.ui.widget.components.AdaptiveSwitch
 import io.legado.app.ui.widget.components.button.SmallIconButton
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
+import io.legado.app.ui.widget.components.text.AppText
 import sh.calvin.reorderable.ReorderableItem
 import sh.calvin.reorderable.ReorderableLazyListState
+import top.yukonga.miuix.kmp.basic.BasicComponent
+import top.yukonga.miuix.kmp.theme.MiuixTheme
 
 @Composable
 fun SelectionItemCard(
@@ -66,11 +71,14 @@ fun SelectionItemCard(
 ) {
     var showMenu by remember { mutableStateOf(false) }
 
+    val composeEngine = ThemeResolver.isMiuixEngine(composeEngine)
     val animatedContainerColor by animateColorAsState(
         targetValue = if (isSelected)
-            selectedContainerColor ?: MaterialTheme.colorScheme.secondaryContainer
+            selectedContainerColor
+                ?: if (composeEngine) MiuixTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.secondaryContainer
         else
-            containerColor ?: MaterialTheme.colorScheme.surfaceContainerLow,
+            containerColor
+                ?: if (composeEngine) MiuixTheme.colorScheme.surfaceContainer else MaterialTheme.colorScheme.surfaceContainerLow,
         animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
         label = "CardColor"
     )
@@ -78,10 +86,9 @@ fun SelectionItemCard(
     GlassCard(
         onClick = onToggleSelection,
         modifier = modifier
-            .padding(horizontal = 16.dp)
             .fillMaxWidth(),
         shape = MaterialTheme.shapes.medium,
-        colors = CardDefaults.cardColors(containerColor = animatedContainerColor),
+        containerColor = animatedContainerColor,
         elevation = CardDefaults.cardElevation(defaultElevation = elevation)
     ) {
         Row(
@@ -113,28 +120,49 @@ fun SelectionItemCard(
                 }
             }
 
-            ListItem(
-                modifier = Modifier.weight(1f),
-                headlineContent = {
-                    Text(
+            if (composeEngine) {
+                BasicComponent(
+                    modifier = Modifier.weight(1f)
+                ) {
+                    AppText(
                         text = title,
-                        style = MaterialTheme.typography.titleSmall,
+                        style = LegadoTheme.typography.titleSmall,
                         maxLines = 1,
                         overflow = TextOverflow.Ellipsis
                     )
-                },
-                supportingContent = if (!subtitle.isNullOrBlank()) {
-                    {
-                        Text(
+                    if (!subtitle.isNullOrBlank()) {
+                        AppText(
                             text = subtitle,
-                            style = MaterialTheme.typography.bodySmall,
+                            style = LegadoTheme.typography.bodySmall,
                             maxLines = 1,
                             overflow = TextOverflow.Ellipsis
                         )
                     }
-                } else null,
-                colors = ListItemDefaults.colors(containerColor = Color.Transparent)
-            )
+                }
+            } else {
+                ListItem(
+                    modifier = Modifier.weight(1f),
+                    headlineContent = {
+                        AppText(
+                            text = title,
+                            style = LegadoTheme.typography.titleSmall,
+                            maxLines = 1,
+                            overflow = TextOverflow.Ellipsis
+                        )
+                    },
+                    supportingContent = if (!subtitle.isNullOrBlank()) {
+                        {
+                            AppText(
+                                text = subtitle,
+                                style = LegadoTheme.typography.bodySmall,
+                                maxLines = 1,
+                                overflow = TextOverflow.Ellipsis
+                            )
+                        }
+                    } else null,
+                    colors = ListItemDefaults.colors(containerColor = Color.Transparent)
+                )
+            }
 
             Row(
                 verticalAlignment = Alignment.CenterVertically,
@@ -143,7 +171,7 @@ fun SelectionItemCard(
                     .padding(end = 8.dp)
             ) {
                 onEnabledChange?.let {
-                    Switch(
+                    AdaptiveSwitch(
                         modifier = Modifier.scale(0.8f),
                         checked = isEnabled,
                         onCheckedChange = it

+ 75 - 0
app/src/main/java/io/legado/app/ui/widget/components/card/SettingCard.kt

@@ -0,0 +1,75 @@
+package io.legado.app.ui.widget.components.card
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardColors
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.CardElevation
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Shape
+import io.legado.app.ui.config.themeConfig.ThemeConfig
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import top.yukonga.miuix.kmp.basic.BasicComponent
+
+// import top.yukonga.miuix.kmp.basic.theme.LocalColors as MiuixLocalColors
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun SettingCard(
+    modifier: Modifier = Modifier,
+    onClick: (() -> Unit)? = null,
+    shape: Shape = CardDefaults.shape,
+    colors: CardColors? = null,
+    elevation: CardElevation = CardDefaults.cardElevation(),
+    border: BorderStroke? = null,
+    content: @Composable ColumnScope.() -> Unit
+) {
+    val composeEngine = LegadoTheme.composeEngine
+    val containerAlpha = ThemeConfig.containerOpacity / 100f
+
+    if (ThemeResolver.isMiuixEngine(composeEngine)) {
+        BasicComponent(
+            modifier = modifier,
+            onClick = onClick,
+            content = content
+        )
+    } else {
+        val baseColors = colors ?: CardDefaults.cardColors(
+            containerColor = MaterialTheme.colorScheme.secondaryContainer,
+            contentColor = MaterialTheme.colorScheme.onSecondaryContainer
+        )
+
+        val finalColors = CardDefaults.cardColors(
+            containerColor = baseColors.containerColor.copy(alpha = containerAlpha),
+            contentColor = baseColors.contentColor,
+            disabledContainerColor = baseColors.disabledContainerColor.copy(alpha = containerAlpha),
+            disabledContentColor = baseColors.disabledContentColor
+        )
+
+        if (onClick != null) {
+            Card(
+                onClick = onClick,
+                modifier = modifier,
+                shape = shape,
+                colors = finalColors,
+                elevation = elevation,
+                border = border,
+                content = content
+            )
+        } else {
+            Card(
+                modifier = modifier,
+                shape = shape,
+                colors = finalColors,
+                elevation = elevation,
+                border = border,
+                content = content
+            )
+        }
+    }
+}

+ 20 - 23
app/src/main/java/io/legado/app/ui/widget/components/card/TextCard.kt

@@ -6,50 +6,47 @@ import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.CardDefaults
-import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.MaterialTheme.colorScheme
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
-import io.legado.app.ui.widget.components.AnimatedTextLine
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.icon.AppIcon
+import io.legado.app.ui.widget.components.text.AnimatedTextLine
 
-@Preview(showBackground = true)
-@Composable
-fun PreviewTextCard() {
-    MaterialTheme {
-        TextCard(
-            text = "v1.0.0"
-        )
-    }
-}
 
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun TextCard(
     modifier: Modifier = Modifier,
     text: String,
     icon: ImageVector? = null,
     onClick: (() -> Unit)? = null,
-    backgroundColor: Color = colorScheme.primaryContainer,
-    contentColor: Color = colorScheme.onPrimaryContainer,
+    backgroundColor: Color? = null,
+    contentColor: Color? = null,
     cornerRadius: Dp = 8.dp,
     horizontalPadding: Dp = 8.dp,
     verticalPadding: Dp = 2.dp,
     iconSize: Dp = 14.dp,
     spacing: Dp = 4.dp,
-    textStyle: TextStyle = MaterialTheme.typography.labelSmall
+    textStyle: TextStyle = LegadoTheme.typography.labelSmallEmphasized
 ) {
-    GlassCard(
+    val defaultBackground = LegadoTheme.colorScheme.surfaceContainer
+    val defaultContent = LegadoTheme.colorScheme.onSurface
+
+    val finalBackgroundColor = backgroundColor ?: defaultBackground
+    val finalContentColor = contentColor ?: defaultContent
+
+    NormalCard(
         modifier = modifier,
         shape = RoundedCornerShape(cornerRadius),
-        colors = CardDefaults.cardColors(containerColor = backgroundColor),
+        containerColor = finalBackgroundColor,
+        contentColor = finalContentColor,
         onClick = onClick
     ) {
         Row(
@@ -61,10 +58,10 @@ fun TextCard(
         ) {
 
             if (icon != null) {
-                Icon(
+                AppIcon(
                     imageVector = icon,
                     contentDescription = null,
-                    tint = contentColor,
+                    tint = finalContentColor,
                     modifier = Modifier.size(iconSize)
                 )
                 Spacer(modifier = Modifier.width(spacing))
@@ -73,7 +70,7 @@ fun TextCard(
             AnimatedTextLine(
                 text = text,
                 style = textStyle,
-                color = contentColor
+                color = finalContentColor
             )
         }
     }

+ 4 - 3
app/src/main/java/io/legado/app/ui/widget/components/checkBox/CheckboxItem.kt

@@ -7,7 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.Checkbox
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+import io.legado.app.ui.widget.components.text.AppText
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -15,6 +15,7 @@ import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
 
 @Composable
 fun CheckboxItem(
@@ -45,9 +46,9 @@ fun CheckboxItem(
             enabled = enabled,
             modifier = Modifier.alpha(alpha)
         )
-        Text(
+        AppText(
             text = title,
-            style = MaterialTheme.typography.bodyMedium,
+            style = LegadoTheme.typography.bodyMedium,
             maxLines = 1,
             modifier = Modifier
                 .padding(start = 12.dp)

+ 3 - 5
app/src/main/java/io/legado/app/ui/widget/components/cover/BookshelfCover.kt

@@ -7,12 +7,12 @@ import androidx.compose.foundation.layout.padding
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Update
 import androidx.compose.material3.LinearProgressIndicator
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.StrokeCap
 import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.card.TextCard
 
 @Composable
@@ -44,8 +44,6 @@ fun BookshelfCover(
                 modifier = Modifier
                     .align(Alignment.TopEnd)
                     .padding(2.dp),
-                backgroundColor = MaterialTheme.colorScheme.secondaryContainer,
-                contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
                 cornerRadius = 4.dp,
                 horizontalPadding = 4.dp,
                 verticalPadding = 0.dp
@@ -59,8 +57,8 @@ fun BookshelfCover(
                     .fillMaxWidth()
                     .padding(horizontal = 4.dp, vertical = 6.dp)
                     .height(3.dp),
-                color = MaterialTheme.colorScheme.primary,
-                trackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f),
+                color = LegadoTheme.colorScheme.primary,
+                trackColor = LegadoTheme.colorScheme.primary.copy(alpha = 0.2f),
                 strokeCap = StrokeCap.Round
             )
         }

+ 11 - 9
app/src/main/java/io/legado/app/ui/widget/components/dialog/ColorPickerSheet.kt

@@ -22,7 +22,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.Button
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -41,11 +40,14 @@ import com.github.skydoves.colorpicker.compose.BrightnessSlider
 import com.github.skydoves.colorpicker.compose.HsvColorPicker
 import com.github.skydoves.colorpicker.compose.rememberColorPickerController
 import io.legado.app.R
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun ColorPickerSheet(
+    show: Boolean,
     initialColor: Int,
     onDismissRequest: () -> Unit,
     onColorSelected: (Int) -> Unit
@@ -79,16 +81,16 @@ fun ColorPickerSheet(
         )
     }
 
-    GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
+    AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
         Column(
             modifier = Modifier
                 .fillMaxWidth()
                 .padding(16.dp),
             horizontalAlignment = Alignment.CenterHorizontally
         ) {
-            Text(
+            AppText(
                 text = "颜色选择",
-                style = MaterialTheme.typography.titleLarge,
+                style = LegadoTheme.typography.titleLarge,
                 modifier = Modifier.padding(bottom = 16.dp)
             )
 
@@ -168,9 +170,9 @@ fun ColorPickerSheet(
                         )
                 )
                 Spacer(modifier = Modifier.width(12.dp))
-                Text(
+                AppText(
                     text = "#${Integer.toHexString(currentColor.toArgb()).uppercase()}",
-                    style = MaterialTheme.typography.bodyLarge
+                    style = LegadoTheme.typography.bodyLarge
                 )
 
                 Row(
@@ -178,14 +180,14 @@ fun ColorPickerSheet(
                     horizontalArrangement = Arrangement.End
                 ) {
                     TextButton(onClick = onDismissRequest) {
-                        Text(stringResource(R.string.cancel))
+                        AppText(stringResource(R.string.cancel))
                     }
                     Spacer(modifier = Modifier.width(8.dp))
                     Button(onClick = {
                         onColorSelected(currentColor.toArgb())
                         onDismissRequest()
                     }) {
-                        Text(stringResource(R.string.ok))
+                        AppText(stringResource(R.string.ok))
                     }
                 }
             }

+ 9 - 9
app/src/main/java/io/legado/app/ui/widget/components/dialog/TextListInputDialog.kt

@@ -10,9 +10,7 @@ import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.items
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.AssistChip
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -22,6 +20,8 @@ import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.text.AppText
 
 @Composable
 fun TextListInputDialog(
@@ -36,22 +36,22 @@ fun TextListInputDialog(
 
     AlertDialog(
         onDismissRequest = onDismissRequest,
-        title = { Text(title) },
+        title = { AppText(title) },
         text = {
             Column {
                 OutlinedTextField(
                     value = text,
                     onValueChange = { text = it },
-                    label = { Text(hint) },
+                    label = { AppText(hint) },
                     modifier = Modifier.fillMaxWidth(),
                     singleLine = true
                 )
 
                 if (suggestions.isNotEmpty()) {
                     Spacer(modifier = Modifier.height(8.dp))
-                    Text(
+                    AppText(
                         text = "建议:",
-                        style = MaterialTheme.typography.labelSmall,
+                        style = LegadoTheme.typography.labelSmall,
                         modifier = Modifier.padding(bottom = 4.dp)
                     )
                     LazyRow(
@@ -61,7 +61,7 @@ fun TextListInputDialog(
                         items(suggestions) { suggestion ->
                             AssistChip(
                                 onClick = { text = suggestion },
-                                label = { Text(suggestion) }
+                                label = { AppText(suggestion) }
                             )
                         }
                     }
@@ -72,12 +72,12 @@ fun TextListInputDialog(
             TextButton(
                 onClick = { onConfirm(text) }
             ) {
-                Text(stringResource(android.R.string.ok))
+                AppText(stringResource(android.R.string.ok))
             }
         },
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(stringResource(android.R.string.cancel))
+                AppText(stringResource(android.R.string.cancel))
             }
         }
     )

+ 24 - 12
app/src/main/java/io/legado/app/ui/widget/components/divider/PillDivider.kt

@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -14,26 +13,39 @@ import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import top.yukonga.miuix.kmp.theme.MiuixTheme
 
 @Composable
 fun PillDivider(
     modifier: Modifier = Modifier,
     thickness: Dp = 2.dp,
     widthFraction: Float = 0.2f,
-    color: Color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f)
+    color: Color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.6f)
 ) {
-    Box(
-        modifier = modifier
-            .fillMaxWidth()
-            .padding(vertical = 4.dp),
-        contentAlignment = Alignment.Center
-    ) {
+    val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
+    if (isMiuix) {
         Box(
             modifier = Modifier
-                .fillMaxWidth(widthFraction)
-                .height(thickness)
-                .clip(CircleShape)
-                .background(color)
+                .fillMaxWidth()
+                .height(4.dp)
+                .background(MiuixTheme.colorScheme.secondaryContainer)
         )
+    } else {
+        Box(
+            modifier = modifier
+                .fillMaxWidth()
+                .padding(vertical = 4.dp),
+            contentAlignment = Alignment.Center
+        ) {
+            Box(
+                modifier = Modifier
+                    .fillMaxWidth(widthFraction)
+                    .height(thickness)
+                    .clip(CircleShape)
+                    .background(color)
+            )
+        }
     }
 }

+ 6 - 6
app/src/main/java/io/legado/app/ui/widget/components/divider/PillHeaderDivider.kt

@@ -8,8 +8,6 @@ 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.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -17,14 +15,16 @@ import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.text.AppText
 
 @Composable
 fun PillHeaderDivider(
     title: String,
     modifier: Modifier = Modifier,
     dotSize: Dp = 2.dp,
-    dotColor: Color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.8f),
-    titleColor: Color = MaterialTheme.colorScheme.primary
+    dotColor: Color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.8f),
+    titleColor: Color = LegadoTheme.colorScheme.primary
 ) {
     Row(
         modifier = modifier
@@ -40,9 +40,9 @@ fun PillHeaderDivider(
                 .background(dotColor)
         )
 
-        Text(
+        AppText(
             text = title,
-            style = MaterialTheme.typography.labelSmall,
+            style = LegadoTheme.typography.labelSmall,
             color = titleColor,
             modifier = Modifier.padding(horizontal = 12.dp),
             maxLines = 1

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

@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
@@ -17,10 +16,8 @@ import androidx.compose.material.icons.filled.CloudUpload
 import androidx.compose.material.icons.filled.EditNote
 import androidx.compose.material.icons.filled.FolderOpen
 import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -28,7 +25,10 @@ import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.icon.AppIcon
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
 
 enum class FilePickerSheetMode {
     DIR, FILE, EXPORT
@@ -37,6 +37,7 @@ enum class FilePickerSheetMode {
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun FilePickerSheet(
+    show: Boolean,
     onDismissRequest: () -> Unit,
     title: String = stringResource(R.string.select_operation),
     onSelectSysDir: (() -> Unit)? = null,
@@ -46,22 +47,17 @@ fun FilePickerSheet(
     onUpload: (() -> Unit)? = null,
     allowExtensions: Array<String>? = null,
 ) {
-    GlassModalBottomSheet(
+    AppModalBottomSheet(
+        show = show,
+        title = title,
         onDismissRequest = onDismissRequest,
-        containerColor = { it.surface }
+        containerColor = LegadoTheme.colorScheme.surface
     ) {
         Column(
             modifier = Modifier
-                .fillMaxWidth()
-                .padding(horizontal = 16.dp, vertical = 8.dp),
+                .fillMaxWidth(),
             horizontalAlignment = Alignment.CenterHorizontally
         ) {
-            Text(
-                text = title,
-                style = MaterialTheme.typography.titleMedium,
-                modifier = Modifier.padding(bottom = 20.dp)
-            )
-
             FlowRow(
                 modifier = Modifier.fillMaxWidth(),
                 horizontalArrangement = Arrangement.spacedBy(12.dp),
@@ -126,7 +122,7 @@ private fun RowScope.FilePickerOptionCard(
             .weight(1f)
             .height(100.dp),
         shape = MaterialTheme.shapes.medium,
-        color = MaterialTheme.colorScheme.surfaceContainerLow,
+        color = LegadoTheme.colorScheme.surfaceContainerLow,
         tonalElevation = 2.dp
     ) {
         Column(
@@ -134,16 +130,16 @@ private fun RowScope.FilePickerOptionCard(
             horizontalAlignment = Alignment.CenterHorizontally,
             verticalArrangement = Arrangement.Center
         ) {
-            Icon(
+            AppIcon(
                 imageVector = icon,
                 contentDescription = null,
                 modifier = Modifier.size(32.dp),
-                tint = MaterialTheme.colorScheme.primary
+                tint = LegadoTheme.colorScheme.primary
             )
             Spacer(modifier = Modifier.height(8.dp))
-            Text(
+            AppText(
                 text = text,
-                style = MaterialTheme.typography.labelMedium,
+                style = LegadoTheme.typography.labelMedium,
                 maxLines = 1
             )
         }

+ 10 - 9
app/src/main/java/io/legado/app/ui/widget/components/heatmap/HeatmapCalendarComponents.kt

@@ -25,7 +25,6 @@ import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedIconButton
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -41,7 +40,9 @@ import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.button.AnimatedActionButton
+import io.legado.app.ui.widget.components.text.AppText
 import java.time.LocalDate
 
 /**
@@ -60,7 +61,7 @@ fun HeatmapCalendarTopBar(
         horizontalArrangement = Arrangement.SpaceBetween,
         verticalAlignment = Alignment.CenterVertically
     ) {
-        Text("时间线", style = MaterialTheme.typography.titleMedium)
+        AppText("时间线", style = LegadoTheme.typography.titleMedium)
 
         Row(verticalAlignment = Alignment.CenterVertically) {
             AnimatedActionButton(
@@ -107,7 +108,7 @@ fun WeekdayLabelsColumn(
 
         labels.forEachIndexed { index, label ->
             if (index % 2 == 0) {
-                Text(
+                AppText(
                     text = label,
                     fontSize = 10.sp,
                     color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -170,7 +171,7 @@ fun NoEarlierDataIndicator(
             verticalArrangement = Arrangement.Center
         ) {
             "没有更早数据".forEach { char ->
-                Text(
+                AppText(
                     text = char.toString(),
                     fontSize = 9.sp,
                     lineHeight = 12.sp,
@@ -252,7 +253,7 @@ fun HeatmapWeekColumn(
 
         // 月份标签
         if (firstDayOfMonth != null) {
-            Text(
+            AppText(
                 text = "${firstDayOfMonth.monthValue}月",
                 fontSize = 10.sp,
                 color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -280,9 +281,9 @@ fun HeatmapLegend(
     ) {
         val legendUnit = if (mode == HeatmapMode.COUNT) "次" else "长"
 
-        Text(
+        AppText(
             "少($legendUnit)",
-            style = MaterialTheme.typography.bodySmall,
+            style = LegadoTheme.typography.bodySmall,
             color = Color.Gray
         )
 
@@ -298,9 +299,9 @@ fun HeatmapLegend(
             Spacer(modifier = Modifier.width(4.dp))
         }
 
-        Text(
+        AppText(
             "多($legendUnit)",
-            style = MaterialTheme.typography.bodySmall,
+            style = LegadoTheme.typography.bodySmall,
             color = Color.Gray
         )
     }

+ 88 - 0
app/src/main/java/io/legado/app/ui/widget/components/icon/AppIcon.kt

@@ -0,0 +1,88 @@
+package io.legado.app.ui.widget.components.icon
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.paint
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.geometry.isUnspecified
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
+
+/**
+ * 1. 基础 AppIcon 实现
+ */
+@Composable
+fun AppIcon(
+    painter: Painter,
+    contentDescription: String?,
+    modifier: Modifier = Modifier,
+    tint: Color = Color.Unspecified,
+) {
+    val defaultTintColor = LegadoTheme.colorScheme.onSurface
+    val finalTint = tint.takeOrElse { defaultTintColor }
+
+    val colorFilter = remember(finalTint) {
+        if (finalTint == Color.Unspecified) null else ColorFilter.tint(finalTint)
+    }
+
+    val semantics = if (contentDescription != null) {
+        Modifier.semantics {
+            this.contentDescription = contentDescription
+            this.role = Role.Image
+        }
+    } else {
+        Modifier
+    }
+
+    Box(
+        modifier
+            .defaultIconSize(painter)
+            .paint(painter, colorFilter = colorFilter, contentScale = ContentScale.Fit)
+            .then(semantics)
+    )
+}
+
+@Composable
+fun AppIcon(
+    imageVector: ImageVector?,
+    contentDescription: String?,
+    modifier: Modifier = Modifier,
+    tint: Color = Color.Unspecified,
+) {
+    if (imageVector == null) return
+
+    AppIcon(
+        painter = rememberVectorPainter(imageVector),
+        contentDescription = contentDescription,
+        modifier = modifier,
+        tint = tint,
+    )
+}
+
+/**
+ * 3. 辅助扩展函数:处理图标的默认尺寸
+ * M3 和 MIUIX 内部都有 defaultSizeFor,如果 painter 没有固有尺寸,默认设为 24.dp
+ */
+private fun Modifier.defaultIconSize(painter: Painter): Modifier {
+    return this.then(
+        if (painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isUnspecified) {
+            Modifier.size(24.dp)
+        } else {
+            Modifier
+        }
+    )
+}

+ 54 - 0
app/src/main/java/io/legado/app/ui/widget/components/icon/AppIcons.kt

@@ -0,0 +1,54 @@
+package io.legado.app.ui.widget.components.icon
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.filled.Clear
+import androidx.compose.material.icons.filled.Replay
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.vector.ImageVector
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import top.yukonga.miuix.kmp.icon.MiuixIcons
+import top.yukonga.miuix.kmp.icon.basic.Search
+import top.yukonga.miuix.kmp.icon.extended.Back
+import top.yukonga.miuix.kmp.icon.extended.Close
+import top.yukonga.miuix.kmp.icon.extended.Refresh
+
+/**
+ * 应用全局图标映射层
+ */
+object AppIcons {
+
+    private val isMiuix: Boolean
+        @Composable
+        get() = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
+
+    val Search: ImageVector
+        @Composable
+        get() = if (isMiuix) {
+            MiuixIcons.Basic.Search
+        } else {
+            Icons.Default.Search
+        }
+
+    val Close: ImageVector
+        @Composable
+        get() = if (isMiuix) {
+            MiuixIcons.Close
+        } else {
+            Icons.Default.Clear
+        }
+
+    val Back: ImageVector
+        @Composable
+        get() = if (isMiuix) {
+            MiuixIcons.Back
+        } else {
+            Icons.AutoMirrored.Filled.ArrowBack
+        }
+
+    val Replay: ImageVector
+        @Composable
+        get() = if (isMiuix) MiuixIcons.Refresh ?: Icons.Default.Replay else Icons.Default.Replay
+}

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

@@ -1,10 +1,6 @@
 package io.legado.app.ui.widget.components.importComponents
 
 import android.annotation.SuppressLint
-import androidx.compose.animation.animateColorAsState
-import androidx.compose.animation.animateContentSize
-import androidx.compose.animation.core.FastOutSlowInEasing
-import androidx.compose.animation.core.tween
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
@@ -26,26 +22,18 @@ import androidx.compose.material.icons.filled.SelectAll
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.AssistChip
 import androidx.compose.material3.Button
-import androidx.compose.material3.Card
-import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.CenterAlignedTopAppBar
-import androidx.compose.material3.Checkbox
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.ListItem
-import androidx.compose.material3.ListItemDefaults
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.OutlinedToggleButton
 import androidx.compose.material3.SnackbarHost
 import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.TopAppBarDefaults
-import androidx.compose.material3.rememberModalBottomSheetState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -59,9 +47,13 @@ import androidx.compose.ui.platform.LocalConfiguration
 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.theme.LegadoTheme
 import io.legado.app.ui.widget.components.AppScaffold
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.button.SmallIconButton
+import io.legado.app.ui.widget.components.card.SelectionItemCard
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AnimatedText
+import io.legado.app.ui.widget.components.text.AppText
 import kotlinx.coroutines.launch
 
 @Composable
@@ -77,25 +69,25 @@ fun SourceInputDialog(
 
     AlertDialog(
         onDismissRequest = onDismissRequest,
-        title = { Text(title) },
+        title = { AppText(title) },
         text = {
             Column {
                 OutlinedTextField(
                     value = text,
                     onValueChange = { text = it },
-                    label = { Text(hint) },
+                    label = { AppText(hint) },
                     modifier = Modifier.fillMaxWidth(),
                     maxLines = 5
                 )
 
                 if (historyValues.isNotEmpty()) {
                     Spacer(modifier = Modifier.height(8.dp))
-                    Text("历史记录:", style = MaterialTheme.typography.labelSmall)
+                    AppText("历史记录:", style = LegadoTheme.typography.labelSmall)
                     LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                         items(historyValues) { history ->
                             AssistChip(
                                 onClick = { text = history },
-                                label = { Text(history, maxLines = 1) }
+                                label = { AppText(history, maxLines = 1) }
                             )
                         }
                     }
@@ -105,41 +97,52 @@ fun SourceInputDialog(
         confirmButton = {
             OutlinedButton(
                 onClick = { if (text.isNotBlank()) onConfirm(text) }
-            ) { Text(stringResource(android.R.string.ok)) }
+            ) { AppText(stringResource(android.R.string.ok)) }
         },
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(stringResource(android.R.string.cancel))
+                AppText(stringResource(android.R.string.cancel))
             }
         }
     )
 }
 
+//TODO: 动画
 @SuppressLint("ConfigurationScreenWidthHeight")
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun <T> BatchImportDialog(
     title: String,
-    importState: BaseImportUiState.Success<T>,
+    importState: BaseImportUiState<T>,
     onDismissRequest: () -> Unit,
-    onConfirm: (List<T>) -> Unit, // 返回选中的原始数据列表
+    onConfirm: (List<T>) -> Unit,
     onToggleItem: (index: Int) -> Unit,
     onToggleAll: (isSelected: Boolean) -> Unit,
     onItemInfoClick: (index: Int) -> Unit = {},
-    // 插槽:允许调用方自定义顶部菜单
     topBarActions: @Composable RowScope.() -> Unit = {},
-    // 插槽:自定义每一行的显示内容
-    itemContent: @Composable (data: T, status: ImportStatus) -> Unit
+    itemTitle: (data: T) -> String,
+    itemSubtitle: (data: T) -> String? = { null }
 ) {
-    val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
+    val show = importState is BaseImportUiState.Success<T>
+
+    var cachedState by remember { mutableStateOf<BaseImportUiState.Success<T>?>(null) }
+    if (importState is BaseImportUiState.Success<T>) {
+        cachedState = importState
+    }
+
+    if (!show && cachedState == null) return
+
     val scope = rememberCoroutineScope()
     val snackbarHostState = remember { SnackbarHostState() }
-    val selectedCount = importState.items.count { it.isSelected }
-    val totalCount = importState.items.size
 
-    GlassModalBottomSheet(
+    val currentState = cachedState!!
+    val selectedCount = currentState.items.count { it.isSelected }
+    val totalCount = currentState.items.size
+
+    AppModalBottomSheet(
+        show = show,
         onDismissRequest = onDismissRequest,
-        containerColor = { it.surfaceContainer }
+        containerColor = LegadoTheme.colorScheme.surfaceContainer
     ) {
         AppScaffold(
             modifier = Modifier
@@ -170,44 +173,34 @@ fun <T> BatchImportDialog(
                 ImportBottomBar(
                     selectedCount = selectedCount,
                     totalCount = totalCount,
-                    onToggleSelectAll = { isAll -> onToggleAll(isAll) },
+                    onToggleSelectAll = onToggleAll,
                     onConfirm = {
-                        scope.launch { sheetState.hide() }.invokeOnCompletion {
-                            if (!sheetState.isVisible) {
-                                val selectedData =
-                                    importState.items.filter { it.isSelected }.map { it.data }
-                                onConfirm(selectedData)
-                            }
-                        }
+                        val selectedData =
+                            currentState.items.filter { it.isSelected }.map { it.data }
+                        onConfirm(selectedData)
                     },
-                    onCancel = {
-                        scope.launch { sheetState.hide() }.invokeOnCompletion {
-                            if (!sheetState.isVisible) {
-                                onDismissRequest()
-                            }
-                        }
-                    }
+                    onCancel = onDismissRequest
                 )
             }
         ) { padding ->
             LazyColumn(
-                modifier = Modifier
-                    .padding(padding),
+                modifier = Modifier.padding(padding),
                 contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
                 verticalArrangement = Arrangement.spacedBy(8.dp)
             ) {
-                itemsIndexed(importState.items, key = { _, item -> item.data.hashCode() }) { index, itemWrapper ->
+                itemsIndexed(
+                    currentState.items,
+                    key = { _, item -> item.data.hashCode() }) { index, itemWrapper ->
                     ImportItemRow(
+                        title = itemTitle(itemWrapper.data),
+                        subtitle = itemSubtitle(itemWrapper.data),
                         isSelected = itemWrapper.isSelected,
                         status = itemWrapper.status,
                         onClick = { onToggleItem(index) },
                         onInfoClick = {
-                            scope.launch {
-                                snackbarHostState.showSnackbar("其实还没写桀桀桀")
-                            }
+                            scope.launch { snackbarHostState.showSnackbar("其实还没写桀桀桀") }
                             onItemInfoClick(index)
-                        },
-                        content = { itemContent(itemWrapper.data, itemWrapper.status) }
+                        }
                     )
                 }
             }
@@ -215,74 +208,47 @@ fun <T> BatchImportDialog(
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
-private fun ImportItemRow(
+fun ImportItemRow(
+    title: String,
+    subtitle: String? = null,
     isSelected: Boolean,
     status: ImportStatus,
     onClick: () -> Unit,
-    onInfoClick: () -> Unit,
-    content: @Composable () -> Unit
+    onInfoClick: () -> Unit
 ) {
-    val containerColor by animateColorAsState(
-        targetValue = if (isSelected)
-            MaterialTheme.colorScheme.secondaryContainer
-        else
-            MaterialTheme.colorScheme.surface,
-        animationSpec = tween(
-            durationMillis = 200,
-            easing = FastOutSlowInEasing
-        ),
-        label = "CardColor"
-    )
+    SelectionItemCard(
+        title = title,
+        subtitle = subtitle,
+        isSelected = isSelected,
+        inSelectionMode = true,
+        onToggleSelection = onClick,
+        modifier = Modifier.padding(vertical = 4.dp),
+        trailingAction = {
+            AppText(
+                text = when (status) {
+                    ImportStatus.New -> "新增"
+                    ImportStatus.Update -> "更新"
+                    ImportStatus.Existing -> "已有"
+                    ImportStatus.Error -> "错误"
+                },
+                style = LegadoTheme.typography.labelMedium,
+                color = when (status) {
+                    ImportStatus.New -> MaterialTheme.colorScheme.primary
+                    ImportStatus.Update -> MaterialTheme.colorScheme.secondary
+                    ImportStatus.Error -> MaterialTheme.colorScheme.error
+                    else -> MaterialTheme.colorScheme.outline
+                },
+                modifier = Modifier.padding(end = 4.dp)
+            )
 
-    Card(
-        onClick = onClick,
-        modifier = Modifier.fillMaxWidth(),
-        shape = MaterialTheme.shapes.medium,
-        colors = CardDefaults.cardColors(
-            containerColor = containerColor
-        )
-    ) {
-        ListItem(
-            modifier = Modifier.animateContentSize(),
-            headlineContent = { content() },
-            leadingContent = {
-                Checkbox(
-                    checked = isSelected,
-                    onCheckedChange = null
-                )
-            },
-            supportingContent = {
-                Text(
-                    text = when (status) {
-                        ImportStatus.New -> "新增"
-                        ImportStatus.Update -> "更新"
-                        ImportStatus.Existing -> "已有"
-                        ImportStatus.Error -> "错误"
-                    },
-                    style = MaterialTheme.typography.labelMedium,
-                    color = when (status) {
-                        ImportStatus.New -> MaterialTheme.colorScheme.primary
-                        ImportStatus.Update -> MaterialTheme.colorScheme.secondary
-                        ImportStatus.Error -> MaterialTheme.colorScheme.error
-                        else -> MaterialTheme.colorScheme.outline
-                    }
-                )
-            },
-            trailingContent = {
-                IconButton(onClick = onInfoClick) {
-                    Icon(
-                        imageVector = Icons.Default.Info,
-                        contentDescription = "详情"
-                    )
-                }
-            },
-            colors = ListItemDefaults.colors(
-                containerColor = Color.Transparent
+            SmallIconButton(
+                onClick = onInfoClick,
+                icon = Icons.Default.Info,
+                contentDescription = "详情"
             )
-        )
-    }
+        }
+    )
 }
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -315,12 +281,12 @@ fun ImportBottomBar(
             )
         }
         Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
-            OutlinedButton(onClick = onCancel) { Text("取消") }
+            OutlinedButton(onClick = onCancel) { AppText("取消") }
             Button(
                 enabled = selectedCount > 0,
                 onClick = onConfirm
             ) {
-                Text("导入")
+                AppText("导入")
             }
         }
     }

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

@@ -46,6 +46,7 @@ import androidx.compose.ui.util.fastFirstOrNull
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastLastOrNull
 import androidx.compose.ui.util.fastMaxBy
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.lazylist.Scroller.STICKY_HEADER_KEY_PREFIX
 import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -67,7 +68,7 @@ fun VerticalFastScroller(
     listState: LazyListState,
     modifier: Modifier = Modifier,
     thumbAllowed: () -> Boolean = { true },
-    thumbColor: Color = MaterialTheme.colorScheme.primary,
+    thumbColor: Color = LegadoTheme.colorScheme.primary,
     topContentPadding: Dp = Dp.Hairline,
     bottomContentPadding: Dp = Dp.Hairline,
     endContentPadding: Dp = Dp.Hairline,

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

@@ -15,19 +15,11 @@ import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-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.SnackbarHost
 import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.Text
-import androidx.compose.material3.TooltipAnchorPosition
-import androidx.compose.material3.TooltipBox
-import androidx.compose.material3.TooltipDefaults
-import androidx.compose.material3.TopAppBarScrollBehavior
 import androidx.compose.material3.animateFloatingActionButton
-import androidx.compose.material3.rememberTooltipState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
@@ -35,11 +27,13 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.zIndex
+import io.legado.app.ui.widget.components.AppFloatingActionButton
 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
 import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarScrollBehavior
 
 @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
 @Composable
@@ -52,28 +46,21 @@ fun <T> ListScaffold(
     onSearchQueryChange: (String) -> Unit,
     searchPlaceholder: String = "搜索...",
     topBarActions: @Composable RowScope.() -> Unit = {},
-    bottomContent: @Composable (ColumnScope.(TopAppBarScrollBehavior) -> Unit)? = null,
+    bottomContent: @Composable (ColumnScope.(GlassTopAppBarScrollBehavior) -> Unit)? = null,
     dropDownMenuContent: @Composable (ColumnScope.(dismiss: () -> Unit) -> Unit)? = null,
     selectionActions: SelectionActions? = null,
     onAddClick: (() -> Unit)? = null,
     floatingActionButton: @Composable () -> Unit = {
         onAddClick?.let { onClick ->
-            TooltipBox(
-                positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
-                    TooltipAnchorPosition.Above
+            AppFloatingActionButton(
+                onClick = onClick,
+                modifier = Modifier.animateFloatingActionButton(
+                    visible = state.selectedIds.isEmpty(),
+                    alignment = Alignment.BottomEnd,
                 ),
-                tooltip = { PlainTooltip { Text("添加") } },
-                state = rememberTooltipState(),
+                tooltipText = "添加"
             ) {
-                FloatingActionButton(
-                    modifier = Modifier.animateFloatingActionButton(
-                        visible = state.selectedIds.isEmpty(),
-                        alignment = Alignment.BottomEnd,
-                    ),
-                    onClick = onClick
-                ) {
-                    Icon(Icons.Default.Add, contentDescription = "Add")
-                }
+                Icon(Icons.Default.Add, contentDescription = "Add")
             }
         }
     },

+ 42 - 18
app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenu.kt

@@ -3,6 +3,8 @@ package io.legado.app.ui.widget.components.menuItem
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
 import androidx.compose.material3.DropdownMenu
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.MaterialExpressiveTheme
@@ -15,8 +17,11 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
 import io.legado.app.ui.theme.rememberOpaqueColorScheme
-
+import top.yukonga.miuix.kmp.basic.ListPopupColumn
+import top.yukonga.miuix.kmp.extra.SuperListPopup
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
@@ -24,31 +29,50 @@ fun RoundDropdownMenu(
     expanded: Boolean,
     onDismissRequest: () -> Unit,
     modifier: Modifier = Modifier,
+    // M3 专属视觉参数,Miuix 模式下会优雅降级(忽略)
     shape: Shape = MaterialTheme.shapes.medium,
     shadowElevation: Dp = 4.dp,
     verticalSpacing: Dp = 8.dp,
     content: @Composable ColumnScope.(dismiss: () -> Unit) -> Unit
 ) {
-    val colorScheme = rememberOpaqueColorScheme()
+    val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
+
+    if (isMiuix) {
+        SuperListPopup(
+            show = expanded,
+            onDismissRequest = onDismissRequest,
+            popupModifier = modifier
+        ) {
+            ListPopupColumn {
+                Column() {
+                    Spacer(Modifier.height(12.dp))
+                    content(onDismissRequest)
+                    Spacer(Modifier.height(12.dp))
+                }
+            }
+        }
+    } else {
+        val colorScheme = rememberOpaqueColorScheme()
 
-    DropdownMenu(
-        expanded = expanded,
-        onDismissRequest = onDismissRequest,
-        modifier = modifier,
-        shape = shape,
-        shadowElevation = shadowElevation,
-        containerColor = colorScheme.surfaceContainerLow
-    ) {
-        MaterialExpressiveTheme(
-            colorScheme = colorScheme,
-            typography = Typography(),
-            motionScheme = MotionScheme.expressive(),
-            shapes = Shapes()
+        DropdownMenu(
+            expanded = expanded,
+            onDismissRequest = onDismissRequest,
+            modifier = modifier,
+            shape = shape,
+            shadowElevation = shadowElevation,
+            containerColor = colorScheme.surfaceContainerLow
         ) {
-            Column(
-                verticalArrangement = Arrangement.spacedBy(verticalSpacing)
+            MaterialExpressiveTheme(
+                colorScheme = colorScheme,
+                typography = Typography(),
+                motionScheme = MotionScheme.expressive(),
+                shapes = Shapes()
             ) {
-                content(onDismissRequest)
+                Column(
+                    verticalArrangement = Arrangement.spacedBy(verticalSpacing)
+                ) {
+                    content(onDismissRequest)
+                }
             }
         }
     }

+ 90 - 24
app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenuItem.kt

@@ -1,6 +1,8 @@
 package io.legado.app.ui.widget.components.menuItem
 
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
@@ -11,53 +13,114 @@ import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.widthIn
-import androidx.compose.material3.Icon
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.LocalTextStyle
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.MenuDefaults
 import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
 import io.legado.app.ui.theme.rememberOpaqueColorScheme
+import io.legado.app.ui.widget.components.icon.AppIcon
+import top.yukonga.miuix.kmp.theme.MiuixTheme
+import top.yukonga.miuix.kmp.basic.Text as MiuixText
 
 @Composable
 fun RoundDropdownMenuItem(
-    text: @Composable () -> Unit,
+    text: String,
+    color: Color = LegadoTheme.colorScheme.surface,
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
+    isSelected: Boolean = false,
     leadingIcon: @Composable (() -> Unit)? = null,
     trailingIcon: @Composable (() -> Unit)? = null,
     enabled: Boolean = true,
     contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding,
     interactionSource: MutableInteractionSource? = null,
 ) {
-    val colorScheme = rememberOpaqueColorScheme()
+    val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
     val interaction = interactionSource ?: remember { MutableInteractionSource() }
 
-    val contentColor =
-        if (enabled) MaterialTheme.colorScheme.onSurface
-        else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
+    if (isMiuix) {
+        val backgroundColor = if (isSelected) {
+            LegadoTheme.colorScheme.primaryContainer
+        } else {
+            Color.Transparent
+        }
+
+        val contentColor = if (isSelected) {
+            LegadoTheme.colorScheme.onPrimaryContainer
+        } else {
+            LegadoTheme.colorScheme.onSurface
+        }
+
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.SpaceBetween,
+            modifier = modifier
+                .fillMaxWidth()
+                .drawBehind { drawRect(backgroundColor) }
+                .clickable(
+                    interactionSource = interaction,
+                    indication = androidx.compose.foundation.LocalIndication.current,
+                    enabled = enabled,
+                    onClick = onClick
+                )
+                .padding(horizontal = 20.dp, vertical = 12.dp)
+        ) {
+            CompositionLocalProvider(
+                LocalContentColor provides contentColor,
+                LocalTextStyle provides MiuixTheme.textStyles.body1
+            ) {
+                if (leadingIcon != null) {
+                    leadingIcon()
+                    Spacer(Modifier.width(12.dp))
+                }
+
+                Box(
+                    modifier = Modifier.weight(1f),
+                    contentAlignment = Alignment.CenterStart
+                ) {
+                    MiuixText(
+                        text = text,
+                        fontWeight = FontWeight.Medium
+                    )
+                }
+
+                if (trailingIcon != null) {
+                    Spacer(Modifier.width(12.dp))
+                    trailingIcon()
+                }
+            }
+        }
+    } else {
+        val colorScheme = rememberOpaqueColorScheme()
+        val contentColor = if (enabled) {
+            MaterialTheme.colorScheme.onSurface
+        } else {
+            MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
+        }
 
-    Surface(
-        onClick = onClick,
-        modifier = modifier
-            .padding(horizontal = 8.dp)
-            .fillMaxWidth(),
-        enabled = enabled,
-        shape = MaterialTheme.shapes.small,
-        color = colorScheme.surface,
-        interactionSource = interaction
-    ) {
-        CompositionLocalProvider(
-            LocalContentColor provides contentColor,
-            LocalTextStyle provides MaterialTheme.typography.labelLarge
+        Surface(
+            onClick = onClick,
+            modifier = modifier
+                .padding(horizontal = 8.dp)
+                .fillMaxWidth(),
+            enabled = enabled,
+            shape = MaterialTheme.shapes.small,
+            color = colorScheme.surface,
+            interactionSource = interaction
         ) {
             Row(
                 modifier = Modifier
@@ -66,17 +129,20 @@ fun RoundDropdownMenuItem(
                     .widthIn(min = 120.dp),
                 verticalAlignment = Alignment.CenterVertically
             ) {
-
                 if (leadingIcon != null) {
                     leadingIcon()
-                    Spacer(Modifier.width(8.dp))
+                    Spacer(Modifier.width(12.dp))
                 }
 
                 Box(
                     modifier = Modifier.weight(1f),
                     contentAlignment = Alignment.CenterStart
                 ) {
-                    text()
+                    Text(
+                        modifier = Modifier.widthIn(max = 200.dp),
+                        text = text,
+                        style = LegadoTheme.typography.bodyMediumEmphasized
+                    )
                 }
 
                 if (trailingIcon != null) {
@@ -93,9 +159,9 @@ fun MenuItemIcon(
     imageVector: ImageVector,
     modifier: Modifier = Modifier,
     contentDescription: String? = null,
-    tint: Color = LocalContentColor.current
+    tint: Color = Color.Unspecified
 ) {
-    Icon(
+    AppIcon(
         imageVector = imageVector,
         contentDescription = contentDescription,
         modifier = modifier.size(18.dp),

+ 138 - 0
app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt

@@ -0,0 +1,138 @@
+package io.legado.app.ui.widget.components.modalBottomSheet
+
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.BottomSheetDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialExpressiveTheme
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.MotionScheme
+import androidx.compose.material3.Shapes
+import androidx.compose.material3.Text
+import androidx.compose.material3.Typography
+import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalWindowInfo
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import io.legado.app.ui.theme.rememberOpaqueColorScheme
+import top.yukonga.miuix.kmp.extra.WindowBottomSheet
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun AppModalBottomSheet(
+    show: Boolean,
+    onDismissRequest: () -> Unit,
+    modifier: Modifier = Modifier,
+    title: String? = null,
+    startAction: @Composable (() -> Unit)? = null,
+    endAction: @Composable (() -> Unit)? = null,
+    containerColor: Color = LegadoTheme.colorScheme.surfaceContainerLow,
+    content: @Composable ColumnScope.() -> Unit
+) {
+    if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
+        WindowBottomSheet(
+            show = show,
+            modifier = modifier,
+            title = title,
+            startAction = startAction,
+            endAction = endAction,
+            insideMargin = DpSize(16.dp, 12.dp),
+            backgroundColor = containerColor,
+            onDismissRequest = onDismissRequest,
+            onDismissFinished = onDismissRequest,
+            enableWindowDim = true,
+            allowDismiss = true
+        ) {
+            Column(
+                modifier = Modifier.fillMaxWidth(),
+                content = content
+            )
+        }
+    } else {
+        if (show) {
+            val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
+            val colorScheme = rememberOpaqueColorScheme()
+            val density = LocalDensity.current
+            val maxHeight = with(density) {
+                LocalWindowInfo.current.containerSize.height.toDp() * 0.8f
+            }
+
+            ModalBottomSheet(
+                onDismissRequest = onDismissRequest,
+                sheetState = sheetState,
+                containerColor = containerColor,
+                contentColor = colorScheme.onSurface,
+                dragHandle = { BottomSheetDefaults.DragHandle() }
+            ) {
+                MaterialExpressiveTheme(
+                    colorScheme = colorScheme,
+                    typography = Typography(),
+                    motionScheme = MotionScheme.expressive(),
+                    shapes = Shapes()
+                ) {
+                    Column(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .padding(horizontal = 16.dp)
+                            .heightIn(max = maxHeight)
+                            .animateContentSize()
+                            .then(modifier)
+                    ) {
+                        val hasHeader =
+                            !title.isNullOrEmpty() || startAction != null || endAction != null
+
+                        if (hasHeader) {
+                            Box(
+                                modifier = Modifier
+                                    .fillMaxWidth()
+                                    .padding(bottom = 16.dp),
+                                contentAlignment = Alignment.Center
+                            ) {
+                                if (startAction != null) {
+                                    Box(modifier = Modifier.align(Alignment.CenterStart)) {
+                                        startAction()
+                                    }
+                                }
+
+                                if (!title.isNullOrEmpty()) {
+                                    Text(
+                                        text = title,
+                                        style = LegadoTheme.typography.titleMediumEmphasized,
+                                        color = colorScheme.onSurface,
+                                        textAlign = TextAlign.Center,
+                                        maxLines = 1,
+                                        overflow = TextOverflow.Ellipsis,
+                                        modifier = Modifier.padding(horizontal = 56.dp)
+                                    )
+                                }
+
+                                if (endAction != null) {
+                                    Box(modifier = Modifier.align(Alignment.CenterEnd)) {
+                                        endAction()
+                                    }
+                                }
+                            }
+                        }
+
+                        content()
+                    }
+                }
+            }
+        }
+    }
+}

+ 0 - 64
app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/GlassModalBottomSheet.kt

@@ -1,64 +0,0 @@
-package io.legado.app.ui.widget.components.modalBottomSheet
-
-import androidx.compose.animation.animateContentSize
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.material3.ColorScheme
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.MaterialExpressiveTheme
-import androidx.compose.material3.ModalBottomSheet
-import androidx.compose.material3.MotionScheme
-import androidx.compose.material3.Shapes
-import androidx.compose.material3.SheetState
-import androidx.compose.material3.Typography
-import androidx.compose.material3.rememberModalBottomSheetState
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalWindowInfo
-import io.legado.app.ui.theme.rememberOpaqueColorScheme
-
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
-@Composable
-fun GlassModalBottomSheet(
-    onDismissRequest: () -> Unit,
-    modifier: Modifier = Modifier,
-    sheetState: SheetState = rememberModalBottomSheetState(
-        skipPartiallyExpanded = true
-    ),
-    containerColor: (ColorScheme) -> Color = { it.surfaceContainerLow },
-    content: @Composable ColumnScope.() -> Unit
-) {
-    val colorScheme = rememberOpaqueColorScheme()
-    val density = LocalDensity.current
-    val maxHeight = with(density) {
-        LocalWindowInfo.current.containerSize.height.toDp() * 0.8f
-    }
-
-    ModalBottomSheet(
-        onDismissRequest = onDismissRequest,
-        sheetState = sheetState,
-        containerColor = containerColor(colorScheme),
-        contentColor = colorScheme.onSurface
-    ) {
-        MaterialExpressiveTheme(
-            colorScheme = colorScheme,
-            typography = Typography(),
-            motionScheme = MotionScheme.expressive(),
-            shapes = Shapes()
-        ) {
-            Column(
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .heightIn(max = maxHeight)
-                    .animateContentSize()
-                    .then(modifier),
-                content = content
-            )
-        }
-    }
-}

+ 11 - 9
app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt

@@ -22,7 +22,6 @@ import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Text
 import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -36,7 +35,8 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
-import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.text.AppText
 import kotlinx.coroutines.launch
 
 /**
@@ -52,6 +52,7 @@ data class RuleEditFields(
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun <T> RuleEditSheet(
+    show: Boolean,
     rule: T?,
     title: String,
     label1: String,
@@ -74,7 +75,8 @@ fun <T> RuleEditSheet(
 
     fun getCurrentEntity() = fromFields(RuleEditFields(name, rule1, rule2), rule)
 
-    GlassModalBottomSheet(
+    AppModalBottomSheet(
+        show = show,
         onDismissRequest = onDismissRequest
     ) {
         Box(modifier = Modifier.fillMaxWidth()) {
@@ -84,7 +86,7 @@ fun <T> RuleEditSheet(
                     .verticalScroll(rememberScrollState())
             ) {
                 CenterAlignedTopAppBar(
-                    title = { Text(title) },
+                    title = { AppText(title) },
                     navigationIcon = {
                         IconButton(onClick = onDismissRequest) {
                             Icon(
@@ -99,7 +101,7 @@ fun <T> RuleEditSheet(
                         }
                         DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
                             DropdownMenuItem(
-                                text = { Text(stringResource(R.string.copy_rule)) },
+                                text = { AppText(stringResource(R.string.copy_rule)) },
                                 leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) },
                                 onClick = {
                                     onCopy(getCurrentEntity())
@@ -107,7 +109,7 @@ fun <T> RuleEditSheet(
                                 }
                             )
                             DropdownMenuItem(
-                                text = { Text(stringResource(R.string.paste_rule)) },
+                                text = { AppText(stringResource(R.string.paste_rule)) },
                                 leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
                                 onClick = {
                                     scope.launch {
@@ -136,20 +138,20 @@ fun <T> RuleEditSheet(
                         modifier = Modifier.fillMaxWidth(),
                         value = name,
                         onValueChange = { name = it },
-                        label = { Text(stringResource(R.string.name)) },
+                        label = { AppText(stringResource(R.string.name)) },
                         singleLine = true
                     )
                     OutlinedTextField(
                         modifier = Modifier.fillMaxWidth(),
                         value = rule1,
                         onValueChange = { rule1 = it },
-                        label = { Text(label1) }
+                        label = { AppText(label1) }
                     )
                     OutlinedTextField(
                         modifier = Modifier.fillMaxWidth(),
                         value = rule2,
                         onValueChange = { rule2 = it },
-                        label = { Text(label2) },
+                        label = { AppText(label2) },
                         minLines = 3
                     )
                 }

Некоторые файлы не были показаны из-за большого количества измененных файлов