From 37c80e1230f9672e37a92f4a25f4eb9025360a17 Mon Sep 17 00:00:00 2001 From: HapeLee <63206378+HapeLee@users.noreply.github.com> Date: Tue, 7 Apr 2026 01:00:46 +0800 Subject: [PATCH] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E4=BE=9D=E6=97=A7?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=BA=9BMiuix=E4=B8=BB=E9=A2=98?= =?UTF-8?q?=E7=9A=84=E7=A8=B3=E5=AE=9A=E6=80=A7=E5=92=8C=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E9=97=AE=E9=A2=98,hmm=E8=AE=AE=E9=A2=98=E4=BE=9D=E6=97=A7?= =?UTF-8?q?=E6=B2=A1=E6=97=B6=E9=97=B4=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 12 +- .../io/legado/app/ui/about/AboutScreen.kt | 4 +- .../io/legado/app/ui/about/AppLogSheet.kt | 40 +- .../app/ui/book/bookmark/AllBookmarkScreen.kt | 236 ++++++-- .../app/ui/book/explore/ExploreShowScreen.kt | 4 +- .../app/ui/book/group/GroupEditSheet.kt | 7 +- .../ui/book/import/remote/RemoteBookScreen.kt | 94 ++- .../ui/book/info/edit/BookInfoEditScreen.kt | 86 ++- .../ui/book/readRecord/ReadRecordScreen.kt | 198 +++---- .../book/searchContent/SearchContentScreen.kt | 126 ++-- .../app/ui/book/toc/rule/TxtRuleScreen.kt | 63 +- .../legado/app/ui/config/ConfigNavScreen.kt | 4 +- .../config/backupConfig/BackupConfigScreen.kt | 352 ++++++----- .../config/coverConfig/CoverConfigScreen.kt | 4 +- .../DirectLinkUploadBottomSheet.kt | 177 +++--- .../config/otherConfig/OtherConfigScreen.kt | 148 ++--- .../ui/config/readConfig/ReadConfigScreen.kt | 4 +- .../config/themeConfig/ThemeConfigScreen.kt | 104 ++-- .../ui/config/themeConfig/ThemeListDialog.kt | 37 +- .../app/ui/dict/rule/DictRuleEditSheet.kt | 12 +- .../legado/app/ui/dict/rule/DictRuleScreen.kt | 68 +-- .../legado/app/ui/main/bookshelf/BookItem.kt | 9 +- .../app/ui/main/bookshelf/BookshelfScreen.kt | 35 +- .../app/ui/main/explore/ExploreScreen.kt | 103 ++-- .../java/io/legado/app/ui/main/my/MyScreen.kt | 221 ++++--- .../io/legado/app/ui/main/rss/RssScreen.kt | 45 +- .../legado/app/ui/replace/ReplaceEditRoute.kt | 5 +- .../app/ui/replace/ReplaceRuleActivity.kt | 150 +++-- .../app/ui/replace/ReplaceRuleScreen.kt | 198 +++---- .../app/ui/replace/edit/ReplaceEditScreen.kt | 555 +++++++++--------- .../ui/replace/edit/ReplaceEditViewModel.kt | 100 ++-- .../ui/rss/favorites/RssFavoritesScreen.kt | 75 ++- .../ui/rss/source/manage/RssSourceScreen.kt | 100 ++-- .../app/ui/rss/subscription/RuleSubScreen.kt | 113 ++-- .../io/legado/app/ui/theme/AdaptivePadding.kt | 34 +- .../java/io/legado/app/ui/theme/AppTheme.kt | 16 +- .../io/legado/app/ui/theme/ThemeResolver.kt | 2 - .../app/ui/widget/components/AppTextField.kt | 231 +++++++- .../components/GroupManageBottomSheet.kt | 6 +- .../ui/widget/components/SearchBarSection.kt | 26 +- .../widget/components/alert/AppAlertDialog.kt | 170 ++++++ .../components/bookmark/BookmarkEditSheet.kt | 58 +- .../components/bookmark/BookmarkItem.kt | 2 +- .../widget/components/button/AlertButton.kt | 3 +- .../components/button/AnimatedActionButton.kt | 86 --- .../components/button/MediumIconButton.kt | 332 +++++++++++ .../widget/components/button/MiuixButton.kt | 41 ++ .../components/button/SmallIconButton.kt | 162 +++-- .../widget/components/button/TopBarButton.kt | 94 ++- .../ui/widget/components/card/GlassCard.kt | 8 + .../widget/components/checkBox/AppCheckbox.kt | 60 ++ .../components/checkBox/CheckboxItem.kt | 9 +- .../ui/widget/components/cover/BookCover.kt | 5 +- .../app/ui/widget/components/cover/Cover.kt | 10 +- .../components/dialog/TextListInputDialog.kt | 62 +- .../widget/components/divider/PillDivider.kt | 3 +- .../components/filePicker/FilePickerSheet.kt | 149 ++--- .../heatmap/HeatmapCalendarComponents.kt | 25 +- .../components/heatmap/RememberDateUtil.kt | 5 +- .../importComponents/ImportComponents.kt | 36 +- .../components/list/TopFloatingStickyItem.kt | 39 ++ .../components/menuItem/RoundDropdownMenu.kt | 4 +- .../modalBottomSheet/AppModalBottomSheet.kt | 2 +- .../modalBottomSheet/OptionSheet.kt | 90 +++ .../widget/components/rules/RuleEditSheet.kt | 142 +++-- .../components/rules/RuleListScaffold.kt | 39 +- .../settingItem/ClickableSettingItem.kt | 4 +- .../settingItem/CompactSettingItems.kt | 8 +- .../components/settingItem/ListSettingItem.kt | 4 +- .../settingItem/SwitchSettingItem.kt | 4 +- .../app/ui/widget/components/text/AppText.kt | 2 - .../components/topbar/DynamicTopAppBar.kt | 7 +- .../topbar/GlassMediumFlexibleTopAppBar.kt | 7 +- gradle.properties | 13 +- gradle/gradle-daemon-jvm.properties | 13 + gradle/libs.versions.toml | 16 +- gradle/wrapper/gradle-wrapper.properties | 2 +- modules/book/build.gradle | 34 -- modules/book/build.gradle.kts | 36 ++ modules/rhino/build.gradle | 47 -- modules/rhino/build.gradle.kts | 42 ++ settings.gradle | 3 + 82 files changed, 3265 insertions(+), 2417 deletions(-) create mode 100644 app/src/main/java/io/legado/app/ui/widget/components/alert/AppAlertDialog.kt delete mode 100644 app/src/main/java/io/legado/app/ui/widget/components/button/AnimatedActionButton.kt create mode 100644 app/src/main/java/io/legado/app/ui/widget/components/button/MediumIconButton.kt create mode 100644 app/src/main/java/io/legado/app/ui/widget/components/checkBox/AppCheckbox.kt create mode 100644 app/src/main/java/io/legado/app/ui/widget/components/list/TopFloatingStickyItem.kt create mode 100644 app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/OptionSheet.kt create mode 100644 gradle/gradle-daemon-jvm.properties delete mode 100644 modules/book/build.gradle create mode 100644 modules/book/build.gradle.kts delete mode 100644 modules/rhino/build.gradle create mode 100644 modules/rhino/build.gradle.kts diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9e48ccb50..5ab4dcde7 100644 --- a/app/build.gradle.kts +++ b/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) diff --git a/app/src/main/java/io/legado/app/ui/about/AboutScreen.kt b/app/src/main/java/io/legado/app/ui/about/AboutScreen.kt index d1bc57845..a42371134 100644 --- a/app/src/main/java/io/legado/app/ui/about/AboutScreen.kt +++ b/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 ) diff --git a/app/src/main/java/io/legado/app/ui/about/AppLogSheet.kt b/app/src/main/java/io/legado/app/ui/about/AppLogSheet.kt index 54bff48eb..5ba55187c 100644 --- a/app/src/main/java/io/legado/app/ui/about/AppLogSheet.kt +++ b/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 diff --git a/app/src/main/java/io/legado/app/ui/book/bookmark/AllBookmarkScreen.kt b/app/src/main/java/io/legado/app/ui/book/bookmark/AllBookmarkScreen.kt index 496916e53..571e838f0 100644 --- a/app/src/main/java/io/legado/app/ui/book/bookmark/AllBookmarkScreen.kt +++ b/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) } - ) - } - - 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 - } + 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 ) + + 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 + ) + } + } + ) +} diff --git a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt index e729672b7..8e2607c68 100644 --- a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt +++ b/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( diff --git a/app/src/main/java/io/legado/app/ui/book/group/GroupEditSheet.kt b/app/src/main/java/io/legado/app/ui/book/group/GroupEditSheet.kt index f10bfccab..39330c835 100644 --- a/app/src/main/java/io/legado/app/ui/book/group/GroupEditSheet.kt +++ b/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) diff --git a/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookScreen.kt b/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookScreen.kt index 85bc59871..4fa0cc3e4 100644 --- a/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookScreen.kt +++ b/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) ) } } diff --git a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt index d7798d3c0..815f002a1 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt +++ b/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 diff --git a/app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt b/app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt index 56314ad97..34421acba 100644 --- a/app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt +++ b/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( ) } - - 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 - ) - } - } - } - }, - confirmButton = { - TextButton( - onClick = { - viewModel.mergeReadRecords( - targetRecord, - candidates.filter { selectedAuthors.contains(it.bookAuthor) } - ) - mergeDialogData = null - } - ) { - AppText("合并") - } - }, - dismissButton = { - TextButton(onClick = { mergeDialogData = null }) { - AppText("取消") - } - } + var selectedAuthors by remember(mergeDialogData != null) { + mutableStateOf( + mergeDialogData?.let { (_, candidates) -> + candidates.map { it.bookAuthor }.toSet() + } ?: emptySet() ) } + + 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 + } + } + ) + } + } + }, + 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 ) diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt index ff6d87a8c..bdb453b58 100644 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt +++ b/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, diff --git a/app/src/main/java/io/legado/app/ui/book/toc/rule/TxtRuleScreen.kt b/app/src/main/java/io/legado/app/ui/book/toc/rule/TxtRuleScreen.kt index 02d004f74..14fbfd54b 100644 --- a/app/src/main/java/io/legado/app/ui/book/toc/rule/TxtRuleScreen.kt +++ b/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) ) { diff --git a/app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt b/app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt index 4b4b1c099..c5828f22a 100644 --- a/app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt +++ b/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) } ) } diff --git a/app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigScreen.kt b/app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigScreen.kt index 7bd26ad3b..459a41a87 100644 --- a/app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigScreen.kt +++ b/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] - 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) - } - } - } - }, - 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)) - } - } - ) } - if (showLoadingDialog) { - AlertDialog( - onDismissRequest = {}, - title = { AppText(loadingText) }, - confirmButton = {}, - dismissButton = {} - ) + 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] + ] = isChecked + } + ) + } + } + }, + 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 } + ) + } @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 diff --git a/app/src/main/java/io/legado/app/ui/config/coverConfig/CoverConfigScreen.kt b/app/src/main/java/io/legado/app/ui/config/coverConfig/CoverConfigScreen.kt index 61b0c88b7..3a87b6c85 100644 --- a/app/src/main/java/io/legado/app/ui/config/coverConfig/CoverConfigScreen.kt +++ b/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) } ) } diff --git a/app/src/main/java/io/legado/app/ui/config/otherConfig/DirectLinkUploadBottomSheet.kt b/app/src/main/java/io/legado/app/ui/config/otherConfig/DirectLinkUploadBottomSheet.kt index 075cb3a89..a0541c287 100644 --- a/app/src/main/java/io/legado/app/ui/config/otherConfig/DirectLinkUploadBottomSheet.kt +++ b/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(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(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) } + } + ) } diff --git a/app/src/main/java/io/legado/app/ui/config/otherConfig/OtherConfigScreen.kt b/app/src/main/java/io/legado/app/ui/config/otherConfig/OtherConfigScreen.kt index 413de582f..84dd2e516 100644 --- a/app/src/main/java/io/legado/app/ui/config/otherConfig/OtherConfigScreen.kt +++ b/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)) - } - } - ) -} diff --git a/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt b/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt index 9b9456f36..ead02cc3e 100644 --- a/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt +++ b/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) } ) } diff --git a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt index 1047c885a..5b03d8376 100644 --- a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt +++ b/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) diff --git a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeListDialog.kt b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeListDialog.kt index 771065d75..eb865dfca 100644 --- a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeListDialog.kt +++ b/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 } + ) } diff --git a/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleEditSheet.kt b/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleEditSheet.kt index 08145220d..e831ab86e 100644 --- a/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleEditSheet.kt +++ b/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") } } } diff --git a/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleScreen.kt b/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleScreen.kt index b67c34077..7dcff4ab9 100644 --- a/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleScreen.kt +++ b/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) ) { diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt index 80f26dfe4..19cdf003d 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt +++ b/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 ) diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt index 37e4486a3..55923aee6 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt +++ b/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), diff --git a/app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt b/app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt index 6a7cb2e50..a99597bfd 100644 --- a/app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt +++ b/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(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 - ) -} diff --git a/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt b/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt index d1a63d6d8..992173b19 100644 --- a/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt +++ b/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:暂时这样解决,改天加模糊 - ) { - Spacer( - modifier = Modifier - .padding( - top = padding.calculateTopPadding() - ) + LazyColumn( + modifier = Modifier, + contentPadding = adaptiveContentPadding( + top = padding.calculateTopPadding(), + bottom = 120.dp ) + ) { + item { + SplicedColumnGroup( + title = "" + ) { + WebServiceSettingBlock( + uiState = uiState, + viewModel = viewModel, + onNavigate = onNavigate + ) + } - SplicedColumnGroup( - modifier = Modifier.adaptiveHorizontalPadding(), - title = "" - ) { - WebServiceSettingBlock( - uiState = uiState, - viewModel = viewModel, - onNavigate = onNavigate - ) + 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( + 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( - 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( - 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) - } - ) - } - - Spacer(Modifier.height(120.dp)) } } } diff --git a/app/src/main/java/io/legado/app/ui/main/rss/RssScreen.kt b/app/src/main/java/io/legado/app/ui/main/rss/RssScreen.kt index fed5dab27..4fe05766d 100644 --- a/app/src/main/java/io/legado/app/ui/main/rss/RssScreen.kt +++ b/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() 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) diff --git a/app/src/main/java/io/legado/app/ui/replace/ReplaceEditRoute.kt b/app/src/main/java/io/legado/app/ui/replace/ReplaceEditRoute.kt index 6dc90c6c7..61ebbf550 100644 --- a/app/src/main/java/io/legado/app/ui/replace/ReplaceEditRoute.kt +++ b/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 -) \ No newline at end of file +) : NavKey diff --git a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt index 88c18503c..48de8a0ba 100644 --- a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt +++ b/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(it) } - } - - val startDestination: Any = initialEditRoute ?: ReplaceRuleRoute - - SharedTransitionLayout { - NavHost( - navController = navController, - startDestination = startDestination - ) { - composable { + 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 { ReplaceRuleScreen( - sharedTransitionScope = this@SharedTransitionLayout, - animatedVisibilityScope = this@composable, onBackClick = { finish() }, - onNavigateToEdit = { route -> navController.navigate(route) } + onNavigateToEdit = { route -> backStack.add(route) } ) } - composable { backStackEntry -> - val route = backStackEntry.toRoute() + entry { 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(it) } ?: ReplaceRuleRoute + } +} diff --git a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt b/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt index a254da5b2..82fc93ff0 100644 --- a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt +++ b/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 - ) + 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() } - ) - 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( } } } + diff --git a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditScreen.kt b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditScreen.kt index 5e473d408..8ed4230e0 100644 --- a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditScreen.kt +++ b/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 { @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 = {}) - } - ) - } - }, - 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) } + 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() ) { - Icon(Icons.Default.Save, contentDescription = "保存") + IconButton(onClick = { + viewModel.save(onSaveSuccess) + }) { + Icon(Icons.Default.Save, contentDescription = "保存") + } } - } - }, contentWindowInsets = WindowInsets(0, 0, 0, 0) - ) { innerPadding -> - Box( - Modifier - .fillMaxSize() - .padding(innerPadding) + IconButton(onClick = { showMenu = true }) { + Icon(Icons.Default.MoreVert, contentDescription = "更多操作") + } + RoundDropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false } + ) { + RoundDropdownMenuItem( + text = "复制规则", + onClick = { + showMenu = false + viewModel.copyRule() + } + ) + RoundDropdownMenuItem( + text = "粘贴规则", + onClick = { + showMenu = false + viewModel.pasteRule(onSuccess = {}) + } + ) + } + }, + scrollBehavior = scrollBehavior + ) + }, + floatingActionButton = { + AppFloatingActionButton( + modifier = Modifier + .navigationBarsPadding() + .animateFloatingActionButton( + visible = !isKeyboardVisible, + alignment = Alignment.BottomEnd, + ), + onClick = { viewModel.save(onSaveSuccess) }, + tooltipText = "添加" ) { - 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( + 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 + ) + + 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 + } + ) + + AppTextField( + value = state.replacement, + onValueChange = viewModel::onReplacementChange, + label = "替换为", + placeholder = { AppText("输入替换内容或捕获组") }, + modifier = Modifier + .fillMaxWidth() + .onFocusChanged { + if (it.isFocused) viewModel.activeField = + ReplaceEditViewModel.ActiveField.Replacement + } + ) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start ) { - - 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) } - ) - - OutlinedTextField( - value = state.pattern, - onValueChange = viewModel::onPatternChange, - label = { AppText("匹配规则") }, - placeholder = { AppText("输入正则表达式或关键字") }, - modifier = Modifier - .fillMaxWidth() - .onFocusChanged { - if (it.isFocused) viewModel.activeField = - ReplaceEditViewModel.ActiveField.Pattern + 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 ) - OutlinedTextField( - value = state.replacement, - onValueChange = viewModel::onReplacementChange, - label = { AppText("替换为") }, - placeholder = { AppText("输入替换内容或捕获组") }, - modifier = Modifier - .fillMaxWidth() - .onFocusChanged { - if (it.isFocused) viewModel.activeField = - ReplaceEditViewModel.ActiveField.Replacement + 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 ) - 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.weight(1f)) - 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.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.excludeScope, - onValueChange = viewModel::onExcludeScopeChange, - label = { AppText("排除范围") }, - placeholder = { AppText("指定规则不适用的范围") }, - modifier = Modifier - .fillMaxWidth() - .onFocusChanged { - if (it.isFocused) viewModel.activeField = - ReplaceEditViewModel.ActiveField.Exclude - } - ) - - 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 + } + ) + + AppTextField( + value = state.timeout, + onValueChange = viewModel::onTimeoutChange, + label = "超时 (ms)", + placeholder = { AppText("3000") }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.height(120.dp)) + } - if (state.showGroupDialog) { - ManageGroupDialog( - groups = state.allGroups.filter { it != "默认" }, - onDismiss = { viewModel.toggleGroupDialog(false) }, - onDelete = { viewModel.deleteGroups(it) } - ) - } + 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, onDismiss: () -> Unit, onDelete: (List) -> Unit ) { + // 💡 使用 Set 来管理多选状态,并且以 show 作为 key。 + // 这样每次重新打开弹窗时,选中状态会自动清空,防止上次的残留 + var selectedGroups by remember(show) { mutableStateOf(emptySet()) } - val selected = remember { mutableStateMapOf() } - - 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 ) } diff --git a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt index 11beac5e1..eb29c681f 100644 --- a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt +++ b/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 = 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 } diff --git a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt index 2cdc02946..887fcc7d3 100644 --- a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt +++ b/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(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), diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceScreen.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceScreen.kt index 513cf214b..bfb9d5ae3 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceScreen.kt +++ b/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), diff --git a/app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubScreen.kt b/app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubScreen.kt index b3894acd9..973669832 100644 --- a/app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubScreen.kt +++ b/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 ) } diff --git a/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt b/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt index 32a896933..690946472 100644 --- a/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt +++ b/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 + ) +} diff --git a/app/src/main/java/io/legado/app/ui/theme/AppTheme.kt b/app/src/main/java/io/legado/app/ui/theme/AppTheme.kt index e8de1fbdf..8ab051faf 100644 --- a/app/src/main/java/io/legado/app/ui/theme/AppTheme.kt +++ b/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), + secondary = miuixColorScheme.secondary, + onSecondary = miuixColorScheme.onSecondary, + secondaryContainer = miuixColorScheme.secondaryContainer, + onSecondaryContainer = miuixColorScheme.onSecondaryContainer, - tertiary = miuixColorScheme.secondary, - onTertiary = miuixColorScheme.onSecondary, - tertiaryContainer = miuixColorScheme.secondaryContainer, - onTertiaryContainer = miuixColorScheme.onSecondaryContainer, + tertiary = miuixColorScheme.primary, + onTertiary = miuixColorScheme.onPrimary, + tertiaryContainer = miuixColorScheme.primaryContainer, + onTertiaryContainer = miuixColorScheme.primaryVariant, // ================= 4. 背景与表面 (Background & Surface) ================= background = miuixColorScheme.background, diff --git a/app/src/main/java/io/legado/app/ui/theme/ThemeResolver.kt b/app/src/main/java/io/legado/app/ui/theme/ThemeResolver.kt index 20ae7b087..6ed598a0a 100644 --- a/app/src/main/java/io/legado/app/ui/theme/ThemeResolver.kt +++ b/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 diff --git a/app/src/main/java/io/legado/app/ui/widget/components/AppTextField.kt b/app/src/main/java/io/legado/app/ui/widget/components/AppTextField.kt index a776115ab..9d018331d 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/AppTextField.kt +++ b/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 + ) } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/components/GroupManageBottomSheet.kt b/app/src/main/java/io/legado/app/ui/widget/components/GroupManageBottomSheet.kt index 81ba5e057..7e8e6fdab 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/GroupManageBottomSheet.kt +++ b/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, diff --git a/app/src/main/java/io/legado/app/ui/widget/components/SearchBarSection.kt b/app/src/main/java/io/legado/app/ui/widget/components/SearchBarSection.kt index 5b5593750..92934955e 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/SearchBarSection.kt +++ b/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 ) { diff --git a/app/src/main/java/io/legado/app/ui/widget/components/alert/AppAlertDialog.kt b/app/src/main/java/io/legado/app/ui/widget/components/alert/AppAlertDialog.kt new file mode 100644 index 000000000..02404ad16 --- /dev/null +++ b/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 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) } } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/components/bookmark/BookmarkEditSheet.kt b/app/src/main/java/io/legado/app/ui/widget/components/bookmark/BookmarkEditSheet.kt index d436c0ca8..ca990e0c0 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/bookmark/BookmarkEditSheet.kt +++ b/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 } + ) } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/bookmark/BookmarkItem.kt b/app/src/main/java/io/legado/app/ui/widget/components/bookmark/BookmarkItem.kt index 470d44a1c..eadc4eed2 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/bookmark/BookmarkItem.kt +++ b/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()) { diff --git a/app/src/main/java/io/legado/app/ui/widget/components/button/AlertButton.kt b/app/src/main/java/io/legado/app/ui/widget/components/button/AlertButton.kt index fd97db5a7..377dfe373 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/button/AlertButton.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/button/AlertButton.kt @@ -26,4 +26,5 @@ fun AlertButton( ) { AppText(text = text) } -} \ No newline at end of file +} + diff --git a/app/src/main/java/io/legado/app/ui/widget/components/button/AnimatedActionButton.kt b/app/src/main/java/io/legado/app/ui/widget/components/button/AnimatedActionButton.kt deleted file mode 100644 index 5b93a9c81..000000000 --- a/app/src/main/java/io/legado/app/ui/widget/components/button/AnimatedActionButton.kt +++ /dev/null @@ -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 - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/components/button/MediumIconButton.kt b/app/src/main/java/io/legado/app/ui/widget/components/button/MediumIconButton.kt new file mode 100644 index 000000000..9a56b606c --- /dev/null +++ b/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 + ) + } + } + } + } +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/button/MiuixButton.kt b/app/src/main/java/io/legado/app/ui/widget/components/button/MiuixButton.kt index 3fa9c73dd..11a6445d4 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/button/MiuixButton.kt +++ b/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 + ) + } +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt b/app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt index 6bd6ffc96..c7aafc470 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt +++ b/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 + SmallNoMinTouchTarget { + OutlinedIconButton( + onClick = onClick, + modifier = Modifier.size(smallContainerSize()), + shapes = IconButtonDefaults.shapes(), + border = ButtonDefaults.outlinedButtonBorder() + ) { + Icon( + imageVector = icon, + contentDescription = contentDescription, + modifier = Modifier.size(smallIconSize) ) - ), - shapes = IconButtonDefaults.shapes(), - border = ButtonDefaults.outlinedButtonBorder() - ) { - Icon( - imageVector = icon, - contentDescription = contentDescription, - modifier = Modifier.size( - IconButtonDefaults.extraSmallIconSize - ) - ) + } } } - } } @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 + ) + } + } + } + } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/button/TopBarButton.kt b/app/src/main/java/io/legado/app/ui/widget/components/button/TopBarButton.kt index 22edae070..c3e926dcb 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/button/TopBarButton.kt +++ b/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 ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/card/GlassCard.kt b/app/src/main/java/io/legado/app/ui/widget/components/card/GlassCard.kt index 59e4e58d4..79dfc772e 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/card/GlassCard.kt +++ b/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, diff --git a/app/src/main/java/io/legado/app/ui/widget/components/checkBox/AppCheckbox.kt b/app/src/main/java/io/legado/app/ui/widget/components/checkBox/AppCheckbox.kt new file mode 100644 index 000000000..2b4ccadd8 --- /dev/null +++ b/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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/components/checkBox/CheckboxItem.kt b/app/src/main/java/io/legado/app/ui/widget/components/checkBox/CheckboxItem.kt index 1bfb8c85d..642d944b8 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/checkBox/CheckboxItem.kt +++ b/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) diff --git a/app/src/main/java/io/legado/app/ui/widget/components/cover/BookCover.kt b/app/src/main/java/io/legado/app/ui/widget/components/cover/BookCover.kt index 0ca60ed76..ccbf2e627 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/cover/BookCover.kt +++ b/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) diff --git a/app/src/main/java/io/legado/app/ui/widget/components/cover/Cover.kt b/app/src/main/java/io/legado/app/ui/widget/components/cover/Cover.kt index 185bd8340..91c683d6b 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/cover/Cover.kt +++ b/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( diff --git a/app/src/main/java/io/legado/app/ui/widget/components/dialog/TextListInputDialog.kt b/app/src/main/java/io/legado/app/ui/widget/components/dialog/TextListInputDialog.kt index 1bb7fddd9..c80131935 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/dialog/TextListInputDialog.kt +++ b/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 TextListInputDialog( + data: T?, + title: String, + hint: String, + initialValue: (T) -> String, + suggestions: List = 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) } + ) + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/components/divider/PillDivider.kt b/app/src/main/java/io/legado/app/ui/widget/components/divider/PillDivider.kt index cd4cff794..e1c66ed41 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/divider/PillDivider.kt +++ b/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( diff --git a/app/src/main/java/io/legado/app/ui/widget/components/filePicker/FilePickerSheet.kt b/app/src/main/java/io/legado/app/ui/widget/components/filePicker/FilePickerSheet.kt index bdd0c3b8a..f70a6dc26 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/filePicker/FilePickerSheet.kt +++ b/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? = 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)) } - ) - } - - onManualInput?.let { - FilePickerOptionCard( - icon = Icons.Default.EditNote, - text = stringResource(R.string.manual_input), - onClick = it - ) - } - - onUpload?.let { - FilePickerOptionCard( - icon = Icons.Default.CloudUpload, - text = stringResource(R.string.upload_url), - onClick = it - ) - } - } - Spacer(modifier = Modifier.height(24.dp)) - } - } -} - -@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 + onSelectSysDir?.let { + OptionCard( + icon = Icons.Default.FolderOpen, + text = stringResource(R.string.sys_folder_picker), + onClick = it ) - Spacer(modifier = Modifier.height(8.dp)) - AppText( - text = text, - style = LegadoTheme.typography.labelMedium, - maxLines = 1 + } + + onSelectSysFile?.let { + OptionCard( + icon = Icons.AutoMirrored.Filled.InsertDriveFile, + text = stringResource(R.string.sys_file_picker), + onClick = { it(typesOfExtensions(allowExtensions)) } + ) + } + + onSelectSysFiles?.let { + OptionCard( + icon = Icons.AutoMirrored.Filled.InsertDriveFile, + text = "多选项目", + onClick = { it(typesOfExtensions(allowExtensions)) } + ) + } + + onManualInput?.let { + OptionCard( + icon = Icons.Default.EditNote, + text = stringResource(R.string.manual_input), + onClick = it + ) + } + + onUpload?.let { + OptionCard( + icon = Icons.Default.CloudUpload, + text = stringResource(R.string.upload_url), + onClick = it ) } } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/heatmap/HeatmapCalendarComponents.kt b/app/src/main/java/io/legado/app/ui/widget/components/heatmap/HeatmapCalendarComponents.kt index 8c75dac87..b9e830bb7 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/heatmap/HeatmapCalendarComponents.kt +++ b/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) diff --git a/app/src/main/java/io/legado/app/ui/widget/components/heatmap/RememberDateUtil.kt b/app/src/main/java/io/legado/app/ui/widget/components/heatmap/RememberDateUtil.kt index 74b5359de..f089d0d3e 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/heatmap/RememberDateUtil.kt +++ b/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 diff --git a/app/src/main/java/io/legado/app/ui/widget/components/importComponents/ImportComponents.kt b/app/src/main/java/io/legado/app/ui/widget/components/importComponents/ImportComponents.kt index e62220280..9b9720aa7 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/importComponents/ImportComponents.kt +++ b/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 = emptyList(), // 历史记录 + historyValues: List = 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 ) } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/list/TopFloatingStickyItem.kt b/app/src/main/java/io/legado/app/ui/widget/components/list/TopFloatingStickyItem.kt new file mode 100644 index 000000000..25903ab31 --- /dev/null +++ b/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 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(null) } + + AnimatedVisibility( + visible = item != null, + modifier = modifier, + enter = enter, + exit = exit + ) { + item?.let { lastItem = it } + val currentItem = lastItem + if (currentItem != null) { + content(currentItem) + } + } +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenu.kt b/app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenu.kt index cbb74cc93..1ab23f7b3 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenu.kt +++ b/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 diff --git a/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt b/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt index ab9989922..77a3ea7b8 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt +++ b/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 diff --git a/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/OptionSheet.kt b/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/OptionSheet.kt new file mode 100644 index 000000000..9f23196e8 --- /dev/null +++ b/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 + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt b/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt index ae07cccf4..798f7df89 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt +++ b/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 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 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") } } } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleListScaffold.kt b/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleListScaffold.kt index 48b588124..c61035e24 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleListScaffold.kt +++ b/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 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, diff --git a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/ClickableSettingItem.kt b/app/src/main/java/io/legado/app/ui/widget/components/settingItem/ClickableSettingItem.kt index 219d6dfa6..5ae9f98e9 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/ClickableSettingItem.kt +++ b/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, diff --git a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/CompactSettingItems.kt b/app/src/main/java/io/legado/app/ui/widget/components/settingItem/CompactSettingItems.kt index 4934919f7..9d4dc45e3 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/CompactSettingItems.kt +++ b/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, diff --git a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/ListSettingItem.kt b/app/src/main/java/io/legado/app/ui/widget/components/settingItem/ListSettingItem.kt index f6db6cc20..dfd2188a9 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/ListSettingItem.kt +++ b/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, diff --git a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/SwitchSettingItem.kt b/app/src/main/java/io/legado/app/ui/widget/components/settingItem/SwitchSettingItem.kt index e9263a254..4633a2784 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/SwitchSettingItem.kt +++ b/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, diff --git a/app/src/main/java/io/legado/app/ui/widget/components/text/AppText.kt b/app/src/main/java/io/legado/app/ui/widget/components/text/AppText.kt index 0826f1579..87eae715f 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/text/AppText.kt +++ b/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 } } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/topbar/DynamicTopAppBar.kt b/app/src/main/java/io/legado/app/ui/widget/components/topbar/DynamicTopAppBar.kt index 9cbd6c4fc..0562a08a2 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/topbar/DynamicTopAppBar.kt +++ b/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 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 DynamicTopAppBar( scrollBehavior = scrollBehavior, bottomContent = { AnimatedVisibility( + modifier = Modifier + .adaptiveHorizontalPadding(), visible = state.isSearch && !isSelecting, enter = expandVertically() + fadeIn(), exit = shrinkVertically() + fadeOut() diff --git a/app/src/main/java/io/legado/app/ui/widget/components/topbar/GlassMediumFlexibleTopAppBar.kt b/app/src/main/java/io/legado/app/ui/widget/components/topbar/GlassMediumFlexibleTopAppBar.kt index 118d14a5c..089fe6666 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/topbar/GlassMediumFlexibleTopAppBar.kt +++ b/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) } } diff --git a/gradle.properties b/gradle.properties index 0672d7083..8bc360dce 100644 --- a/gradle.properties +++ b/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 diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 000000000..5c34300fa --- /dev/null +++ b/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 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8dadc71c4..d0432698d 100644 --- a/gradle/libs.versions.toml +++ b/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" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a35649f5f..5f38436fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/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 diff --git a/modules/book/build.gradle b/modules/book/build.gradle deleted file mode 100644 index 2637857a9..000000000 --- a/modules/book/build.gradle +++ /dev/null @@ -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' -} \ No newline at end of file diff --git a/modules/book/build.gradle.kts b/modules/book/build.gradle.kts new file mode 100644 index 000000000..9986e5f36 --- /dev/null +++ b/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) +} diff --git a/modules/rhino/build.gradle b/modules/rhino/build.gradle deleted file mode 100644 index fa90d4313..000000000 --- a/modules/rhino/build.gradle +++ /dev/null @@ -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") -} \ No newline at end of file diff --git a/modules/rhino/build.gradle.kts b/modules/rhino/build.gradle.kts new file mode 100644 index 000000000..3ed7d0b64 --- /dev/null +++ b/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().configureEach { + options.compilerArgs.add("-Xlint:deprecation") + } +} + +dependencies { + api(libs.mozilla.rhino) + + implementation(libs.kotlinx.coroutines.core) + implementation(libs.okhttp) + implementation(libs.androidx.collection) +} diff --git a/settings.gradle b/settings.gradle index 1e1bfee81..05c6bbb47 100644 --- a/settings.gradle +++ b/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)