Преглед изворни кода

[优化] 依旧修复一些Miuix主题的稳定性和界面问题,hmm议题依旧没时间修

HapeLee пре 7 месеци
родитељ
комит
37c80e1230
82 измењених фајлова са 3247 додато и 2399 уклоњено
  1. 8 4
      app/build.gradle.kts
  2. 2 2
      app/src/main/java/io/legado/app/ui/about/AboutScreen.kt
  3. 22 18
      app/src/main/java/io/legado/app/ui/about/AppLogSheet.kt
  4. 178 56
      app/src/main/java/io/legado/app/ui/book/bookmark/AllBookmarkScreen.kt
  5. 2 2
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt
  6. 5 2
      app/src/main/java/io/legado/app/ui/book/group/GroupEditSheet.kt
  7. 41 53
      app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookScreen.kt
  8. 35 51
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt
  9. 82 112
      app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt
  10. 60 66
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt
  11. 29 34
      app/src/main/java/io/legado/app/ui/book/toc/rule/TxtRuleScreen.kt
  12. 2 2
      app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt
  13. 171 173
      app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigScreen.kt
  14. 2 2
      app/src/main/java/io/legado/app/ui/config/coverConfig/CoverConfigScreen.kt
  15. 82 95
      app/src/main/java/io/legado/app/ui/config/otherConfig/DirectLinkUploadBottomSheet.kt
  16. 60 88
      app/src/main/java/io/legado/app/ui/config/otherConfig/OtherConfigScreen.kt
  17. 2 2
      app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt
  18. 47 57
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt
  19. 14 23
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeListDialog.kt
  20. 5 7
      app/src/main/java/io/legado/app/ui/dict/rule/DictRuleEditSheet.kt
  21. 27 41
      app/src/main/java/io/legado/app/ui/dict/rule/DictRuleScreen.kt
  22. 4 5
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt
  23. 15 20
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt
  24. 36 67
      app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt
  25. 104 113
      app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt
  26. 16 29
      app/src/main/java/io/legado/app/ui/main/rss/RssScreen.kt
  27. 3 2
      app/src/main/java/io/legado/app/ui/replace/ReplaceEditRoute.kt
  28. 115 35
      app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt
  29. 83 115
      app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt
  30. 260 283
      app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditScreen.kt
  31. 38 62
      app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt
  32. 36 39
      app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt
  33. 47 53
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceScreen.kt
  34. 53 60
      app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubScreen.kt
  35. 33 1
      app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt
  36. 9 9
      app/src/main/java/io/legado/app/ui/theme/AppTheme.kt
  37. 0 2
      app/src/main/java/io/legado/app/ui/theme/ThemeResolver.kt
  38. 215 16
      app/src/main/java/io/legado/app/ui/widget/components/AppTextField.kt
  39. 2 4
      app/src/main/java/io/legado/app/ui/widget/components/GroupManageBottomSheet.kt
  40. 16 10
      app/src/main/java/io/legado/app/ui/widget/components/SearchBarSection.kt
  41. 170 0
      app/src/main/java/io/legado/app/ui/widget/components/alert/AppAlertDialog.kt
  42. 21 37
      app/src/main/java/io/legado/app/ui/widget/components/bookmark/BookmarkEditSheet.kt
  43. 1 1
      app/src/main/java/io/legado/app/ui/widget/components/bookmark/BookmarkItem.kt
  44. 2 1
      app/src/main/java/io/legado/app/ui/widget/components/button/AlertButton.kt
  45. 0 86
      app/src/main/java/io/legado/app/ui/widget/components/button/AnimatedActionButton.kt
  46. 332 0
      app/src/main/java/io/legado/app/ui/widget/components/button/MediumIconButton.kt
  47. 41 0
      app/src/main/java/io/legado/app/ui/widget/components/button/MiuixButton.kt
  48. 103 59
      app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt
  49. 81 13
      app/src/main/java/io/legado/app/ui/widget/components/button/TopBarButton.kt
  50. 8 0
      app/src/main/java/io/legado/app/ui/widget/components/card/GlassCard.kt
  51. 60 0
      app/src/main/java/io/legado/app/ui/widget/components/checkBox/AppCheckbox.kt
  52. 4 5
      app/src/main/java/io/legado/app/ui/widget/components/checkBox/CheckboxItem.kt
  53. 3 2
      app/src/main/java/io/legado/app/ui/widget/components/cover/BookCover.kt
  54. 5 5
      app/src/main/java/io/legado/app/ui/widget/components/cover/Cover.kt
  55. 44 18
      app/src/main/java/io/legado/app/ui/widget/components/dialog/TextListInputDialog.kt
  56. 2 1
      app/src/main/java/io/legado/app/ui/widget/components/divider/PillDivider.kt
  57. 36 105
      app/src/main/java/io/legado/app/ui/widget/components/filePicker/FilePickerSheet.kt
  58. 8 17
      app/src/main/java/io/legado/app/ui/widget/components/heatmap/HeatmapCalendarComponents.kt
  59. 3 2
      app/src/main/java/io/legado/app/ui/widget/components/heatmap/RememberDateUtil.kt
  60. 17 19
      app/src/main/java/io/legado/app/ui/widget/components/importComponents/ImportComponents.kt
  61. 39 0
      app/src/main/java/io/legado/app/ui/widget/components/list/TopFloatingStickyItem.kt
  62. 2 2
      app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenu.kt
  63. 1 1
      app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt
  64. 90 0
      app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/OptionSheet.kt
  65. 67 75
      app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt
  66. 14 25
      app/src/main/java/io/legado/app/ui/widget/components/rules/RuleListScaffold.kt
  67. 2 2
      app/src/main/java/io/legado/app/ui/widget/components/settingItem/ClickableSettingItem.kt
  68. 4 4
      app/src/main/java/io/legado/app/ui/widget/components/settingItem/CompactSettingItems.kt
  69. 2 2
      app/src/main/java/io/legado/app/ui/widget/components/settingItem/ListSettingItem.kt
  70. 2 2
      app/src/main/java/io/legado/app/ui/widget/components/settingItem/SwitchSettingItem.kt
  71. 0 2
      app/src/main/java/io/legado/app/ui/widget/components/text/AppText.kt
  72. 5 2
      app/src/main/java/io/legado/app/ui/widget/components/topbar/DynamicTopAppBar.kt
  73. 3 4
      app/src/main/java/io/legado/app/ui/widget/components/topbar/GlassMediumFlexibleTopAppBar.kt
  74. 9 4
      gradle.properties
  75. 13 0
      gradle/gradle-daemon-jvm.properties
  76. 10 6
      gradle/libs.versions.toml
  77. 1 1
      gradle/wrapper/gradle-wrapper.properties
  78. 0 34
      modules/book/build.gradle
  79. 36 0
      modules/book/build.gradle.kts
  80. 0 47
      modules/rhino/build.gradle
  81. 42 0
      modules/rhino/build.gradle.kts
  82. 3 0
      settings.gradle

+ 8 - 4
app/build.gradle.kts

@@ -29,7 +29,7 @@ val appName = "legado"
 val projectVersionName = "$versionMajor.$versionMinor.$versionPatch"
 
 android {
-    compileSdk = 36
+    compileSdk = 37
     namespace = "io.legado.app"
 
     kotlin {
@@ -56,7 +56,7 @@ android {
     defaultConfig {
         applicationId = "io.legato.kazusa"
         minSdk = 26
-        targetSdk = 36
+        targetSdk = 37
         versionCode = System.getenv("COMMIT_NUMBER")?.toInt()?.let { 10000 + it } ?: 32640
         versionName = System.getenv("APP_VERSION_NAME") ?: projectVersionName
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -285,8 +285,12 @@ dependencies {
     implementation(libs.material.kolor)
     implementation(libs.haze.core)
     implementation(libs.haze.materials)
-    implementation(libs.miuix)
-    implementation(libs.miuix.icons)
+    implementation(libs.miuix.ui.android)
+    implementation(libs.miuix.preference.android)
+    implementation(libs.miuix.icons.android)
+    //implementation(libs.miuix.blur.android)
+    implementation(libs.miuix.core)
+    implementation(libs.miuix.shapes.android)
     implementation(libs.capsule)
     implementation(libs.backdrop)
     implementation(libs.timber)

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

@@ -31,7 +31,7 @@ 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.SplicedColumnGroup
-import io.legado.app.ui.widget.components.button.TopbarNavigationButton
+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
@@ -60,7 +60,7 @@ fun AboutScreen(
             GlassMediumFlexibleTopAppBar(
                 title = stringResource(R.string.about),
                 navigationIcon = {
-                    TopbarNavigationButton(onClick = onBack)
+                    TopBarNavigationButton(onClick = onBack)
                 },
                 scrollBehavior = scrollBehavior
             )

+ 22 - 18
app/src/main/java/io/legado/app/ui/about/AppLogSheet.kt

@@ -5,6 +5,7 @@ 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
@@ -13,12 +14,10 @@ import androidx.compose.foundation.text.selection.SelectionContainer
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.DeleteSweep
-import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -31,6 +30,7 @@ import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.constant.AppLog
 import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
 import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.LogUtils
@@ -82,24 +82,28 @@ fun AppLogSheet(
         }
     }
 
-    showStackTrace?.let { stackTrace ->
-        AlertDialog(
-            onDismissRequest = { showStackTrace = null },
-            title = { AppText("Log") },
-            text = {
-                SelectionContainer {
-                    Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
-                        AppText(text = stackTrace, style = LegadoTheme.typography.bodySmall)
-                    }
-                }
-            },
-            confirmButton = {
-                TextButton(onClick = { showStackTrace = null }) {
-                    AppText(stringResource(android.R.string.ok))
+
+    AppAlertDialog(
+        data = showStackTrace,
+        onDismissRequest = { showStackTrace = null },
+        title = "Log",
+        content = { stackTrace ->
+            SelectionContainer {
+                Column(
+                    modifier = Modifier
+                        .heightIn(max = 400.dp)
+                        .verticalScroll(rememberScrollState())
+                ) {
+                    AppText(
+                        text = stackTrace,
+                        style = LegadoTheme.typography.bodySmall
+                    )
                 }
             }
-        )
-    }
+        },
+        confirmText = stringResource(android.R.string.ok),
+        onConfirm = { showStackTrace = null }
+    )
 }
 
 @Composable

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

@@ -6,11 +6,16 @@ import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.tween
 import androidx.compose.animation.expandVertically
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.shrinkVertically
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -24,33 +29,46 @@ import androidx.compose.material.icons.filled.UnfoldLess
 import androidx.compose.material.icons.filled.UnfoldMore
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.ListItemDefaults
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
 import io.legado.app.data.entities.Bookmark
-import io.legado.app.ui.widget.CollapsibleHeader
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.theme.ThemeResolver
+import io.legado.app.ui.theme.adaptiveHorizontalPadding
 import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.EmptyMessageView
 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
-import io.legado.app.ui.widget.components.button.TopbarNavigationButton
+import io.legado.app.ui.widget.components.button.TopBarActionButton
+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.card.TextCard
 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.list.TopFloatingStickyItem
 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.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
+import kotlinx.coroutines.launch
 import org.koin.androidx.compose.koinViewModel
+import top.yukonga.miuix.kmp.theme.MiuixTheme
 
 @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class,
     ExperimentalMaterial3ExpressiveApi::class
@@ -71,6 +89,7 @@ fun AllBookmarkScreen(
     val searchText = uiState.searchQuery
     val collapsedGroups = uiState.collapsedGroups
     val bookmarksGrouped = uiState.bookmarks
+    val bookmarkGroups = remember(bookmarksGrouped) { bookmarksGrouped.entries.toList() }
     val allKeys = bookmarksGrouped.keys
     val isAllCollapsed =
         allKeys.isNotEmpty() && allKeys.all { collapsedGroups.contains(it.toString()) }
@@ -82,6 +101,21 @@ fun AllBookmarkScreen(
     var showBottomSheet by remember { mutableStateOf(false) }
     var pendingExportIsMd by remember { mutableStateOf(false) }
     val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
+    val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
+    val stickyGroup by remember(bookmarkGroups, collapsedGroups, listState) {
+        derivedStateOf {
+            val firstVisibleIndex = listState.firstVisibleItemIndex
+            val firstVisibleGroup = bookmarkGroups.getOrNull(firstVisibleIndex)
+                ?: return@derivedStateOf null
+            val isCollapsed = collapsedGroups.contains(firstVisibleGroup.key.toString())
+            val shouldStick = firstVisibleIndex > 0 || listState.firstVisibleItemScrollOffset > 24
+            if (!isCollapsed && shouldStick) {
+                firstVisibleGroup.key
+            } else {
+                null
+            }
+        }
+    }
 
     val exportLauncher = rememberLauncherForActivityResult(
         contract = ActivityResultContracts.OpenDocumentTree()
@@ -100,28 +134,31 @@ fun AllBookmarkScreen(
                     title = "所有书签",
                     scrollBehavior = scrollBehavior,
                     navigationIcon = {
-                        TopbarNavigationButton(onClick = onBack)
+                        TopBarNavigationButton(onClick = onBack)
                     },
                     actions = {
                         if (bookmarksGrouped.isNotEmpty()) {
-                            IconButton(onClick = { viewModel.toggleAllCollapse(allKeys) }) {
-                                Icon(
-                                    imageVector = if (isAllCollapsed) Icons.Default.UnfoldMore else Icons.Default.UnfoldLess,
-                                    contentDescription = null
-                                )
-                            }
-                        }
-                        IconButton(onClick = {
-                            showSearch = !showSearch
-                            if (!showSearch) {
-                                viewModel.onSearchQueryChanged("")
-                            }
-                        }) {
-                            Icon(Icons.Default.Search, contentDescription = "Search")
-                        }
-                        IconButton(onClick = { showMenu = true }) {
-                            Icon(Icons.Default.MoreVert, contentDescription = "Menu")
+                            TopBarActionButton(
+                                onClick = { viewModel.toggleAllCollapse(allKeys) },
+                                imageVector = if (isAllCollapsed) Icons.Default.UnfoldMore else Icons.Default.UnfoldLess,
+                                contentDescription = null
+                            )
                         }
+                        TopBarActionButton(
+                            onClick = {
+                                showSearch = !showSearch
+                                if (!showSearch) {
+                                    viewModel.onSearchQueryChanged("")
+                                }
+                            },
+                            imageVector = Icons.Default.Search,
+                            contentDescription = null
+                        )
+                        TopBarActionButton(
+                            onClick = { showMenu = true },
+                            imageVector = Icons.Default.MoreVert,
+                            contentDescription = "Menu"
+                        )
                         RoundDropdownMenu(
                             expanded = showMenu,
                             onDismissRequest = { showMenu = false }
@@ -147,6 +184,7 @@ fun AllBookmarkScreen(
                 )
 
                 AnimatedVisibility(
+                    modifier = Modifier.adaptiveHorizontalPadding(),
                     visible = showSearch,
                     enter = expandVertically() + fadeIn(),
                     exit = shrinkVertically() + fadeOut()
@@ -182,7 +220,6 @@ fun AllBookmarkScreen(
                     }
 
                     "EMPTY" -> {
-
                         EmptyMessageView(
                             message = "没有书签!",
                             modifier = Modifier
@@ -191,46 +228,87 @@ fun AllBookmarkScreen(
                     }
 
                     "CONTENT" -> {
-                        FastScrollLazyColumn(
-                            state = listState,
+                        Box(
                             modifier = Modifier.fillMaxSize()
                         ) {
-                            uiState.bookmarks.forEach { (headerKey, bookmarks) ->
-                                val isCollapsed = collapsedGroups.contains(headerKey.toString())
+                            FastScrollLazyColumn(
+                                state = listState,
+                                modifier = Modifier.fillMaxSize()
+                            ) {
+                                items(
+                                    items = bookmarkGroups,
+                                    key = { it.key.toString() }
+                                ) { (headerKey, bookmarks) ->
+                                    val isCollapsed = collapsedGroups.contains(headerKey.toString())
 
-                                stickyHeader(key = "${Scroller.STICKY_HEADER_KEY_PREFIX}${headerKey}") {
-                                    CollapsibleHeader(
-                                        modifier = Modifier.animateItem(),
-                                        title = headerKey.bookName,
-                                        subtitle = headerKey.bookAuthor,
-                                        isCollapsed = isCollapsed,
-                                        onToggle = { viewModel.toggleGroupCollapse(headerKey) }
-                                    )
-                                }
+                                    GlassCard(
+                                        modifier = Modifier
+                                            .animateItem()
+                                            .fillMaxWidth()
+                                            .padding(horizontal = 16.dp, vertical = 4.dp),
+                                        shape = MaterialTheme.shapes.medium,
+                                        containerColor = LegadoTheme.colorScheme.surfaceContainer
+                                    ) {
+                                        BookmarkGroupHeaderContent(
+                                            title = headerKey.bookName,
+                                            subtitle = headerKey.bookAuthor,
+                                            isCollapsed = isCollapsed,
+                                            onToggle = { viewModel.toggleGroupCollapse(headerKey) },
+                                            isMiuix = isMiuix
+                                        )
 
-                                if (!isCollapsed) {
-                                    items(
-                                        items = bookmarks,
-                                        key = { it.id }
-                                    ) { bookmarkUi ->
-                                        BookmarkItem(
-                                            bookmark = bookmarkUi.rawBookmark,
-                                            modifier = Modifier
-                                                .animateItem()
-                                                .fillMaxWidth(),
-                                            isDur = false,
-                                            onClick = {
-                                                editingBookmark = bookmarkUi.rawBookmark
-                                                showBottomSheet = true
-                                            },
-                                            onLongClick = {
-                                                editingBookmark = bookmarkUi.rawBookmark
-                                                showBottomSheet = true
+                                        AnimatedVisibility(
+                                            visible = !isCollapsed && bookmarks.isNotEmpty()
+                                        ) {
+                                            Column() {
+                                                HorizontalDivider(
+                                                    color = LegadoTheme.colorScheme.surface
+                                                )
+                                                bookmarks.forEach { bookmarkUi ->
+                                                    BookmarkItem(
+                                                        bookmark = bookmarkUi.rawBookmark,
+                                                        modifier = Modifier.fillMaxWidth(),
+                                                        isDur = false,
+                                                        onClick = {
+                                                            editingBookmark = bookmarkUi.rawBookmark
+                                                            showBottomSheet = true
+                                                        },
+                                                        onLongClick = {
+                                                            editingBookmark = bookmarkUi.rawBookmark
+                                                            showBottomSheet = true
+                                                        }
+                                                    )
+                                                }
                                             }
-                                        )
+                                        }
                                     }
                                 }
                             }
+
+                            TopFloatingStickyItem(
+                                item = stickyGroup,
+                                modifier = Modifier
+                                    .padding(top = 4.dp, start = 8.dp)
+                            ) { group ->
+                                TextCard(
+                                    text = group.bookName,
+                                    textStyle = LegadoTheme.typography.labelLarge,
+                                    backgroundColor = LegadoTheme.colorScheme.cardContainer,
+                                    contentColor = LegadoTheme.colorScheme.onCardContainer,
+                                    cornerRadius = 8.dp,
+                                    horizontalPadding = 8.dp,
+                                    verticalPadding = 6.dp,
+                                    onClick = {
+                                        scope.launch {
+                                            val index =
+                                                bookmarkGroups.indexOfFirst { it.key == group }
+                                            if (index >= 0) {
+                                                listState.animateScrollToItem(index)
+                                            }
+                                        }
+                                    }
+                                )
+                            }
                         }
                     }
                 }
@@ -255,3 +333,47 @@ fun AllBookmarkScreen(
         )
     }
 }
+
+@Composable
+private fun BookmarkGroupHeaderContent(
+    modifier: Modifier = Modifier,
+    title: String,
+    subtitle: String?,
+    isCollapsed: Boolean,
+    onToggle: () -> Unit,
+    isMiuix: Boolean
+) {
+
+    val contentColor by animateColorAsState(
+        if (isMiuix) MiuixTheme.colorScheme.primary else MaterialTheme.colorScheme.primary,
+        animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
+        label = "CardColor"
+    )
+
+    ListItem(
+        modifier = modifier
+            .fillMaxWidth()
+            .combinedClickable(onClick = onToggle),
+        colors = ListItemDefaults.colors(
+            containerColor = Color.Transparent,
+            supportingColor = LegadoTheme.colorScheme.onSurfaceVariant,
+            trailingIconColor = LegadoTheme.colorScheme.onSurfaceVariant
+        ),
+        headlineContent = {
+            AppText(
+                text = title,
+                style = LegadoTheme.typography.titleMedium,
+                color = contentColor
+            )
+        },
+        supportingContent = {
+            subtitle?.let {
+                AppText(
+                    text = it,
+                    style = LegadoTheme.typography.labelMedium,
+                    color = LegadoTheme.colorScheme.onSurfaceVariant
+                )
+            }
+        }
+    )
+}

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

@@ -82,7 +82,7 @@ import io.legado.app.ui.widget.components.AppScaffold
 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
-import io.legado.app.ui.widget.components.button.TopbarNavigationButton
+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.cover.Cover
 import io.legado.app.ui.widget.components.explore.ExploreKindItem
@@ -296,7 +296,7 @@ fun ExploreShowScreen(
                 ),
                 title = selectedTitle ?: title,
                 navigationIcon = {
-                    TopbarNavigationButton(onClick = onBack)
+                    TopBarNavigationButton(onClick = onBack)
                 },
                 actions = {
                     Row(

+ 5 - 2
app/src/main/java/io/legado/app/ui/book/group/GroupEditSheet.kt

@@ -35,7 +35,7 @@ import androidx.compose.ui.unit.dp
 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.cover.BookCover
 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
@@ -115,7 +115,10 @@ fun GroupEditContent(
             verticalAlignment = Alignment.Top,
             horizontalArrangement = Arrangement.spacedBy(16.dp)
         ) {
-            Cover(
+            BookCover(
+                name = null,
+                author = null,
+                sourceOrigin = group?.groupName,
                 path = coverPath,
                 modifier = Modifier
                     .width(96.dp)

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

@@ -83,6 +83,7 @@ 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
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.button.SmallIconButton
 import io.legado.app.ui.widget.components.button.SmallTonalIconButton
 import io.legado.app.ui.widget.components.card.GlassCard
@@ -154,51 +155,39 @@ fun RemoteBookScreen(
             }
         }
 
-    dialogState?.let { state ->
-        when (state) {
-            is RemoteBookDialog.DownloadArchive -> {
-                AlertDialog(
-                    onDismissRequest = { dialogState = null },
-                    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
-                        }) {
-                            AppText(stringResource(android.R.string.ok))
-                        }
-                    },
-                    dismissButton = {
-                        TextButton(onClick = { dialogState = null }) {
-                            AppText(stringResource(android.R.string.cancel))
-                        }
-                    }
-                )
-            }
 
-            is RemoteBookDialog.ReImport -> {
-                AlertDialog(
-                    onDismissRequest = { dialogState = null },
-                    title = { AppText("是否重新加入书架?") },
-                    text = { AppText("将会覆盖书籍") },
-                    confirmButton = {
-                        OutlinedButton(onClick = {
-                            scope.launch { viewModel.addToBookshelf(setOf(state.remoteBook)) }
-                            dialogState = null
-                        }) {
-                            AppText(stringResource(android.R.string.ok))
-                        }
-                    },
-                    dismissButton = {
-                        TextButton(onClick = { dialogState = null }) {
-                            AppText(stringResource(android.R.string.cancel))
-                        }
-                    }
-                )
-            }
-        }
-    }
+
+    AppAlertDialog(
+        data = dialogState as? RemoteBookDialog.DownloadArchive,
+        onDismissRequest = { dialogState = null },
+        title = stringResource(R.string.draw),
+        content = {
+            AppText(stringResource(R.string.archive_not_found))
+        },
+        confirmText = stringResource(android.R.string.ok),
+        onConfirm = { state ->
+            scope.launch { viewModel.addToBookshelf(setOf(state.remoteBook)) }
+            dialogState = null
+        },
+        dismissText = stringResource(android.R.string.cancel),
+        onDismiss = { dialogState = null }
+    )
+
+    AppAlertDialog(
+        data = dialogState as? RemoteBookDialog.ReImport,
+        onDismissRequest = { dialogState = null },
+        title = "是否重新加入书架?",
+        content = {
+            AppText("将会覆盖书籍")
+        },
+        confirmText = stringResource(android.R.string.ok),
+        onConfirm = { state ->
+            scope.launch { viewModel.addToBookshelf(setOf(state.remoteBook)) }
+            dialogState = null
+        },
+        dismissText = stringResource(android.R.string.cancel),
+        onDismiss = { dialogState = null }
+    )
 
     AppModalBottomSheet(
         show = showSheet != null,
@@ -378,7 +367,7 @@ private fun ServersSheetContent(
         Row(
             modifier = Modifier
                 .fillMaxWidth()
-                .padding(horizontal = 16.dp, vertical = 8.dp),
+                .padding(vertical = 8.dp),
             verticalAlignment = Alignment.CenterVertically,
             horizontalArrangement = Arrangement.SpaceBetween
         ) {
@@ -430,16 +419,16 @@ private fun ServerItem(
     onDelete: (() -> Unit)? = null
 ) {
     val containerColor = if (isSelected) {
-        MaterialTheme.colorScheme.secondaryContainer
+        LegadoTheme.colorScheme.secondaryContainer
     } else {
-        MaterialTheme.colorScheme.surface
+        LegadoTheme.colorScheme.surface
     }
 
     Card(
         onClick = onClick,
         modifier = modifier
             .fillMaxWidth()
-            .padding(horizontal = 16.dp, vertical = 4.dp),
+            .padding(vertical = 4.dp),
         shape = MaterialTheme.shapes.medium,
         colors = CardDefaults.cardColors(
             containerColor = containerColor
@@ -466,7 +455,7 @@ private fun ServerItem(
                     AppText(
                         text = it,
                         style = LegadoTheme.typography.bodySmall,
-                        color = MaterialTheme.colorScheme.onSurfaceVariant,
+                        color = LegadoTheme.colorScheme.onSurfaceVariant,
                         maxLines = 1,
                         overflow = TextOverflow.Ellipsis
                     )
@@ -512,7 +501,6 @@ private fun ServerConfigSheetContent(
         modifier = Modifier
             .fillMaxWidth()
             .navigationBarsPadding()
-            .padding(16.dp)
             .verticalScroll(rememberScrollState())
     ) {
         AppText(
@@ -628,9 +616,9 @@ private fun PathNavigationBar(
                         style = LegadoTheme.typography.labelSmall,
                         fontWeight = if (isLast) FontWeight.SemiBold else FontWeight.Medium,
                         color = if (isLast)
-                            MaterialTheme.colorScheme.primary
+                            LegadoTheme.colorScheme.primary
                         else
-                            MaterialTheme.colorScheme.onSurfaceVariant,
+                            LegadoTheme.colorScheme.onSurfaceVariant,
                         modifier = Modifier
                             .clip(MaterialTheme.shapes.small)
                             .then(
@@ -646,7 +634,7 @@ private fun PathNavigationBar(
                             imageVector = Icons.Default.ChevronRight,
                             contentDescription = null,
                             modifier = Modifier.size(12.dp),
-                            tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
+                            tint = LegadoTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
                         )
                     }
                 }

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

@@ -20,16 +20,11 @@ import androidx.compose.material.icons.filled.FolderOpen
 import androidx.compose.material.icons.filled.ImageSearch
 import androidx.compose.material.icons.filled.Replay
 import androidx.compose.material.icons.filled.Save
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.ExposedDropdownMenuAnchorType
 import androidx.compose.material3.ExposedDropdownMenuBox
 import androidx.compose.material3.ExposedDropdownMenuDefaults
-import androidx.compose.material3.Icon
-import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.OutlinedTextField
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
@@ -46,11 +41,14 @@ 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.AppTextField
+import io.legado.app.ui.widget.components.button.MediumOutlinedIconButton
 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.button.TopBarNavigationButton
+import io.legado.app.ui.widget.components.cover.BookCover
+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.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
@@ -73,13 +71,13 @@ fun BookInfoEditScreen(
             GlassMediumFlexibleTopAppBar(
                 title = stringResource(id = R.string.book_info_edit),
                 navigationIcon = {
-                    TopbarNavigationButton(
+                    TopBarNavigationButton(
                         onClick = onBack,
                         style = TopBarButtonVariant.Outlined
                     )
                 },
                 actions = {
-                    TopbarNavigationButton(
+                    TopBarNavigationButton(
                         onClick = { viewModel.save(onSave) },
                         imageVector = Icons.Default.Save
                     )
@@ -125,11 +123,12 @@ fun BookInfoEditContent(
             horizontalArrangement = Arrangement.spacedBy(16.dp),
             verticalAlignment = Alignment.CenterVertically
         ) {
-            Cover(
+            BookCover(
+                name = uiState.name,
+                author = uiState.author,
                 path = uiState.coverUrl,
                 modifier = Modifier
                     .width(110.dp)
-                    .height(154.dp)
             )
             Column(
                 modifier = Modifier.weight(1f),
@@ -137,7 +136,7 @@ fun BookInfoEditContent(
                 horizontalAlignment = Alignment.CenterHorizontally
             ) {
                 Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
-                    OutlinedButton(
+                    MediumOutlinedIconButton(
                         onClick = {
                             (context as? BookInfoEditActivity)?.showDialogFragment(
                                 ChangeCoverDialog(
@@ -146,31 +145,16 @@ fun BookInfoEditContent(
                                 )
                             )
                         },
-                        shapes = ButtonDefaults.shapes()
-                    ) {
-                        Icon(
-                            Icons.Default.ImageSearch,
-                            contentDescription = stringResource(id = R.string.default_cover)
-                        )
-                    }
-                    OutlinedButton(
+                        icon = Icons.Default.ImageSearch
+                    )
+                    MediumOutlinedIconButton(
                         onClick = { selectCover.launch() },
-                        shapes = ButtonDefaults.shapes()
-                    ) {
-                        Icon(
-                            Icons.Default.FolderOpen,
-                            contentDescription = stringResource(id = R.string.default_cover)
-                        )
-                    }
-                    OutlinedButton(
+                        icon = Icons.Default.FolderOpen
+                    )
+                    MediumOutlinedIconButton(
                         onClick = { viewModel.resetCover() },
-                        shapes = ButtonDefaults.shapes()
-                    ) {
-                        Icon(
-                            Icons.Default.Replay,
-                            contentDescription = stringResource(id = R.string.default_cover)
-                        )
-                    }
+                        icon = Icons.Default.Replay
+                    )
                 }
                 Spacer(modifier = Modifier.height(4.dp))
                 BookTypeDropdown(
@@ -188,38 +172,38 @@ fun BookInfoEditContent(
             onCheckedChange = { viewModel.onFixedTypeChange(it) }
         )
         Spacer(modifier = Modifier.height(16.dp))
-        OutlinedTextField(
+        AppTextField(
             value = uiState.name,
             onValueChange = { viewModel.onNameChange(it) },
-            label = { AppText("书名") },
+            label = "书名",
             modifier = Modifier.fillMaxWidth()
         )
         Spacer(modifier = Modifier.height(8.dp))
-        OutlinedTextField(
+        AppTextField(
             value = uiState.author,
             onValueChange = { viewModel.onAuthorChange(it) },
-            label = { AppText("作者") },
+            label = "作者",
             modifier = Modifier.fillMaxWidth()
         )
         Spacer(modifier = Modifier.height(8.dp))
-        OutlinedTextField(
+        AppTextField(
             value = uiState.coverUrl ?: "",
             onValueChange = { viewModel.onCoverUrlChange(it) },
-            label = { AppText("封面链接") },
+            label = "封面链接",
             modifier = Modifier.fillMaxWidth()
         )
         Spacer(modifier = Modifier.height(8.dp))
-        OutlinedTextField(
+        AppTextField(
             value = uiState.intro ?: "",
             onValueChange = { viewModel.onIntroChange(it) },
-            label = { AppText("简介") },
+            label = "简介",
             modifier = Modifier.fillMaxWidth()
         )
         Spacer(modifier = Modifier.height(8.dp))
-        OutlinedTextField(
+        AppTextField(
             value = uiState.remark ?: "",
             onValueChange = { viewModel.onRemarkChange(it) },
-            label = { AppText("备注") },
+            label = "备注",
             modifier = Modifier.fillMaxWidth()
         )
     }
@@ -247,11 +231,11 @@ fun BookTypeDropdown(
         expanded = expanded,
         onExpandedChange = { expanded = !expanded }
     ) {
-        OutlinedTextField(
+        AppTextField(
             state = textFieldState,
             readOnly = true,
             lineLimits = TextFieldLineLimits.SingleLine,
-            label = { AppText("书籍类型") },
+            label = "书籍类型",
             trailingIcon = {
                 ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
             },
@@ -263,13 +247,13 @@ fun BookTypeDropdown(
                 ),
         )
 
-        ExposedDropdownMenu(
+        RoundDropdownMenu(
             expanded = expanded,
             onDismissRequest = { expanded = false }
         ) {
             bookTypes.forEach { option ->
-                DropdownMenuItem(
-                    text = { AppText(option) },
+                RoundDropdownMenuItem(
+                    text = option,
                     onClick = {
                         onTypeSelected(option)
                         expanded = false

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

@@ -74,14 +74,17 @@ 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.theme.LegadoTheme
+import io.legado.app.ui.theme.adaptiveHorizontalPadding
 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.SearchBarSection
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 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.button.TopBarNavigationButton
 import io.legado.app.ui.widget.components.card.GlassCard
+import io.legado.app.ui.widget.components.checkBox.CheckboxItem
 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
@@ -160,7 +163,7 @@ fun ReadRecordScreen(
                         )
                     },
                     navigationIcon = {
-                        TopbarNavigationButton(onClick = onBackClick)
+                        TopBarNavigationButton(onClick = onBackClick)
                     },
                     actions = {
                         AppIconButton(onClick = {
@@ -192,7 +195,10 @@ fun ReadRecordScreen(
                     scrollBehavior = scrollBehavior
                 )
 
-                AnimatedVisibility(visible = showSearch) {
+                AnimatedVisibility(
+                    modifier = Modifier.adaptiveHorizontalPadding(),
+                    visible = showSearch
+                ) {
                     SearchBarSection(
                         query = state.searchKey ?: "",
                         onQueryChange = { viewModel.setSearchKey(it) }
@@ -264,57 +270,34 @@ fun ReadRecordScreen(
         }
     }
 
-    if (pendingDeleteAction != null) {
-        var skipDeleteConfirmTemp by remember { mutableStateOf(false) }
-        AlertDialog(
-            onDismissRequest = {
-                pendingDeleteAction = null
-            },
-            title = { AppText("确认删除") },
-            text = {
-                Column {
-                    AppText("确定要删除这条记录吗?")
-                    Spacer(modifier = Modifier.height(8.dp))
-                    Row(
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .clip(MaterialTheme.shapes.small)
-                            .background(
-                                color = if (skipDeleteConfirmTemp) MaterialTheme.colorScheme.secondaryContainer else Color.Transparent,
-                                shape = MaterialTheme.shapes.small
-                            )
-                            .clickable { skipDeleteConfirmTemp = !skipDeleteConfirmTemp }
-                            .padding(12.dp),
-                        verticalAlignment = Alignment.CenterVertically
-                    ) {
-                        Checkbox(checked = skipDeleteConfirmTemp, onCheckedChange = null)
-                        AppText(
-                            text = "不再提示",
-                            modifier = Modifier.padding(start = 8.dp),
-                            style = LegadoTheme.typography.bodyMedium
-                        )
-                    }
-                }
-            },
-            confirmButton = {
-                AlertButton(
-                    onClick = {
-                        pendingDeleteAction?.invoke()
-                        pendingDeleteAction = null
-                        skipDeleteConfirm = skipDeleteConfirmTemp
-                    },
-                    text = "删除"
+    var skipDeleteConfirmTemp by remember(pendingDeleteAction != null) { mutableStateOf(false) }
+
+    AppAlertDialog(
+        data = pendingDeleteAction,
+        onDismissRequest = { pendingDeleteAction = null },
+        title = "确认删除",
+        content = { _ ->
+            Column {
+                AppText("确定要删除这条记录吗?")
+                Spacer(modifier = Modifier.height(8.dp))
+                CheckboxItem(
+                    title = "不再提示",
+                    checked = skipDeleteConfirmTemp,
+                    onCheckedChange = { skipDeleteConfirmTemp = it }
                 )
-            },
-            dismissButton = {
-                TextButton(onClick = {
-                    pendingDeleteAction = null
-                }) {
-                    AppText("取消")
-                }
             }
-        )
-    }
+        },
+        confirmText = "删除",
+        onConfirm = { action ->
+            action.invoke()
+            pendingDeleteAction = null
+            skipDeleteConfirm = skipDeleteConfirmTemp
+        },
+        dismissText = "取消",
+        onDismiss = {
+            pendingDeleteAction = null
+        }
+    )
 
     AppModalBottomSheet(
         show = showCalendar,
@@ -335,65 +318,52 @@ fun ReadRecordScreen(
         )
     }
 
+    var selectedAuthors by remember(mergeDialogData != null) {
+        mutableStateOf(
+            mergeDialogData?.let { (_, candidates) ->
+                candidates.map { it.bookAuthor }.toSet()
+            } ?: emptySet()
+        )
+    }
 
-    mergeDialogData?.let { (targetRecord, candidates) ->
-
-        var selectedAuthors by remember(targetRecord, candidates) {
-            mutableStateOf(candidates.map { it.bookAuthor }.toSet())
-        }
-
-        AlertDialog(
-            onDismissRequest = { mergeDialogData = null },
-            title = { AppText("合并阅读记录") },
-            text = {
-                Column {
-                    AppText("将以下作者的“${targetRecord.bookName}”合并到 ${targetRecord.bookAuthor.ifBlank { "未知作者" }}")
-                    Spacer(modifier = Modifier.height(8.dp))
-
-                    candidates.forEach { candidate ->
-                        val author = candidate.bookAuthor.ifBlank { "未知作者" }
-                        val isChecked = selectedAuthors.contains(candidate.bookAuthor)
-                        Row(
-                            modifier = Modifier
-                                .fillMaxWidth()
-                                .clickable {
-                                    selectedAuthors =
-                                        if (isChecked) selectedAuthors - candidate.bookAuthor
-                                        else selectedAuthors + candidate.bookAuthor
-                                }
-                                .padding(vertical = 6.dp),
-                            verticalAlignment = Alignment.CenterVertically
-                        ) {
-                            Checkbox(checked = isChecked, onCheckedChange = null)
-                            AppText(
-                                text = "$author(${formatDuring(candidate.readTime)})",
-                                modifier = Modifier.padding(start = 8.dp),
-                                style = LegadoTheme.typography.bodyMedium
-                            )
+    AppAlertDialog(
+        data = mergeDialogData,
+        onDismissRequest = { mergeDialogData = null },
+        title = "合并阅读记录",
+        content = { (targetRecord, candidates) ->
+            Column {
+                AppText("将以下作者的“${targetRecord.bookName}”合并到 ${targetRecord.bookAuthor.ifBlank { "未知作者" }}")
+                Spacer(modifier = Modifier.height(8.dp))
+
+                candidates.forEach { candidate ->
+                    val author = candidate.bookAuthor.ifBlank { "未知作者" }
+                    val isChecked = selectedAuthors.contains(candidate.bookAuthor)
+
+                    CheckboxItem(
+                        title = "$author(${formatDuring(candidate.readTime)})",
+                        checked = isChecked,
+                        onCheckedChange = { checked ->
+                            selectedAuthors = if (checked) {
+                                selectedAuthors + candidate.bookAuthor
+                            } else {
+                                selectedAuthors - candidate.bookAuthor
+                            }
                         }
-                    }
-                }
-            },
-            confirmButton = {
-                TextButton(
-                    onClick = {
-                        viewModel.mergeReadRecords(
-                            targetRecord,
-                            candidates.filter { selectedAuthors.contains(it.bookAuthor) }
-                        )
-                        mergeDialogData = null
-                    }
-                ) {
-                    AppText("合并")
-                }
-            },
-            dismissButton = {
-                TextButton(onClick = { mergeDialogData = null }) {
-                    AppText("取消")
+                    )
                 }
             }
-        )
-    }
+        },
+        confirmText = "合并",
+        onConfirm = { (targetRecord, candidates) ->
+            viewModel.mergeReadRecords(
+                targetRecord,
+                candidates.filter { selectedAuthors.contains(it.bookAuthor) }
+            )
+            mergeDialogData = null
+        },
+        dismissText = "取消",
+        onDismiss = { mergeDialogData = null }
+    )
 }
 
 @Composable
@@ -590,7 +560,7 @@ fun LazyListScope.renderListByMode(
                         modifier = Modifier.animateItem(),
                         startAction = SwipeAction(
                             icon = Icons.Default.Delete,
-                            background = MaterialTheme.colorScheme.error,
+                            background = LegadoTheme.colorScheme.error,
                             onSwipe = {
                                 onConfirmDelete { viewModel.deleteDetail(detail) }
                             }
@@ -613,7 +583,7 @@ fun LazyListScope.renderListByMode(
                         modifier = Modifier.animateItem(),
                         startAction = SwipeAction(
                             icon = Icons.Default.Delete,
-                            background = MaterialTheme.colorScheme.error,
+                            background = LegadoTheme.colorScheme.error,
                             onSwipe = {
                                 onConfirmDelete { viewModel.deleteSession(session) }
                             }
@@ -635,14 +605,14 @@ fun LazyListScope.renderListByMode(
                     modifier = Modifier.animateItem(),
                     startAction = SwipeAction(
                         icon = Icons.Default.Delete,
-                        background = MaterialTheme.colorScheme.error,
+                        background = LegadoTheme.colorScheme.error,
                         onSwipe = {
                             onConfirmDelete { viewModel.deleteReadRecord(record) }
                         }
                     ),
                     endAction = SwipeAction(
                         icon = Icons.Default.Merge,
-                        background = MaterialTheme.colorScheme.primary,
+                        background = LegadoTheme.colorScheme.primary,
                         onSwipe = {
                             onMergeClick(record)
                         }
@@ -693,7 +663,7 @@ fun LatestReadItem(
             AppText(
                 text = record.bookAuthor.ifBlank { "未知作者" },
                 style = LegadoTheme.typography.bodySmall,
-                color = MaterialTheme.colorScheme.outline,
+                color = LegadoTheme.colorScheme.outline,
                 maxLines = 1,
                 overflow = TextOverflow.Ellipsis
             )

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

@@ -36,19 +36,15 @@ import androidx.compose.material3.Card
 import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.FloatingActionButton
+import io.legado.app.ui.widget.components.AppFloatingActionButton
 import androidx.compose.material3.HorizontalDivider
 import androidx.compose.material3.Icon
 import androidx.compose.material3.LinearProgressIndicator
 import androidx.compose.material3.ListItem
+import androidx.compose.material3.ListItemDefaults
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.PlainTooltip
-import androidx.compose.material3.TooltipAnchorPosition
-import androidx.compose.material3.TooltipBox
-import androidx.compose.material3.TooltipDefaults
 import androidx.compose.material3.animateFloatingActionButton
-import androidx.compose.material3.rememberTooltipState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
@@ -64,14 +60,18 @@ 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.theme.LegadoTheme
+import io.legado.app.ui.theme.adaptiveHorizontalPadding
 import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.EmptyMessageView
 import io.legado.app.ui.widget.components.SearchBarSection
+import io.legado.app.ui.widget.components.button.MediumOutlinedButton
+import io.legado.app.ui.widget.components.button.MediumOutlinedIconButton
 import io.legado.app.ui.widget.components.button.SmallAnimatedActionButton
 import io.legado.app.ui.widget.components.button.SmallIconButton
 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.button.TopBarNavigationButton
 import io.legado.app.ui.widget.components.card.TextCard
+import io.legado.app.ui.widget.components.icon.AppIcon
 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
@@ -143,10 +143,9 @@ fun SearchContentScreen(
                     title = if (searchQuery.isNotBlank() && searchResults.isNotEmpty()) {
                         "共 ${searchResults.size} 条结果"
                     } else "搜索内容",
-                    navigationIcon = { TopbarNavigationButton(onClick = onBack) },
+                    navigationIcon = { TopBarNavigationButton(onClick = onBack) },
                     actions = {
                         Row(
-                            modifier = Modifier.padding(end = 8.dp),
                             horizontalArrangement = Arrangement.spacedBy(4.dp)
                         ) {
                             TopBarAnimatedActionButton(
@@ -170,11 +169,16 @@ fun SearchContentScreen(
                     },
                     scrollBehavior = scrollBehavior
                 )
-                SearchBarSection(
-                    query = searchQuery,
-                    scrollState = listState,
-                    onQueryChange = { viewModel.onQueryChange(it) }
-                )
+                Box(
+                    modifier = Modifier.adaptiveHorizontalPadding()
+                ) {
+                    SearchBarSection(
+                        query = searchQuery,
+                        scrollState = listState,
+                        onQueryChange = { viewModel.onQueryChange(it) }
+                    )
+                }
+
                 AnimatedVisibility(visible = isSearching) {
                     LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
                 }
@@ -182,39 +186,28 @@ fun SearchContentScreen(
         },
         floatingActionButton = {
             val fabVisible = (isSearching || searchResults.isNotEmpty()) && searchQuery.isNotBlank()
-            TooltipBox(
-                positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
-                    TooltipAnchorPosition.Above
+            AppFloatingActionButton(
+                modifier = Modifier.animateFloatingActionButton(
+                    visible = fabVisible,
+                    alignment = Alignment.BottomEnd,
                 ),
-                tooltip = {
-                    PlainTooltip {
-                        AppText(if (isSearching) "停止搜索" else "跳转到当前章节")
+                onClick = {
+                    if (isSearching) {
+                        viewModel.stopSearch()
+                    } else {
+                        scrollToCurrentChapter()
                     }
                 },
-                state = rememberTooltipState(),
+                tooltipText = if (isSearching) "停止搜索" else "跳转到当前章节"
             ) {
-                FloatingActionButton(
-                    modifier = Modifier.animateFloatingActionButton(
-                        visible = fabVisible,
-                        alignment = Alignment.BottomEnd,
-                    ),
-                    onClick = {
-                        if (isSearching) {
-                            viewModel.stopSearch()
-                        } else {
-                            scrollToCurrentChapter()
-                        }
-                    }
-                ) {
-                    AnimatedContent(
-                        targetState = isSearching,
-                        label = "FabIconTransition"
-                    ) { searching ->
-                        if (searching) {
-                            Icon(Icons.Default.Stop, contentDescription = "停止搜索")
-                        } else {
-                            Icon(Icons.Default.MyLocation, contentDescription = "定位当前章节")
-                        }
+                AnimatedContent(
+                    targetState = isSearching,
+                    label = "FabIconTransition"
+                ) { searching ->
+                    if (searching) {
+                        AppIcon(Icons.Default.Stop, contentDescription = "停止搜索")
+                    } else {
+                        AppIcon(Icons.Default.MyLocation, contentDescription = "定位当前章节")
                     }
                 }
             }
@@ -305,7 +298,8 @@ fun SearchHistoryList(
         Box(
             modifier = Modifier
                 .fillMaxWidth()
-                .padding(horizontal = 16.dp, vertical = 8.dp)
+                .adaptiveHorizontalPadding()
+                .padding(vertical = 4.dp),
         ) {
             AppText(
                 text = "搜索历史",
@@ -354,7 +348,11 @@ fun SearchHistoryList(
                                 icon = Icons.Default.Close,
                                 contentDescription = "删除"
                             )
-                        }
+                        },
+                        colors = ListItemDefaults.colors(
+                            containerColor = LegadoTheme.colorScheme.surface,
+                            contentColor = LegadoTheme.colorScheme.onSurface
+                        )
                     )
                 }
                 item {
@@ -365,18 +363,12 @@ fun SearchHistoryList(
                             .animateItem(),
                         contentAlignment = Alignment.Center
                     ) {
-                        OutlinedButton(
+                        MediumOutlinedButton(
                             onClick = onClearHistory,
-                            modifier = Modifier.fillMaxWidth(0.6f)
-                        ) {
-                            Icon(
-                                imageVector = Icons.Outlined.DeleteSweep,
-                                contentDescription = null,
-                                modifier = Modifier.size(18.dp)
-                            )
-                            Spacer(Modifier.width(8.dp))
-                            AppText("清除搜索历史")
-                        }
+                            modifier = Modifier.fillMaxWidth(0.6f),
+                            icon = Icons.Outlined.DeleteSweep,
+                            text = "清除搜索历史"
+                        )
                     }
                 }
             }
@@ -398,7 +390,7 @@ fun SearchResultItem(
             .padding(horizontal = 16.dp, vertical = 8.dp),
         colors = CardDefaults.cardColors(
             containerColor =
-                MaterialTheme.colorScheme.surfaceContainerLow
+                LegadoTheme.colorScheme.surfaceContainer
         )
     ) {
         Box(modifier = Modifier.padding(16.dp)) {
@@ -408,7 +400,7 @@ fun SearchResultItem(
                     text = buildAnnotatedString {
                         append(
                             result.getTitleSpannable(
-                                MaterialTheme.colorScheme.primary.toArgb()
+                                LegadoTheme.colorScheme.primary.toArgb()
                             )
                         )
                     },
@@ -416,16 +408,18 @@ fun SearchResultItem(
                 )
 
                 Spacer(modifier = Modifier.height(8.dp))
-                HorizontalDivider()
+                HorizontalDivider(
+                    color = LegadoTheme.colorScheme.surface
+                )
                 Spacer(modifier = Modifier.height(8.dp))
 
                 AppText(
                     text = buildAnnotatedString {
                         append(
                             result.getContentSpannable(
-                                textColor = MaterialTheme.colorScheme.onSurface.toArgb(),
-                                accentColor = MaterialTheme.colorScheme.primary.toArgb(),
-                                bgColor = MaterialTheme.colorScheme.primaryContainer.toArgb()
+                                textColor = LegadoTheme.colorScheme.onSurface.toArgb(),
+                                accentColor = LegadoTheme.colorScheme.primary.toArgb(),
+                                bgColor = LegadoTheme.colorScheme.primaryContainer.toArgb()
                             )
                         )
                     },
@@ -441,8 +435,8 @@ fun SearchResultItem(
                 if (isCurrentChapter) {
                     TextCard(
                         text = "当前章节",
-                        backgroundColor = MaterialTheme.colorScheme.secondaryContainer,
-                        contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
+                        backgroundColor = LegadoTheme.colorScheme.secondaryContainer,
+                        contentColor = LegadoTheme.colorScheme.onSecondaryContainer,
                         cornerRadius = 8.dp,
                         horizontalPadding = 4.dp,
                         verticalPadding = 2.dp,
@@ -452,8 +446,8 @@ fun SearchResultItem(
                 if (result.progressPercent > 0f) {
                     TextCard(
                         text = String.format("%.1f%%", result.progressPercent),
-                        backgroundColor = MaterialTheme.colorScheme.secondaryContainer,
-                        contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
+                        backgroundColor = LegadoTheme.colorScheme.secondaryContainer,
+                        contentColor = LegadoTheme.colorScheme.onSecondaryContainer,
                         cornerRadius = 8.dp,
                         horizontalPadding = 4.dp,
                         verticalPadding = 2.dp,

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

@@ -21,6 +21,7 @@ import androidx.compose.material3.Icon
 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
@@ -43,8 +44,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
 import io.legado.app.base.BaseRuleEvent
 import io.legado.app.data.entities.TxtTocRule
+import io.legado.app.ui.theme.adaptiveContentPadding
 import io.legado.app.ui.widget.components.ActionItem
 import io.legado.app.ui.widget.components.DraggableSelectionHandler
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 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
@@ -140,16 +143,15 @@ fun TxtRuleScreen(
         }
     )
 
-    if (showUrlInput) {
-        SourceInputDialog(
-            title = stringResource(R.string.import_on_line),
-            onDismissRequest = { showUrlInput = false },
-            onConfirm = {
-                showUrlInput = false
-                viewModel.importSource(it)
-            }
-        )
-    }
+    SourceInputDialog(
+        show = showUrlInput,
+        title = stringResource(R.string.import_on_line),
+        onDismissRequest = { showUrlInput = false },
+        onConfirm = {
+            showUrlInput = false
+            viewModel.importSource(it)
+        }
+    )
 
     FilePickerSheet(
         show = showExportSheet,
@@ -201,25 +203,19 @@ fun TxtRuleScreen(
         }
     }
 
-    showDeleteRuleDialog?.let { rule ->
-        AlertDialog(
-            onDismissRequest = { showDeleteRuleDialog = null },
-            title = { AppText(stringResource(R.string.delete)) },
-            text = { AppText(stringResource(R.string.del_msg)) },
-            confirmButton = {
-                OutlinedButton(onClick = {
-                    viewModel.delete(rule); showDeleteRuleDialog = null
-                }) { AppText(stringResource(R.string.ok)) }
-            },
-            dismissButton = {
-                TextButton(onClick = { showDeleteRuleDialog = null }) {
-                    AppText(
-                        stringResource(R.string.cancel)
-                    )
-                }
-            }
-        )
-    }
+    AppAlertDialog(
+        data = showDeleteRuleDialog, // 传入 nullable 的 rule 对象
+        onDismissRequest = { showDeleteRuleDialog = null },
+        title = stringResource(R.string.delete),
+        confirmText = stringResource(R.string.ok),
+        onConfirm = { rule ->
+            viewModel.delete(rule)
+            showDeleteRuleDialog = null
+        },
+        dismissText = stringResource(R.string.cancel),
+        onDismiss = { showDeleteRuleDialog = null },
+        content = { Text(text = stringResource(R.string.del_msg)) }
+    )
 
     RuleEditSheet(
         show = showEditSheet,
@@ -304,8 +300,7 @@ fun TxtRuleScreen(
         dropDownMenuContent = { dismiss ->
             RoundDropdownMenuItem(
                 text = stringResource(R.string.import_str),
-                onClick = { showImportSheet = true; dismiss() },
-                leadingIcon = { Icon(Icons.Default.FileOpen, null) }
+                onClick = { showImportSheet = true; dismiss() }
             )
         }
     ) { paddingValues ->
@@ -316,9 +311,9 @@ fun TxtRuleScreen(
             FastScrollLazyColumn(
                 modifier = Modifier.fillMaxSize(),
                 state = listState,
-                contentPadding = PaddingValues(
-                    top = paddingValues.calculateTopPadding() + 8.dp,
-                    bottom = paddingValues.calculateBottomPadding() + 120.dp
+                contentPadding = adaptiveContentPadding(
+                    top = paddingValues.calculateTopPadding(),
+                    bottom = 120.dp
                 ),
                 verticalArrangement = Arrangement.spacedBy(8.dp)
             ) {

+ 2 - 2
app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt

@@ -14,7 +14,7 @@ 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.SplicedColumnGroup
-import io.legado.app.ui.widget.components.button.TopbarNavigationButton
+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
@@ -38,7 +38,7 @@ fun ConfigNavScreen(
                 title = stringResource(R.string.setting),
                 scrollBehavior = scrollBehavior,
                 navigationIcon = {
-                    TopbarNavigationButton(onClick = onBackClick)
+                    TopBarNavigationButton(onClick = onBackClick)
                 }
             )
         }

+ 171 - 173
app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigScreen.kt

@@ -1,28 +1,31 @@
 package io.legado.app.ui.config.backupConfig
 
+import android.content.Context
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 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.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Visibility
 import androidx.compose.material.icons.filled.VisibilityOff
-import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
-import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.SnackbarHost
 import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.TextButton
-import androidx.compose.material3.TextField
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
@@ -37,23 +40,30 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.text.input.VisualTransformation
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
+import io.legado.app.help.storage.ImportOldData
 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.theme.LegadoTheme
 import io.legado.app.ui.widget.components.AppScaffold
+import io.legado.app.ui.widget.components.AppTextField
 import io.legado.app.ui.widget.components.SplicedColumnGroup
-import io.legado.app.ui.widget.components.button.TopbarNavigationButton
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
+import io.legado.app.ui.widget.components.button.TopBarNavigationButton
+import io.legado.app.ui.widget.components.card.SelectionItemCard
+import io.legado.app.ui.widget.components.checkBox.CheckboxItem
 import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
 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
 import org.koin.androidx.compose.koinViewModel
+import top.yukonga.miuix.kmp.theme.MiuixTheme
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
@@ -153,7 +163,7 @@ fun BackupConfigScreen(
         contract = ActivityResultContracts.OpenDocument()
     ) { uri ->
         uri?.let {
-            io.legado.app.help.storage.ImportOldData.importUri(context, uri)
+            ImportOldData.importUri(context, uri)
         }
     }
 
@@ -169,7 +179,7 @@ fun BackupConfigScreen(
                 title = stringResource(R.string.backup_restore),
                 scrollBehavior = scrollBehavior,
                 navigationIcon = {
-                    TopbarNavigationButton(onClick = onBackClick)
+                    TopBarNavigationButton(onClick = onBackClick)
                 }
             )
         }
@@ -382,76 +392,69 @@ fun BackupConfigScreen(
         }
     }
 
-    if (showWebDavAuthDialog) {
-        AlertDialog(
-            onDismissRequest = { showWebDavAuthDialog = false },
-            title = { AppText(stringResource(R.string.web_dav_account)) },
-            text = {
-                Column {
-                    TextField(
-                        value = tempAccount,
-                        onValueChange = { tempAccount = it },
-                        label = { AppText("账号") }
-                    )
-                    Spacer(modifier = Modifier.padding(8.dp))
-                    TextField(
-                        value = tempPassword,
-                        onValueChange = { tempPassword = it },
-                        label = { AppText("密码") },
-                        visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
-                        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
-                        trailingIcon = {
-                            val image = if (passwordVisible)
-                                Icons.Filled.Visibility
-                            else Icons.Filled.VisibilityOff
-
-                            val description = if (passwordVisible) "隐藏密码" else "显示密码"
-
-                            IconButton(onClick = { passwordVisible = !passwordVisible }) {
-                                Icon(imageVector = image, description)
-                            }
-                        }
-                    )
-                }
-            },
-            confirmButton = {
-                TextButton(onClick = {
-                    viewModel.setWebDavAccount(tempAccount, tempPassword)
-                    showWebDavAuthDialog = false
-                    scope.launch {
-                        showLoadingDialog = true
-                        loadingText = "测试中…"
-                        val success = viewModel.testWebDav()
-                        showLoadingDialog = false
-                        if (success) {
-                            snackbarHostState.showSnackbar("WebDav 配置正确")
-                        } else {
-                            snackbarHostState.showSnackbar("WebDav 配置错误")
+    AppAlertDialog(
+        show = showWebDavAuthDialog,
+        onDismissRequest = { showWebDavAuthDialog = false },
+        title = stringResource(R.string.web_dav_account),
+        content = {
+            Column {
+                AppTextField(
+                    value = tempAccount,
+                    onValueChange = { tempAccount = it },
+                    backgroundColor = LegadoTheme.colorScheme.surface,
+                    label = "账号"
+                )
+                Spacer(modifier = Modifier.height(12.dp))
+                AppTextField(
+                    value = tempPassword,
+                    onValueChange = { tempPassword = it },
+                    backgroundColor = MiuixTheme.colorScheme.surface,
+                    label = "密码",
+                    visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
+                    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
+                    trailingIcon = {
+                        val image =
+                            if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff
+                        val description = if (passwordVisible) "隐藏密码" else "显示密码"
+                        IconButton(onClick = { passwordVisible = !passwordVisible }) {
+                            Icon(imageVector = image, contentDescription = description)
                         }
                     }
-                }) {
-                    AppText(stringResource(R.string.ok))
-                }
-            },
-            dismissButton = {
-                TextButton(onClick = { showWebDavAuthDialog = false }) {
-                    AppText(stringResource(R.string.cancel))
+                )
+            }
+        },
+        confirmText = stringResource(R.string.ok),
+        onConfirm = {
+            viewModel.setWebDavAccount(tempAccount, tempPassword)
+            showWebDavAuthDialog = false
+            scope.launch {
+                showLoadingDialog = true
+                loadingText = "测试中…"
+                val success = viewModel.testWebDav()
+                showLoadingDialog = false
+                if (success) {
+                    snackbarHostState.showSnackbar("WebDav 配置正确")
+                } else {
+                    snackbarHostState.showSnackbar("WebDav 配置错误")
                 }
             }
-        )
-    }
+        },
+        dismissText = stringResource(R.string.cancel),
+        onDismiss = {
+            showWebDavAuthDialog = false
+        }
+    )
 
-    if (showConfirmDialog) {
-        ConfirmDialog(
-            title = confirmDialogTitle,
-            text = confirmDialogText,
-            onConfirm = {
-                onConfirmAction?.invoke()
-                showConfirmDialog = false
-            },
-            onDismiss = { showConfirmDialog = false }
-        )
-    }
+    ConfirmDialog(
+        show = showConfirmDialog,
+        title = confirmDialogTitle,
+        text = confirmDialogText,
+        onConfirm = {
+            onConfirmAction?.invoke()
+            showConfirmDialog = false
+        },
+        onDismiss = { showConfirmDialog = false }
+    )
 
     FilePickerSheet(
         show = showBackupFilePicker,
@@ -478,132 +481,127 @@ fun BackupConfigScreen(
         }
     )
 
-    if (showRestoreSheet && backupNames.isNotEmpty()) {
-        AlertDialog(
-            onDismissRequest = { showRestoreSheet = false },
-            title = { AppText(stringResource(R.string.select_restore_file)) },
-            text = {
-                Column {
-                    backupNames.forEach { name ->
-                        TextButton(onClick = {
-                            showRestoreSheet = false
-                            showLoadingDialog = true
-                            loadingText = "恢复中…"
-                            viewModel.restoreWebDav(
-                                name,
-                                {
-                                    showLoadingDialog = false
-                                    scope.launch {
-                                        snackbarHostState.showSnackbar("恢复成功")
-                                    }
-                                },
-                                { error ->
-                                    showLoadingDialog = false
-                                    scope.launch {
-                                        snackbarHostState.showSnackbar("WebDav恢复出错\n$error")
-                                    }
+    AppModalBottomSheet(
+        show = showRestoreSheet && backupNames.isNotEmpty(),
+        onDismissRequest = { showRestoreSheet = false },
+        title = stringResource(R.string.select_restore_file)
+    ) {
+        LazyColumn(
+            modifier = Modifier
+                .fillMaxWidth()
+                .padding(bottom = 16.dp),
+            verticalArrangement = Arrangement.spacedBy(8.dp)
+        ) {
+            items(backupNames) {
+                SelectionItemCard(
+                    title = it,
+                    containerColor = LegadoTheme.colorScheme.surface,
+                    onToggleSelection = {
+                        showRestoreSheet = false
+                        showLoadingDialog = true
+                        loadingText = "恢复中…"
+                        viewModel.restoreWebDav(
+                            it,
+                            {
+                                showLoadingDialog = false
+                                scope.launch {
+                                    snackbarHostState.showSnackbar("恢复成功")
                                 }
-                            )
-                        }) {
-                            AppText(name)
-                        }
+                            },
+                            { error ->
+                                showLoadingDialog = false
+                                scope.launch {
+                                    snackbarHostState.showSnackbar("WebDav恢复出错\n$error")
+                                }
+                            }
+                        )
                     }
-                }
-            },
-            confirmButton = {},
-            dismissButton = {
-                TextButton(onClick = { showRestoreSheet = false }) {
-                    AppText(stringResource(R.string.cancel))
-                }
+                )
             }
-        )
+        }
     }
 
-    if (showBackupIgnoreDialog) {
-        val checkedItems = remember {
-            BooleanArray(io.legado.app.help.storage.BackupConfig.ignoreKeys.size) { index ->
-                io.legado.app.help.storage.BackupConfig.ignoreConfig[
-                    io.legado.app.help.storage.BackupConfig.ignoreKeys[index]
-                ] ?: false
-            }
-        }
-        AlertDialog(
-            onDismissRequest = {
-                io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
-                showBackupIgnoreDialog = false
-            },
-            title = { AppText(stringResource(R.string.restore_ignore)) },
-            text = {
-                Column {
-                    io.legado.app.help.storage.BackupConfig.ignoreTitle.forEachIndexed { index, title ->
-                        TextButton(onClick = {
-                            checkedItems[index] = !checkedItems[index]
+    val checkedItems = remember(showBackupIgnoreDialog) {
+        val keys = io.legado.app.help.storage.BackupConfig.ignoreKeys
+        val config = io.legado.app.help.storage.BackupConfig.ignoreConfig
+
+        // 初始化可观察的 List 状态
+        mutableStateListOf(*Array(keys.size) { index ->
+            config[keys[index]] ?: false
+        })
+    }
+
+    AppAlertDialog(
+        show = showBackupIgnoreDialog,
+        onDismissRequest = {
+            io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
+            showBackupIgnoreDialog = false
+        },
+        title = stringResource(R.string.restore_ignore),
+        content = {
+            Column(
+                verticalArrangement = Arrangement.spacedBy(4.dp)
+            ) {
+                io.legado.app.help.storage.BackupConfig.ignoreTitle.forEachIndexed { index, title ->
+                    CheckboxItem(
+                        title = title,
+                        checked = checkedItems[index],
+                        onCheckedChange = { isChecked ->
+                            checkedItems[index] = isChecked
                             io.legado.app.help.storage.BackupConfig.ignoreConfig[
                                 io.legado.app.help.storage.BackupConfig.ignoreKeys[index]
-                            ] = checkedItems[index]
-                        }) {
-                            val isChecked = checkedItems[index]
-                            AppText(if (isChecked) "✓ $title" else title)
+                            ] = isChecked
                         }
-                    }
-                }
-            },
-            confirmButton = {
-                TextButton(onClick = {
-                    io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
-                    showBackupIgnoreDialog = false
-                }) {
-                    AppText(stringResource(R.string.ok))
-                }
-            },
-            dismissButton = {
-                TextButton(onClick = {
-                    io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
-                    showBackupIgnoreDialog = false
-                }) {
-                    AppText(stringResource(R.string.cancel))
+                    )
                 }
             }
-        )
-    }
+        },
+        confirmText = stringResource(R.string.ok),
+        onConfirm = {
+            io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
+            showBackupIgnoreDialog = false
+        },
+        dismissText = stringResource(R.string.cancel),
+        onDismiss = {
+            io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
+            showBackupIgnoreDialog = false
+        }
+    )
+
+
+    AppAlertDialog(
+        show = showLoadingDialog,
+        onDismiss = {},
+        onConfirm = {},
+        title = loadingText,
+        onDismissRequest = { showLoadingDialog = false }
+    )
 
-    if (showLoadingDialog) {
-        AlertDialog(
-            onDismissRequest = {},
-            title = { AppText(loadingText) },
-            confirmButton = {},
-            dismissButton = {}
-        )
-    }
 }
 
 @Composable
 fun ConfirmDialog(
+    show: Boolean,
     title: String,
     text: String,
     onConfirm: () -> Unit,
     onDismiss: () -> Unit
 ) {
-    AlertDialog(
+    AppAlertDialog(
+        show = show,
         onDismissRequest = onDismiss,
-        title = { AppText(title) },
-        text = { AppText(text) },
-        confirmButton = {
-            OutlinedButton(onClick = onConfirm) {
-                AppText(stringResource(R.string.ok))
-            }
-        },
-        dismissButton = {
-            TextButton(onClick = onDismiss) {
-                AppText(stringResource(R.string.cancel))
-            }
-        }
+        title = title,
+        text = text,
+        confirmText = stringResource(R.string.ok),
+        onConfirm = onConfirm,
+        dismissText = stringResource(R.string.cancel),
+        onDismiss = onDismiss
     )
 }
 
 private fun startBackup(
     path: String,
-    context: android.content.Context,
+    context: Context,
     viewModel: BackupConfigViewModel,
     onSuccess: () -> Unit,
     onError: (String) -> Unit

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

@@ -27,7 +27,7 @@ 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.SplicedColumnGroup
-import io.legado.app.ui.widget.components.button.TopbarNavigationButton
+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
@@ -54,7 +54,7 @@ fun CoverConfigScreen(
                 title = stringResource(R.string.cover_config),
                 scrollBehavior = scrollBehavior,
                 navigationIcon = {
-                    TopbarNavigationButton(onClick = onBackClick)
+                    TopBarNavigationButton(onClick = onBackClick)
                 }
             )
         }

+ 82 - 95
app/src/main/java/io/legado/app/ui/config/otherConfig/DirectLinkUploadBottomSheet.kt

@@ -7,25 +7,19 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.selection.SelectionContainer
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ContentCopy
 import androidx.compose.material.icons.filled.ContentPaste
 import androidx.compose.material.icons.filled.Download
 import androidx.compose.material.icons.filled.MoreVert
-import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Button
-import androidx.compose.material3.CenterAlignedTopAppBar
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.TextButton
-import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
@@ -33,15 +27,18 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 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.help.DirectLinkUpload
 import io.legado.app.lib.dialogs.selector
-import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.AppTextField
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
+import io.legado.app.ui.widget.components.button.MediumIconButton
 import io.legado.app.ui.widget.components.checkBox.CheckboxItem
+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.AppModalBottomSheet
 import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.GSON
@@ -67,102 +64,89 @@ fun DirectLinkUploadBottomSheet(
 
     AppModalBottomSheet(
         show = show,
+        title = stringResource(R.string.direct_link_upload_config),
+        endAction = {
+            MediumIconButton(
+                onClick = { showMenu = true },
+                icon = Icons.Default.MoreVert
+            )
+            RoundDropdownMenu(
+                expanded = showMenu,
+                onDismissRequest = { showMenu = false }
+            ) {
+                RoundDropdownMenuItem(
+                    text = "导入默认",
+                    leadingIcon = { Icon(Icons.Default.Download, null) },
+                    onClick = {
+                        showMenu = false
+                        context.selector(DirectLinkUpload.defaultRules) { _, rule, _ ->
+                            viewModel.upView(rule)
+                        }
+                    }
+                )
+                RoundDropdownMenuItem(
+                    text = stringResource(R.string.copy_rule),
+                    leadingIcon = { Icon(Icons.Default.ContentCopy, null) },
+                    onClick = {
+                        showMenu = false
+                        val rule = DirectLinkUpload.Rule(
+                            viewModel.uploadUrl,
+                            viewModel.downloadUrlRule,
+                            viewModel.summary,
+                            viewModel.compress
+                        )
+                        context.sendToClip(GSON.toJson(rule))
+                    }
+                )
+                RoundDropdownMenuItem(
+                    text = stringResource(R.string.paste_rule),
+                    leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
+                    onClick = {
+                        showMenu = false
+                        runCatching {
+                            context.getClipText()?.let {
+                                val rule =
+                                    GSON.fromJsonObject<DirectLinkUpload.Rule>(it)
+                                        .getOrThrow()
+                                viewModel.upView(rule)
+                            }
+                        }.onFailure {
+                            context.toastOnUi("剪贴板为空或格式不对")
+                        }
+                    }
+                )
+            }
+        },
         onDismissRequest = onDismiss
     ) {
         Column(
             modifier = Modifier
                 .fillMaxWidth()
-                .padding(horizontal = 16.dp)
                 .padding(bottom = 24.dp)
                 .verticalScroll(rememberScrollState())
         ) {
-            CenterAlignedTopAppBar(
-                title = {
-                    AppText(
-                        text = stringResource(R.string.direct_link_upload_config),
-                        style = LegadoTheme.typography.titleLarge
-                    )
-                },
-                actions = {
-                    IconButton(onClick = { showMenu = true }) {
-                        Icon(Icons.Default.MoreVert, contentDescription = "More")
-                    }
-                    DropdownMenu(
-                        expanded = showMenu,
-                        onDismissRequest = { showMenu = false }
-                    ) {
-                        DropdownMenuItem(
-                            text = { AppText("导入默认") },
-                            leadingIcon = { Icon(Icons.Default.Download, null) },
-                            onClick = {
-                                showMenu = false
-                                context.selector(DirectLinkUpload.defaultRules) { _, rule, _ ->
-                                    viewModel.upView(rule)
-                                }
-                            }
-                        )
-                        DropdownMenuItem(
-                            text = { AppText(stringResource(R.string.copy_rule)) },
-                            leadingIcon = { Icon(Icons.Default.ContentCopy, null) },
-                            onClick = {
-                                showMenu = false
-                                val rule = DirectLinkUpload.Rule(
-                                    viewModel.uploadUrl,
-                                    viewModel.downloadUrlRule,
-                                    viewModel.summary,
-                                    viewModel.compress
-                                )
-                                context.sendToClip(GSON.toJson(rule))
-                            }
-                        )
-                        DropdownMenuItem(
-                            text = { AppText(stringResource(R.string.paste_rule)) },
-                            leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
-                            onClick = {
-                                showMenu = false
-                                runCatching {
-                                    context.getClipText()?.let {
-                                        val rule =
-                                            GSON.fromJsonObject<DirectLinkUpload.Rule>(it)
-                                                .getOrThrow()
-                                        viewModel.upView(rule)
-                                    }
-                                }.onFailure {
-                                    context.toastOnUi("剪贴板为空或格式不对")
-                                }
-                            }
-                        )
-                    }
-                },
-                colors = TopAppBarDefaults.topAppBarColors(
-                    containerColor = Color.Transparent
-                )
-            )
-
-            Spacer(Modifier.height(16.dp))
-
-            OutlinedTextField(
+            AppTextField(
                 value = viewModel.uploadUrl,
                 onValueChange = { viewModel.uploadUrl = it },
-                label = { AppText(stringResource(R.string.upload_url)) },
+                label = stringResource(R.string.upload_url),
                 modifier = Modifier.fillMaxWidth(),
             )
 
             Spacer(Modifier.height(16.dp))
 
-            OutlinedTextField(
+            AppTextField(
                 value = viewModel.downloadUrlRule,
                 onValueChange = { viewModel.downloadUrlRule = it },
-                label = { AppText(stringResource(R.string.download_url_rule)) },
+                label = stringResource(R.string.download_url_rule),
                 modifier = Modifier.fillMaxWidth(),
             )
 
             Spacer(Modifier.height(16.dp))
 
-            OutlinedTextField(
+            AppTextField(
                 value = viewModel.summary,
                 onValueChange = { viewModel.summary = it },
-                label = { AppText(stringResource(R.string.summary)) },
+                label = stringResource(R.string.summary),
                 modifier = Modifier.fillMaxWidth(),
             )
 
@@ -200,19 +184,22 @@ fun DirectLinkUploadBottomSheet(
         }
     }
 
-    showTestResult?.let { result ->
-        AlertDialog(
-            onDismissRequest = { showTestResult = null },
-            title = { AppText("Result") },
-            text = { AppText(result) },
-            confirmButton = {
-                TextButton(onClick = {
-                    showTestResult = null
-                }) { AppText(stringResource(R.string.ok)) }
-            },
-            dismissButton = {
-                TextButton(onClick = { context.sendToClip(result) }) { AppText(stringResource(R.string.copy_text)) }
+    AppAlertDialog(
+        data = showTestResult,
+        onDismissRequest = { showTestResult = null },
+        title = "Result",
+        content = { result ->
+            SelectionContainer {
+                AppText(text = result)
             }
-        )
-    }
+        },
+        confirmText = stringResource(R.string.ok),
+        onConfirm = {
+            showTestResult = null
+        },
+        dismissText = stringResource(R.string.copy_text),
+        onDismiss = {
+            showTestResult?.let { context.sendToClip(it) }
+        }
+    )
 }

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

@@ -7,14 +7,11 @@ import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.TextButton
-import androidx.compose.material3.TextField
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -28,16 +25,18 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.service.WebService
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.AppScaffold
+import io.legado.app.ui.widget.components.AppTextField
 import io.legado.app.ui.widget.components.SplicedColumnGroup
-import io.legado.app.ui.widget.components.button.TopbarNavigationButton
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
+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.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
@@ -86,7 +85,7 @@ fun OtherConfigScreen(
                 title = stringResource(R.string.other_setting),
                 scrollBehavior = scrollBehavior,
                 navigationIcon = {
-                    TopbarNavigationButton(onClick = onBackClick)
+                    TopBarNavigationButton(onClick = onBackClick)
                 }
             )
         }
@@ -423,90 +422,63 @@ fun OtherConfigScreen(
             onDismiss = { showDirectLinkUploadSheet = false }
         )
 
-        if (showClearCacheDialog) {
-            ConfirmDialog(
-                title = stringResource(R.string.clear_cache),
-                text = stringResource(R.string.sure_del),
-                onConfirm = {
-                    viewModel.clearCache(context)
-                    showClearCacheDialog = false
-                },
-                onDismiss = { showClearCacheDialog = false }
-            )
-        }
+        AppAlertDialog(
+            show = showClearCacheDialog,
+            onDismissRequest = { showClearCacheDialog = false },
+            title = stringResource(R.string.clear_cache),
+            text = stringResource(R.string.sure_del),
+            onConfirm = {
+                viewModel.clearCache(context)
+                showClearCacheDialog = false
+            },
+            onDismiss = { showClearCacheDialog = false }
+        )
 
-        if (showShrinkDbDialog) {
-            ConfirmDialog(
-                title = stringResource(R.string.shrink_database),
-                text = stringResource(R.string.sure),
-                onConfirm = {
-                    viewModel.shrinkDatabase()
-                    showShrinkDbDialog = false
-                },
-                onDismiss = { showShrinkDbDialog = false }
-            )
-        }
+        AppAlertDialog(
+            show = showShrinkDbDialog,
+            onDismissRequest = { showShrinkDbDialog = false },
+            title = stringResource(R.string.shrink_database),
+            text = stringResource(R.string.sure),
+            onConfirm = {
+                viewModel.shrinkDatabase()
+                showShrinkDbDialog = false
+            },
+            onDismiss = { showShrinkDbDialog = false }
+        )
 
-        if (showClearWebViewDialog) {
-            ConfirmDialog(
-                title = stringResource(R.string.clear_webview_data),
-                text = stringResource(R.string.sure_del),
-                onConfirm = {
-                    viewModel.clearWebViewData(context)
-                    showClearWebViewDialog = false
-                },
-                onDismiss = { showClearWebViewDialog = false }
-            )
-        }
+        AppAlertDialog(
+            show = showClearWebViewDialog,
+            onDismissRequest = { showClearWebViewDialog = false },
+            title = stringResource(R.string.clear_webview_data),
+            text = stringResource(R.string.sure_del),
+            onConfirm = {
+                viewModel.clearWebViewData(context)
+                showClearWebViewDialog = false
+            },
+            onDismiss = { showClearWebViewDialog = false }
+        )
 
-        if (showPasswordDialog) {
-            AlertDialog(
-                onDismissRequest = { showPasswordDialog = false },
-                title = { AppText(stringResource(R.string.set_local_password)) },
-                text = {
-                    TextField(
-                        value = tempPassword,
-                        onValueChange = { tempPassword = it },
-                        label = { AppText("Password") }
-                    )
-                },
-                confirmButton = {
-                    TextButton(onClick = {
-                        viewModel.setLocalPassword(tempPassword)
-                        showPasswordDialog = false
-                    }) { AppText(stringResource(R.string.ok)) }
-                },
-                dismissButton = {
-                    TextButton(onClick = { showPasswordDialog = false }) {
-                        AppText(stringResource(R.string.cancel))
-                    }
-                }
-            )
-        }
+        AppAlertDialog(
+            show = showPasswordDialog,
+            onDismissRequest = { showPasswordDialog = false },
+            title = stringResource(R.string.set_local_password),
+            content = {
+                AppTextField(
+                    value = tempPassword,
+                    onValueChange = { tempPassword = it },
+                    label = "Password",
+                    backgroundColor = LegadoTheme.colorScheme.surface,
+                    modifier = Modifier.fillMaxWidth()
+                )
+            },
+            confirmText = stringResource(R.string.ok),
+            onConfirm = {
+                viewModel.setLocalPassword(tempPassword)
+                showPasswordDialog = false
+            },
+            dismissText = stringResource(R.string.cancel),
+            onDismiss = { showPasswordDialog = false }
+        )
     }
 
 }
-
-@Composable
-fun ConfirmDialog(
-    title: String,
-    text: String,
-    onConfirm: () -> Unit,
-    onDismiss: () -> Unit
-) {
-    AlertDialog(
-        onDismissRequest = onDismiss,
-        title = { AppText(title) },
-        text = { AppText(text) },
-        confirmButton = {
-            OutlinedButton(onClick = onConfirm) {
-                AppText(stringResource(R.string.ok))
-            }
-        },
-        dismissButton = {
-            TextButton(onClick = onDismiss) {
-                AppText(stringResource(R.string.cancel))
-            }
-        }
-    )
-}

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

@@ -20,7 +20,7 @@ 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.SplicedColumnGroup
-import io.legado.app.ui.widget.components.button.TopbarNavigationButton
+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
@@ -47,7 +47,7 @@ fun ReadConfigScreen(
                 title = stringResource(R.string.read_config),
                 scrollBehavior = scrollBehavior,
                 navigationIcon = {
-                    TopbarNavigationButton(onClick = onBackClick)
+                    TopBarNavigationButton(onClick = onBackClick)
                 }
             )
         }

+ 47 - 57
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt

@@ -83,8 +83,10 @@ 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.AppTextField
 import io.legado.app.ui.widget.components.SplicedColumnGroup
-import io.legado.app.ui.widget.components.button.TopbarNavigationButton
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
+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
@@ -131,7 +133,7 @@ fun ThemeConfigScreen(
                 title = stringResource(R.string.theme_setting),
                 scrollBehavior = scrollBehavior,
                 navigationIcon = {
-                    TopbarNavigationButton(onClick = onBackClick)
+                    TopBarNavigationButton(onClick = onBackClick)
                 }
             )
         }
@@ -527,30 +529,25 @@ fun ThemeConfigScreen(
         }
     }
 
-    if (showRestartDialog) {
-        AlertDialog(
-            onDismissRequest = { showRestartDialog = false },
-            title = { AppText(stringResource(R.string.restart_required_message)) },
-            confirmButton = {
-                OutlinedButton(onClick = {
-                    showRestartDialog = false
-                    Handler(Looper.getMainLooper()).postDelayed({
-                        context.restart()
-                    }, 100)
-                }) {
-                    AppText(stringResource(R.string.ok))
-                }
-            },
-            dismissButton = {
-                TextButton(onClick = {
-                    showRestartDialog = false
-                    context.toastOnUi(R.string.restart_later_message)
-                }) {
-                    AppText(stringResource(R.string.cancel))
-                }
-            }
-        )
-    }
+
+    AppAlertDialog(
+        show = showRestartDialog,
+        onDismissRequest = { showRestartDialog = false },
+        title = stringResource(R.string.restart_required_message),
+        onConfirm = {
+            showRestartDialog = false
+            Handler(Looper.getMainLooper()).postDelayed({
+                context.restart()
+            }, 100)
+        },
+        confirmText = stringResource(R.string.ok),
+        onDismiss = {
+            showRestartDialog = false
+            context.toastOnUi(R.string.restart_later_message)
+        },
+        dismissText = stringResource(R.string.cancel)
+    )
+
 
     manageKey?.let { isDark ->
         BackgroundImageManageSheet(
@@ -595,38 +592,31 @@ fun ThemeConfigScreen(
         onDismissRequest = { showThemeListDialog = false }
     )
 
-    saveThemeKey?.let { key ->
-        AlertDialog(
-            onDismissRequest = { saveThemeKey = null },
-            title = { AppText(stringResource(R.string.theme_name)) },
-            text = {
-                OutlinedTextField(
-                    value = themeName,
-                    onValueChange = { themeName = it },
-                    label = { AppText("name") },
-                    singleLine = true
-                )
-            },
-            confirmButton = {
-                TextButton(
-                    onClick = {
-                        when (key) {
-                            "saveDayTheme" -> OldThemeConfig.saveDayTheme(context, themeName)
-                            "saveNightTheme" -> OldThemeConfig.saveNightTheme(context, themeName)
-                        }
-                        saveThemeKey = null
-                    }
-                ) {
-                    AppText(stringResource(android.R.string.ok))
-                }
-            },
-            dismissButton = {
-                TextButton(onClick = { saveThemeKey = null }) {
-                    AppText(stringResource(android.R.string.cancel))
-                }
+    AppAlertDialog(
+        data = saveThemeKey,
+        onDismissRequest = { saveThemeKey = null },
+        title = stringResource(R.string.theme_name),
+        content = {
+            AppTextField(
+                value = themeName,
+                onValueChange = { themeName = it },
+                label = "name",
+                singleLine = true,
+                modifier = Modifier.fillMaxWidth(),
+                backgroundColor = LegadoTheme.colorScheme.surface
+            )
+        },
+        confirmText = stringResource(android.R.string.ok),
+        onConfirm = { key ->
+            when (key) {
+                "saveDayTheme" -> OldThemeConfig.saveDayTheme(context, themeName)
+                "saveNightTheme" -> OldThemeConfig.saveNightTheme(context, themeName)
             }
-        )
-    }
+            saveThemeKey = null
+        },
+        dismissText = stringResource(android.R.string.cancel),
+        onDismiss = { saveThemeKey = null }
+    )
 }
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)

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

@@ -37,6 +37,7 @@ 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.alert.AppAlertDialog
 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.text.AppText
@@ -158,27 +159,17 @@ fun ThemeListDialog(
         }
     }
 
-    deleteIndex?.let { index ->
-        AlertDialog(
-            onDismissRequest = { deleteIndex = null },
-            title = { AppText(stringResource(R.string.delete)) },
-            text = { AppText(stringResource(R.string.sure_del)) },
-            confirmButton = {
-                TextButton(
-                    onClick = {
-                        OldThemeConfig.delConfig(index)
-                        listVersion++
-                        deleteIndex = null
-                    }
-                ) {
-                    AppText(stringResource(android.R.string.ok))
-                }
-            },
-            dismissButton = {
-                TextButton(onClick = { deleteIndex = null }) {
-                    AppText(stringResource(android.R.string.cancel))
-                }
-            }
-        )
-    }
+    AppAlertDialog(
+        data = deleteIndex,
+        onDismissRequest = { deleteIndex = null },
+        title = stringResource(R.string.delete),
+        confirmText = stringResource(android.R.string.ok),
+        onConfirm = { index ->
+            OldThemeConfig.delConfig(index)
+            listVersion++
+            deleteIndex = null
+        },
+        dismissText = stringResource(android.R.string.cancel),
+        onDismiss = { deleteIndex = null }
+    )
 }

+ 5 - 7
app/src/main/java/io/legado/app/ui/dict/rule/DictRuleEditSheet.kt

@@ -17,10 +17,8 @@ import androidx.compose.material3.CenterAlignedTopAppBar
 import androidx.compose.material3.DropdownMenu
 import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.ExperimentalMaterial3Api
-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.TopAppBarDefaults
 import androidx.compose.runtime.Composable
@@ -36,6 +34,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.AppFloatingActionButton
+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
 import kotlinx.coroutines.launch
@@ -152,7 +152,7 @@ fun DictRuleEditSheet(
                 }
             }
 
-            FloatingActionButton(
+            AppFloatingActionButton(
                 onClick = {
                     onSave(
                         rule?.copy(name = name, urlRule = urlRule, showRule = showRule)
@@ -161,11 +161,9 @@ fun DictRuleEditSheet(
                 },
                 modifier = Modifier
                     .align(Alignment.BottomEnd)
-                    .padding(16.dp),
-                containerColor = MaterialTheme.colorScheme.primaryContainer,
-                contentColor = MaterialTheme.colorScheme.onPrimaryContainer
+                    .padding(16.dp)
             ) {
-                Icon(Icons.Default.Save, contentDescription = "Save")
+                AppIcon(Icons.Default.Save, contentDescription = "Save")
             }
         }
     }

+ 27 - 41
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.PaddingValues
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.width
@@ -13,15 +12,10 @@ import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Delete
-import androidx.compose.material.icons.filled.FileOpen
-import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.Icon
-import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.SnackbarResult
-import androidx.compose.material3.TextButton
 import androidx.compose.material3.rememberModalBottomSheetState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -43,8 +37,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
 import io.legado.app.base.BaseRuleEvent
 import io.legado.app.data.entities.DictRule
+import io.legado.app.ui.theme.adaptiveContentPadding
 import io.legado.app.ui.widget.components.ActionItem
 import io.legado.app.ui.widget.components.DraggableSelectionHandler
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 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
@@ -55,7 +51,6 @@ 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
 
@@ -139,16 +134,15 @@ fun DictRuleScreen(
         }
     )
 
-    if (showUrlInput) {
-        SourceInputDialog(
-            title = stringResource(R.string.import_on_line),
-            onDismissRequest = { showUrlInput = false },
-            onConfirm = {
-                showUrlInput = false
-                viewModel.importSource(it)
-            }
-        )
-    }
+    SourceInputDialog(
+        show = showUrlInput,
+        title = stringResource(R.string.import_on_line),
+        onDismissRequest = { showUrlInput = false },
+        onConfirm = {
+            showUrlInput = false
+            viewModel.importSource(it)
+        }
+    )
 
     FilePickerSheet(
         show = showExportSheet,
@@ -200,25 +194,18 @@ fun DictRuleScreen(
         }
     }
 
-    showDeleteRuleDialog?.let { rule ->
-        AlertDialog(
-            onDismissRequest = { showDeleteRuleDialog = null },
-            title = { AppText(stringResource(R.string.delete)) },
-            text = { AppText(stringResource(R.string.del_msg)) },
-            confirmButton = {
-                OutlinedButton(onClick = {
-                    viewModel.delete(rule); showDeleteRuleDialog = null
-                }) { AppText(stringResource(R.string.ok)) }
-            },
-            dismissButton = {
-                TextButton(onClick = { showDeleteRuleDialog = null }) {
-                    AppText(
-                        stringResource(R.string.cancel)
-                    )
-                }
-            }
-        )
-    }
+    AppAlertDialog(
+        data = showDeleteRuleDialog,
+        onDismissRequest = { showDeleteRuleDialog = null },
+        title = stringResource(R.string.delete),
+        confirmText = stringResource(R.string.ok),
+        onConfirm = { rule ->
+            viewModel.delete(rule)
+            showDeleteRuleDialog = null
+        },
+        dismissText = stringResource(R.string.cancel),
+        onDismiss = { showDeleteRuleDialog = null }
+    )
 
     RuleEditSheet(
         show = showEditSheet,
@@ -302,8 +289,7 @@ fun DictRuleScreen(
         dropDownMenuContent = { dismiss ->
             RoundDropdownMenuItem(
                 text = stringResource(R.string.import_str),
-                onClick = { showImportSheet = true; dismiss() },
-                leadingIcon = { Icon(Icons.Default.FileOpen, null) }
+                onClick = { showImportSheet = true; dismiss() }
             )
         }
     ) { paddingValues ->
@@ -314,9 +300,9 @@ fun DictRuleScreen(
             FastScrollLazyColumn(
                 modifier = Modifier.fillMaxSize(),
                 state = listState,
-                contentPadding = PaddingValues(
-                    top = paddingValues.calculateTopPadding() + 8.dp,
-                    bottom = paddingValues.calculateBottomPadding() + 120.dp
+                contentPadding = adaptiveContentPadding(
+                    top = paddingValues.calculateTopPadding(),
+                    bottom = 120.dp
                 ),
                 verticalArrangement = Arrangement.spacedBy(8.dp)
             ) {

+ 4 - 5
app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt

@@ -36,7 +36,6 @@ 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
 
@@ -208,10 +207,12 @@ fun BookGroupCover(
         modifier = modifier
             .aspectRatio(5f / 7f)
             .clip(RoundedCornerShape(4.dp))
-            .background(MaterialTheme.colorScheme.surfaceContainer)
+            .background(LegadoTheme.colorScheme.surfaceContainer)
     ) {
         if (!coverPath.isNullOrBlank()) {
-            Cover(
+            BookCover(
+                name = null,
+                author = null,
                 path = coverPath,
                 modifier = Modifier.fillMaxSize()
             )
@@ -339,8 +340,6 @@ fun BookGroupItemList(
         titleMaxLines = titleMaxLines,
         coverShadow = coverShadow,
         modifier = modifier,
-        subTitle = "${previewBooks.size} 本书籍",
-        desc = "点击打开文件夹",
         onClick = onClick,
         onLongClick = onLongClick
     )

+ 15 - 20
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt

@@ -26,7 +26,6 @@ import androidx.compose.material.icons.automirrored.filled.FormatListBulleted
 import androidx.compose.material.icons.filled.Check
 import androidx.compose.material.icons.filled.Download
 import androidx.compose.material.icons.filled.Edit
-import androidx.compose.material.icons.filled.FileOpen
 import androidx.compose.material.icons.filled.GridView
 import androidx.compose.material.icons.filled.History
 import androidx.compose.material.icons.filled.ImportExport
@@ -74,6 +73,7 @@ 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.theme.adaptiveContentPadding
 import io.legado.app.ui.theme.adaptiveHorizontalPadding
 import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton
 import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
@@ -299,8 +299,7 @@ fun BookshelfScreen(
             )
             RoundDropdownMenuItem(
                 text = stringResource(R.string.import_bookshelf),
-                onClick = { showImportSheet = true; dismiss() },
-                leadingIcon = { Icon(Icons.Default.FileOpen, null) }
+                onClick = { showImportSheet = true; dismiss() }
             )
             RoundDropdownMenuItem(
                 text = stringResource(R.string.log),
@@ -404,11 +403,9 @@ fun BookshelfScreen(
                     FastScrollLazyVerticalGrid(
                         columns = GridCells.Fixed(bookshelfLayoutGrid.coerceAtLeast(1)),
                         modifier = Modifier.fillMaxSize(),
-                        contentPadding = PaddingValues(
+                        contentPadding = adaptiveContentPadding(
                             top = paddingValues.calculateTopPadding(),
-                            bottom = 120.dp,
-                            start = if (bookshelfLayoutMode != 0) 12.dp else 0.dp,
-                            end = if (bookshelfLayoutMode != 0) 12.dp else 0.dp
+                            bottom = 120.dp
                         ),
                         verticalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
                         horizontalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
@@ -499,16 +496,15 @@ fun BookshelfScreen(
         onDismissRequest = { showGroupManageSheet = false }
     )
 
-    if (showAddUrlDialog) {
-        SourceInputDialog(
-            title = stringResource(R.string.add_book_url),
-            onDismissRequest = { showAddUrlDialog = false },
-            onConfirm = { url ->
-                viewModel.addBookByUrl(url)
-                showAddUrlDialog = false
-            }
-        )
-    }
+    SourceInputDialog(
+        show = showAddUrlDialog,
+        title = stringResource(R.string.add_book_url),
+        onDismissRequest = { showAddUrlDialog = false },
+        onConfirm = { url ->
+            viewModel.addBookByUrl(url)
+            showAddUrlDialog = false
+        }
+    )
 
     FilePickerSheet(
         show = showImportSheet,
@@ -583,11 +579,10 @@ fun BookshelfPage(
     FastScrollLazyVerticalGrid(
         columns = GridCells.Fixed(columns.coerceAtLeast(1)),
         modifier = Modifier.fillMaxSize(),
-        contentPadding = PaddingValues(
+        contentPadding = adaptiveContentPadding(
             top = paddingValues.calculateTopPadding(),
             bottom = 120.dp,
-            start = if (bookshelfLayoutMode != 0) 12.dp else 0.dp,
-            end = if (bookshelfLayoutMode != 0) 12.dp else 0.dp
+            horizontal = if (bookshelfLayoutMode != 0) 12.dp else 0.dp
         ),
         verticalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
         horizontalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),

+ 36 - 67
app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt

@@ -1,19 +1,13 @@
 package io.legado.app.ui.main.explore
 
-import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.FastOutSlowInEasing
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.slideInVertically
-import androidx.compose.animation.slideOutVertically
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.combinedClickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
@@ -32,14 +26,12 @@ import androidx.compose.material.icons.filled.Group
 import androidx.compose.material.icons.filled.Refresh
 import androidx.compose.material.icons.filled.Search
 import androidx.compose.material.icons.filled.VerticalAlignTop
-import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.ListItem
 import androidx.compose.material3.ListItemDefaults
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
@@ -67,12 +59,15 @@ 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.theme.adaptiveContentPadding
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 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
 import io.legado.app.ui.widget.components.explore.ExploreKindItem
 import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
 import io.legado.app.ui.widget.components.list.ListScaffold
+import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
 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
@@ -163,7 +158,7 @@ fun ExploreScreen(
             FastScrollLazyColumn(
                 state = listState,
                 modifier = Modifier.fillMaxSize(),
-                contentPadding = PaddingValues(
+                contentPadding = adaptiveContentPadding(
                     top = paddingValues.calculateTopPadding(),
                     bottom = 120.dp
                 )
@@ -211,7 +206,7 @@ fun ExploreScreen(
                                 modifier = Modifier
                                     .fillMaxWidth()
                                     .animateItem()
-                                    .padding(horizontal = 16.dp, vertical = 4.dp),
+                                    .padding(vertical = 4.dp),
                                 horizontalArrangement = Arrangement.spacedBy(8.dp)
                             ) {
                                 rowItems.forEach { (kind, span) ->
@@ -245,54 +240,42 @@ fun ExploreScreen(
                 }
             }
 
-            AnimatedVisibility(
-                visible = stickyHeaderSource != null,
+            TopFloatingStickyItem(
+                item = stickyHeaderSource,
                 modifier = Modifier
                     .align(Alignment.TopStart)
-                    .padding(top = paddingValues.calculateTopPadding() + 4.dp, start = 8.dp),
-                enter = fadeIn() + slideInVertically { -it },
-                exit = fadeOut() + slideOutVertically { -it }
-            ) {
-                var lastSource by remember { mutableStateOf<BookSourcePart?>(null) }
-                stickyHeaderSource?.let { lastSource = it }
-
-                lastSource?.let { item ->
-                    ExploreStickyCard(
-                        item = item,
-                        onClick = {
-                            scope.launch {
-                                val index =
-                                    uiState.items.indexOfFirst { it.bookSourceUrl == item.bookSourceUrl }
-                                if (index >= 0) listState.animateScrollToItem(index)
-                            }
-                        },
-                        isMiuix = composeEngine
-                    )
-                }
+                    .padding(top = paddingValues.calculateTopPadding() + 4.dp, start = 8.dp)
+            ) { item ->
+                TextCard(
+                    text = item.bookSourceName,
+                    textStyle = LegadoTheme.typography.labelLarge,
+                    horizontalPadding = 8.dp,
+                    verticalPadding = 6.dp,
+                    onClick = {
+                        scope.launch {
+                            val index =
+                                uiState.items.indexOfFirst { it.bookSourceUrl == item.bookSourceUrl }
+                            if (index >= 0) listState.animateScrollToItem(index)
+                        }
+                    }
+                )
             }
         }
     }
 
-    sourceToDelete?.let { source ->
-        AlertDialog(
-            onDismissRequest = { sourceToDelete = null },
-            title = { AppText(stringResource(R.string.draw)) },
-            text = { AppText(stringResource(R.string.sure_del) + "\n" + source.bookSourceName) },
-            confirmButton = {
-                TextButton(onClick = {
-                    viewModel.deleteSource(source)
-                    sourceToDelete = null
-                }) {
-                    AppText(stringResource(R.string.yes))
-                }
-            },
-            dismissButton = {
-                TextButton(onClick = { sourceToDelete = null }) {
-                    AppText(stringResource(R.string.no))
-                }
-            }
-        )
-    }
+
+    AppAlertDialog(
+        data = sourceToDelete,
+        onDismissRequest = { sourceToDelete = null },
+        title = stringResource(R.string.sure_del),
+        confirmText = stringResource(android.R.string.ok),
+        onConfirm = { source ->
+            viewModel.deleteSource(source)
+            sourceToDelete = null
+        },
+        dismissText = stringResource(android.R.string.cancel),
+        onDismiss = { sourceToDelete = null },
+    )
 }
 
 private fun calculateRows(
@@ -369,7 +352,7 @@ fun ExploreSourceHeader(
     GlassCard(
         modifier = modifier
             .fillMaxWidth()
-            .padding(horizontal = 16.dp, vertical = 4.dp),
+            .padding(vertical = 4.dp),
         shape = MaterialTheme.shapes.medium,
         containerColor = containerColor
     ) {
@@ -452,18 +435,4 @@ fun ExploreSourceHeader(
     }
 }
 
-@Composable
-fun ExploreStickyCard(
-    item: BookSourcePart,
-    onClick: () -> Unit,
-    isMiuix: Boolean,
-) {
-    TextCard(
-        text = item.bookSourceName,
-        textStyle = LegadoTheme.typography.labelLarge,
-        horizontalPadding = 8.dp,
-        verticalPadding = 6.dp,
-        onClick = onClick
-    )
-}
 

+ 104 - 113
app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt

@@ -10,13 +10,11 @@ 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.foundation.lazy.LazyColumn
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.filled.ExitToApp
 import androidx.compose.material.icons.automirrored.filled.HelpOutline
@@ -52,7 +50,7 @@ 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.theme.adaptiveContentPadding
 import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.SplicedColumnGroup
 import io.legado.app.ui.widget.components.button.SmallTextButton
@@ -91,120 +89,113 @@ fun MyScreen(
             )
         }
     ) { padding ->
-        Column(
-            modifier = Modifier
-                .verticalScroll(rememberScrollState()),
-            //TODO:暂时这样解决,改天加模糊
+        LazyColumn(
+            modifier = Modifier,
+            contentPadding = adaptiveContentPadding(
+                top = padding.calculateTopPadding(),
+                bottom = 120.dp
+            )
         ) {
-            Spacer(
-                modifier = Modifier
-                    .padding(
-                        top = padding.calculateTopPadding()
+            item {
+                SplicedColumnGroup(
+                    title = ""
+                ) {
+                    WebServiceSettingBlock(
+                        uiState = uiState,
+                        viewModel = viewModel,
+                        onNavigate = onNavigate
                     )
-            )
-
-            SplicedColumnGroup(
-                modifier = Modifier.adaptiveHorizontalPadding(),
-                title = ""
-            ) {
-                WebServiceSettingBlock(
-                    uiState = uiState,
-                    viewModel = viewModel,
-                    onNavigate = onNavigate
-                )
-            }
+                }
 
-            SplicedColumnGroup(
-                modifier = Modifier.adaptiveHorizontalPadding(),
-                title = "规则"
-            ) {
-                ClickableSettingItem(
-                    title = stringResource(R.string.book_source_manage),
-                    description = stringResource(R.string.book_source_manage_desc),
-                    imageVector = Icons.Default.Source,
-                    onClick = {
-                        onNavigate(
-                            PrefClickEvent.StartActivity(BookSourceActivity::class.java)
-                        )
-                    }
-                )
-                ClickableSettingItem(
-                    title = stringResource(R.string.replace_purify),
-                    imageVector = Icons.Default.FindReplace,
-                    onClick = {
-                        onNavigate(
-                            PrefClickEvent.StartActivity(ReplaceRuleActivity::class.java)
-                        )
-                    }
-                )
-                ClickableSettingItem(
-                    title = stringResource(R.string.txt_toc_rule),
-                    imageVector = Icons.AutoMirrored.Filled.Rule,
-                    onClick = {
-                        onNavigate(
-                            PrefClickEvent.StartActivity(TxtTocRuleActivity::class.java)
-                        )
-                    }
-                )
-                ClickableSettingItem(
-                    title = stringResource(R.string.dict_rule),
-                    imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
-                    onClick = {
-                        onNavigate(
-                            PrefClickEvent.StartActivity(DictRuleActivity::class.java)
-                        )
-                    }
-                )
-            }
+                SplicedColumnGroup(
+                    title = "规则"
+                ) {
+                    ClickableSettingItem(
+                        title = stringResource(R.string.book_source_manage),
+                        description = stringResource(R.string.book_source_manage_desc),
+                        imageVector = Icons.Default.Source,
+                        onClick = {
+                            onNavigate(
+                                PrefClickEvent.StartActivity(BookSourceActivity::class.java)
+                            )
+                        }
+                    )
+                    ClickableSettingItem(
+                        title = stringResource(R.string.replace_purify),
+                        imageVector = Icons.Default.FindReplace,
+                        onClick = {
+                            onNavigate(
+                                PrefClickEvent.StartActivity(ReplaceRuleActivity::class.java)
+                            )
+                        }
+                    )
+                    ClickableSettingItem(
+                        title = stringResource(R.string.txt_toc_rule),
+                        imageVector = Icons.AutoMirrored.Filled.Rule,
+                        onClick = {
+                            onNavigate(
+                                PrefClickEvent.StartActivity(TxtTocRuleActivity::class.java)
+                            )
+                        }
+                    )
+                    ClickableSettingItem(
+                        title = stringResource(R.string.dict_rule),
+                        imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
+                        onClick = {
+                            onNavigate(
+                                PrefClickEvent.StartActivity(DictRuleActivity::class.java)
+                            )
+                        }
+                    )
+                }
 
-            SplicedColumnGroup(
-                modifier = Modifier.adaptiveHorizontalPadding(),
-                title = stringResource(R.string.other)) {
-                ClickableSettingItem(
-                    title = stringResource(R.string.setting),
-                    imageVector = Icons.Default.Settings,
-                    onClick = {
-                        onOpenSettings()
-                    }
-                )
-                ClickableSettingItem(
-                    title = stringResource(R.string.bookmark),
-                    imageVector = Icons.Default.Bookmark,
-                    onClick = {
-                        onNavigate(PrefClickEvent.StartActivity(AllBookmarkActivity::class.java))
-                    }
-                )
-                ClickableSettingItem(
-                    title = stringResource(R.string.read_record),
-                    imageVector = Icons.Default.History,
-                    onClick = {
-                        onNavigate(PrefClickEvent.StartActivity(ReadRecordActivity::class.java))
-                    }
-                )
-                ClickableSettingItem(
-                    title = stringResource(R.string.file_manage),
-                    imageVector = Icons.Default.Folder,
-                    onClick = {
-                        onNavigate(PrefClickEvent.StartActivity(FileManageActivity::class.java))
-                    }
-                )
-                ClickableSettingItem(
-                    title = stringResource(R.string.about),
-                    imageVector = Icons.Default.Info,
-                    onClick = {
-                        onNavigate(PrefClickEvent.StartActivity(AboutActivity::class.java))
-                    }
-                )
-                ClickableSettingItem(
-                    title = stringResource(R.string.exit),
-                    imageVector = Icons.AutoMirrored.Filled.ExitToApp,
-                    onClick = {
-                        onNavigate(PrefClickEvent.ExitApp)
-                    }
-                )
+                SplicedColumnGroup(
+                    title = stringResource(R.string.other)
+                ) {
+                    ClickableSettingItem(
+                        title = stringResource(R.string.setting),
+                        imageVector = Icons.Default.Settings,
+                        onClick = {
+                            onOpenSettings()
+                        }
+                    )
+                    ClickableSettingItem(
+                        title = stringResource(R.string.bookmark),
+                        imageVector = Icons.Default.Bookmark,
+                        onClick = {
+                            onNavigate(PrefClickEvent.StartActivity(AllBookmarkActivity::class.java))
+                        }
+                    )
+                    ClickableSettingItem(
+                        title = stringResource(R.string.read_record),
+                        imageVector = Icons.Default.History,
+                        onClick = {
+                            onNavigate(PrefClickEvent.StartActivity(ReadRecordActivity::class.java))
+                        }
+                    )
+                    ClickableSettingItem(
+                        title = stringResource(R.string.file_manage),
+                        imageVector = Icons.Default.Folder,
+                        onClick = {
+                            onNavigate(PrefClickEvent.StartActivity(FileManageActivity::class.java))
+                        }
+                    )
+                    ClickableSettingItem(
+                        title = stringResource(R.string.about),
+                        imageVector = Icons.Default.Info,
+                        onClick = {
+                            onNavigate(PrefClickEvent.StartActivity(AboutActivity::class.java))
+                        }
+                    )
+                    ClickableSettingItem(
+                        title = stringResource(R.string.exit),
+                        imageVector = Icons.AutoMirrored.Filled.ExitToApp,
+                        onClick = {
+                            onNavigate(PrefClickEvent.ExitApp)
+                        }
+                    )
+                }
             }
-
-            Spacer(Modifier.height(120.dp))
         }
     }
 }

+ 16 - 29
app/src/main/java/io/legado/app/ui/main/rss/RssScreen.kt

@@ -55,7 +55,9 @@ 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.theme.adaptiveContentPadding
 import io.legado.app.ui.widget.components.SourceIcon
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.button.TopBarActionButton
 import io.legado.app.ui.widget.components.divider.PillDivider
 import io.legado.app.ui.widget.components.divider.PillHeaderDivider
@@ -135,12 +137,10 @@ fun RssScreen(
                     context.startActivity<RssSourceActivity>()
                     dismiss()
                 },
-                leadingIcon = { MenuItemIcon(Icons.Default.Settings) },
                 text = "订阅源管理"
             )
             PillDivider()
             RoundDropdownMenuItem(
-                leadingIcon = { MenuItemIcon(Icons.Default.Group) },
                 text = stringResource(R.string.all),
                 onClick = {
                     viewModel.setGroup("")
@@ -149,7 +149,6 @@ fun RssScreen(
             )
             uiState.groups.forEach { group ->
                 RoundDropdownMenuItem(
-                    leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) },
                     text = group,
                     onClick = {
                         viewModel.setGroup(group)
@@ -162,10 +161,8 @@ fun RssScreen(
         LazyVerticalGrid(
             columns = GridCells.Adaptive(minSize = 72.dp),
             modifier = Modifier.fillMaxSize(),
-            contentPadding = PaddingValues(
-                start = 12.dp,
-                end = 12.dp,
-                top = paddingValues.calculateTopPadding() + 8.dp,
+            contentPadding = adaptiveContentPadding(
+                top = paddingValues.calculateTopPadding(),
                 bottom = 120.dp
             ),
             horizontalArrangement = Arrangement.spacedBy(12.dp),
@@ -186,28 +183,18 @@ fun RssScreen(
         }
     }
 
-    sourceToDelete?.let { source ->
-        AlertDialog(
-            onDismissRequest = { sourceToDelete = null },
-            title = { AppText(stringResource(R.string.draw)) },
-            text = { AppText(stringResource(R.string.sure_del) + "\n" + source.sourceName) },
-            confirmButton = {
-                TextButton(
-                    onClick = {
-                        viewModel.del(source)
-                        sourceToDelete = null
-                    }
-                ) {
-                    AppText(stringResource(R.string.yes))
-                }
-            },
-            dismissButton = {
-                TextButton(onClick = { sourceToDelete = null }) {
-                    AppText(stringResource(R.string.no))
-                }
-            }
-        )
-    }
+    AppAlertDialog(
+        data = sourceToDelete,
+        onDismissRequest = { sourceToDelete = null },
+        title = stringResource(R.string.draw),
+        confirmText = stringResource(R.string.yes),
+        onConfirm = { source ->
+            viewModel.del(source)
+            sourceToDelete = null
+        },
+        dismissText = stringResource(R.string.no),
+        onDismiss = { sourceToDelete = null }
+    )
 }
 
 @OptIn(ExperimentalFoundationApi::class)

+ 3 - 2
app/src/main/java/io/legado/app/ui/replace/ReplaceEditRoute.kt

@@ -1,9 +1,10 @@
 package io.legado.app.ui.replace
 
+import androidx.navigation3.runtime.NavKey
 import kotlinx.serialization.Serializable
 
 @Serializable
-object ReplaceRuleRoute
+object ReplaceRuleRoute : NavKey
 
 @Serializable
 data class ReplaceEditRoute(
@@ -13,4 +14,4 @@ data class ReplaceEditRoute(
     val scope: String? = null,
     val isScopeTitle: Boolean = false,
     val isScopeContent: Boolean = false
-)
+) : NavKey

+ 115 - 35
app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt

@@ -1,16 +1,23 @@
-package io.legado.app.ui.replace
+package io.legado.app.ui.replace
 
 import android.content.Context
 import android.content.Intent
 import androidx.activity.compose.LocalActivity
-import androidx.compose.animation.SharedTransitionLayout
+import androidx.compose.animation.AnimatedContentTransitionScope
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.scaleOut
+import androidx.compose.animation.togetherWith
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.remember
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
-import androidx.navigation.toRoute
+import androidx.navigation3.runtime.NavKey
+import androidx.navigation3.runtime.entryProvider
+import androidx.navigation3.runtime.rememberNavBackStack
+import androidx.navigation3.ui.NavDisplay
 import io.legado.app.base.BaseComposeActivity
 import io.legado.app.ui.replace.edit.ReplaceEditScreen
 import io.legado.app.ui.replace.edit.ReplaceEditViewModel
@@ -36,59 +43,132 @@ class ReplaceRuleActivity : BaseComposeActivity() {
     @Composable
     override fun Content() {
         AppTheme {
-            val navController = rememberNavController()
             val context = LocalActivity.current
+            val startRouteJson = intent.getStringExtra(EXTRA_START_ROUTE)
+            val backStack = rememberNavBackStack(
+                remember(startRouteJson) {
+                    resolveStartRoute(startRouteJson)
+                }
+            )
 
             LaunchedEffect(Unit) {
                 context?.setResult(RESULT_OK)
             }
 
-            val startRouteJson = intent.getStringExtra(EXTRA_START_ROUTE)
-            val initialEditRoute = remember(startRouteJson) {
-                startRouteJson?.let { Json.decodeFromString<ReplaceEditRoute>(it) }
-            }
-
-            val startDestination: Any = initialEditRoute ?: ReplaceRuleRoute
-
-            SharedTransitionLayout {
-                NavHost(
-                    navController = navController,
-                    startDestination = startDestination
-                ) {
-                    composable<ReplaceRuleRoute> {
+            NavDisplay(
+                backStack = backStack,
+                transitionSpec = {
+                    (slideIntoContainer(
+                        towards = AnimatedContentTransitionScope.SlideDirection.Start,
+                        animationSpec = tween(
+                            durationMillis = 480,
+                            easing = FastOutSlowInEasing
+                        ),
+                        initialOffset = { fullWidth -> fullWidth }
+                    ) + fadeIn(
+                        animationSpec = tween(
+                            durationMillis = 360,
+                            easing = LinearOutSlowInEasing
+                        )
+                    )) togetherWith (slideOutOfContainer(
+                        towards = AnimatedContentTransitionScope.SlideDirection.Start,
+                        animationSpec = tween(
+                            durationMillis = 480,
+                            easing = FastOutSlowInEasing
+                        ),
+                        targetOffset = { fullWidth -> fullWidth / 4 }
+                    ) + fadeOut(
+                        animationSpec = tween(
+                            durationMillis = 360,
+                            easing = LinearOutSlowInEasing
+                        )
+                    ))
+                },
+                popTransitionSpec = {
+                    (slideIntoContainer(
+                        towards = AnimatedContentTransitionScope.SlideDirection.Start,
+                        animationSpec = tween(
+                            durationMillis = 480,
+                            easing = FastOutSlowInEasing
+                        ),
+                        initialOffset = { fullWidth -> -fullWidth / 4 }
+                    ) + fadeIn(
+                        animationSpec = tween(
+                            durationMillis = 360,
+                            easing = LinearOutSlowInEasing
+                        )
+                    )) togetherWith (scaleOut(
+                        targetScale = 0.8f,
+                        animationSpec = tween(
+                            durationMillis = 480,
+                            easing = FastOutSlowInEasing
+                        )
+                    ) + fadeOut(
+                        animationSpec = tween(durationMillis = 360)
+                    ))
+                },
+                predictivePopTransitionSpec = { _ ->
+                    (slideIntoContainer(
+                        towards = AnimatedContentTransitionScope.SlideDirection.Start,
+                        animationSpec = tween(
+                            easing = FastOutSlowInEasing
+                        ),
+                        initialOffset = { fullWidth -> -fullWidth / 4 }
+                    ) + fadeIn(
+                        animationSpec = tween(
+                            easing = LinearOutSlowInEasing
+                        )
+                    )) togetherWith (scaleOut(
+                        targetScale = 0.8f,
+                        animationSpec = tween(
+                            easing = FastOutSlowInEasing
+                        )
+                    ) + fadeOut(
+                        animationSpec = tween()
+                    ))
+                },
+                onBack = {
+                    if (backStack.size > 1) {
+                        backStack.removeLastOrNull()
+                    } else {
+                        finish()
+                    }
+                },
+                entryProvider = entryProvider {
+                    entry<ReplaceRuleRoute> {
                         ReplaceRuleScreen(
-                            sharedTransitionScope = this@SharedTransitionLayout,
-                            animatedVisibilityScope = this@composable,
                             onBackClick = { finish() },
-                            onNavigateToEdit = { route -> navController.navigate(route) }
+                            onNavigateToEdit = { route -> backStack.add(route) }
                         )
                     }
 
-                    composable<ReplaceEditRoute> { backStackEntry ->
-                        val route = backStackEntry.toRoute<ReplaceEditRoute>()
+                    entry<ReplaceEditRoute> { route ->
                         val viewModel: ReplaceEditViewModel = koinViewModel { parametersOf(route) }
 
                         ReplaceEditScreen(
-                            sharedTransitionScope = this@SharedTransitionLayout,
-                            animatedVisibilityScope = this@composable,
                             viewModel = viewModel,
                             onBack = {
-                                // 如果编辑页是唯一的页面,点击返回应关闭 Activity
-                                if (navController.previousBackStackEntry == null) {
-                                    finish()
+                                if (backStack.size > 1) {
+                                    backStack.removeLastOrNull()
                                 } else {
-                                    navController.popBackStack()
+                                    finish()
                                 }
                             },
                             onSaveSuccess = {
-                                if (navController.previousBackStackEntry == null) finish()
-                                else navController.popBackStack()
+                                if (backStack.size > 1) {
+                                    backStack.removeLastOrNull()
+                                } else {
+                                    finish()
+                                }
                             }
                         )
                     }
                 }
-            }
+            )
         }
     }
-}
 
+    private fun resolveStartRoute(route: String?): NavKey {
+        return route?.let { Json.decodeFromString<ReplaceEditRoute>(it) } ?: ReplaceRuleRoute
+    }
+}

+ 83 - 115
app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt

@@ -3,14 +3,13 @@ package io.legado.app.ui.replace
 import android.content.ClipData
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
-import androidx.compose.animation.AnimatedVisibilityScope
-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.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,19 +19,14 @@ import androidx.compose.material.icons.filled.FileOpen
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.FloatingActionButton
+import io.legado.app.ui.widget.components.AppFloatingActionButton
 import androidx.compose.material3.Icon
 import androidx.compose.material3.LoadingIndicator
 import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.PlainTooltip
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.SnackbarResult
 import androidx.compose.material3.TextButton
-import androidx.compose.material3.TooltipAnchorPosition
-import androidx.compose.material3.TooltipBox
-import androidx.compose.material3.TooltipDefaults
 import androidx.compose.material3.animateFloatingActionButton
-import androidx.compose.material3.rememberTooltipState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
@@ -55,12 +49,16 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
 import io.legado.app.base.BaseRuleEvent
 import io.legado.app.data.entities.ReplaceRule
+import io.legado.app.ui.theme.adaptiveContentPadding
+import io.legado.app.ui.theme.adaptiveHorizontalPadding
 import io.legado.app.ui.widget.components.ActionItem
 import io.legado.app.ui.widget.components.DraggableSelectionHandler
 import io.legado.app.ui.widget.components.GroupManageBottomSheet
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
 import io.legado.app.ui.widget.components.divider.PillDivider
 import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
+import io.legado.app.ui.widget.components.icon.AppIcon
 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
@@ -78,8 +76,6 @@ import sh.calvin.reorderable.rememberReorderableLazyListState
 )
 @Composable
 fun ReplaceRuleScreen(
-    sharedTransitionScope: SharedTransitionScope,
-    animatedVisibilityScope: AnimatedVisibilityScope,
     viewModel: ReplaceRuleViewModel = koinViewModel(),
     onBackClick: () -> Unit,
     onNavigateToEdit: (ReplaceEditRoute) -> Unit,
@@ -147,16 +143,15 @@ fun ReplaceRuleScreen(
         }
     )
 
-    if (showUrlInput) {
-        SourceInputDialog(
-            title = stringResource(R.string.import_on_line),
-            onDismissRequest = { showUrlInput = false },
-            onConfirm = {
-                showUrlInput = false
-                viewModel.importSource(it)
-            }
-        )
-    }
+    SourceInputDialog(
+        show = showUrlInput,
+        title = stringResource(R.string.import_on_line),
+        onDismissRequest = { showUrlInput = false },
+        onConfirm = {
+            showUrlInput = false
+            viewModel.importSource(it)
+        }
+    )
 
     FilePickerSheet(
         show = showImportSheet,
@@ -262,25 +257,18 @@ fun ReplaceRuleScreen(
     )
 
 
-    showDeleteRuleDialog?.let { rule ->
-        AlertDialog(
-            onDismissRequest = { showDeleteRuleDialog = null },
-            title = { AppText(stringResource(R.string.delete)) },
-            text = { AppText(stringResource(R.string.del_msg)) },
-            confirmButton = {
-                OutlinedButton(onClick = {
-                    viewModel.delete(rule); showDeleteRuleDialog = null
-                }) { AppText(stringResource(R.string.ok)) }
-            },
-            dismissButton = {
-                TextButton(onClick = { showDeleteRuleDialog = null }) {
-                    AppText(
-                        stringResource(R.string.cancel)
-                    )
-                }
-            }
-        )
-    }
+    AppAlertDialog(
+        data = showDeleteRuleDialog,
+        onDismissRequest = { showDeleteRuleDialog = null },
+        title = stringResource(R.string.delete),
+        confirmText = stringResource(R.string.ok),
+        onConfirm = { rule ->
+            viewModel.delete(rule)
+            showDeleteRuleDialog = null
+        },
+        dismissText = stringResource(R.string.cancel),
+        onDismiss = { showDeleteRuleDialog = null }
+    )
 
     RuleListScaffold(
         title = "替换规则",
@@ -334,6 +322,8 @@ fun ReplaceRuleScreen(
         bottomContent = {
             if (tabItems.size > 1) {
                 AppTabRow(
+                    modifier = Modifier
+                        .adaptiveHorizontalPadding(),
                     tabTitles = tabItems,
                     selectedTabIndex = selectedTabIndex,
                     onTabSelected = { index ->
@@ -344,39 +334,24 @@ fun ReplaceRuleScreen(
             }
         },
         floatingActionButton = {
-            TooltipBox(
-                positionProvider =
-                    TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
-                tooltip = { PlainTooltip { AppText("Localized description") } },
-                state = rememberTooltipState(),
+            AppFloatingActionButton(
+                modifier = Modifier.animateFloatingActionButton(
+                    visible = !inSelectionMode,
+                    alignment = Alignment.BottomEnd,
+                ),
+                onClick = {
+                    onNavigateToEdit(ReplaceEditRoute(id = -1))
+                },
+                tooltipText = "Localized description"
             ) {
-                with(sharedTransitionScope) {
-                    FloatingActionButton(
-                        modifier = Modifier
-                            .animateFloatingActionButton(
-                                visible = !inSelectionMode,
-                                alignment = Alignment.BottomEnd,
-                            )
-                            .sharedBounds(
-                                sharedContentState = rememberSharedContentState(key = "fab_add"),
-                                animatedVisibilityScope = animatedVisibilityScope,
-                                resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds
-                            ),
-                        onClick = {
-                            onNavigateToEdit(ReplaceEditRoute(id = -1))
-                        }
-                    ) {
-                        Icon(Icons.Default.Add, contentDescription = "Add Rule")
-                    }
-                }
+                AppIcon(Icons.Default.Add, contentDescription = "Add Rule")
             }
         },
         snackbarHostState = snackbarHostState,
         dropDownMenuContent = { dismiss ->
             RoundDropdownMenuItem(
                 text = stringResource(R.string.import_str),
-                onClick = { showImportSheet = true; dismiss() },
-                leadingIcon = { Icon(Icons.Default.FileOpen, null) }
+                onClick = { showImportSheet = true; dismiss() }
             )
             RoundDropdownMenuItem(
                 text = "分组管理",
@@ -401,7 +376,7 @@ fun ReplaceRuleScreen(
                     viewModel.setSortMode("name_asc")
                     dismiss()
                     scope.launch {
-                        snackbarHostState.showSnackbar("非时间排序模式下将禁用拖动")
+                        snackbarHostState.showSnackbar("当前排序模式下禁用拖动")
                     }
                 }
             )
@@ -411,7 +386,7 @@ fun ReplaceRuleScreen(
                     viewModel.setSortMode("name_desc")
                     dismiss()
                     scope.launch {
-                        snackbarHostState.showSnackbar("非时间排序模式下将禁用拖动")
+                        snackbarHostState.showSnackbar("当前排序模式下禁用拖动")
                     }
                 }
             )
@@ -425,59 +400,51 @@ fun ReplaceRuleScreen(
                 state = listState,
                 modifier = Modifier
                     .fillMaxSize(),
-                contentPadding = PaddingValues(
-                    top = padding.calculateTopPadding() + 8.dp,
-                    bottom = padding.calculateBottomPadding() + 120.dp
+                contentPadding = adaptiveContentPadding(
+                    top = padding.calculateTopPadding(),
+                    bottom = 120.dp
                 ),
                 verticalArrangement = Arrangement.spacedBy(8.dp)
             ) {
                 items(filteredRules, key = { it.id }) { ui ->
-                    with(sharedTransitionScope) {
-                        ReorderableSelectionItem(
-                            state = reorderableState,
-                            key = ui.id,
-                            title = ui.name,
-                            isEnabled = ui.isEnabled,
-                            isSelected = selectedIds.contains(ui.id),
-                            inSelectionMode = inSelectionMode,
-                            canReorder = canReorder,
-                            onToggleSelection = {
-                                viewModel.toggleSelection(ui.id)
-                            },
-                            onEnabledChange = { enabled ->
-                                viewModel.update(ui.rule.copy(isEnabled = enabled))
-                            },
-                            onClickEdit = {
-                                onNavigateToEdit(
-                                    ReplaceEditRoute(
-                                        id = ui.id,
-                                        pattern = ui.rule.pattern
-                                    )
-                                )
-                            },
-                            modifier = Modifier
-                                .sharedBounds(
-                                    sharedContentState = rememberSharedContentState(key = "rule_${ui.id}"),
-                                    animatedVisibilityScope = animatedVisibilityScope,
-                                    resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds,
-                                    //clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(12.dp))
-                                ),
-                            dropdownContent = { dismiss ->
-                                RoundDropdownMenuItem(
-                                    text = "移至顶部",
-                                    onClick = { viewModel.toTop(ui.rule); dismiss() }
-                                )
-                                RoundDropdownMenuItem(
-                                    text = "移至底部",
-                                    onClick = { viewModel.toBottom(ui.rule); dismiss() }
+                    ReorderableSelectionItem(
+                        state = reorderableState,
+                        key = ui.id,
+                        title = ui.name,
+                        isEnabled = ui.isEnabled,
+                        isSelected = selectedIds.contains(ui.id),
+                        inSelectionMode = inSelectionMode,
+                        canReorder = canReorder,
+                        onToggleSelection = {
+                            viewModel.toggleSelection(ui.id)
+                        },
+                        onEnabledChange = { enabled ->
+                            viewModel.update(ui.rule.copy(isEnabled = enabled))
+                        },
+                        onClickEdit = {
+                            onNavigateToEdit(
+                                ReplaceEditRoute(
+                                    id = ui.id,
+                                    pattern = ui.rule.pattern
                                 )
-                                RoundDropdownMenuItem(
-                                    text = "删除",
-                                    onClick = { showDeleteRuleDialog = ui.rule; dismiss() }
-                                )
-                            }
-                        )
-                    }
+                            )
+                        },
+                        modifier = Modifier,
+                        dropdownContent = { dismiss ->
+                            RoundDropdownMenuItem(
+                                text = "移至顶部",
+                                onClick = { viewModel.toTop(ui.rule); dismiss() }
+                            )
+                            RoundDropdownMenuItem(
+                                text = "移至底部",
+                                onClick = { viewModel.toBottom(ui.rule); dismiss() }
+                            )
+                            RoundDropdownMenuItem(
+                                text = "删除",
+                                onClick = { showDeleteRuleDialog = ui.rule; dismiss() }
+                            )
+                        }
+                    )
                 }
             }
             if (inSelectionMode) {
@@ -496,3 +463,4 @@ fun ReplaceRuleScreen(
         }
     }
 }
+

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

@@ -1,8 +1,6 @@
-package io.legado.app.ui.replace.edit
+package io.legado.app.ui.replace.edit
 
 import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.AnimatedVisibilityScope
-import androidx.compose.animation.SharedTransitionScope
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.slideInVertically
@@ -46,18 +44,13 @@ import androidx.compose.material3.ExposedDropdownMenuBox
 import androidx.compose.material3.ExposedDropdownMenuDefaults
 import androidx.compose.material3.FilterChip
 import androidx.compose.material3.FilterChipDefaults
-import androidx.compose.material3.FloatingActionButton
+import io.legado.app.ui.widget.components.AppFloatingActionButton
 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.TextButton
-import androidx.compose.material3.TooltipAnchorPosition
-import androidx.compose.material3.TooltipBox
-import androidx.compose.material3.TooltipDefaults
 import androidx.compose.material3.animateFloatingActionButton
-import androidx.compose.material3.rememberTooltipState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.collectAsState
@@ -77,8 +70,13 @@ 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.AppTextField
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 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.button.TopBarNavigationButton
+import io.legado.app.ui.widget.components.checkBox.CheckboxItem
+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.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
@@ -93,272 +91,252 @@ fun keyboardAsState(): State<Boolean> {
 @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun ReplaceEditScreen(
-    sharedTransitionScope: SharedTransitionScope,
-    animatedVisibilityScope: AnimatedVisibilityScope,
     onBack: () -> Unit,
     onSaveSuccess: () -> Unit,
     viewModel: ReplaceEditViewModel = koinViewModel()
 ) {
     val state by viewModel.uiState.collectAsState()
-    val sharedKey = remember(state.id) {
-        if (state.id > 0) "rule_${state.id}" else "fab_add"
-    }
     val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
     var showMenu by remember { mutableStateOf(false) }
     val isKeyboardVisible by keyboardAsState()
 
-    with(sharedTransitionScope) {
-        AppScaffold(
-            modifier = Modifier
-                .nestedScroll(scrollBehavior.nestedScrollConnection)
-                .sharedBounds(
-                    sharedContentState = rememberSharedContentState(key = sharedKey),
-                    animatedVisibilityScope = animatedVisibilityScope,
-                    resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds
-                )
-                .imePadding(),
-            topBar = {
-                GlassMediumFlexibleTopAppBar(
-                    title = if (state.id > 0) "编辑替换规则" else "新增替换规则",
-                    navigationIcon = {
-                        TopbarNavigationButton(onClick = onBack)
-                    },
-                    actions = {
-                        AnimatedVisibility(
-                            visible = isKeyboardVisible,
-                            enter = fadeIn(),
-                            exit = fadeOut()
-                        ) {
-                            IconButton(onClick = {
-                                viewModel.save(onSaveSuccess)
-                            }) {
-                                Icon(Icons.Default.Save, contentDescription = "保存")
-                            }
-                        }
-                        IconButton(onClick = { showMenu = true }) {
-                            Icon(Icons.Default.MoreVert, contentDescription = "更多操作")
-                        }
-                        DropdownMenu(
-                            expanded = showMenu,
-                            onDismissRequest = { showMenu = false }
-                        ) {
-                            DropdownMenuItem(
-                                text = { AppText("复制规则") },
-                                onClick = {
-                                    showMenu = false
-                                    viewModel.copyRule()
-                                }
-                            )
-                            DropdownMenuItem(
-                                text = { AppText("粘贴规则") },
-                                onClick = {
-                                    showMenu = false
-                                    viewModel.pasteRule(onSuccess = {})
-                                }
-                            )
+    AppScaffold(
+        modifier = Modifier
+            .nestedScroll(scrollBehavior.nestedScrollConnection),
+        topBar = {
+            GlassMediumFlexibleTopAppBar(
+                title = if (state.id > 0) "编辑替换规则" else "新增替换规则",
+                navigationIcon = {
+                    TopBarNavigationButton(onClick = onBack)
+                },
+                actions = {
+                    AnimatedVisibility(
+                        visible = isKeyboardVisible,
+                        enter = fadeIn(),
+                        exit = fadeOut()
+                    ) {
+                        IconButton(onClick = {
+                            viewModel.save(onSaveSuccess)
+                        }) {
+                            Icon(Icons.Default.Save, contentDescription = "保存")
                         }
-                    },
-                    scrollBehavior = scrollBehavior
-                )
-            },
-            floatingActionButton = {
-                TooltipBox(
-                    modifier = Modifier.navigationBarsPadding(),
-                    positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
-                        TooltipAnchorPosition.Above
-                    ),
-                    tooltip = { PlainTooltip { AppText("添加") } },
-                    state = rememberTooltipState(),
-                ) {
-                    FloatingActionButton(
-                        modifier = Modifier.animateFloatingActionButton(
-                            visible = !isKeyboardVisible,
-                            alignment = Alignment.BottomEnd,
-                        ),
-                        onClick = { viewModel.save(onSaveSuccess) }
+                    }
+                    IconButton(onClick = { showMenu = true }) {
+                        Icon(Icons.Default.MoreVert, contentDescription = "更多操作")
+                    }
+                    RoundDropdownMenu(
+                        expanded = showMenu,
+                        onDismissRequest = { showMenu = false }
                     ) {
-                        Icon(Icons.Default.Save, contentDescription = "保存")
+                        RoundDropdownMenuItem(
+                            text = "复制规则",
+                            onClick = {
+                                showMenu = false
+                                viewModel.copyRule()
+                            }
+                        )
+                        RoundDropdownMenuItem(
+                            text = "粘贴规则",
+                            onClick = {
+                                showMenu = false
+                                viewModel.pasteRule(onSuccess = {})
+                            }
+                        )
                     }
-                }
-            }, contentWindowInsets = WindowInsets(0, 0, 0, 0)
-        ) { innerPadding ->
-            Box(
-                Modifier
-                    .fillMaxSize()
-                    .padding(innerPadding)
-            ) {
-                AnimatedVisibility(
-                    visible = isKeyboardVisible,
-                    enter = slideInVertically(
-                        initialOffsetY = { fullHeight -> fullHeight },
-                    ),
-                    exit = slideOutVertically(
-                        targetOffsetY = { fullHeight -> fullHeight },
+                },
+                scrollBehavior = scrollBehavior
+            )
+        },
+        floatingActionButton = {
+            AppFloatingActionButton(
+                modifier = Modifier
+                    .navigationBarsPadding()
+                    .animateFloatingActionButton(
+                        visible = !isKeyboardVisible,
+                        alignment = Alignment.BottomEnd,
                     ),
-                    modifier = Modifier
-                        .align(Alignment.BottomCenter)
-                        .zIndex(1f)
-                ) {
-                    QuickInputBar(
-                        onInsert = { text -> viewModel.insertTextAtCursor(text) }
-                    )
-                }
-                Column(
+                onClick = { viewModel.save(onSaveSuccess) },
+                tooltipText = "添加"
+            ) {
+                Icon(Icons.Default.Save, contentDescription = "保存")
+            }
+        }, contentWindowInsets = WindowInsets(0, 0, 0, 0)
+    ) { innerPadding ->
+        Box(
+            Modifier
+                .fillMaxSize()
+                .padding(innerPadding)
+        ) {
+            AnimatedVisibility(
+                visible = isKeyboardVisible,
+                enter = slideInVertically(
+                    initialOffsetY = { fullHeight -> fullHeight },
+                ),
+                exit = slideOutVertically(
+                    targetOffsetY = { fullHeight -> fullHeight },
+                ),
+                modifier = Modifier
+                    .align(Alignment.BottomCenter)
+                    .zIndex(1f)
+            ) {
+                QuickInputBar(
+                    onInsert = { text -> viewModel.insertTextAtCursor(text) }
+                )
+            }
+            Column(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(horizontal = 16.dp)
+                    .verticalScroll(rememberScrollState()),
+                verticalArrangement = Arrangement.spacedBy(16.dp)
+            ) {
+
+                AppTextField(
+                    value = state.name,
+                    onValueChange = viewModel::onNameChange,
+                    label = "规则名称",
                     modifier = Modifier
                         .fillMaxWidth()
-                        .padding(horizontal = 16.dp)
-                        .verticalScroll(rememberScrollState()),
-                    verticalArrangement = Arrangement.spacedBy(16.dp)
-                ) {
+                        .onFocusChanged {
+                            if (it.isFocused) viewModel.activeField =
+                                ReplaceEditViewModel.ActiveField.Name
+                        },
+                    singleLine = true
+                )
 
-                    OutlinedTextField(
-                        value = state.name,
-                        onValueChange = viewModel::onNameChange,
-                        label = { AppText("规则名称") },
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .onFocusChanged {
-                                if (it.isFocused) viewModel.activeField =
-                                    ReplaceEditViewModel.ActiveField.Name
-                            },
-                        singleLine = true
-                    )
+                GroupSelector(
+                    currentGroup = state.group,
+                    allGroups = state.allGroups,
+                    onGroupChange = viewModel::onGroupChange,
+                    onManageClick = { viewModel.toggleGroupDialog(true) }
+                )
 
-                    GroupSelector(
-                        currentGroup = state.group,
-                        allGroups = state.allGroups,
-                        onGroupChange = viewModel::onGroupChange,
-                        onManageClick = { viewModel.toggleGroupDialog(true) }
-                    )
+                AppTextField(
+                    value = state.pattern,
+                    onValueChange = viewModel::onPatternChange,
+                    label = "匹配规则",
+                    placeholder = { AppText("输入正则表达式或关键字") },
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .onFocusChanged {
+                            if (it.isFocused) viewModel.activeField =
+                                ReplaceEditViewModel.ActiveField.Pattern
+                        }
+                )
 
-                    OutlinedTextField(
-                        value = state.pattern,
-                        onValueChange = viewModel::onPatternChange,
-                        label = { AppText("匹配规则") },
-                        placeholder = { AppText("输入正则表达式或关键字") },
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .onFocusChanged {
-                                if (it.isFocused) viewModel.activeField =
-                                    ReplaceEditViewModel.ActiveField.Pattern
-                            }
-                    )
+                AppTextField(
+                    value = state.replacement,
+                    onValueChange = viewModel::onReplacementChange,
+                    label = "替换为",
+                    placeholder = { AppText("输入替换内容或捕获组") },
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .onFocusChanged {
+                            if (it.isFocused) viewModel.activeField =
+                                ReplaceEditViewModel.ActiveField.Replacement
+                        }
+                )
 
-                    OutlinedTextField(
-                        value = state.replacement,
-                        onValueChange = viewModel::onReplacementChange,
-                        label = { AppText("替换为") },
-                        placeholder = { AppText("输入替换内容或捕获组") },
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .onFocusChanged {
-                                if (it.isFocused) viewModel.activeField =
-                                    ReplaceEditViewModel.ActiveField.Replacement
+                Row(
+                    modifier = Modifier.fillMaxWidth(),
+                    verticalAlignment = Alignment.CenterVertically,
+                    horizontalArrangement = Arrangement.Start
+                ) {
+                    FilterChip(
+                        selected = state.scopeTitle,
+                        onClick = { viewModel.onScopeTitleChange(!state.scopeTitle) },
+                        label = { AppText("标题") },
+                        leadingIcon = if (state.scopeTitle) {
+                            {
+                                Icon(
+                                    Icons.Default.Check,
+                                    contentDescription = "已选择",
+                                    Modifier.size(FilterChipDefaults.IconSize)
+                                )
                             }
+                        } else null
                     )
 
-                    Row(
-                        modifier = Modifier.fillMaxWidth(),
-                        verticalAlignment = Alignment.CenterVertically,
-                        horizontalArrangement = Arrangement.Start
-                    ) {
-                        FilterChip(
-                            selected = state.scopeTitle,
-                            onClick = { viewModel.onScopeTitleChange(!state.scopeTitle) },
-                            label = { AppText("标题") },
-                            leadingIcon = if (state.scopeTitle) {
-                                {
-                                    Icon(
-                                        Icons.Default.Check,
-                                        contentDescription = "已选",
-                                        Modifier.size(FilterChipDefaults.IconSize)
-                                    )
-                                }
-                            } else null
-                        )
-
-                        Spacer(Modifier.width(8.dp))
+                    Spacer(Modifier.width(8.dp))
 
-                        FilterChip(
-                            selected = state.scopeContent,
-                            onClick = { viewModel.onScopeContentChange(!state.scopeContent) },
-                            label = { AppText("内容") },
-                            leadingIcon = if (state.scopeContent) {
-                                {
-                                    Icon(
-                                        Icons.Default.Check,
-                                        contentDescription = "已选",
-                                        Modifier.size(FilterChipDefaults.IconSize)
-                                    )
-                                }
-                            } else null
-                        )
-
-                        Spacer(Modifier.weight(1f))
-
-                        FilterChip(
-                            selected = state.isRegex,
-                            onClick = { viewModel.onRegexChange(!state.isRegex) },
-                            label = { AppText("使用正则") },
-                            leadingIcon = if (state.isRegex) {
-                                {
-                                    Icon(
-                                        Icons.Default.Check,
-                                        contentDescription = "正则已启用",
-                                        Modifier.size(FilterChipDefaults.IconSize)
-                                    )
-                                }
-                            } else null
-                        )
-
-                    }
-
-                    OutlinedTextField(
-                        value = state.scope,
-                        onValueChange = viewModel::onScopeChange,
-                        label = { AppText("特定范围") },
-                        placeholder = { AppText("指定规则适用的范围") },
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .onFocusChanged {
-                                if (it.isFocused) viewModel.activeField =
-                                    ReplaceEditViewModel.ActiveField.Scope
+                    FilterChip(
+                        selected = state.scopeContent,
+                        onClick = { viewModel.onScopeContentChange(!state.scopeContent) },
+                        label = { AppText("内容") },
+                        leadingIcon = if (state.scopeContent) {
+                            {
+                                Icon(
+                                    Icons.Default.Check,
+                                    contentDescription = "已选择",
+                                    Modifier.size(FilterChipDefaults.IconSize)
+                                )
                             }
+                        } else null
                     )
 
-                    OutlinedTextField(
-                        value = state.excludeScope,
-                        onValueChange = viewModel::onExcludeScopeChange,
-                        label = { AppText("排除范围") },
-                        placeholder = { AppText("指定规则不适用的范围") },
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .onFocusChanged {
-                                if (it.isFocused) viewModel.activeField =
-                                    ReplaceEditViewModel.ActiveField.Exclude
+                    Spacer(Modifier.weight(1f))
+
+                    FilterChip(
+                        selected = state.isRegex,
+                        onClick = { viewModel.onRegexChange(!state.isRegex) },
+                        label = { AppText("使用正则") },
+                        leadingIcon = if (state.isRegex) {
+                            {
+                                Icon(
+                                    Icons.Default.Check,
+                                    contentDescription = "正则已启用",
+                                    Modifier.size(FilterChipDefaults.IconSize)
+                                )
                             }
+                        } else null
                     )
 
-                    OutlinedTextField(
-                        value = state.timeout,
-                        onValueChange = viewModel::onTimeoutChange,
-                        label = { AppText("超时 (ms)") },
-                        placeholder = { AppText("3000") },
-                        modifier = Modifier.fillMaxWidth()
-                    )
+                }
 
-                    Spacer(Modifier.height(120.dp))
+                AppTextField(
+                    value = state.scope,
+                    onValueChange = viewModel::onScopeChange,
+                    label = "特定范围",
+                    placeholder = { AppText("指定规则适用的范围") },
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .onFocusChanged {
+                            if (it.isFocused) viewModel.activeField =
+                                ReplaceEditViewModel.ActiveField.Scope
+                        }
+                )
 
-                }
-            }
+                AppTextField(
+                    value = state.excludeScope,
+                    onValueChange = viewModel::onExcludeScopeChange,
+                    label = "排除范围",
+                    placeholder = { AppText("指定规则不适用的范围") },
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .onFocusChanged {
+                            if (it.isFocused) viewModel.activeField =
+                                ReplaceEditViewModel.ActiveField.Exclude
+                        }
+                )
 
-            if (state.showGroupDialog) {
-                ManageGroupDialog(
-                    groups = state.allGroups.filter { it != "默认" },
-                    onDismiss = { viewModel.toggleGroupDialog(false) },
-                    onDelete = { viewModel.deleteGroups(it) }
+                AppTextField(
+                    value = state.timeout,
+                    onValueChange = viewModel::onTimeoutChange,
+                    label = "超时 (ms)",
+                    placeholder = { AppText("3000") },
+                    modifier = Modifier.fillMaxWidth()
                 )
+
+                Spacer(Modifier.height(120.dp))
+
             }
+
+            ManageGroupDialog(
+                show = state.showGroupDialog,
+                groups = state.allGroups.filter { it != "默认" },
+                onDismiss = { viewModel.toggleGroupDialog(false) },
+                onDelete = { viewModel.deleteGroups(it) }
+            )
         }
     }
 }
@@ -380,13 +358,12 @@ fun GroupSelector(
             onExpandedChange = { expanded = !expanded },
             modifier = Modifier.weight(1f)
         ) {
-            OutlinedTextField(
+            AppTextField(
                 value = currentGroup,
                 onValueChange = onGroupChange,
-                label = { AppText("分组") },
+                label = "分组",
                 placeholder = { AppText("默认") },
                 trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
-                colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
                 modifier = Modifier
                     .fillMaxWidth()
                     .menuAnchor(
@@ -394,13 +371,13 @@ fun GroupSelector(
                         true
                     )
             )
-            ExposedDropdownMenu(
+            RoundDropdownMenu(
                 expanded = expanded,
                 onDismissRequest = { expanded = false }
             ) {
                 allGroups.forEach { selectionOption ->
-                    DropdownMenuItem(
-                        text = { AppText(selectionOption) },
+                    RoundDropdownMenuItem(
+                        text = selectionOption,
                         onClick = {
                             onGroupChange(selectionOption)
                             expanded = false
@@ -417,52 +394,52 @@ fun GroupSelector(
 
 @Composable
 fun ManageGroupDialog(
+    show: Boolean, // 💡 新增 show 参数,外层直接传值,不要用 if 包裹!
     groups: List<String>,
     onDismiss: () -> Unit,
     onDelete: (List<String>) -> Unit
 ) {
+    // 💡 使用 Set 来管理多选状态,并且以 show 作为 key。
+    // 这样每次重新打开弹窗时,选中状态会自动清空,防止上次的残留
+    var selectedGroups by remember(show) { mutableStateOf(emptySet<String>()) }
 
-    val selected = remember { mutableStateMapOf<String, Boolean>() }
-
-    AlertDialog(
+    AppAlertDialog(
+        show = show,
         onDismissRequest = onDismiss,
-        title = { AppText("分组管理") },
-        text = {
-            if (groups.isEmpty()) AppText("暂无其他分组")
-            else Column(
-                modifier = Modifier.verticalScroll(rememberScrollState()),
-                verticalArrangement = Arrangement.spacedBy(8.dp)
-            ) {
-                groups.forEach { group ->
-                    val isSelected = selected[group] ?: false
-                    Row(
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .clip(MaterialTheme.shapes.small)
-                            .background(
-                                color = if (isSelected) MaterialTheme.colorScheme.secondaryContainer else Color.Transparent,
-                                shape = MaterialTheme.shapes.small
-                            )
-                            .clickable { selected[group] = !isSelected }
-                            .padding(12.dp),
-                        verticalAlignment = Alignment.CenterVertically
-                    ) {
-                        Checkbox(checked = isSelected, onCheckedChange = null)
-                        AppText(group, Modifier.padding(start = 8.dp))
+        title = "分组管理",
+        content = {
+            if (groups.isEmpty()) {
+                AppText("暂无其他分组")
+            } else {
+                Column(
+                    modifier = Modifier.verticalScroll(rememberScrollState()),
+                    // 去掉了 spacedBy(8.dp),因为 CheckboxItem 内部已经有了合适的 padding
+                ) {
+                    groups.forEach { group ->
+                        val isSelected = selectedGroups.contains(group)
+
+                        CheckboxItem(
+                            title = group,
+                            checked = isSelected,
+                            onCheckedChange = { checked ->
+                                selectedGroups = if (checked) {
+                                    selectedGroups + group
+                                } else {
+                                    selectedGroups - group
+                                }
+                            }
+                        )
                     }
                 }
             }
         },
-        confirmButton = {
-            AlertButton(
-                onClick = {
-                    val toDelete = selected.filter { it.value }.keys.toList()
-                    onDelete(toDelete)
-                },
-                text = "删除选中"
-            )
+        confirmText = "删除选中",
+        onConfirm = {
+            onDelete(selectedGroups.toList())
+            // 注意:外层需要在 onDelete 回调中将 show 置为 false
         },
-        dismissButton = { TextButton(onClick = onDismiss) { AppText("关闭") } }
+        dismissText = "关闭",
+        onDismiss = onDismiss
     )
 }
 

+ 38 - 62
app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt

@@ -1,8 +1,6 @@
 package io.legado.app.ui.replace.edit
 
 import android.app.Application
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.input.TextFieldValue
 import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
@@ -26,15 +24,15 @@ import kotlinx.coroutines.launch
 
 data class ReplaceEditUiState(
     val id: Long = 0,
-    val name: TextFieldValue = TextFieldValue(""),
+    val name: String = "",
     val group: String = "默认",
-    val pattern: TextFieldValue = TextFieldValue(""),
-    val replacement: TextFieldValue = TextFieldValue(""),
+    val pattern: String = "",
+    val replacement: String = "",
     val isRegex: Boolean = false,
-    val scope: TextFieldValue = TextFieldValue(""),
+    val scope: String = "",
     val scopeTitle: Boolean = false,
     val scopeContent: Boolean = false,
-    val excludeScope: TextFieldValue = TextFieldValue(""),
+    val excludeScope: String = "",
     val timeout: String = "3000",
     val allGroups: List<String> = emptyList(),
     val showGroupDialog: Boolean = false
@@ -71,13 +69,13 @@ class ReplaceEditViewModel(
                 _uiState.update {
                     it.copy(
                         id = id,
-                        name = TextFieldValue(route.pattern ?: ""),
-                        pattern = TextFieldValue(route.pattern ?: ""),
+                        name = route.pattern ?: "",
+                        pattern = route.pattern ?: "",
                         isRegex = route.isRegex,
-                        scope = TextFieldValue(route.scope ?: ""),
+                        scope = route.scope ?: "",
                         scopeTitle = route.isScopeTitle,
                         scopeContent = route.isScopeContent,
-                        excludeScope = TextFieldValue(""),
+                        excludeScope = "",
                     )
                 }
             }
@@ -96,15 +94,15 @@ class ReplaceEditViewModel(
         _uiState.update {
             it.copy(
                 id = rule.id,
-                name = TextFieldValue(rule.name),
+                name = rule.name,
                 group = rule.group ?: "默认",
-                pattern = TextFieldValue(rule.pattern),
-                replacement = TextFieldValue(rule.replacement),
+                pattern = rule.pattern,
+                replacement = rule.replacement,
                 isRegex = rule.isRegex,
                 scopeTitle = rule.scopeTitle,
                 scopeContent = rule.scopeContent,
-                scope = TextFieldValue(rule.scope ?: ""),
-                excludeScope = TextFieldValue(rule.excludeScope ?: ""),
+                scope = rule.scope ?: "",
+                excludeScope = rule.excludeScope ?: "",
                 timeout = rule.timeoutMillisecond.toString()
             )
         }
@@ -114,15 +112,15 @@ class ReplaceEditViewModel(
         val state = _uiState.value
         val rule = ReplaceRule().apply {
             id = state.id
-            name = state.name.text
+            name = state.name
             group = if (state.group == "默认" || state.group.isBlank()) null else state.group
-            pattern = state.pattern.text
-            replacement = state.replacement.text
+            pattern = state.pattern
+            replacement = state.replacement
             isRegex = state.isRegex
             scopeTitle = state.scopeTitle
             scopeContent = state.scopeContent
-            scope = state.scope.text
-            excludeScope = state.excludeScope.text
+            scope = state.scope
+            excludeScope = state.excludeScope
             timeoutMillisecond = state.timeout.toLongOrNull() ?: 3000L
         }
         return rule
@@ -160,23 +158,27 @@ class ReplaceEditViewModel(
         }
     }
 
-    fun onNameChange(v: TextFieldValue) {
+    fun onNameChange(v: String) {
         _uiState.update { it.copy(name = v) }
         activeField = ActiveField.Name
     }
-    fun onScopeChange(v: TextFieldValue) {
+
+    fun onScopeChange(v: String) {
         _uiState.update { it.copy(scope = v) }
         activeField = ActiveField.Scope
     }
-    fun onPatternChange(v: TextFieldValue) {
+
+    fun onPatternChange(v: String) {
         _uiState.update { it.copy(pattern = v) }
         activeField = ActiveField.Pattern
     }
-    fun onReplacementChange(v: TextFieldValue) {
+
+    fun onReplacementChange(v: String) {
         _uiState.update { it.copy(replacement = v) }
         activeField = ActiveField.Replacement
     }
-    fun onExcludeScopeChange(v: TextFieldValue) {
+
+    fun onExcludeScopeChange(v: String) {
         _uiState.update { it.copy(excludeScope = v) }
         activeField = ActiveField.Exclude
     }
@@ -189,56 +191,30 @@ class ReplaceEditViewModel(
     fun insertTextAtCursor(text: String) {
         val state = _uiState.value
         when (activeField) {
-            ActiveField.Name -> {
-                val newTfv = insertIntoTextFieldValue(state.name, text)
-                _uiState.update { it.copy(name = newTfv) }
-            }
-            ActiveField.Pattern -> {
-                val newTfv = insertIntoTextFieldValue(state.pattern, text)
-                _uiState.update { it.copy(pattern = newTfv) }
-            }
-            ActiveField.Replacement -> {
-                val newTfv = insertIntoTextFieldValue(state.replacement, text)
-                _uiState.update { it.copy(replacement = newTfv) }
-            }
-            ActiveField.Scope -> {
-                val newTfv = insertIntoTextFieldValue(state.scope, text)
-                _uiState.update { it.copy(scope = newTfv) }
-            }
-            ActiveField.Exclude -> {
-                val newTfv = insertIntoTextFieldValue(state.excludeScope, text)
-                _uiState.update { it.copy(excludeScope = newTfv) }
-            }
+            ActiveField.Name -> _uiState.update { it.copy(name = it.name + text) }
+            ActiveField.Pattern -> _uiState.update { it.copy(pattern = it.pattern + text) }
+            ActiveField.Replacement -> _uiState.update { it.copy(replacement = it.replacement + text) }
+            ActiveField.Scope -> _uiState.update { it.copy(scope = it.scope + text) }
+            ActiveField.Exclude -> _uiState.update { it.copy(excludeScope = it.excludeScope + text) }
             else -> {}
         }
     }
 
-    private fun insertIntoTextFieldValue(current: TextFieldValue, insert: String): TextFieldValue {
-        val start = current.selection.start
-        val end = current.selection.end
-        val newText = current.text.replaceRange(start, end, insert)
-        val newCursor = start + insert.length
-        return TextFieldValue(
-            text = newText,
-            selection = TextRange(newCursor)
-        )
-    }
-
     fun save(onSuccess: () -> Unit) {
         viewModelScope.launch(Dispatchers.IO) {
             val state = _uiState.value
 
             val rule = ReplaceRule().apply {
                 id = if (state.id <= 0) System.currentTimeMillis() else state.id
-                name = state.name.text
+                name = state.name
                 group = if (state.group == "默认" || state.group.isBlank()) null else state.group
-                pattern = state.pattern.text
-                replacement = state.replacement.text
+                pattern = state.pattern
+                replacement = state.replacement
                 isRegex = state.isRegex
                 scopeTitle = state.scopeTitle
                 scopeContent = state.scopeContent
-                scope = state.scope.text
-                excludeScope = state.excludeScope.text
+                scope = state.scope
+                excludeScope = state.excludeScope
                 timeoutMillisecond = state.timeout.toLongOrNull() ?: 3000L
             }
 

+ 36 - 39
app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt

@@ -59,47 +59,44 @@ fun RssFavoritesScreen(
     var showRemoveFromGroupDialog by remember { mutableStateOf(false) }
     var showSetGroupDialog by remember { mutableStateOf<RssStar?>(null) }
 
-    if (showAddToGroupDialog) {
-        TextListInputDialog(
-            title = stringResource(R.string.add_group),
-            hint = stringResource(R.string.group_name),
-            suggestions = groups,
-            onDismissRequest = { showAddToGroupDialog = false },
-            onConfirm = {
-                viewModel.selectionAddToGroups(state.selectedIds, it)
-                showAddToGroupDialog = false
-                viewModel.clearSelection()
-            }
-        )
-    }
+    TextListInputDialog(
+        show = showAddToGroupDialog,
+        title = stringResource(R.string.add_group),
+        hint = stringResource(R.string.group_name),
+        suggestions = groups,
+        onDismissRequest = { showAddToGroupDialog = false },
+        onConfirm = { text ->
+            viewModel.selectionAddToGroups(state.selectedIds, text)
+            showAddToGroupDialog = false
+            viewModel.clearSelection()
+        }
+    )
 
-    if (showRemoveFromGroupDialog) {
-        TextListInputDialog(
-            title = stringResource(R.string.remove_group),
-            hint = stringResource(R.string.group_name),
-            suggestions = groups,
-            onDismissRequest = { showRemoveFromGroupDialog = false },
-            onConfirm = {
-                viewModel.selectionRemoveFromGroups(state.selectedIds, it)
-                showRemoveFromGroupDialog = false
-                viewModel.clearSelection()
-            }
-        )
-    }
+    TextListInputDialog(
+        show = showRemoveFromGroupDialog,
+        title = stringResource(R.string.remove_group),
+        hint = stringResource(R.string.group_name),
+        suggestions = groups,
+        onDismissRequest = { showRemoveFromGroupDialog = false },
+        onConfirm = { text ->
+            viewModel.selectionRemoveFromGroups(state.selectedIds, text)
+            showRemoveFromGroupDialog = false
+            viewModel.clearSelection()
+        }
+    )
 
-    showSetGroupDialog?.let { rssStar ->
-        TextListInputDialog(
-            title = stringResource(R.string.change_group),
-            hint = stringResource(R.string.group_name),
-            initialValue = rssStar.group,
-            suggestions = groups,
-            onDismissRequest = { showSetGroupDialog = null },
-            onConfirm = {
-                viewModel.updateGroup(rssStar, it)
-                showSetGroupDialog = null
-            }
-        )
-    }
+    TextListInputDialog(
+        data = showSetGroupDialog,
+        title = stringResource(R.string.change_group),
+        hint = stringResource(R.string.group_name),
+        initialValue = { rssStar -> rssStar.group },
+        suggestions = groups,
+        onDismissRequest = { showSetGroupDialog = null },
+        onConfirm = { rssStar, text ->
+            viewModel.updateGroup(rssStar, text)
+            showSetGroupDialog = null
+        }
+    )
 
     RuleListScaffold(
         title = stringResource(R.string.favorite),

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

@@ -43,6 +43,7 @@ import io.legado.app.data.entities.RssSource
 import io.legado.app.ui.widget.components.ActionItem
 import io.legado.app.ui.widget.components.DraggableSelectionHandler
 import io.legado.app.ui.widget.components.GroupManageBottomSheet
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 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.dialog.TextListInputDialog
@@ -133,44 +134,42 @@ fun RssSourceScreen(
         }
     )
 
-    if (showUrlInput) {
-        SourceInputDialog(
-            title = stringResource(R.string.import_on_line),
-            onDismissRequest = { showUrlInput = false },
-            onConfirm = {
-                showUrlInput = false
-                viewModel.importSource(it)
-            }
-        )
-    }
+    SourceInputDialog(
+        show = showUrlInput,
+        title = stringResource(R.string.import_on_line),
+        onDismissRequest = { showUrlInput = false },
+        onConfirm = {
+            showUrlInput = false
+            viewModel.importSource(it)
+        }
+    )
 
-    if (showAddToGroupDialog) {
-        TextListInputDialog(
-            title = stringResource(R.string.add_group),
-            hint = stringResource(R.string.group_name),
-            suggestions = groups,
-            onDismissRequest = { showAddToGroupDialog = false },
-            onConfirm = {
-                viewModel.selectionAddToGroups(selectedIds, it)
-                showAddToGroupDialog = false
-                viewModel.setSelection(emptySet())
-            }
-        )
-    }
 
-    if (showRemoveFromGroupDialog) {
-        TextListInputDialog(
-            title = stringResource(R.string.remove_group),
-            hint = stringResource(R.string.group_name),
-            suggestions = groups,
-            onDismissRequest = { showRemoveFromGroupDialog = false },
-            onConfirm = {
-                viewModel.selectionRemoveFromGroups(selectedIds, it)
-                showRemoveFromGroupDialog = false
-                viewModel.setSelection(emptySet())
-            }
-        )
-    }
+    TextListInputDialog(
+        show = showAddToGroupDialog,
+        title = stringResource(R.string.add_group),
+        hint = stringResource(R.string.group_name),
+        suggestions = groups,
+        onDismissRequest = { showAddToGroupDialog = false },
+        onConfirm = { text ->
+            viewModel.selectionAddToGroups(selectedIds, text)
+            showAddToGroupDialog = false
+            viewModel.setSelection(emptySet())
+        }
+    )
+
+    TextListInputDialog(
+        show = showRemoveFromGroupDialog,
+        title = stringResource(R.string.remove_group),
+        hint = stringResource(R.string.group_name),
+        suggestions = groups,
+        onDismissRequest = { showRemoveFromGroupDialog = false },
+        onConfirm = { text ->
+            viewModel.selectionRemoveFromGroups(selectedIds, text)
+            showRemoveFromGroupDialog = false
+            viewModel.setSelection(emptySet())
+        }
+    )
 
     GroupManageBottomSheet(
         show = showGroupManageSheet,
@@ -214,23 +213,18 @@ fun RssSourceScreen(
         }
     }
 
-    showDeleteRuleDialog?.let { source ->
-        AlertDialog(
-            onDismissRequest = { showDeleteRuleDialog = null },
-            title = { AppText(stringResource(R.string.delete)) },
-            text = { AppText(stringResource(R.string.del_msg)) },
-            confirmButton = {
-                OutlinedButton(onClick = {
-                    viewModel.del(source); showDeleteRuleDialog = null
-                }) { AppText(stringResource(R.string.ok)) }
-            },
-            dismissButton = {
-                TextButton(onClick = { showDeleteRuleDialog = null }) {
-                    AppText(stringResource(R.string.cancel))
-                }
-            }
-        )
-    }
+    AppAlertDialog(
+        data = showDeleteRuleDialog,
+        onDismissRequest = { showDeleteRuleDialog = null },
+        title = stringResource(R.string.delete),
+        confirmText = stringResource(R.string.ok),
+        onConfirm = { rule ->
+            viewModel.del(rule)
+            showDeleteRuleDialog = null
+        },
+        dismissText = stringResource(R.string.cancel),
+        onDismiss = { showDeleteRuleDialog = null }
+    )
 
     RuleListScaffold(
         title = stringResource(R.string.rss_source),

+ 53 - 60
app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubScreen.kt

@@ -19,18 +19,13 @@ import androidx.compose.material.icons.filled.Edit
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.FloatingActionButton
+import io.legado.app.ui.widget.components.AppFloatingActionButton
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.PlainTooltip
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.TextButton
-import androidx.compose.material3.TooltipAnchorPosition
-import androidx.compose.material3.TooltipBox
-import androidx.compose.material3.TooltipDefaults
 import androidx.compose.material3.animateFloatingActionButton
-import androidx.compose.material3.rememberTooltipState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
@@ -53,7 +48,9 @@ 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.AppTextField
 import io.legado.app.ui.widget.components.EmptyMessageView
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 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
@@ -104,24 +101,18 @@ fun RuleSubScreen(
             )
         },
         floatingActionButton = {
-            TooltipBox(
-                positionProvider =
-                    TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
-                tooltip = { PlainTooltip { AppText("Localized description") } },
-                state = rememberTooltipState(),
+            AppFloatingActionButton(
+                modifier = Modifier
+                    .animateFloatingActionButton(
+                        visible = true,
+                        alignment = Alignment.BottomEnd,
+                    ),
+                onClick = {
+                    showEditDialog = RuleSub(customOrder = state.items.size + 1)
+                },
+                tooltipText = "Localized description"
             ) {
-                FloatingActionButton(
-                    modifier = Modifier
-                        .animateFloatingActionButton(
-                            visible = true,
-                            alignment = Alignment.BottomEnd,
-                        ),
-                    onClick = {
-                        showEditDialog = RuleSub(customOrder = state.items.size + 1)
-                    }
-                ) {
-                    Icon(Icons.Default.Add, contentDescription = "Add Rule")
-                }
+                Icon(Icons.Default.Add, contentDescription = "Add Rule")
             }
         }
     ) { paddingValues ->
@@ -199,80 +190,82 @@ fun RuleSubScreen(
         }
     }
 
-    showEditDialog?.let { ruleSub ->
-        RuleSubEditDialog(
-            ruleSub = ruleSub,
-            onDismiss = { showEditDialog = null },
-            onConfirm = { updatedRuleSub ->
-                viewModel.save(
-                    updatedRuleSub,
-                    onSuccess = { showEditDialog = null },
-                    onError = { context.toastOnUi(it) }
-                )
-            }
-        )
-    }
+    RuleSubEditDialog(
+        ruleSub = showEditDialog,
+        onDismiss = { showEditDialog = null },
+        onConfirm = { updatedRuleSub ->
+            viewModel.save(
+                updatedRuleSub,
+                onSuccess = { showEditDialog = null },
+                onError = { context.toastOnUi(it) }
+            )
+        }
+    )
 }
 
 @Composable
 fun RuleSubEditDialog(
-    ruleSub: RuleSub,
+    ruleSub: RuleSub?,
     onDismiss: () -> Unit,
     onConfirm: (RuleSub) -> Unit
 ) {
-    var name by remember { mutableStateOf(ruleSub.name) }
-    var url by remember { mutableStateOf(ruleSub.url) }
-    var type by remember { mutableIntStateOf(ruleSub.type) }
+    var cachedRule by remember { mutableStateOf(ruleSub) }
+    if (ruleSub != null) {
+        cachedRule = ruleSub
+    }
+
+    var name by remember(cachedRule) { mutableStateOf(cachedRule?.name ?: "") }
+    var url by remember(cachedRule) { mutableStateOf(cachedRule?.url ?: "") }
+    var type by remember(cachedRule) { mutableIntStateOf(cachedRule?.type ?: 0) }
+
     val typeArray = stringArrayResource(R.array.rule_type)
 
-    AlertDialog(
+    AppAlertDialog(
+        show = ruleSub != null,
         onDismissRequest = onDismiss,
-        title = { AppText(stringResource(R.string.rule_subscription)) },
-        text = {
+        title = stringResource(R.string.rule_subscription),
+        content = {
             Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
-                OutlinedTextField(
+                // 替换原生的 OutlinedTextField,确保双端主题适配
+                AppTextField(
                     value = name,
                     onValueChange = { name = it },
-                    label = { AppText(stringResource(R.string.name)) },
+                    label = stringResource(R.string.name),
                     modifier = Modifier.fillMaxWidth()
                 )
-                OutlinedTextField(
+                AppTextField(
                     value = url,
                     onValueChange = { url = it },
-                    label = { AppText("URL") },
+                    label = "URL",
                     modifier = Modifier.fillMaxWidth()
                 )
+
                 AppText(
                     text = "订阅类型",
                     style = LegadoTheme.typography.titleSmall,
                     modifier = Modifier.padding(top = 8.dp)
                 )
+
                 CheckboxGroupContainer(columns = 2) {
                     typeArray.forEachIndexed { index, text ->
                         item {
                             CheckboxItem(
                                 title = text,
                                 checked = (index == type),
-                                onCheckedChange = {
-                                    if (it) type = index
-                                }
+                                onCheckedChange = { if (it) type = index }
                             )
                         }
                     }
                 }
             }
         },
-        confirmButton = {
-            TextButton(onClick = {
-                onConfirm(ruleSub.copy(name = name, url = url, type = type))
-            }) {
-                AppText(stringResource(R.string.ok))
+        confirmText = stringResource(R.string.ok),
+        onConfirm = {
+            cachedRule?.let {
+                onConfirm(it.copy(name = name, url = url, type = type))
             }
         },
-        dismissButton = {
-            TextButton(onClick = onDismiss) {
-                AppText(stringResource(R.string.cancel))
-            }
-        }
+        dismissText = stringResource(R.string.cancel),
+        onDismiss = onDismiss
     )
 }

+ 33 - 1
app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt

@@ -1,14 +1,16 @@
 package io.legado.app.ui.theme
 
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
 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)) 8.dp else 16.dp
+    val horizontal = if (ThemeResolver.isMiuixEngine(composeEngine)) 12.dp else 16.dp
     return this.padding(horizontal = horizontal)
 }
 
@@ -17,3 +19,33 @@ fun Modifier.adaptiveVerticalPadding(): Modifier {
     val horizontal = if (ThemeResolver.isMiuixEngine(composeEngine)) 12.dp else 8.dp
     return this.padding(horizontal = horizontal)
 }
+
+@Composable
+fun adaptiveContentPadding(
+    top: Dp,
+    bottom: Dp
+): PaddingValues {
+    val horizontal = if (ThemeResolver.isMiuixEngine(composeEngine)) 12.dp else 16.dp
+    val adjustedTop = if (ThemeResolver.isMiuixEngine(composeEngine)) top + 8.dp else top
+    return PaddingValues(
+        top = adjustedTop,
+        bottom = bottom,
+        start = horizontal,
+        end = horizontal
+    )
+}
+
+@Composable
+fun adaptiveContentPadding(
+    top: Dp,
+    bottom: Dp,
+    horizontal: Dp
+): PaddingValues {
+    val adjustedTop = if (ThemeResolver.isMiuixEngine(composeEngine)) top else top + 4.dp
+    return PaddingValues(
+        top = adjustedTop,
+        bottom = bottom,
+        start = horizontal,
+        end = horizontal
+    )
+}

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

@@ -120,15 +120,15 @@ fun AppTheme(
                         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,
+                        secondary = miuixColorScheme.secondary,
+                        onSecondary = miuixColorScheme.onSecondary,
+                        secondaryContainer = miuixColorScheme.secondaryContainer,
+                        onSecondaryContainer = miuixColorScheme.onSecondaryContainer,
+
+                        tertiary = miuixColorScheme.primary,
+                        onTertiary = miuixColorScheme.onPrimary,
+                        tertiaryContainer = miuixColorScheme.primaryContainer,
+                        onTertiaryContainer = miuixColorScheme.primaryVariant,
 
                         // ================= 4. 背景与表面 (Background & Surface) =================
                         background = miuixColorScheme.background,

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

@@ -58,8 +58,6 @@ object ThemeResolver {
 
     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

+ 215 - 16
app/src/main/java/io/legado/app/ui/widget/components/AppTextField.kt

@@ -4,6 +4,7 @@ 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.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.foundation.text.input.InputTransformation
 import androidx.compose.foundation.text.input.KeyboardActionHandler
@@ -12,16 +13,14 @@ 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.text.input.VisualTransformation
 import androidx.compose.ui.unit.Density
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.ThemeResolver
@@ -36,8 +35,7 @@ fun AppTextField(
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     readOnly: Boolean = false,
-    textStyle: TextStyle? = null,
-    textColor: Color = Color.Unspecified,
+    backgroundColor: Color = Color.Unspecified,
     label: String? = null,
     labelPosition: TextFieldLabelPosition = TextFieldLabelPosition.Attached(),
     placeholder: @Composable (() -> Unit)? = null,
@@ -55,16 +53,9 @@ fun AppTextField(
     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) {
@@ -73,8 +64,7 @@ fun AppTextField(
             modifier = modifier,
             enabled = enabled,
             readOnly = readOnly,
-            textStyle = MiuixTheme.textStyles.main,
-            backgroundColor = MiuixTheme.colorScheme.surfaceContainerHigh,
+            backgroundColor = if (backgroundColor != Color.Unspecified) backgroundColor else MiuixTheme.colorScheme.surfaceContainerHigh,
             label = label ?: "",
             leadingIcon = leadingIcon,
             trailingIcon = trailingIcon,
@@ -94,12 +84,26 @@ fun AppTextField(
             } else {
                 TextFieldDefaults.contentPaddingWithLabel()
             }
+
+        val resolvedColors = if (backgroundColor != Color.Unspecified) {
+            TextFieldDefaults.colors(
+                focusedContainerColor = backgroundColor,
+                unfocusedContainerColor = backgroundColor,
+                disabledContainerColor = backgroundColor,
+                errorContainerColor = backgroundColor,
+                focusedIndicatorColor = if (backgroundColor == Color.Transparent) Color.Transparent else TextFieldDefaults.colors().focusedIndicatorColor,
+                unfocusedIndicatorColor = if (backgroundColor == Color.Transparent) Color.Transparent else TextFieldDefaults.colors().unfocusedIndicatorColor,
+                disabledIndicatorColor = if (backgroundColor == Color.Transparent) Color.Transparent else TextFieldDefaults.colors().disabledIndicatorColor,
+            )
+        } else {
+            TextFieldDefaults.colors()
+        }
+
         TextField(
             state = state,
             modifier = modifier,
             enabled = enabled,
             readOnly = readOnly,
-            textStyle = mergedTextStyle,
             labelPosition = labelPosition,
             label = label?.let { { AppText(it) } },
             placeholder = placeholder,
@@ -117,9 +121,204 @@ fun AppTextField(
             onTextLayout = onTextLayout,
             scrollState = scrollState,
             shape = shape,
-            colors = colors,
+            colors = resolvedColors,
             contentPadding = resolvedContentPadding,
             interactionSource = interactionSource
         )
     }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AppTextField(
+    value: String,
+    onValueChange: (String) -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    readOnly: Boolean = false,
+    backgroundColor: Color = Color.Unspecified,
+    label: String? = null,
+    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,
+    visualTransformation: VisualTransformation = VisualTransformation.None,
+    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
+    keyboardActions: KeyboardActions = KeyboardActions.Default,
+    singleLine: Boolean = false,
+    maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
+    minLines: Int = 1,
+    shape: Shape = TextFieldDefaults.shape,
+    interactionSource: MutableInteractionSource? = null,
+) {
+    val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
+
+    if (isMiuix) {
+        MiuixTextField(
+            value = value,
+            onValueChange = onValueChange,
+            modifier = modifier,
+            enabled = enabled,
+            readOnly = readOnly,
+            backgroundColor = if (backgroundColor != Color.Unspecified) backgroundColor else MiuixTheme.colorScheme.surfaceContainerHigh,
+            label = label ?: "",
+            leadingIcon = leadingIcon,
+            trailingIcon = trailingIcon,
+            visualTransformation = visualTransformation,
+            keyboardOptions = keyboardOptions,
+            keyboardActions = keyboardActions,
+            singleLine = singleLine,
+            maxLines = maxLines,
+            minLines = minLines,
+            interactionSource = interactionSource
+        )
+    } else {
+        val resolvedColors = if (backgroundColor != Color.Unspecified) {
+            TextFieldDefaults.colors(
+                focusedContainerColor = backgroundColor,
+                unfocusedContainerColor = backgroundColor,
+                disabledContainerColor = backgroundColor,
+                errorContainerColor = backgroundColor,
+                focusedIndicatorColor = if (backgroundColor == Color.Transparent) Color.Transparent else TextFieldDefaults.colors().focusedIndicatorColor,
+                unfocusedIndicatorColor = if (backgroundColor == Color.Transparent) Color.Transparent else TextFieldDefaults.colors().unfocusedIndicatorColor,
+                disabledIndicatorColor = if (backgroundColor == Color.Transparent) Color.Transparent else TextFieldDefaults.colors().disabledIndicatorColor,
+            )
+        } else {
+            TextFieldDefaults.colors()
+        }
+
+        TextField(
+            value = value,
+            onValueChange = onValueChange,
+            modifier = modifier,
+            enabled = enabled,
+            readOnly = readOnly,
+            label = label?.let { { AppText(it) } },
+            placeholder = placeholder,
+            leadingIcon = leadingIcon,
+            trailingIcon = trailingIcon,
+            prefix = prefix,
+            suffix = suffix,
+            supportingText = supportingText,
+            isError = isError,
+            visualTransformation = visualTransformation,
+            keyboardOptions = keyboardOptions,
+            keyboardActions = keyboardActions,
+            singleLine = singleLine,
+            maxLines = maxLines,
+            minLines = minLines,
+            shape = shape,
+            colors = resolvedColors,
+            interactionSource = interactionSource
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AppTextFieldSurface(
+    state: TextFieldState,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    readOnly: Boolean = false,
+    label: String? = null,
+    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,
+    contentPadding: PaddingValues? = null,
+    interactionSource: MutableInteractionSource? = null,
+) {
+    AppTextField(
+        state = state,
+        modifier = modifier,
+        enabled = enabled,
+        readOnly = readOnly,
+        backgroundColor = LegadoTheme.colorScheme.surface,
+        label = label,
+        labelPosition = labelPosition,
+        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,
+        contentPadding = contentPadding,
+        interactionSource = interactionSource
+    )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AppTextFieldSurface(
+    value: String,
+    onValueChange: (String) -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    readOnly: Boolean = false,
+    label: String? = null,
+    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,
+    visualTransformation: VisualTransformation = VisualTransformation.None,
+    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
+    keyboardActions: KeyboardActions = KeyboardActions.Default,
+    singleLine: Boolean = false,
+    maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
+    minLines: Int = 1,
+    shape: Shape = TextFieldDefaults.shape,
+    interactionSource: MutableInteractionSource? = null,
+) {
+    AppTextField(
+        value = value,
+        onValueChange = onValueChange,
+        modifier = modifier,
+        enabled = enabled,
+        readOnly = readOnly,
+        backgroundColor = LegadoTheme.colorScheme.surface,
+        label = label,
+        placeholder = placeholder,
+        leadingIcon = leadingIcon,
+        trailingIcon = trailingIcon,
+        prefix = prefix,
+        suffix = suffix,
+        supportingText = supportingText,
+        isError = isError,
+        visualTransformation = visualTransformation,
+        keyboardOptions = keyboardOptions,
+        keyboardActions = keyboardActions,
+        singleLine = singleLine,
+        maxLines = maxLines,
+        minLines = minLines,
+        shape = shape,
+        interactionSource = interactionSource
+    )
 }

+ 2 - 4
app/src/main/java/io/legado/app/ui/widget/components/GroupManageBottomSheet.kt

@@ -16,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.TextField
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
@@ -30,7 +29,6 @@ import io.legado.app.R
 import io.legado.app.ui.widget.components.button.SmallTextButton
 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
@@ -98,12 +96,12 @@ private fun GroupItem(
             }
         },
         expandContent = {
-            TextField(
+            AppTextField(
                 state = state,
                 modifier = Modifier
                     .fillMaxWidth()
                     .heightIn(min = 48.dp),
-                label = { AppText(stringResource(R.string.edit)) },
+                label = stringResource(R.string.edit),
                 contentPadding = PaddingValues(
                     top = 4.dp,
                     bottom = 4.dp,

+ 16 - 10
app/src/main/java/io/legado/app/ui/widget/components/SearchBarSection.kt

@@ -27,10 +27,12 @@ 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 top.yukonga.miuix.kmp.theme.MiuixTheme
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun SearchBarSection(
+    modifier: Modifier = Modifier,
     query: String,
     onQueryChange: (String) -> Unit,
     placeholder: String = "搜索...",
@@ -69,21 +71,25 @@ fun SearchBarSection(
 
     val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
 
+    val modifier = modifier
+        .fillMaxWidth()
+        .then(
+            if (isMiuix) modifier.padding(vertical = 4.dp)
+            else modifier
+        )
+
     val searchTextField = @Composable {
         AppTextField(
             state = textFieldState,
-            modifier = Modifier
-                .fillMaxWidth(),
+            modifier = modifier,
             placeholder = { AppText(placeholder) },
             leadingIcon = leadingIcon,
             lineLimits = TextFieldLineLimits.SingleLine,
-            colors = TextFieldDefaults.colors(
-                focusedContainerColor = Color.Transparent,
-                unfocusedContainerColor = Color.Transparent,
-                disabledContainerColor = Color.Transparent,
-                focusedIndicatorColor = Color.Transparent,
-                unfocusedIndicatorColor = Color.Transparent,
-            )
+            backgroundColor = if (!isMiuix) {
+                Color.Transparent
+            } else {
+                MiuixTheme.colorScheme.surfaceContainer
+            }
         )
     }
 
@@ -93,7 +99,7 @@ fun SearchBarSection(
         Surface(
             modifier = Modifier
                 .fillMaxWidth()
-                .padding(start = 16.dp, end = 16.dp, bottom = 4.dp),
+                .padding(bottom = 4.dp),
             shape = RoundedCornerShape(32.dp),
             color = backgroundColor
         ) {

+ 170 - 0
app/src/main/java/io/legado/app/ui/widget/components/alert/AppAlertDialog.kt

@@ -0,0 +1,170 @@
+package io.legado.app.ui.widget.components.alert
+
+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.material3.AlertDialog
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import io.legado.app.ui.theme.LegadoTheme.composeEngine
+import io.legado.app.ui.theme.ThemeResolver
+import io.legado.app.ui.widget.components.button.MiuixPrimaryButton
+import io.legado.app.ui.widget.components.button.MiuixSecondaryButton
+import top.yukonga.miuix.kmp.window.WindowDialog
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun AppAlertDialog(
+    modifier: Modifier = Modifier,
+    show: Boolean,
+    onDismissRequest: () -> Unit,
+    title: String? = null,
+    text: String? = null,
+    content: (@Composable () -> Unit)? = null,
+    confirmText: String = "确定", // 默认文字
+    onConfirm: (() -> Unit)? = null,
+    dismissText: String = "取消",
+    onDismiss: (() -> Unit)? = null,
+) {
+    if (ThemeResolver.isMiuixEngine(composeEngine)) {
+        WindowDialog(
+            show = show,
+            modifier = modifier,
+            title = title,
+            summary = text,
+            onDismissRequest = onDismissRequest,
+            content = {
+                if (content != null) {
+                    Column(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .padding(top = 12.dp)
+                    ) {
+                        content()
+                    }
+                }
+
+                Row(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(top = 24.dp),
+                    horizontalArrangement = Arrangement.spacedBy(12.dp),
+                    verticalAlignment = Alignment.CenterVertically
+                ) {
+                    if (onDismiss != null) {
+                        MiuixSecondaryButton(
+                            text = dismissText,
+                            modifier = Modifier.weight(1f),
+                            onClick = {
+                                onDismiss()
+                                onDismissRequest()
+                            }
+                        )
+                    }
+
+                    if (onConfirm != null) {
+                        MiuixPrimaryButton(
+                            text = confirmText,
+                            modifier = Modifier.weight(1f),
+                            onClick = {
+                                onConfirm()
+                            }
+                        )
+                    }
+                }
+            }
+        )
+    } else {
+        if (show) {
+            AlertDialog(
+                onDismissRequest = onDismissRequest,
+                modifier = modifier,
+                title = title?.let { { Text(text = it) } },
+                text = {
+                    Column {
+                        if (text != null) {
+                            Text(
+                                text = text,
+                                modifier = Modifier.padding(bottom = if (content != null) 16.dp else 0.dp)
+                            )
+                        }
+                        if (content != null) {
+                            content()
+                        }
+                    }
+                },
+                confirmButton = {
+                    if (onConfirm != null) {
+                        OutlinedButton(
+                            onClick = onConfirm
+                        ) {
+                            Text(text = confirmText)
+                        }
+                    }
+                },
+                dismissButton = {
+                    if (onDismiss != null) {
+                        TextButton(
+                            onClick = {
+                                onDismiss()
+                                onDismissRequest()
+                            }
+                        ) {
+                            Text(text = dismissText)
+                        }
+                    }
+                }
+            )
+        }
+    }
+}
+
+/**
+ * 专为 nullable 数据设计的 AppAlertDialog 重载。
+ * 当 [data] 不为 null 时显示弹窗;当 [data] 变为 null 时,自动缓存最后一次数据并播放退出动画。
+ */
+@Composable
+fun <T> AppAlertDialog(
+    data: T?,
+    onDismissRequest: () -> Unit,
+    title: String? = null,
+    confirmText: String = "确定",
+    onConfirm: ((T) -> Unit)? = null,
+    dismissText: String = "取消",
+    onDismiss: (() -> Unit)? = null,
+    modifier: Modifier = Modifier,
+    content: (@Composable (T) -> Unit)? = null
+) {
+    var cachedData by remember { mutableStateOf(data) }
+
+    if (data != null) {
+        cachedData = data
+    }
+
+    val currentData = cachedData
+    if (currentData != null) {
+        AppAlertDialog(
+            show = data != null,
+            onDismissRequest = onDismissRequest,
+            title = title,
+            modifier = modifier,
+            confirmText = confirmText,
+            onConfirm = onConfirm?.let { { it(currentData) } },
+            dismissText = dismissText,
+            onDismiss = onDismiss,
+            content = content?.let { { it(currentData) } }
+        )
+    }
+}

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

@@ -7,12 +7,7 @@ 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.material3.AlertDialog
-import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -22,10 +17,11 @@ 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.AppTextFieldSurface
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 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
@@ -38,8 +34,8 @@ fun BookmarkEditSheet(
 ) {
 
     var showDeleteConfirmDialog by remember { mutableStateOf(false) }
-    var bookText by remember { mutableStateOf(bookmark.bookText) }
-    var content by remember { mutableStateOf(bookmark.content) }
+    var bookText by remember(bookmark) { mutableStateOf(bookmark.bookText) }
+    var content by remember(bookmark) { mutableStateOf(bookmark.content) }
 
     AppModalBottomSheet(
         show = show,
@@ -51,20 +47,20 @@ fun BookmarkEditSheet(
                 .fillMaxWidth()
                 .navigationBarsPadding()
         ) {
-            OutlinedTextField(
+            AppTextFieldSurface(
                 value = bookText,
                 onValueChange = { bookText = it },
-                label = { AppText("原文") },
+                label = "原文",
                 modifier = Modifier.fillMaxWidth(),
                 maxLines = 10
             )
 
             Spacer(modifier = Modifier.height(12.dp))
 
-            OutlinedTextField(
+            AppTextFieldSurface(
                 value = content,
                 onValueChange = { content = it },
-                label = { AppText("摘要/笔记") },
+                label = "摘要/笔记",
                 modifier = Modifier.fillMaxWidth(),
                 maxLines = 5
             )
@@ -98,29 +94,17 @@ fun BookmarkEditSheet(
         }
     }
 
-    if (showDeleteConfirmDialog) {
-        AlertDialog(
-            onDismissRequest = { showDeleteConfirmDialog = false },
-            title = { AppText("确认删除") },
-            text = { AppText("你确定要删除这条书签吗?") },
-            confirmButton = {
-                TextButton(
-                    onClick = {
-                        showDeleteConfirmDialog = false
-                        onDelete(bookmark)
-                    },
-                    colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error)
-                ) {
-                    AppText("删除")
-                }
-            },
-            dismissButton = {
-                TextButton(
-                    onClick = { showDeleteConfirmDialog = false }
-                ) {
-                    AppText("取消")
-                }
-            }
-        )
-    }
+    AppAlertDialog(
+        show = showDeleteConfirmDialog,
+        onDismissRequest = { showDeleteConfirmDialog = false },
+        title = "确认删除",
+        text = "你确定要删除这条书签吗?",
+        confirmText = "删除",
+        onConfirm = {
+            showDeleteConfirmDialog = false
+            onDelete(bookmark)
+        },
+        dismissText = "取消",
+        onDismiss = { showDeleteConfirmDialog = false }
+    )
 }

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

@@ -52,7 +52,7 @@ fun BookmarkItem(
             AppText(
                 text = bookmark.chapterName,
                 style = LegadoTheme.typography.titleSmallEmphasized,
-                color = if (isDur) LegadoTheme.colorScheme.primary else LegadoTheme.colorScheme.secondary
+                color = if (isDur) LegadoTheme.colorScheme.primary else LegadoTheme.colorScheme.onSurface
             )
 
             if (bookmark.bookText.isNotEmpty()) {

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

@@ -26,4 +26,5 @@ fun AlertButton(
     ) {
         AppText(text = text)
     }
-}
+}
+

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

@@ -1,86 +0,0 @@
-package io.legado.app.ui.widget.components.button
-
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.PaddingValues
-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.ToggleButton
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-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.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)
-@Composable
-fun AnimatedActionButton(
-    checked: Boolean,
-    onCheckedChange: (Boolean) -> Unit,
-    iconChecked: ImageVector,
-    iconUnchecked: ImageVector,
-    activeText: String,
-    inactiveText: String
-) {
-
-    var showText by remember { mutableStateOf(false) }
-    var lastCheckedState by remember { mutableStateOf(checked) }
-
-    LaunchedEffect(showText) {
-        if (showText) {
-            delay(1000)
-            showText = false
-        }
-    }
-
-    ToggleButton(
-        contentPadding = PaddingValues(4.dp),
-        checked = checked,
-        onCheckedChange = {
-            lastCheckedState = it
-            onCheckedChange(it)
-            showText = true
-        }
-    ) {
-        Row(
-            verticalAlignment = Alignment.CenterVertically,
-            horizontalArrangement = Arrangement.Center,
-            modifier = Modifier
-                .padding(horizontal = 4.dp)
-        ) {
-
-            AnimatedContent(
-                targetState = checked,
-                label = "IconAnimation"
-            ) { targetChecked ->
-                Icon(
-                    imageVector = if (targetChecked) iconChecked else iconUnchecked,
-                    contentDescription = null
-                )
-            }
-
-            AnimatedVisibility(
-                visible = showText
-            ) {
-                AppText(
-                    text = if (lastCheckedState) activeText else inactiveText,
-                    style = LegadoTheme.typography.labelMedium,
-                    modifier = Modifier.padding(start = 8.dp),
-                    maxLines = 1,
-                    softWrap = false
-                )
-            }
-        }
-    }
-}

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

@@ -0,0 +1,332 @@
+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
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.FilledTonalIconButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.OutlinedIconButton
+import androidx.compose.material3.OutlinedIconToggleButton
+import androidx.compose.material3.Text
+import androidx.compose.material3.TonalToggleButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+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.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 kotlinx.coroutines.delay
+import top.yukonga.miuix.kmp.theme.MiuixTheme
+import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
+import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
+import top.yukonga.miuix.kmp.basic.Text as MiuixText
+
+
+@Composable
+fun MediumIconButton(
+    onClick: () -> Unit,
+    icon: ImageVector,
+    modifier: Modifier = Modifier,
+    contentDescription: String? = null
+) {
+    if (ThemeResolver.isMiuixEngine(composeEngine)) {
+        MiuixIconButton(
+            onClick = onClick,
+            modifier = modifier
+        ) {
+            MiuixIcon(
+                imageVector = icon,
+                contentDescription = contentDescription
+            )
+        }
+    } else {
+        IconButton(
+            onClick = onClick,
+            modifier = modifier
+        ) {
+            Icon(
+                imageVector = icon,
+                contentDescription = contentDescription
+            )
+        }
+    }
+}
+
+@Composable
+fun MediumOutlinedIconButton(
+    onClick: () -> Unit,
+    icon: ImageVector,
+    modifier: Modifier = Modifier,
+    contentDescription: String? = null
+) {
+    val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
+    if (isMiuix) {
+        MiuixIconButton(
+            onClick = onClick,
+            modifier = modifier,
+            backgroundColor = LegadoTheme.colorScheme.surfaceContainerHigh
+        ) {
+            MiuixIcon(
+                imageVector = icon,
+                contentDescription = contentDescription
+            )
+        }
+    } else {
+        OutlinedIconButton(
+            onClick = onClick,
+            border = ButtonDefaults.outlinedButtonBorder()
+        ) {
+            Icon(
+                imageVector = icon,
+                contentDescription = contentDescription
+            )
+        }
+    }
+}
+
+@Composable
+fun MediumOutlinedButton(
+    onClick: () -> Unit,
+    icon: ImageVector,
+    text: String,
+    modifier: Modifier = Modifier,
+    contentDescription: String? = null
+) {
+    val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
+    if (isMiuix) {
+        MiuixIconButton(
+            onClick = onClick,
+            modifier = modifier,
+            backgroundColor = LegadoTheme.colorScheme.surfaceContainerHigh
+        ) {
+            Row(
+                verticalAlignment = Alignment.CenterVertically,
+                horizontalArrangement = Arrangement.spacedBy(8.dp)
+            ) {
+                MiuixIcon(
+                    imageVector = icon,
+                    contentDescription = contentDescription
+                )
+                MiuixText(text = text)
+            }
+        }
+    } else {
+        OutlinedIconButton(
+            onClick = onClick,
+            border = ButtonDefaults.outlinedButtonBorder()
+        ) {
+            Row(
+                verticalAlignment = Alignment.CenterVertically,
+                horizontalArrangement = Arrangement.spacedBy(8.dp)
+            ) {
+                Icon(
+                    imageVector = icon,
+                    contentDescription = contentDescription
+                )
+                Text(text = text)
+            }
+        }
+    }
+}
+
+@Composable
+fun MediumTonalIconButton(
+    onClick: () -> Unit,
+    icon: ImageVector,
+    modifier: Modifier = Modifier,
+    contentDescription: String? = null
+) {
+    val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
+
+    if (isMiuix) {
+        MiuixIconButton(
+            onClick = onClick,
+            modifier = modifier,
+            backgroundColor = LegadoTheme.colorScheme.surfaceContainer
+        ) {
+            MiuixIcon(
+                imageVector = icon,
+                contentDescription = contentDescription
+            )
+        }
+    } else {
+        FilledTonalIconButton(
+            onClick = onClick,
+            modifier = modifier.size(40.dp),
+            colors = IconButtonDefaults.filledTonalIconButtonColors()
+        ) {
+            Icon(
+                imageVector = icon,
+                contentDescription = contentDescription
+            )
+        }
+    }
+}
+
+@Composable
+fun MediumOutlinedIconToggleButton(
+    checked: Boolean,
+    onCheckedChange: (Boolean) -> Unit,
+    icon: ImageVector,
+    modifier: Modifier = Modifier,
+    contentDescription: String? = null
+) {
+    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"
+        )
+
+        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,
+            backgroundColor = containerColor
+        ) {
+            MiuixIcon(
+                imageVector = icon,
+                contentDescription = contentDescription,
+                tint = iconTint
+            )
+        }
+    } else {
+        OutlinedIconToggleButton(
+            checked = checked,
+            onCheckedChange = onCheckedChange,
+            modifier = modifier
+        ) {
+            Icon(
+                imageVector = icon,
+                contentDescription = contentDescription
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun MediumAnimatedActionButton(
+    checked: Boolean,
+    onCheckedChange: (Boolean) -> Unit,
+    iconChecked: ImageVector,
+    iconUnchecked: ImageVector,
+    activeText: String,
+    inactiveText: String,
+    modifier: Modifier = Modifier
+) {
+    var showText by remember { mutableStateOf(false) }
+    var lastCheckedState by remember { mutableStateOf(checked) }
+
+    LaunchedEffect(showText) {
+        if (showText) {
+            delay(1000)
+            showText = false
+        }
+    }
+
+    val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
+
+    if (isMiuix) {
+
+        val containerColor by animateColorAsState(
+            targetValue = if (checked) MiuixTheme.colorScheme.primaryContainer else MiuixTheme.colorScheme.surfaceContainerHigh,
+            animationSpec = tween(150),
+            label = "MiuixActionButtonContainer"
+        )
+
+        val contentColor by animateColorAsState(
+            targetValue = if (checked) MiuixTheme.colorScheme.onPrimaryContainer else MiuixTheme.colorScheme.onSurface,
+            animationSpec = tween(150),
+            label = "MiuixActionButtonContainer"
+        )
+
+        MiuixIconButton(
+            onClick = {
+                lastCheckedState = !checked
+                onCheckedChange(!checked)
+                showText = true
+            },
+            backgroundColor = containerColor
+        ) {
+            Row(
+                verticalAlignment = Alignment.CenterVertically,
+                horizontalArrangement = Arrangement.Center,
+                modifier = Modifier.padding(horizontal = 8.dp)
+            ) {
+                MiuixIcon(
+                    tint = contentColor,
+                    imageVector = if (checked) iconChecked else iconUnchecked,
+                    contentDescription = null
+                )
+
+                AnimatedVisibility(
+                    visible = showText
+                ) {
+                    MiuixText(
+                        text = if (lastCheckedState) activeText else inactiveText,
+                        color = contentColor,
+                        style = LegadoTheme.typography.labelMedium,
+                        modifier = Modifier.padding(start = 8.dp),
+                        maxLines = 1,
+                        softWrap = false
+                    )
+                }
+            }
+        }
+    } else {
+        TonalToggleButton(
+            checked = checked,
+            onCheckedChange = {
+                lastCheckedState = it
+                onCheckedChange(it)
+                showText = true
+            },
+            contentPadding = PaddingValues(horizontal = 8.dp)
+        ) {
+            Row(
+                verticalAlignment = Alignment.CenterVertically,
+                horizontalArrangement = Arrangement.Center
+            ) {
+                AnimatedIcon(
+                    imageVector = if (checked) iconChecked else iconUnchecked,
+                    contentDescription = null
+                )
+
+                AnimatedVisibility(
+                    visible = showText
+                ) {
+                    Text(
+                        text = if (lastCheckedState) activeText else inactiveText,
+                        modifier = Modifier.padding(start = 8.dp),
+                        maxLines = 1,
+                        softWrap = false
+                    )
+                }
+            }
+        }
+    }
+}

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

@@ -11,7 +11,10 @@ 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.Button as MiuixButton
+import top.yukonga.miuix.kmp.basic.ButtonDefaults as MiuixButtonDefaults
 import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
+import top.yukonga.miuix.kmp.basic.Text as MiuixText
 
 @Composable
 fun MiuixIconButton(
@@ -55,3 +58,41 @@ fun MiuixToggleButton(
         )
     }
 }
+
+@Composable
+fun MiuixPrimaryButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    text: String
+) {
+    MiuixButton(
+        onClick = onClick,
+        modifier = modifier,
+        enabled = enabled,
+        colors = MiuixButtonDefaults.buttonColorsPrimary()
+    ) {
+        MiuixText(
+            text = text
+        )
+    }
+}
+
+@Composable
+fun MiuixSecondaryButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    text: String
+) {
+    MiuixButton(
+        onClick = onClick,
+        modifier = modifier,
+        enabled = enabled,
+        colors = MiuixButtonDefaults.buttonColors()
+    ) {
+        MiuixText(
+            text = text
+        )
+    }
+}

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

@@ -19,7 +19,8 @@ import androidx.compose.material3.IconToggleButtonShapes
 import androidx.compose.material3.LocalMinimumInteractiveComponentSize
 import androidx.compose.material3.OutlinedIconButton
 import androidx.compose.material3.OutlinedIconToggleButton
-import androidx.compose.material3.OutlinedToggleButton
+import androidx.compose.material3.Text
+import androidx.compose.material3.TonalToggleButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
@@ -34,11 +35,30 @@ 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
+import top.yukonga.miuix.kmp.basic.Text as MiuixText
+import top.yukonga.miuix.kmp.theme.MiuixTheme
 
+private val SmallMiuixButtonSize = 32.dp
+private val SmallMiuixIconSize = 18.dp
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+private fun smallContainerSize() = IconButtonDefaults.extraSmallContainerSize(
+    IconButtonDefaults.IconButtonWidthOption.Uniform
+)
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+private val smallIconSize: androidx.compose.ui.unit.Dp
+    get() = IconButtonDefaults.extraSmallIconSize
+
+@Composable
+private fun SmallNoMinTouchTarget(content: @Composable () -> Unit) {
+    CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
+        content()
+    }
+}
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
@@ -57,20 +77,16 @@ fun SmallIconButton(
             )
         }
     } else {
-        CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
+        SmallNoMinTouchTarget {
             IconButton(
                 onClick = onClick,
-                modifier = Modifier.size(
-                    IconButtonDefaults.extraSmallContainerSize(
-                        IconButtonDefaults.IconButtonWidthOption.Uniform
-                    )
-                ),
+                modifier = Modifier.size(smallContainerSize()),
                 shape = IconButtonDefaults.extraSmallRoundShape,
             ) {
                 Icon(
                     imageVector = icon,
                     contentDescription = contentDescription,
-                    modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
+                    modifier = Modifier.size(smallIconSize),
                 )
             }
         }
@@ -90,37 +106,31 @@ fun SmallOutlinedIconButton(
     if (isMiuix) {
         MiuixIconButton(
             onClick = onClick,
-            modifier = modifier.size(32.dp),
+            modifier = modifier.size(SmallMiuixButtonSize),
             backgroundColor = LegadoTheme.colorScheme.surfaceContainer
         ) {
             MiuixIcon(
                 imageVector = icon,
                 contentDescription = contentDescription,
-                modifier = Modifier.size(18.dp)
+                modifier = Modifier.size(SmallMiuixIconSize)
             )
         }
     } else {
-    CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
-        OutlinedIconButton(
-            onClick = onClick,
-            modifier = Modifier.size(
-                IconButtonDefaults.extraSmallContainerSize(
-                    IconButtonDefaults.IconButtonWidthOption.Uniform
-                )
-            ),
-            shapes = IconButtonDefaults.shapes(),
-            border = ButtonDefaults.outlinedButtonBorder()
-        ) {
-            Icon(
-                imageVector = icon,
-                contentDescription = contentDescription,
-                modifier = Modifier.size(
-                    IconButtonDefaults.extraSmallIconSize
+        SmallNoMinTouchTarget {
+            OutlinedIconButton(
+                onClick = onClick,
+                modifier = Modifier.size(smallContainerSize()),
+                shapes = IconButtonDefaults.shapes(),
+                border = ButtonDefaults.outlinedButtonBorder()
+            ) {
+                Icon(
+                    imageVector = icon,
+                    contentDescription = contentDescription,
+                    modifier = Modifier.size(smallIconSize)
                 )
-            )
+            }
         }
     }
-    }
 }
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -136,31 +146,27 @@ fun SmallTonalIconButton(
     if (isMiuix) {
         MiuixIconButton(
             onClick = onClick,
-            modifier = modifier.size(32.dp),
+            modifier = modifier.size(SmallMiuixButtonSize),
             backgroundColor = LegadoTheme.colorScheme.surfaceContainer
         ) {
             MiuixIcon(
                 imageVector = icon,
                 contentDescription = contentDescription,
-                modifier = Modifier.size(18.dp)
+                modifier = Modifier.size(SmallMiuixIconSize)
             )
         }
     } else {
-        CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
+        SmallNoMinTouchTarget {
             FilledTonalIconButton(
                 onClick = onClick,
-                modifier = modifier.size(
-                    IconButtonDefaults.extraSmallContainerSize(
-                        IconButtonDefaults.IconButtonWidthOption.Uniform
-                    )
-                ),
+                modifier = modifier.size(smallContainerSize()),
                 shapes = IconButtonDefaults.shapes(),
                 colors = IconButtonDefaults.filledTonalIconButtonColors()
             ) {
                 Icon(
                     imageVector = icon,
                     contentDescription = contentDescription,
-                    modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize)
+                    modifier = Modifier.size(smallIconSize)
                 )
             }
         }
@@ -195,14 +201,14 @@ fun SmallOutlinedIconToggleButton(
         MiuixIconButton(
             onClick = { onCheckedChange(!checked) },
             modifier = modifier
-                .size(32.dp),
+                .size(SmallMiuixButtonSize),
             backgroundColor = containerColor
         ) {
             MiuixIcon(
                 imageVector = icon,
                 contentDescription = contentDescription,
                 tint = iconTint,
-                modifier = Modifier.size(18.dp)
+                modifier = Modifier.size(SmallMiuixIconSize)
             )
         }
 
@@ -223,17 +229,13 @@ fun SmallOutlinedIconToggleButton(
             OutlinedIconToggleButton(
                 checked = checked,
                 onCheckedChange = onCheckedChange,
-                modifier = modifier.size(
-                    IconButtonDefaults.extraSmallContainerSize(
-                        IconButtonDefaults.IconButtonWidthOption.Uniform
-                    )
-                ),
+                modifier = modifier.size(smallContainerSize()),
                 shapes = toggleShapes
             ) {
                 Icon(
                     imageVector = icon,
                     contentDescription = contentDescription,
-                    modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
+                    modifier = Modifier.size(smallIconSize),
                 )
             }
         }
@@ -261,23 +263,29 @@ fun SmallAnimatedActionButton(
         }
     }
 
-    CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
-        OutlinedToggleButton(
-            checked = checked,
-            onCheckedChange = {
-                lastCheckedState = it
-                onCheckedChange(it)
+    val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
+
+    if (isMiuix) {
+        val containerColor by animateColorAsState(
+            targetValue = if (checked) MiuixTheme.colorScheme.primaryContainer else MiuixTheme.colorScheme.surfaceContainer,
+            animationSpec = tween(150),
+            label = "MiuixActionButtonContainer"
+        )
+
+        MiuixIconButton(
+            onClick = {
+                lastCheckedState = !checked
+                onCheckedChange(!checked)
                 showText = true
             },
-            modifier = modifier.height(36.dp),
-            contentPadding = PaddingValues(horizontal = 8.dp)
+            backgroundColor = containerColor
         ) {
             Row(
                 verticalAlignment = Alignment.CenterVertically,
-                horizontalArrangement = Arrangement.Center
+                horizontalArrangement = Arrangement.Center,
+                modifier = Modifier.padding(horizontal = 8.dp, vertical = 6.dp)
             ) {
-                AnimatedIcon(
-                    modifier = Modifier.size(16.dp),
+                MiuixIcon(
                     imageVector = if (checked) iconChecked else iconUnchecked,
                     contentDescription = null
                 )
@@ -285,7 +293,7 @@ fun SmallAnimatedActionButton(
                 AnimatedVisibility(
                     visible = showText
                 ) {
-                    AppText(
+                    MiuixText(
                         text = if (lastCheckedState) activeText else inactiveText,
                         style = LegadoTheme.typography.labelSmall,
                         modifier = Modifier.padding(start = 6.dp),
@@ -295,5 +303,41 @@ fun SmallAnimatedActionButton(
                 }
             }
         }
+    } else {
+        CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
+            TonalToggleButton(
+                checked = checked,
+                onCheckedChange = {
+                    lastCheckedState = it
+                    onCheckedChange(it)
+                    showText = true
+                },
+                modifier = modifier.height(36.dp),
+                contentPadding = PaddingValues(horizontal = 8.dp)
+            ) {
+                Row(
+                    verticalAlignment = Alignment.CenterVertically,
+                    horizontalArrangement = Arrangement.Center
+                ) {
+                    AnimatedIcon(
+                        modifier = Modifier.size(16.dp),
+                        imageVector = if (checked) iconChecked else iconUnchecked,
+                        contentDescription = null
+                    )
+
+                    AnimatedVisibility(
+                        visible = showText
+                    ) {
+                        Text(
+                            text = if (lastCheckedState) activeText else inactiveText,
+                            style = LegadoTheme.typography.labelSmall,
+                            modifier = Modifier.padding(start = 6.dp),
+                            maxLines = 1,
+                            softWrap = false
+                        )
+                    }
+                }
+            }
+        }
     }
-}
+}

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

@@ -2,6 +2,8 @@ package io.legado.app.ui.widget.components.button
 
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.scaleIn
@@ -22,6 +24,7 @@ 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
@@ -32,14 +35,19 @@ import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 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.config.themeConfig.ThemeConfig
 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
+import top.yukonga.miuix.kmp.theme.MiuixTheme
+import top.yukonga.miuix.kmp.basic.Text as MiuixText
+import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
+import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
 
 enum class TopBarButtonVariant {
     Filled, Outlined, Icon
@@ -105,19 +113,18 @@ fun TopBarButton(
 }
 
 @Composable
-fun TopbarNavigationButton(
+fun TopBarNavigationButton(
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
     imageVector: ImageVector = AppIcons.Back,
-    contentDescription: String? = "返回",
+    contentDescription: String? = stringResource(id = R.string.back),
     style: TopBarButtonVariant = TopBarButtonVariant.Filled
 ) {
     if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
         MiuixIconButton(
             onClick = onClick,
             imageVector = imageVector,
-            contentDescription = contentDescription,
-            modifier = modifier.padding(horizontal = 16.dp)
+            contentDescription = contentDescription
         )
     } else {
         TopBarButton(
@@ -130,6 +137,27 @@ fun TopbarNavigationButton(
     }
 }
 
+@Deprecated(
+    message = "Use TopBarNavigationButton for consistent TopBar naming.",
+    replaceWith = ReplaceWith("TopBarNavigationButton(onClick, modifier, imageVector, contentDescription, style)")
+)
+@Composable
+fun TopbarNavigationButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    imageVector: ImageVector = AppIcons.Back,
+    contentDescription: String? = stringResource(id = R.string.back),
+    style: TopBarButtonVariant = TopBarButtonVariant.Filled
+) {
+    TopBarNavigationButton(
+        onClick = onClick,
+        modifier = modifier,
+        imageVector = imageVector,
+        contentDescription = contentDescription,
+        style = style
+    )
+}
+
 @Composable
 fun TopBarActionButton(
     onClick: () -> Unit,
@@ -143,7 +171,7 @@ fun TopBarActionButton(
             onClick = onClick,
             imageVector = imageVector,
             contentDescription = contentDescription,
-            modifier = modifier.padding(end = 8.dp),
+            modifier = modifier,
         )
     } else {
         if (enableProgressive) {
@@ -186,12 +214,52 @@ fun TopBarAnimatedActionButton(
         }
     }
     if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
-        MiuixToggleButton(
-            checked = checked,
-            onCheckedChange = onCheckedChange,
-            iconChecked = iconChecked,
-            iconUnchecked = iconUnchecked
+
+        val containerColor by animateColorAsState(
+            targetValue = if (checked) MiuixTheme.colorScheme.primaryContainer else MiuixTheme.colorScheme.surfaceContainerHigh,
+            animationSpec = tween(150),
+            label = "MiuixActionButtonContainer"
+        )
+
+        val contentColor by animateColorAsState(
+            targetValue = if (checked) MiuixTheme.colorScheme.onPrimaryContainer else MiuixTheme.colorScheme.onSurface,
+            animationSpec = tween(150),
+            label = "MiuixActionButtonContainer"
         )
+
+        MiuixIconButton(
+            onClick = {
+                lastCheckedState = !checked
+                onCheckedChange(!checked)
+                showText = true
+            },
+            backgroundColor = containerColor
+        ) {
+            Row(
+                verticalAlignment = Alignment.CenterVertically,
+                horizontalArrangement = Arrangement.Center,
+                modifier = Modifier.padding(horizontal = 8.dp)
+            ) {
+                MiuixIcon(
+                    tint = contentColor,
+                    imageVector = if (checked) iconChecked else iconUnchecked,
+                    contentDescription = null
+                )
+
+                AnimatedVisibility(
+                    visible = showText
+                ) {
+                    MiuixText(
+                        text = if (lastCheckedState) activeText else inactiveText,
+                        color = contentColor,
+                        style = LegadoTheme.typography.labelMedium,
+                        modifier = Modifier.padding(start = 8.dp),
+                        maxLines = 1,
+                        softWrap = false
+                    )
+                }
+            }
+        }
     } else {
         ToggleButton(
             modifier = modifier.height(36.dp),
@@ -222,7 +290,7 @@ fun TopBarAnimatedActionButton(
                 AnimatedVisibility(
                     visible = showText
                 ) {
-                    AppText(
+                    Text(
                         text = if (lastCheckedState) activeText else inactiveText,
                         style = LegadoTheme.typography.labelMedium,
                         modifier = Modifier.padding(start = 8.dp),
@@ -255,4 +323,4 @@ fun AnimatedIcon(
             modifier = modifier
         )
     }
-}
+}

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

@@ -16,6 +16,7 @@ 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.utils.PressFeedbackType
 import top.yukonga.miuix.kmp.basic.Card as MiuixCard
 import top.yukonga.miuix.kmp.basic.CardDefaults as MiuixCardDefaults
 
@@ -26,6 +27,7 @@ private fun BaseCard(
     onClick: (() -> Unit)? = null,
     shape: Shape = CardDefaults.shape,
     cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
+    pressFeedbackType: PressFeedbackType = PressFeedbackType.None,
     containerColor: Color? = null,
     contentColor: Color? = null,
     elevation: CardElevation = CardDefaults.cardElevation(),
@@ -41,6 +43,8 @@ private fun BaseCard(
         MiuixCard(
             modifier = modifier,
             cornerRadius = cornerRadius,
+            pressFeedbackType = pressFeedbackType,
+            showIndication = true,
             onClick = onClick,
             content = content,
             colors = colors
@@ -84,6 +88,7 @@ fun GlassCard(
     onClick: (() -> Unit)? = null,
     shape: Shape = CardDefaults.shape,
     cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
+    pressFeedbackType: PressFeedbackType = PressFeedbackType.None,
     containerColor: Color? = null,
     contentColor: Color? = null,
     elevation: CardElevation = CardDefaults.cardElevation(),
@@ -95,6 +100,7 @@ fun GlassCard(
         onClick = onClick,
         shape = shape,
         cornerRadius = cornerRadius,
+        pressFeedbackType = pressFeedbackType,
         containerColor = containerColor,
         contentColor = contentColor,
         elevation = elevation,
@@ -111,6 +117,7 @@ fun NormalCard(
     onClick: (() -> Unit)? = null,
     shape: Shape = CardDefaults.shape,
     cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
+    pressFeedbackType: PressFeedbackType = PressFeedbackType.None,
     containerColor: Color? = null,
     contentColor: Color? = null,
     elevation: CardElevation = CardDefaults.cardElevation(),
@@ -122,6 +129,7 @@ fun NormalCard(
         onClick = onClick,
         shape = shape,
         cornerRadius = cornerRadius,
+        pressFeedbackType = pressFeedbackType,
         containerColor = containerColor,
         contentColor = contentColor,
         elevation = elevation,

+ 60 - 0
app/src/main/java/io/legado/app/ui/widget/components/checkBox/AppCheckbox.kt

@@ -0,0 +1,60 @@
+package io.legado.app.ui.widget.components.checkBox
+
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.TriStateCheckbox
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.state.ToggleableState
+import io.legado.app.ui.theme.LegadoTheme.composeEngine
+import io.legado.app.ui.theme.ThemeResolver
+import top.yukonga.miuix.kmp.basic.Checkbox as MiuixCheckbox
+
+@Composable
+fun AppCheckbox(
+    checked: Boolean,
+    onCheckedChange: ((Boolean) -> Unit)?,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true
+) {
+    val state = if (checked) ToggleableState.On else ToggleableState.Off
+
+    if (ThemeResolver.isMiuixEngine(composeEngine)) {
+        MiuixCheckbox(
+            state = state,
+            onClick = onCheckedChange?.let { { it(!checked) } },
+            modifier = modifier,
+            enabled = enabled
+        )
+    } else {
+        Checkbox(
+            checked = checked,
+            onCheckedChange = onCheckedChange,
+            modifier = modifier,
+            enabled = enabled
+        )
+    }
+}
+
+@Composable
+fun AppTriStateCheckbox(
+    state: ToggleableState,
+    onClick: (() -> Unit)?,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true
+) {
+    if (ThemeResolver.isMiuixEngine(composeEngine)) {
+        MiuixCheckbox(
+            state = state,
+            onClick = onClick,
+            modifier = modifier,
+            enabled = enabled
+        )
+    } else {
+        TriStateCheckbox(
+            state = state,
+            onClick = onClick,
+            modifier = modifier,
+            enabled = enabled
+        )
+    }
+}

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

@@ -5,9 +5,7 @@ import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Checkbox
 import androidx.compose.material3.MaterialTheme
-import io.legado.app.ui.widget.components.text.AppText
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -16,6 +14,7 @@ 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
+import io.legado.app.ui.widget.components.text.AppText
 
 @Composable
 fun CheckboxItem(
@@ -32,7 +31,7 @@ fun CheckboxItem(
             .fillMaxWidth()
             .clip(MaterialTheme.shapes.small)
             .background(
-                color = if (checked && enabled) MaterialTheme.colorScheme.secondaryContainer
+                color = if (checked && enabled) LegadoTheme.colorScheme.secondaryContainer
                 else color,
                 shape = MaterialTheme.shapes.small
             )
@@ -40,7 +39,7 @@ fun CheckboxItem(
             .padding(vertical = 12.dp, horizontal = 12.dp),
         verticalAlignment = Alignment.CenterVertically
     ) {
-        Checkbox(
+        AppCheckbox(
             checked = checked,
             onCheckedChange = null,
             enabled = enabled,
@@ -48,7 +47,7 @@ fun CheckboxItem(
         )
         AppText(
             text = title,
-            style = LegadoTheme.typography.bodyMedium,
+            style = LegadoTheme.typography.bodyMediumEmphasized,
             maxLines = 1,
             modifier = Modifier
                 .padding(start = 12.dp)

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

@@ -41,6 +41,7 @@ import androidx.core.graphics.withSave
 import coil.compose.AsyncImage
 import coil.request.ImageRequest
 import io.legado.app.ui.config.coverConfig.CoverConfig
+import io.legado.app.ui.theme.LegadoTheme
 import org.koin.compose.koinInject
 import io.legado.app.model.BookCover as BookCoverModel
 
@@ -91,7 +92,7 @@ fun BookCover(
             .clip(RoundedCornerShape(4.dp))
             .then(
                 if (!hasCustomDefault && !isOnlineCoverLoaded) {
-                    Modifier.background(MaterialTheme.colorScheme.surfaceContainer)
+                    Modifier.background(LegadoTheme.colorScheme.surfaceContainer)
                 } else Modifier
             )
     ) {
@@ -145,7 +146,7 @@ fun BookCover(
             Icon(
                 Icons.Default.Book,
                 contentDescription = null,
-                tint = MaterialTheme.colorScheme.secondary,
+                tint = LegadoTheme.colorScheme.secondary,
                 modifier = Modifier
                     .size(iconSizeDp)
                     .align(Alignment.Center)

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

@@ -14,7 +14,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Book
 import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
@@ -26,6 +25,7 @@ import androidx.compose.ui.unit.dp
 import coil.ImageLoader
 import coil.compose.AsyncImage
 import coil.request.ImageRequest
+import io.legado.app.ui.theme.LegadoTheme
 import org.koin.compose.koinInject
 
 @Composable
@@ -43,14 +43,14 @@ fun Cover(
     val boxModifier = modifier
         .aspectRatio(3f / 4f)
         .clip(RoundedCornerShape(4.dp))
-        .background(MaterialTheme.colorScheme.surfaceContainer)
+        .background(LegadoTheme.colorScheme.surfaceContainer)
 
     Box(modifier = boxModifier) {
         if (path == null) {
             Icon(
                 Icons.Default.Book,
                 contentDescription = null,
-                tint = MaterialTheme.colorScheme.surfaceContainerHighest,
+                tint = LegadoTheme.colorScheme.surfaceContainerHighest,
                 modifier = Modifier
                     .size(24.dp)
                     .align(Alignment.Center)
@@ -80,8 +80,8 @@ fun Cover(
                     .align(Alignment.TopEnd)
                     .padding(2.dp),
                 shape = RoundedCornerShape(4.dp),
-                color = MaterialTheme.colorScheme.secondaryContainer,
-                contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
+                color = LegadoTheme.colorScheme.secondaryContainer,
+                contentColor = LegadoTheme.colorScheme.onSecondaryContainer,
                 tonalElevation = 2.dp
             ) {
                 Row(

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

@@ -21,10 +21,16 @@ 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.AppTextField
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.text.AppText
 
+/**
+ * 基础版:由 Boolean 控制显示/隐藏
+ */
 @Composable
 fun TextListInputDialog(
+    show: Boolean,
     title: String,
     hint: String,
     initialValue: String = "",
@@ -32,17 +38,18 @@ fun TextListInputDialog(
     onDismissRequest: () -> Unit,
     onConfirm: (String) -> Unit
 ) {
-    var text by remember { mutableStateOf(initialValue) }
+    var text by remember(show) { mutableStateOf(initialValue) }
 
-    AlertDialog(
+    AppAlertDialog(
+        show = show,
         onDismissRequest = onDismissRequest,
-        title = { AppText(title) },
-        text = {
+        title = title,
+        content = {
             Column {
-                OutlinedTextField(
+                AppTextField(
                     value = text,
                     onValueChange = { text = it },
-                    label = { AppText(hint) },
+                    label = hint,
                     modifier = Modifier.fillMaxWidth(),
                     singleLine = true
                 )
@@ -68,17 +75,36 @@ fun TextListInputDialog(
                 }
             }
         },
-        confirmButton = {
-            TextButton(
-                onClick = { onConfirm(text) }
-            ) {
-                AppText(stringResource(android.R.string.ok))
-            }
-        },
-        dismissButton = {
-            TextButton(onClick = onDismissRequest) {
-                AppText(stringResource(android.R.string.cancel))
-            }
-        }
+        confirmText = stringResource(android.R.string.ok),
+        onConfirm = { onConfirm(text) },
+        dismissText = stringResource(android.R.string.cancel),
+        onDismiss = onDismissRequest
     )
+}
+
+@Composable
+fun <T> TextListInputDialog(
+    data: T?,
+    title: String,
+    hint: String,
+    initialValue: (T) -> String,
+    suggestions: List<String> = emptyList(),
+    onDismissRequest: () -> Unit,
+    onConfirm: (T, String) -> Unit
+) {
+    var cachedData by remember { mutableStateOf(data) }
+    if (data != null) cachedData = data
+
+    val currentData = cachedData
+    if (currentData != null) {
+        TextListInputDialog(
+            show = data != null,
+            title = title,
+            hint = hint,
+            initialValue = initialValue(currentData),
+            suggestions = suggestions,
+            onDismissRequest = onDismissRequest,
+            onConfirm = { text -> onConfirm(currentData, text) }
+        )
+    }
 }

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

@@ -29,8 +29,9 @@ fun PillDivider(
         Box(
             modifier = Modifier
                 .fillMaxWidth()
+                .padding(vertical = 2.dp)
                 .height(4.dp)
-                .background(MiuixTheme.colorScheme.secondaryContainer)
+                .background(MiuixTheme.colorScheme.dividerLine)
         )
     } else {
         Box(

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

@@ -1,34 +1,17 @@
 package io.legado.app.ui.widget.components.filePicker
 
 import android.webkit.MimeTypeMap
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.FlowRow
-import androidx.compose.foundation.layout.RowScope
-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.size
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
 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.MaterialTheme
-import androidx.compose.material3.Surface
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-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.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
+import io.legado.app.ui.widget.components.modalBottomSheet.OptionCard
+import io.legado.app.ui.widget.components.modalBottomSheet.OptionSheet
 
 enum class FilePickerSheetMode {
     DIR, FILE, EXPORT
@@ -47,100 +30,48 @@ fun FilePickerSheet(
     onUpload: (() -> Unit)? = null,
     allowExtensions: Array<String>? = null,
 ) {
-    AppModalBottomSheet(
+    OptionSheet(
         show = show,
-        title = title,
         onDismissRequest = onDismissRequest,
-        containerColor = LegadoTheme.colorScheme.surface
+        title = title
     ) {
-        Column(
-            modifier = Modifier
-                .fillMaxWidth(),
-            horizontalAlignment = Alignment.CenterHorizontally
-        ) {
-            FlowRow(
-                modifier = Modifier.fillMaxWidth(),
-                horizontalArrangement = Arrangement.spacedBy(12.dp),
-                verticalArrangement = Arrangement.spacedBy(12.dp),
-                maxItemsInEachRow = 2
-            ) {
-
-                onSelectSysDir?.let {
-                    FilePickerOptionCard(
-                        icon = Icons.Default.FolderOpen,
-                        text = stringResource(R.string.sys_folder_picker),
-                        onClick = it
-                    )
-                }
-
-                onSelectSysFile?.let {
-                    FilePickerOptionCard(
-                        icon = Icons.AutoMirrored.Filled.InsertDriveFile,
-                        text = stringResource(R.string.sys_file_picker),
-                        onClick = { it(typesOfExtensions(allowExtensions)) }
-                    )
-                }
-
-                onSelectSysFiles?.let {
-                    FilePickerOptionCard(
-                        icon = Icons.AutoMirrored.Filled.InsertDriveFile,
-                        text = "多选项目",
-                        onClick = { it(typesOfExtensions(allowExtensions)) }
-                    )
-                }
+        onSelectSysDir?.let {
+            OptionCard(
+                icon = Icons.Default.FolderOpen,
+                text = stringResource(R.string.sys_folder_picker),
+                onClick = it
+            )
+        }
 
-                onManualInput?.let {
-                    FilePickerOptionCard(
-                        icon = Icons.Default.EditNote,
-                        text = stringResource(R.string.manual_input),
-                        onClick = it
-                    )
-                }
+        onSelectSysFile?.let {
+            OptionCard(
+                icon = Icons.AutoMirrored.Filled.InsertDriveFile,
+                text = stringResource(R.string.sys_file_picker),
+                onClick = { it(typesOfExtensions(allowExtensions)) }
+            )
+        }
 
-                onUpload?.let {
-                    FilePickerOptionCard(
-                        icon = Icons.Default.CloudUpload,
-                        text = stringResource(R.string.upload_url),
-                        onClick = it
-                    )
-                }
-            }
-            Spacer(modifier = Modifier.height(24.dp))
+        onSelectSysFiles?.let {
+            OptionCard(
+                icon = Icons.AutoMirrored.Filled.InsertDriveFile,
+                text = "多选项目",
+                onClick = { it(typesOfExtensions(allowExtensions)) }
+            )
         }
-    }
-}
 
-@Composable
-private fun RowScope.FilePickerOptionCard(
-    icon: ImageVector,
-    text: String,
-    onClick: () -> Unit
-) {
-    Surface(
-        onClick = onClick,
-        modifier = Modifier
-            .weight(1f)
-            .height(100.dp),
-        shape = MaterialTheme.shapes.medium,
-        color = LegadoTheme.colorScheme.surfaceContainerLow,
-        tonalElevation = 2.dp
-    ) {
-        Column(
-            modifier = Modifier.fillMaxSize(),
-            horizontalAlignment = Alignment.CenterHorizontally,
-            verticalArrangement = Arrangement.Center
-        ) {
-            AppIcon(
-                imageVector = icon,
-                contentDescription = null,
-                modifier = Modifier.size(32.dp),
-                tint = LegadoTheme.colorScheme.primary
+        onManualInput?.let {
+            OptionCard(
+                icon = Icons.Default.EditNote,
+                text = stringResource(R.string.manual_input),
+                onClick = it
             )
-            Spacer(modifier = Modifier.height(8.dp))
-            AppText(
-                text = text,
-                style = LegadoTheme.typography.labelMedium,
-                maxLines = 1
+        }
+
+        onUpload?.let {
+            OptionCard(
+                icon = Icons.Default.CloudUpload,
+                text = stringResource(R.string.upload_url),
+                onClick = it
             )
         }
     }

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

@@ -19,12 +19,8 @@ import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.AccessTime
 import androidx.compose.material.icons.filled.FormatListNumbered
 import androidx.compose.material.icons.outlined.Delete
-import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedIconButton
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -41,7 +37,8 @@ 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.button.MediumAnimatedActionButton
+import io.legado.app.ui.widget.components.button.MediumOutlinedIconButton
 import io.legado.app.ui.widget.components.text.AppText
 import java.time.LocalDate
 
@@ -64,7 +61,7 @@ fun HeatmapCalendarTopBar(
         AppText("时间线", style = LegadoTheme.typography.titleMedium)
 
         Row(verticalAlignment = Alignment.CenterVertically) {
-            AnimatedActionButton(
+            MediumAnimatedActionButton(
                 checked = currentMode == HeatmapMode.TIME,
                 onCheckedChange = {
                     onModeChanged(if (it) HeatmapMode.TIME else HeatmapMode.COUNT)
@@ -77,16 +74,10 @@ fun HeatmapCalendarTopBar(
 
             Spacer(modifier = Modifier.width(8.dp))
 
-            OutlinedIconButton(
+            MediumOutlinedIconButton(
                 onClick = onClearDate,
-                shapes = IconButtonDefaults.shapes(),
-                border = ButtonDefaults.outlinedButtonBorder()
-            ) {
-                Icon(
-                    imageVector = Icons.Outlined.Delete,
-                    contentDescription = "清除选择"
-                )
-            }
+                icon = Icons.Outlined.Delete,
+            )
         }
     }
 }
@@ -111,7 +102,7 @@ fun WeekdayLabelsColumn(
                 AppText(
                     text = label,
                     fontSize = 10.sp,
-                    color = MaterialTheme.colorScheme.onSurfaceVariant,
+                    color = LegadoTheme.colorScheme.onSurfaceVariant,
                     modifier = Modifier.height(cellSize)
                 )
             } else {
@@ -256,7 +247,7 @@ fun HeatmapWeekColumn(
             AppText(
                 text = "${firstDayOfMonth.monthValue}月",
                 fontSize = 10.sp,
-                color = MaterialTheme.colorScheme.onSurfaceVariant,
+                color = LegadoTheme.colorScheme.onSurfaceVariant,
                 modifier = Modifier
                     .align(Alignment.TopStart)
                     .wrapContentWidth(unbounded = true)

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

@@ -6,6 +6,7 @@ import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.ui.graphics.Color
+import io.legado.app.ui.theme.LegadoTheme
 import java.time.LocalDate
 
 /**
@@ -104,8 +105,8 @@ fun rememberHeatmapLevel(
 @Composable
 fun heatmapColorForLevel(
     level: Int,
-    primary: Color = MaterialTheme.colorScheme.primary,
-    emptyColor: Color = MaterialTheme.colorScheme.surfaceVariant
+    primary: Color = LegadoTheme.colorScheme.primary,
+    emptyColor: Color = LegadoTheme.colorScheme.surface
 ): Color {
     return when (level) {
         0 -> emptyColor

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

@@ -19,7 +19,6 @@ import androidx.compose.foundation.lazy.itemsIndexed
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Info
 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.CenterAlignedTopAppBar
@@ -28,11 +27,9 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.Icon
 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.TextButton
 import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -49,6 +46,8 @@ 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.AppScaffold
+import io.legado.app.ui.widget.components.AppTextField
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 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
@@ -58,24 +57,26 @@ import kotlinx.coroutines.launch
 
 @Composable
 fun SourceInputDialog(
+    show: Boolean,
     title: String = "网络导入",
     hint: String = "请输入 URL 或 JSON",
     initialValue: String = "",
-    historyValues: List<String> = emptyList(), // 历史记录
+    historyValues: List<String> = emptyList(),
     onDismissRequest: () -> Unit,
     onConfirm: (String) -> Unit
 ) {
-    var text by remember { mutableStateOf(initialValue) }
+    var text by remember(show) { mutableStateOf(initialValue) }
 
-    AlertDialog(
+    AppAlertDialog(
+        show = show,
         onDismissRequest = onDismissRequest,
-        title = { AppText(title) },
-        text = {
+        title = title,
+        content = {
             Column {
-                OutlinedTextField(
+                AppTextField(
                     value = text,
                     onValueChange = { text = it },
-                    label = { AppText(hint) },
+                    label = hint,
                     modifier = Modifier.fillMaxWidth(),
                     maxLines = 5
                 )
@@ -94,16 +95,13 @@ fun SourceInputDialog(
                 }
             }
         },
-        confirmButton = {
-            OutlinedButton(
-                onClick = { if (text.isNotBlank()) onConfirm(text) }
-            ) { AppText(stringResource(android.R.string.ok)) }
+        confirmText = stringResource(android.R.string.ok),
+        onConfirm = {
+            // 拦截空输入,非空才执行回调
+            if (text.isNotBlank()) onConfirm(text)
         },
-        dismissButton = {
-            TextButton(onClick = onDismissRequest) {
-                AppText(stringResource(android.R.string.cancel))
-            }
-        }
+        dismissText = stringResource(android.R.string.cancel),
+        onDismiss = onDismissRequest
     )
 }
 

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

@@ -0,0 +1,39 @@
+package io.legado.app.ui.widget.components.list
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+
+@Composable
+fun <T : Any> TopFloatingStickyItem(
+    item: T?,
+    modifier: Modifier = Modifier,
+    enter: EnterTransition = fadeIn() + slideInVertically { -it },
+    exit: ExitTransition = fadeOut() + slideOutVertically { -it },
+    content: @Composable (T) -> Unit
+) {
+    var lastItem by remember { mutableStateOf<T?>(null) }
+
+    AnimatedVisibility(
+        visible = item != null,
+        modifier = modifier,
+        enter = enter,
+        exit = exit
+    ) {
+        item?.let { lastItem = it }
+        val currentItem = lastItem
+        if (currentItem != null) {
+            content(currentItem)
+        }
+    }
+}

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

@@ -21,7 +21,7 @@ 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
+import top.yukonga.miuix.kmp.overlay.OverlayListPopup
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
@@ -38,7 +38,7 @@ fun RoundDropdownMenu(
     val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
 
     if (isMiuix) {
-        SuperListPopup(
+        OverlayListPopup(
             show = expanded,
             onDismissRequest = onDismissRequest,
             popupModifier = modifier

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

@@ -30,7 +30,7 @@ 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
+import top.yukonga.miuix.kmp.window.WindowBottomSheet
 
 @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
 @Composable

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

@@ -0,0 +1,90 @@
+package io.legado.app.ui.widget.components.modalBottomSheet
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.RowScope
+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.size
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+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.icon.AppIcon
+import io.legado.app.ui.widget.components.text.AppText
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun OptionSheet(
+    show: Boolean,
+    onDismissRequest: () -> Unit,
+    title: String,
+    content: @Composable RowScope.() -> Unit
+) {
+    AppModalBottomSheet(
+        show = show,
+        title = title,
+        onDismissRequest = onDismissRequest,
+        containerColor = LegadoTheme.colorScheme.surface
+    ) {
+        Column(
+            modifier = Modifier
+                .fillMaxWidth(),
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            FlowRow(
+                modifier = Modifier.fillMaxWidth(),
+                horizontalArrangement = Arrangement.spacedBy(12.dp),
+                verticalArrangement = Arrangement.spacedBy(12.dp),
+                maxItemsInEachRow = 2
+            ) {
+                content()
+            }
+            Spacer(modifier = Modifier.height(24.dp))
+        }
+    }
+}
+
+@Composable
+fun RowScope.OptionCard(
+    icon: ImageVector,
+    text: String,
+    onClick: () -> Unit
+) {
+    Surface(
+        onClick = onClick,
+        modifier = Modifier
+            .weight(1f)
+            .height(100.dp),
+        shape = MaterialTheme.shapes.medium,
+        color = LegadoTheme.colorScheme.surfaceContainerLow,
+        tonalElevation = 2.dp
+    ) {
+        Column(
+            modifier = Modifier.fillMaxSize(),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.Center
+        ) {
+            AppIcon(
+                imageVector = icon,
+                contentDescription = null,
+                modifier = Modifier.size(32.dp),
+                tint = LegadoTheme.colorScheme.primary
+            )
+            Spacer(modifier = Modifier.height(8.dp))
+            AppText(
+                text = text,
+                style = LegadoTheme.typography.labelMedium,
+                maxLines = 1
+            )
+        }
+    }
+}

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

@@ -17,7 +17,6 @@ import androidx.compose.material3.CenterAlignedTopAppBar
 import androidx.compose.material3.DropdownMenu
 import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.FloatingActionButton
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
@@ -35,6 +34,9 @@ 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.AppFloatingActionButton
+import io.legado.app.ui.widget.components.AppTextField
+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
 import kotlinx.coroutines.launch
@@ -76,6 +78,45 @@ fun <T> RuleEditSheet(
     fun getCurrentEntity() = fromFields(RuleEditFields(name, rule1, rule2), rule)
 
     AppModalBottomSheet(
+        title = title,
+        startAction = {
+            IconButton(onClick = onDismissRequest) {
+                Icon(
+                    Icons.Default.Close,
+                    contentDescription = stringResource(R.string.cancel)
+                )
+            }
+        },
+        endAction = {
+            IconButton(onClick = { showMenu = true }) {
+                Icon(Icons.Default.MoreVert, contentDescription = "More")
+            }
+            DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
+                DropdownMenuItem(
+                    text = { AppText(stringResource(R.string.copy_rule)) },
+                    leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) },
+                    onClick = {
+                        onCopy(getCurrentEntity())
+                        showMenu = false
+                    }
+                )
+                DropdownMenuItem(
+                    text = { AppText(stringResource(R.string.paste_rule)) },
+                    leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
+                    onClick = {
+                        scope.launch {
+                            onPaste()?.let { pasted ->
+                                val fields = toFields(pasted)
+                                name = fields.name
+                                rule1 = fields.rule1
+                                rule2 = fields.rule2
+                            }
+                        }
+                        showMenu = false
+                    }
+                )
+            }
+        },
         show = show,
         onDismissRequest = onDismissRequest
     ) {
@@ -83,88 +124,39 @@ fun <T> RuleEditSheet(
             Column(
                 modifier = Modifier
                     .fillMaxWidth()
-                    .verticalScroll(rememberScrollState())
+                    .padding(bottom = 120.dp)
+                    .verticalScroll(rememberScrollState()),
+                verticalArrangement = Arrangement.spacedBy(8.dp)
             ) {
-                CenterAlignedTopAppBar(
-                    title = { AppText(title) },
-                    navigationIcon = {
-                        IconButton(onClick = onDismissRequest) {
-                            Icon(
-                                Icons.Default.Close,
-                                contentDescription = stringResource(R.string.cancel)
-                            )
-                        }
-                    },
-                    actions = {
-                        IconButton(onClick = { showMenu = true }) {
-                            Icon(Icons.Default.MoreVert, contentDescription = "More")
-                        }
-                        DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
-                            DropdownMenuItem(
-                                text = { AppText(stringResource(R.string.copy_rule)) },
-                                leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) },
-                                onClick = {
-                                    onCopy(getCurrentEntity())
-                                    showMenu = false
-                                }
-                            )
-                            DropdownMenuItem(
-                                text = { AppText(stringResource(R.string.paste_rule)) },
-                                leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
-                                onClick = {
-                                    scope.launch {
-                                        onPaste()?.let { pasted ->
-                                            val fields = toFields(pasted)
-                                            name = fields.name
-                                            rule1 = fields.rule1
-                                            rule2 = fields.rule2
-                                        }
-                                    }
-                                    showMenu = false
-                                }
-                            )
-                        }
-                    },
-                    colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)
+                AppTextField(
+                    modifier = Modifier.fillMaxWidth(),
+                    value = name,
+                    onValueChange = { name = it },
+                    label = stringResource(R.string.name),
+                    singleLine = true
+                )
+                AppTextField(
+                    modifier = Modifier.fillMaxWidth(),
+                    value = rule1,
+                    onValueChange = { rule1 = it },
+                    label = label1
+                )
+                AppTextField(
+                    modifier = Modifier.fillMaxWidth(),
+                    value = rule2,
+                    onValueChange = { rule2 = it },
+                    label = label2,
+                    minLines = 3
                 )
-
-                Column(
-                    modifier = Modifier
-                        .padding(horizontal = 16.dp)
-                        .padding(bottom = 96.dp),
-                    verticalArrangement = Arrangement.spacedBy(8.dp)
-                ) {
-                    OutlinedTextField(
-                        modifier = Modifier.fillMaxWidth(),
-                        value = name,
-                        onValueChange = { name = it },
-                        label = { AppText(stringResource(R.string.name)) },
-                        singleLine = true
-                    )
-                    OutlinedTextField(
-                        modifier = Modifier.fillMaxWidth(),
-                        value = rule1,
-                        onValueChange = { rule1 = it },
-                        label = { AppText(label1) }
-                    )
-                    OutlinedTextField(
-                        modifier = Modifier.fillMaxWidth(),
-                        value = rule2,
-                        onValueChange = { rule2 = it },
-                        label = { AppText(label2) },
-                        minLines = 3
-                    )
-                }
             }
 
-            FloatingActionButton(
+            AppFloatingActionButton(
                 onClick = { onSave(getCurrentEntity()) },
                 modifier = Modifier
                     .align(Alignment.BottomEnd)
-                    .padding(16.dp),
-                containerColor = MaterialTheme.colorScheme.primaryContainer
+                    .padding(16.dp)
             ) {
-                Icon(Icons.Default.Save, contentDescription = "Save")
+                AppIcon(Icons.Default.Save, contentDescription = "Save")
             }
         }
     }

+ 14 - 25
app/src/main/java/io/legado/app/ui/widget/components/rules/RuleListScaffold.kt

@@ -28,6 +28,7 @@ import io.legado.app.R
 import io.legado.app.ui.widget.components.ActionItem
 import io.legado.app.ui.widget.components.AppFloatingActionButton
 import io.legado.app.ui.widget.components.SelectionActions
+import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.list.ListScaffold
 import io.legado.app.ui.widget.components.list.ListUiState
 import io.legado.app.ui.widget.components.text.AppText
@@ -71,31 +72,19 @@ fun <T> RuleListScaffold(
 ) {
     var showDeleteConfirmDialog by remember { mutableStateOf(false) }
 
-    if (showDeleteConfirmDialog) {
-        AlertDialog(
-            onDismissRequest = { showDeleteConfirmDialog = false },
-            title = { AppText(stringResource(R.string.delete)) },
-            text = { AppText(stringResource(R.string.del_msg)) },
-            confirmButton = {
-                OutlinedButton(
-                    onClick = {
-                        onDeleteSelected(state.selectedIds)
-                        showDeleteConfirmDialog = false
-                    },
-                    colors = ButtonDefaults.outlinedButtonColors(
-                        contentColor = MaterialTheme.colorScheme.error
-                    )
-                ) {
-                    AppText(stringResource(R.string.ok))
-                }
-            },
-            dismissButton = {
-                TextButton(onClick = { showDeleteConfirmDialog = false }) {
-                    AppText(stringResource(R.string.cancel))
-                }
-            }
-        )
-    }
+    AppAlertDialog(
+        show = showDeleteConfirmDialog,
+        onDismissRequest = { showDeleteConfirmDialog = false },
+        title = stringResource(R.string.delete),
+        text = stringResource(R.string.del_msg),
+        confirmText = stringResource(R.string.ok),
+        onConfirm = {
+            onDeleteSelected(state.selectedIds)
+            showDeleteConfirmDialog = false
+        },
+        dismissText = stringResource(R.string.cancel),
+        onDismiss = { showDeleteConfirmDialog = false }
+    )
 
     ListScaffold(
         title = title,

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

@@ -10,7 +10,7 @@ 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.BasicComponentDefaults
-import top.yukonga.miuix.kmp.extra.SuperArrow
+import top.yukonga.miuix.kmp.preference.ArrowPreference
 
 @Composable
 fun ClickableSettingItem(
@@ -25,7 +25,7 @@ fun ClickableSettingItem(
 ) {
     val composeEngine = LegadoTheme.composeEngine
     if (ThemeResolver.isMiuixEngine(composeEngine)) {
-        SuperArrow(
+        ArrowPreference(
             title = title,
             summary = description,
             insideMargin = BasicComponentDefaults.InsideMargin,

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

@@ -32,8 +32,8 @@ import io.legado.app.ui.widget.components.ValueStepper
 import io.legado.app.ui.widget.components.card.TextCard
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 import top.yukonga.miuix.kmp.basic.BasicComponent
-import top.yukonga.miuix.kmp.extra.SuperSwitch
-import top.yukonga.miuix.kmp.extra.WindowDropdown
+import top.yukonga.miuix.kmp.preference.SwitchPreference
+import top.yukonga.miuix.kmp.preference.WindowDropdownPreference
 import top.yukonga.miuix.kmp.basic.Slider as MiuixSlider
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -53,7 +53,7 @@ fun CompactDropdownSettingItem(
         val selectedIndex = entryValues.indexOf(selectedValue).coerceAtLeast(0)
         val spinnerItems = displayEntries.toList()
 
-        WindowDropdown(
+        WindowDropdownPreference(
             title = title,
             summary = description,
             items = spinnerItems,
@@ -225,7 +225,7 @@ fun CompactSwitchSettingItem(
     onCheckedChange: (Boolean) -> Unit
 ) {
     if (ThemeResolver.isMiuixEngine(composeEngine)) {
-        SuperSwitch(
+        SwitchPreference(
             title = title,
             summary = description,
             checked = checked,

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

@@ -12,7 +12,7 @@ import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.ThemeResolver
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 import top.yukonga.miuix.kmp.basic.SpinnerEntry
-import top.yukonga.miuix.kmp.extra.SuperSpinner
+import top.yukonga.miuix.kmp.preference.OverlaySpinnerPreference
 
 @Composable
 fun DropdownListSettingItem(
@@ -32,7 +32,7 @@ fun DropdownListSettingItem(
             SpinnerEntry(title = display)
         }
 
-        SuperSpinner(
+        OverlaySpinnerPreference(
             title = title,
             summary = description,
             items = spinnerItems,

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

@@ -6,7 +6,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.ThemeResolver
 import io.legado.app.ui.widget.components.AdaptiveSwitch
-import top.yukonga.miuix.kmp.extra.SuperSwitch
+import top.yukonga.miuix.kmp.preference.SwitchPreference
 
 
 @Composable
@@ -22,7 +22,7 @@ fun SwitchSettingItem(
     val composeEngine = LegadoTheme.composeEngine
 
     if (ThemeResolver.isMiuixEngine(composeEngine)) {
-        SuperSwitch(
+        SwitchPreference(
             title = title,
             summary = description,
             checked = checked,

+ 0 - 2
app/src/main/java/io/legado/app/ui/widget/components/text/AppText.kt

@@ -40,10 +40,8 @@ fun AppText(
 ) {
     val baseStyle = style ?: LegadoTheme.typography.bodyMedium
 
-    // 2. 获取默认文本色:直接拿你自己封装的 LegadoTheme.colorScheme.onSurface
     val defaultTextColor = LegadoTheme.colorScheme.onSurface
 
-    // 3. 颜色降级逻辑:传入的 color -> style 中的 color -> 主题默认色
     val finalTextColor = color.takeOrElse {
         baseStyle.color.takeOrElse { defaultTextColor }
     }

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

@@ -20,9 +20,10 @@ import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
+import io.legado.app.ui.theme.adaptiveHorizontalPadding
 import io.legado.app.ui.widget.components.SearchBarSection
 import io.legado.app.ui.widget.components.button.TopBarActionButton
-import io.legado.app.ui.widget.components.button.TopbarNavigationButton
+import io.legado.app.ui.widget.components.button.TopBarNavigationButton
 import io.legado.app.ui.widget.components.icon.AppIcons
 import io.legado.app.ui.widget.components.list.ListUiState
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
@@ -66,7 +67,7 @@ fun <T> DynamicTopAppBar(
         },
         navigationIcon = {
             if (isSelecting || onBackClick != null) {
-                TopbarNavigationButton(
+                TopBarNavigationButton(
                     onClick = { if (isSelecting) onClearSelection() else onBackClick?.invoke() },
                     imageVector = if (isSelecting) AppIcons.Close else AppIcons.Back,
                     contentDescription = if (isSelecting) "取消选择" else "返回"
@@ -103,6 +104,8 @@ fun <T> DynamicTopAppBar(
         scrollBehavior = scrollBehavior,
         bottomContent = {
             AnimatedVisibility(
+                modifier = Modifier
+                    .adaptiveHorizontalPadding(),
                 visible = state.isSearch && !isSelecting,
                 enter = expandVertically() + fadeIn(),
                 exit = shrinkVertically() + fadeOut()

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

@@ -84,7 +84,7 @@ fun GlassMediumFlexibleTopAppBar(
                     title = title,
                     navigationIcon = navigationIcon,
                     actions = actions,
-                    color = Color.Transparent, // 内部强制透明,透出 Column 背景
+                    color = Color.Transparent,
                     scrollBehavior = (scrollBehavior as? MiuixGlassScrollBehavior)?.miuixBehavior
                 )
             }
@@ -105,7 +105,7 @@ fun GlassMediumFlexibleTopAppBar(
                         navigationIcon = navigationIcon,
                         actions = actions,
                         scrollBehavior = (scrollBehavior as? M3GlassScrollBehavior)?.m3Behavior,
-                        colors = transparentColors // 应用透明背景
+                        colors = transparentColors
                     )
                 } else {
                     TopAppBar(
@@ -121,13 +121,12 @@ fun GlassMediumFlexibleTopAppBar(
                         navigationIcon = navigationIcon,
                         actions = actions,
                         scrollBehavior = (scrollBehavior as? M3GlassScrollBehavior)?.m3Behavior,
-                        colors = transparentColors // 应用透明背景
+                        colors = transparentColors
                     )
                 }
             }
         }
 
-        // 现在的 bottomContent 完美包裹在外层 Column 内,共享渐变和模糊效果!
         bottomContent?.invoke(this)
     }
 }

+ 9 - 4
gradle.properties

@@ -21,7 +21,6 @@ android.useAndroidX=true
 android.enableJetifier=false
 # Kotlin code style for this project: "official" or "obsolete":
 kotlin.code.style=official
-android.enableResourceOptimizations=true
 # android.enableNewResourceShrinker' is deprecated.
 # It was removed in version 8.0 of the Android Gradle plugin.
 # android.enableNewResourceShrinker=true
@@ -31,9 +30,6 @@ org.gradle.vfs.watch=true
 # enable cache can not up app version
 org.gradle.unsafe.configuration-cache=false
 # Disable buildFeatures flags by default
-android.defaults.buildfeatures.aidl=false
-android.defaults.buildfeatures.buildconfig=false
-android.defaults.buildfeatures.renderscript=false
 android.defaults.buildfeatures.resvalues=false
 android.defaults.buildfeatures.shaders=false
 # enables namespacing of each library's R class so that its R class includes only the resources declared in the library itself
@@ -45,5 +41,14 @@ CronetMainVersion=128.0.0.0
 android.injected.testOnly=false
 android.nonFinalResIds=true
 android.injected.androidTest.leaveApksInstalledAfterRun=true
+android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
+android.enableAppCompileTimeRClass=false
+android.usesSdkInManifest.disallowed=false
+android.uniquePackageNames=false
+android.dependency.useConstraints=true
+android.r8.strictFullModeForKeepRules=false
+android.r8.optimizedResourceShrinking=false
+android.builtInKotlin=false
+android.newDsl=false
 
 

+ 13 - 0
gradle/gradle-daemon-jvm.properties

@@ -0,0 +1,13 @@
+#This file is generated by updateDaemonJvm
+toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
+toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
+toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
+toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
+toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e99bae143b75f9a10ead10248f02055e/redirect
+toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/04e088f8677de3b384108493cc9481d0/redirect
+toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
+toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
+toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e55dccbfe27cb97945148c61a39c89c5/redirect
+toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/dbd05c4936d573642f94cd149e1356c8/redirect
+toolchainVendor=JETBRAINS
+toolchainVersion=21

+ 10 - 6
gradle/libs.versions.toml

@@ -17,7 +17,7 @@ kotlinxSerialization = "1.10.0"
 kotlinxCoroutinesAndroid = "1.10.2"
 kotlinxSerializationJson = "1.10.0"
 ksp = "2.3.6"
-agp = "8.13.2"
+agp = "9.1.0"
 appcompat = "1.7.1"
 colorpicker = "1.1.0"
 colorpicker-compose = "1.1.3"
@@ -84,7 +84,7 @@ biometricKtx = "1.1.0"
 reorderable = "3.0.0"
 haze = "1.7.2"
 timber = "5.0.1"
-miuix = "0.8.8"
+miuix = "0.9.0"
 backdrop = "1.0.6"
 capsule = "2.1.3"
 [libraries]
@@ -128,6 +128,8 @@ androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayo
 
 haze-core = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" }
 haze-materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = "haze" }
+kmp-miuix-preference = { module = "top.yukonga.miuix.kmp:miuix-preference" }
+kmp-miuix-ui = { module = "top.yukonga.miuix.kmp:miuix-ui" }
 koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin-bom" }
 koin-core = { module = "io.insert-koin:koin-core" }
 koin-android = { group = "io.insert-koin", name = "koin-android" }
@@ -173,11 +175,9 @@ markwon-image-glide = { module = "io.noties.markwon:image-glide", version.ref =
 markwon-ext-tables = { module = "io.noties.markwon:ext-tables", version.ref = "markwon" }
 markwon-html = { module = "io.noties.markwon:html", version.ref = "markwon" }
 
-
 nanohttpd-nanohttpd = { module = "org.nanohttpd:nanohttpd", version.ref = "nanoHttpd" }
 nanohttpd-websocket = { module = "org.nanohttpd:nanohttpd-websocket", version.ref = "nanoHttpd" }
 
-
 navigation-compose = { module = "androidx.navigation:navigation-compose" }
 preference-preference = { module = "androidx.preference:preference", version.ref = "preference" }
 preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preference" }
@@ -247,8 +247,12 @@ reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reo
 timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" }
 
 # miuix
-miuix = { group = "top.yukonga.miuix.kmp", name = "miuix-android", version.ref = "miuix" }
-miuix-icons = { group = "top.yukonga.miuix.kmp", name = "miuix-icons-android", version.ref = "miuix" }
+miuix-core = { module = "top.yukonga.miuix.kmp:miuix-core", version.ref = "miuix" }
+miuix-blur-android = { module = "top.yukonga.miuix.kmp:miuix-blur-android", version.ref = "miuix" }
+miuix-icons-android = { module = "top.yukonga.miuix.kmp:miuix-icons-android", version.ref = "miuix" }
+miuix-preference-android = { module = "top.yukonga.miuix.kmp:miuix-preference-android", version.ref = "miuix" }
+miuix-shapes-android = { module = "top.yukonga.miuix.kmp:miuix-shapes-android", version.ref = "miuix" }
+miuix-ui-android = { module = "top.yukonga.miuix.kmp:miuix-ui-android", version.ref = "miuix" }
 
 capsule = { module = "io.github.kyant0:capsule", version.ref = "capsule" }
 backdrop = { module = "io.github.kyant0:backdrop", version.ref = "backdrop" }

+ 1 - 1
gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip
 networkTimeout=10000
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME

+ 0 - 34
modules/book/build.gradle

@@ -1,34 +0,0 @@
-plugins {
-    alias libs.plugins.android.library
-    alias libs.plugins.kotlin.android
-}
-
-android {
-    compileSdk = compile_sdk_version
-    namespace = 'me.ag2s'
-    kotlin {
-        jvmToolchain {
-            languageVersion.set(JavaLanguageVersion.of(21))
-        }
-    }
-    defaultConfig {
-        minSdk 26
-        targetSdk 36
-
-        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-        consumerProguardFiles "consumer-rules.pro"
-    }
-
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_21
-        targetCompatibility JavaVersion.VERSION_21
-    }
-    lint {
-        checkDependencies = true
-    }
-}
-
-dependencies {
-    implementation(libs.androidx.annotation)
-    //implementation 'androidx.annotation:annotation:1.7.1'
-}

+ 36 - 0
modules/book/build.gradle.kts

@@ -0,0 +1,36 @@
+plugins {
+    alias(libs.plugins.android.library)
+    alias(libs.plugins.kotlin.android)
+}
+
+android {
+    compileSdk = 37
+    namespace = "me.ag2s"
+    kotlin {
+        jvmToolchain {
+            languageVersion.set(JavaLanguageVersion.of(21))
+        }
+    }
+    defaultConfig {
+        minSdk = 26
+
+        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles += file("consumer-rules.pro")
+    }
+
+    compileOptions {
+        sourceCompatibility = JavaVersion.VERSION_21
+        targetCompatibility = JavaVersion.VERSION_21
+    }
+    lint {
+        checkDependencies = true
+        targetSdk = 37
+    }
+    testOptions {
+        targetSdk = 37
+    }
+}
+
+dependencies {
+    implementation(libs.androidx.annotation)
+}

+ 0 - 47
modules/rhino/build.gradle

@@ -1,47 +0,0 @@
-plugins {
-    //d 'com.android.library'
-    //id 'org.jetbrains.kotlin.android'
-    alias libs.plugins.android.library
-    alias libs.plugins.kotlin.android
-}
-
-android {
-    compileSdk = compile_sdk_version
-    namespace = 'com.script'
-    kotlin {
-        jvmToolchain {
-            languageVersion.set(JavaLanguageVersion.of(21))
-        }
-    }
-    defaultConfig {
-        minSdk 26
-        targetSdk 36
-
-        consumerProguardFiles "consumer-rules.pro"
-    }
-
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_21
-        targetCompatibility JavaVersion.VERSION_21
-    }
-    lint {
-        checkDependencies = true
-    }
-    tasks.withType(JavaCompile).tap {
-        configureEach {
-            options.compilerArgs << "-Xlint:deprecation"
-        }
-    }
-}
-
-dependencies {
-//    api(fileTree(dir: 'lib', include: ['rhino-1.7.14.jar']))
-    api libs.mozilla.rhino
-
-    implementation(libs.kotlinx.coroutines.core)
-    implementation(libs.okhttp)
-    implementation(libs.androidx.collection)
-
-//    def coroutines_version = '1.7.3'
-//    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version")
-}

+ 42 - 0
modules/rhino/build.gradle.kts

@@ -0,0 +1,42 @@
+plugins {
+    alias(libs.plugins.android.library)
+    alias(libs.plugins.kotlin.android)
+}
+
+android {
+    compileSdk = 37
+    namespace = "com.script"
+    kotlin {
+        jvmToolchain {
+            languageVersion.set(JavaLanguageVersion.of(21))
+        }
+    }
+    defaultConfig {
+        minSdk = 26
+
+        consumerProguardFiles += file("consumer-rules.pro")
+    }
+
+    compileOptions {
+        sourceCompatibility = JavaVersion.VERSION_21
+        targetCompatibility = JavaVersion.VERSION_21
+    }
+    lint {
+        checkDependencies = true
+        targetSdk = 36
+    }
+    testOptions {
+        targetSdk = 36
+    }
+    tasks.withType<JavaCompile>().configureEach {
+        options.compilerArgs.add("-Xlint:deprecation")
+    }
+}
+
+dependencies {
+    api(libs.mozilla.rhino)
+
+    implementation(libs.kotlinx.coroutines.core)
+    implementation(libs.okhttp)
+    implementation(libs.androidx.collection)
+}

+ 3 - 0
settings.gradle

@@ -18,6 +18,9 @@ pluginManagement {
         //maven { url 'https://repo.huaweicloud.com/repository/maven/' }
     }
 }
+plugins {
+    id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
+}
 
 dependencyResolutionManagement {
     repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)