From 6a99e1023fad9e7554b4fa9e62f81348749053b1 Mon Sep 17 00:00:00 2001 From: HapeLee <1321903405@qq.com> Date: Sun, 22 Mar 2026 02:59:40 +0800 Subject: [PATCH] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E4=BF=AE=E5=A4=8DCompos?= =?UTF-8?q?e=E7=95=8C=E9=9D=A2=E7=9A=84=E8=83=8C=E6=99=AF=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E6=98=BE=E7=A4=BA=EF=BC=8C=E6=B7=BB=E5=8A=A0=E4=BA=86?= =?UTF-8?q?=E4=B8=80=E4=BA=9B=E7=95=8C=E9=9D=A2=E8=AE=BE=E7=BD=AE=E9=80=89?= =?UTF-8?q?=E9=A1=B9=E8=BF=9BCompose=E8=AE=BE=E7=BD=AE=E9=A1=B5=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/base/BaseComposeActivity.kt | 7 +- .../main/java/io/legado/app/di/appModule.kt | 2 + .../io/legado/app/ui/about/AboutScreen.kt | 6 +- .../app/ui/book/bookmark/AllBookmarkScreen.kt | 6 +- .../app/ui/book/explore/ExploreShowScreen.kt | 6 +- .../ui/book/info/edit/BookInfoEditScreen.kt | 6 +- .../ui/book/readRecord/ReadRecordScreen.kt | 6 +- .../book/searchContent/SearchContentScreen.kt | 6 +- .../io/legado/app/ui/book/toc/TocScreen.kt | 6 +- .../app/ui/book/toc/rule/TxtRuleScreen.kt | 55 +++--- .../io/legado/app/ui/config/PrefDelegate.kt | 15 +- .../app/ui/config/TestConfigActivity.kt | 27 +-- .../config/coverConfig/CoverConfigScreen.kt | 6 +- .../config/otherConfig/OtherConfigScreen.kt | 6 +- .../ui/config/readConfig/ReadConfigScreen.kt | 6 +- .../themeConfig/BackgroundImageManageSheet.kt | 153 ++++++++++++++++ .../app/ui/config/themeConfig/ThemeConfig.kt | 31 +++- .../config/themeConfig/ThemeConfigScreen.kt | 170 ++++++++++++++++++ .../themeConfig/ThemeConfigViewModel.kt | 80 +++++++++ .../legado/app/ui/dict/rule/DictRuleScreen.kt | 53 +++--- .../java/io/legado/app/ui/main/MainScreen.kt | 10 +- .../java/io/legado/app/ui/main/my/MyScreen.kt | 9 +- .../app/ui/replace/ReplaceRuleScreen.kt | 45 +++-- .../app/ui/replace/edit/ReplaceEditScreen.kt | 6 +- .../io/legado/app/ui/theme/AppBackground.kt | 42 +++++ .../java/io/legado/app/ui/theme/AppTheme.kt | 13 +- .../legado/app/ui/theme/OpaqueColorScheme.kt | 1 - .../io/legado/app/ui/theme/ThemeManager.kt | 3 +- .../app/ui/widget/components/AppScaffold.kt | 39 ++++ .../GlassMediumFlexibleTopAppBar.kt | 11 +- .../app/ui/widget/components/SectionHeader.kt | 9 +- .../widget/components/SplicedColumnGroup.kt | 6 +- .../ui/widget/components/card/GlassCard.kt | 41 ++--- .../components/filePicker/FilePickerSheet.kt | 2 +- .../importComponents/ImportComponents.kt | 4 +- .../ui/widget/components/list/ListScaffold.kt | 14 +- .../settingItem/CompactSettingItems.kt | 16 +- .../components/swipe/SwipeActionContainer.kt | 3 +- 38 files changed, 745 insertions(+), 182 deletions(-) create mode 100644 app/src/main/java/io/legado/app/ui/config/themeConfig/BackgroundImageManageSheet.kt create mode 100644 app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt create mode 100644 app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigViewModel.kt create mode 100644 app/src/main/java/io/legado/app/ui/theme/AppBackground.kt create mode 100644 app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt diff --git a/app/src/main/java/io/legado/app/base/BaseComposeActivity.kt b/app/src/main/java/io/legado/app/base/BaseComposeActivity.kt index 3629c7c9a..020db124d 100644 --- a/app/src/main/java/io/legado/app/base/BaseComposeActivity.kt +++ b/app/src/main/java/io/legado/app/base/BaseComposeActivity.kt @@ -3,15 +3,14 @@ package io.legado.app.base import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity -import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.core.graphics.drawable.toDrawable import androidx.core.view.WindowCompat import io.legado.app.constant.EventBus import io.legado.app.constant.Theme +import io.legado.app.help.config.AppConfig import io.legado.app.help.config.OldThemeConfig import io.legado.app.ui.theme.AppTheme -import io.legado.app.help.config.AppConfig import io.legado.app.utils.disableAutoFill import io.legado.app.utils.fullScreen import io.legado.app.utils.observeEvent @@ -40,9 +39,7 @@ abstract class BaseComposeActivity( // Compose 入口 setContent { AppTheme { - Surface() { - Content() - } + Content() } } diff --git a/app/src/main/java/io/legado/app/di/appModule.kt b/app/src/main/java/io/legado/app/di/appModule.kt index be2b9d925..0fc235929 100644 --- a/app/src/main/java/io/legado/app/di/appModule.kt +++ b/app/src/main/java/io/legado/app/di/appModule.kt @@ -31,6 +31,7 @@ import io.legado.app.ui.book.toc.rule.TxtTocRuleViewModel import io.legado.app.ui.config.coverConfig.CoverConfigViewModel import io.legado.app.ui.config.otherConfig.OtherConfigViewModel import io.legado.app.ui.config.readConfig.ReadConfigViewModel +import io.legado.app.ui.config.themeConfig.ThemeConfigViewModel import io.legado.app.ui.dict.rule.DictRuleViewModel import io.legado.app.ui.main.MainViewModel import io.legado.app.ui.main.bookshelf.BookshelfViewModel @@ -92,6 +93,7 @@ val appModule = module { viewModelOf(::OtherConfigViewModel) viewModelOf(::ReadConfigViewModel) viewModelOf(::CoverConfigViewModel) + viewModelOf(::ThemeConfigViewModel) viewModelOf(::TocViewModel) viewModelOf(::RemoteBookViewModel) viewModelOf(::BookInfoViewModel) 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 a7448e68b..51b148e92 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 @@ -17,7 +17,6 @@ import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -32,6 +31,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import io.legado.app.R import io.legado.app.constant.AppConst.appInfo +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.SplicedColumnGroup @@ -55,7 +55,7 @@ fun AboutScreen( val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() - Scaffold( + AppScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { GlassMediumFlexibleTopAppBar( @@ -188,4 +188,4 @@ fun AboutScreen( } } } -} \ No newline at end of file +} 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 302fc5bb0..ed2f086dd 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 @@ -26,7 +26,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -40,6 +39,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import io.legado.app.data.entities.Bookmark import io.legado.app.ui.widget.CollapsibleHeader +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.EmptyMessageView import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassTopAppBarDefaults @@ -93,7 +93,7 @@ fun AllBookmarkScreen( } } - Scaffold( + AppScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { Column { @@ -260,4 +260,4 @@ fun AllBookmarkScreen( ) } } -} \ No newline at end of file +} 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 05e89dd60..2f5c164d5 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 @@ -50,7 +50,6 @@ import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -83,6 +82,7 @@ import io.legado.app.model.BookShelfState import io.legado.app.ui.theme.responsiveHazeEffect import io.legado.app.ui.theme.responsiveHazeSource import io.legado.app.ui.widget.components.AnimatedTextLine +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.SearchBarSection @@ -286,7 +286,7 @@ fun ExploreShowScreen( } } - Scaffold( + AppScaffold( modifier = Modifier .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { @@ -762,4 +762,4 @@ fun LoadMoreFooter( ) } } -} \ No newline at end of file +} 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 5c325a5d3..f84aea298 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 @@ -30,7 +30,6 @@ import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Icon import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -47,6 +46,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.legado.app.R import io.legado.app.ui.book.changecover.ChangeCoverDialog +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.button.TopBarButtonVariant @@ -67,7 +67,7 @@ fun BookInfoEditScreen( val uiState by viewModel.uiState.collectAsStateWithLifecycle() val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() - Scaffold( + AppScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { GlassMediumFlexibleTopAppBar( @@ -278,4 +278,4 @@ fun BookTypeDropdown( } } } -} \ No newline at end of file +} 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 e1876d52a..e2fec7f18 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 @@ -40,7 +40,6 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface @@ -80,6 +79,7 @@ import cn.hutool.core.date.DateUtil import io.legado.app.data.entities.readRecord.ReadRecord import io.legado.app.data.entities.readRecord.ReadRecordDetail import io.legado.app.ui.widget.components.AnimatedTextLine +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.EmptyMessageView import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassTopAppBarDefaults @@ -142,7 +142,7 @@ fun ReadRecordScreen( } } - Scaffold( + AppScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(snackbarHostState) }, topBar = { @@ -1019,4 +1019,4 @@ fun formatDuring(mss: Long): String { val m = if (minutes > 0) "${minutes}分钟" else "" val s = if (seconds > 0) "${seconds}秒" else "" return if ("$d$h$m$s".isBlank()) "0秒" else "$d$h$m$s" -} \ No newline at end of file +} 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 3750d048c..948144d6d 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 @@ -44,7 +44,6 @@ import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.PlainTooltip -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipBox @@ -66,6 +65,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import io.legado.app.data.entities.SearchContentHistory import io.legado.app.ui.widget.components.AnimatedText +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.EmptyMessageView import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassTopAppBarDefaults @@ -135,7 +135,7 @@ fun SearchContentScreen( else -> null } - Scaffold( + AppScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { Column { @@ -465,4 +465,4 @@ fun SearchResultItem( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt b/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt index 66e18f29f..fb052de37 100644 --- a/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt @@ -61,7 +61,6 @@ import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.PrimaryScrollableTabRow -import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Tab import androidx.compose.material3.Text @@ -98,6 +97,7 @@ import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity import io.legado.app.ui.replace.ReplaceEditRoute import io.legado.app.ui.widget.CollapsibleHeader import io.legado.app.ui.widget.components.ActionItem +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.EmptyMessageView import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.SelectionBottomBar @@ -295,7 +295,7 @@ fun TocScreen( viewModel.clearSelection() } - Scaffold( + AppScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { DynamicTopAppBar( @@ -948,4 +948,4 @@ private fun StatusIcon( } } } -} \ No newline at end of file +} 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 89587aad0..30738041b 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 @@ -14,10 +14,11 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.FileOpen import androidx.compose.material3.AlertDialog -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.SnackbarHostState @@ -50,11 +51,11 @@ import io.legado.app.ui.widget.components.DraggableSelectionHandler import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.filePicker.FilePickerSheet -import io.legado.app.ui.widget.components.filePicker.FilePickerSheetMode import io.legado.app.ui.widget.components.importComponents.BaseImportUiState import io.legado.app.ui.widget.components.importComponents.BatchImportDialog import io.legado.app.ui.widget.components.importComponents.SourceInputDialog import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn +import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.rules.RuleEditFields import io.legado.app.ui.widget.components.rules.RuleEditSheet import io.legado.app.ui.widget.components.rules.RuleListScaffold @@ -84,9 +85,10 @@ fun TxtRuleScreen( var showEditSheet by remember { mutableStateOf(false) } var editingRule by remember { mutableStateOf(null) } var showDeleteRuleDialog by remember { mutableStateOf(null) } + var showUrlInput by remember { mutableStateOf(false) } - var showFilePickerSheet by remember { mutableStateOf(false) } - var filePickerMode by remember { mutableStateOf(FilePickerSheetMode.EXPORT) } + var showImportSheet by remember { mutableStateOf(false) } + var showExportSheet by remember { mutableStateOf(false) } val reorderableState = rememberReorderableLazyListState(listState) { from, to -> viewModel.moveItemInList(from.index, to.index) @@ -152,21 +154,38 @@ fun TxtRuleScreen( ) } - if (showFilePickerSheet) { + if (showExportSheet) { FilePickerSheet( - onDismissRequest = { showFilePickerSheet = false }, + onDismissRequest = { showExportSheet = false }, + title = stringResource(R.string.export), onSelectSysDir = { - showFilePickerSheet = false + showExportSheet = false exportDoc.launch("exportDictRule.json") }, onUpload = { - showFilePickerSheet = false + showExportSheet = false viewModel.uploadSelectedRules(selectedIds, rules) }, allowExtensions = arrayOf("json") ) } + if (showImportSheet) { + FilePickerSheet( + onDismissRequest = { showImportSheet = false }, + title = stringResource(R.string.import_txt_toc_rule), + onSelectSysFile = { types -> + importDoc.launch(types) + showImportSheet = false + }, + onManualInput = { + showUrlInput = true + showImportSheet = false + }, + allowExtensions = arrayOf("json", "txt") + ) + } + (importState as? BaseImportUiState.Success)?.let { state -> BatchImportDialog( title = "导入词典规则", @@ -279,7 +298,7 @@ fun TxtRuleScreen( }), ActionItem( text = stringResource(R.string.export), - onClick = { showFilePickerSheet = true }) + onClick = { showExportSheet = true }) ), onDeleteSelected = { ids -> @Suppress("UNCHECKED_CAST") @@ -292,19 +311,11 @@ fun TxtRuleScreen( }, snackbarHostState = snackbarHostState, dropDownMenuContent = { dismiss -> - DropdownMenuItem( - text = { Text("在线导入") }, - onClick = { dismiss(); showUrlInput = true }) - DropdownMenuItem( - text = { Text("本地导入") }, - onClick = { - dismiss(); importDoc.launch( - arrayOf( - "text/plain", - "application/json" - ) - ) - }) + RoundDropdownMenuItem( + text = { Text(stringResource(R.string.import_str)) }, + onClick = { showImportSheet = true; dismiss() }, + leadingIcon = { Icon(Icons.Default.FileOpen, null) } + ) } ) { paddingValues -> Box( diff --git a/app/src/main/java/io/legado/app/ui/config/PrefDelegate.kt b/app/src/main/java/io/legado/app/ui/config/PrefDelegate.kt index 60e866f2d..bfb5145b9 100644 --- a/app/src/main/java/io/legado/app/ui/config/PrefDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/config/PrefDelegate.kt @@ -53,11 +53,16 @@ fun prefDelegate( @Suppress("UNCHECKED_CAST") private fun readInitialValue(): T { - return when (defaultValue) { - is String -> appCtx.getPrefString(key, defaultValue) as T - is Int -> appCtx.getPrefInt(key, defaultValue) as T - is Boolean -> appCtx.getPrefBoolean(key, defaultValue) as T - is Long -> appCtx.getPrefLong(key, defaultValue) as T + val prefs = appCtx.defaultSharedPreferences + return when { + defaultValue is String -> appCtx.getPrefString(key, defaultValue) as T + defaultValue == null && prefs.contains(key) -> { + appCtx.getPrefString(key, null) as T + } + + defaultValue is Int -> appCtx.getPrefInt(key, defaultValue) as T + defaultValue is Boolean -> appCtx.getPrefBoolean(key, defaultValue) as T + defaultValue is Long -> appCtx.getPrefLong(key, defaultValue) as T else -> defaultValue } } diff --git a/app/src/main/java/io/legado/app/ui/config/TestConfigActivity.kt b/app/src/main/java/io/legado/app/ui/config/TestConfigActivity.kt index 5663e4d44..49435c230 100644 --- a/app/src/main/java/io/legado/app/ui/config/TestConfigActivity.kt +++ b/app/src/main/java/io/legado/app/ui/config/TestConfigActivity.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -21,13 +20,13 @@ import io.legado.app.base.BaseComposeActivity import io.legado.app.ui.config.coverConfig.CoverConfigScreen import io.legado.app.ui.config.otherConfig.OtherConfigScreen import io.legado.app.ui.config.readConfig.ReadConfigScreen -import io.legado.app.ui.config.themeConfig.ThemeConfig +import io.legado.app.ui.config.themeConfig.ThemeConfigScreen +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.SplicedColumnGroup import io.legado.app.ui.widget.components.button.TopbarNavigationButton import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem -import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem import kotlinx.serialization.Serializable class TestConfigActivity : BaseComposeActivity() { @@ -44,6 +43,9 @@ class TestConfigActivity : BaseComposeActivity() { @Serializable object CoverConfigRoute + @Serializable + object ThemeConfigRoute + @Composable override fun Content() { val navController = rememberNavController() @@ -57,7 +59,8 @@ class TestConfigActivity : BaseComposeActivity() { onBackClick = { finish() }, onNavigateToOther = { navController.navigate(OtherConfigRoute) }, onNavigateToRead = { navController.navigate(ReadConfigRoute) }, - onNavigateToCover = { navController.navigate(CoverConfigRoute) } + onNavigateToCover = { navController.navigate(CoverConfigRoute) }, + onNavigateToTheme = { navController.navigate(ThemeConfigRoute) } ) } @@ -72,6 +75,10 @@ class TestConfigActivity : BaseComposeActivity() { composable { CoverConfigScreen(onBackClick = { navController.popBackStack() }) } + + composable { + ThemeConfigScreen(onBackClick = { navController.popBackStack() }) + } } } @@ -81,11 +88,12 @@ class TestConfigActivity : BaseComposeActivity() { onBackClick: () -> Unit, onNavigateToOther: () -> Unit, onNavigateToRead: () -> Unit, - onNavigateToCover: () -> Unit + onNavigateToCover: () -> Unit, + onNavigateToTheme: () -> Unit ) { val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() - Scaffold( + AppScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { GlassMediumFlexibleTopAppBar( @@ -105,10 +113,9 @@ class TestConfigActivity : BaseComposeActivity() { .padding(16.dp) ) { SplicedColumnGroup { - SwitchSettingItem( - title = "使用折叠应用栏", - checked = ThemeConfig.useFlexibleTopAppBar, - onCheckedChange = { ThemeConfig.useFlexibleTopAppBar = it } + ClickableSettingItem( + title = stringResource(R.string.theme_setting), + onClick = onNavigateToTheme ) ClickableSettingItem( title = stringResource(R.string.other_setting), 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 10b752b6e..5aee17431 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 @@ -12,7 +12,6 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -27,6 +26,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import io.legado.app.R import io.legado.app.constant.PreferKey +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.SplicedColumnGroup @@ -48,7 +48,7 @@ fun CoverConfigScreen( var manageKey by remember { mutableStateOf(null) } var showColorPickerByField by remember { mutableStateOf(null) } - Scaffold( + AppScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { GlassMediumFlexibleTopAppBar( @@ -282,4 +282,4 @@ fun CoverConfigScreen( } ) } -} \ No newline at end of file +} 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 f9761d678..d6c6f6426 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 @@ -13,7 +13,6 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TextField @@ -30,6 +29,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import io.legado.app.R import io.legado.app.service.WebService +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.SplicedColumnGroup @@ -79,7 +79,7 @@ fun OtherConfigScreen( } } - Scaffold( + AppScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { GlassMediumFlexibleTopAppBar( @@ -505,4 +505,4 @@ fun ConfirmDialog( } } ) -} \ No newline at end of file +} 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 62fa60221..e5565d4f5 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 @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -20,6 +19,7 @@ import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import io.legado.app.R +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.SplicedColumnGroup @@ -41,7 +41,7 @@ fun ReadConfigScreen( val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() var showPageKeySheet by remember { mutableStateOf(false) } - Scaffold( + AppScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { GlassMediumFlexibleTopAppBar( @@ -326,4 +326,4 @@ fun ReadConfigScreen( if (showPageKeySheet) { PageKeySheet(onDismissRequest = { showPageKeySheet = false }) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/config/themeConfig/BackgroundImageManageSheet.kt b/app/src/main/java/io/legado/app/ui/config/themeConfig/BackgroundImageManageSheet.kt new file mode 100644 index 000000000..bcf0e4cec --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/config/themeConfig/BackgroundImageManageSheet.kt @@ -0,0 +1,153 @@ +package io.legado.app.ui.config.themeConfig + +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import io.legado.app.R +import io.legado.app.ui.widget.components.filePicker.FilePickerSheet +import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet +import kotlinx.coroutines.launch +import org.koin.androidx.compose.koinViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun BackgroundImageManageSheet( + preferenceKey: String, + isDarkTheme: Boolean, + onDismissRequest: () -> Unit, + viewModel: ThemeConfigViewModel = koinViewModel() +) { + val scope = rememberCoroutineScope() + var showFilePicker by remember { mutableStateOf(false) } + + val selectImage = + rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri -> + uri?.let { + scope.launch { + viewModel.setBackgroundFromUri( + uri = it, + preferenceKey = preferenceKey + ) + } + } + } + + val currentPath = if (isDarkTheme) { + ThemeConfig.bgImageDark + } else { + ThemeConfig.bgImageLight + } + + GlassModalBottomSheet( + onDismissRequest = onDismissRequest + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 24.dp) + ) { + Text( + text = stringResource(R.string.background_image), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(16.dp) + ) + + if (currentPath.isNullOrBlank()) { + Surface( + onClick = { showFilePicker = true }, + shape = MaterialTheme.shapes.medium, + color = MaterialTheme.colorScheme.surfaceContainerHigh, + modifier = Modifier + .fillMaxWidth() + .aspectRatio(16f / 9f) + .padding(16.dp) + ) { + Box(contentAlignment = Alignment.Center) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = "Add", + modifier = Modifier.size(48.dp), + tint = MaterialTheme.colorScheme.primary + ) + } + } + } else { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Surface( + shape = MaterialTheme.shapes.medium, + tonalElevation = 2.dp + ) { + AsyncImage( + model = currentPath, + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .aspectRatio(9f / 16f), + contentScale = ContentScale.Crop + ) + } + IconButton( + onClick = { viewModel.removeBackground(preferenceKey) }, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(8.dp) + .size(32.dp) + ) { + Surface( + shape = CircleShape, + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.7f) + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Remove", + modifier = Modifier.size(20.dp) + ) + } + } + } + } + } + } + + if (showFilePicker) { + FilePickerSheet( + onDismissRequest = { showFilePicker = false }, + onSelectSysFile = { + selectImage.launch("image/*") + showFilePicker = false + }, + allowExtensions = arrayOf("jpg", "jpeg", "png", "webp") + ) + } +} diff --git a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt index 6fbb15223..750bbcfed 100644 --- a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt +++ b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt @@ -1,7 +1,10 @@ package io.legado.app.ui.config.themeConfig +import android.util.Log +import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.ui.config.prefDelegate +import io.legado.app.utils.postEvent object ThemeConfig { @@ -19,16 +22,34 @@ object ThemeConfig { var isPureBlack by prefDelegate(PreferKey.pureBlack, false) - var bgImageLight by prefDelegate(PreferKey.bgImage, null) + var bgImageLight by prefDelegate(PreferKey.bgImage, null) { + postEvent(EventBus.RECREATE, false) + } - var bgImageDark by prefDelegate(PreferKey.bgImageN, null) + var bgImageDark by prefDelegate(PreferKey.bgImageN, null) { + postEvent(EventBus.RECREATE, false) + } + + var bgImageBlurring by prefDelegate(PreferKey.bgImageBlurring, 0) + + var bgImageNBlurring by prefDelegate(PreferKey.bgImageNBlurring, 0) fun hasImageBg(isDark: Boolean): Boolean { - return if (isDark) { - !bgImageDark.isNullOrEmpty() + val result = if (isDark) { + !bgImageDark.isNullOrBlank() } else { - !bgImageLight.isNullOrEmpty() + !bgImageLight.isNullOrBlank() } + + Log.d( + "MainConfig", + "hasImageBg -> isDark=$isDark, " + + "bgImageDark=$bgImageDark, " + + "bgImageLight=$bgImageLight, " + + "result=$result" + ) + + return result } /* 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 new file mode 100644 index 000000000..bbb2dc2e7 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt @@ -0,0 +1,170 @@ +package io.legado.app.ui.config.themeConfig + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import io.legado.app.R +import io.legado.app.constant.PreferKey +import io.legado.app.ui.widget.components.AppScaffold +import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar +import io.legado.app.ui.widget.components.GlassTopAppBarDefaults +import io.legado.app.ui.widget.components.SplicedColumnGroup +import io.legado.app.ui.widget.components.button.TopbarNavigationButton +import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem +import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem +import io.legado.app.ui.widget.components.settingItem.SliderSettingItem +import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem +import org.koin.androidx.compose.koinViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ThemeConfigScreen( + onBackClick: () -> Unit, + viewModel: ThemeConfigViewModel = koinViewModel() +) { + val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() + var manageKey by remember { mutableStateOf?>(null) } + + AppScaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + GlassMediumFlexibleTopAppBar( + title = { Text(stringResource(R.string.theme_setting)) }, + scrollBehavior = scrollBehavior, + navigationIcon = { + TopbarNavigationButton(onClick = onBackClick) + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .verticalScroll(rememberScrollState()) + .padding(16.dp) + ) { + SplicedColumnGroup { + SwitchSettingItem( + title = stringResource(R.string.pure_black), + checked = ThemeConfig.isPureBlack, + onCheckedChange = { ThemeConfig.isPureBlack = it } + ) + DropdownListSettingItem( + title = stringResource(R.string.palette_style), + selectedValue = ThemeConfig.paletteStyle, + displayEntries = arrayOf( + "柔和", "中性", "鲜艳", "表现力", "彩虹", + "水果拼盘", "单色", "仿真", "内容取色" + ), + entryValues = arrayOf( + "tonalSpot", "neutral", "vibrant", "expressive", "rainbow", + "fruitSalad", "monochrome", "fidelity", "content" + ), + onValueChange = { ThemeConfig.paletteStyle = it } + ) + } + + SplicedColumnGroup(title = "Compose 相关") { + SwitchSettingItem( + title = "使用折叠应用栏", + checked = ThemeConfig.useFlexibleTopAppBar, + onCheckedChange = { ThemeConfig.useFlexibleTopAppBar = it } + ) + SliderSettingItem( + title = stringResource(R.string.container_opacity), + description = stringResource( + R.string.container_opacity_summary, + ThemeConfig.containerOpacity + ), + value = ThemeConfig.containerOpacity.toFloat(), + defaultValue = 100f, + valueRange = 0f..100f, + steps = 99, + onValueChange = { ThemeConfig.containerOpacity = it.toInt() } + ) + SwitchSettingItem( + title = stringResource(R.string.is_blur_enable), + checked = ThemeConfig.enableBlur, + onCheckedChange = { ThemeConfig.enableBlur = it } + ) + if (ThemeConfig.enableBlur) { + SwitchSettingItem( + title = stringResource(R.string.is_blur_progressive_enable), + checked = ThemeConfig.enableProgressiveBlur, + onCheckedChange = { ThemeConfig.enableProgressiveBlur = it } + ) + } + } + + SplicedColumnGroup(title = stringResource(R.string.day)) { + val hasLightBg = !ThemeConfig.bgImageLight.isNullOrBlank() + ClickableSettingItem( + title = stringResource(R.string.background_image), + description = if (hasLightBg) stringResource(R.string.click_to_delete) else stringResource( + R.string.select_image + ), + onClick = { manageKey = Pair(PreferKey.bgImage, false) } + ) + + if (hasLightBg) { + SliderSettingItem( + title = stringResource(R.string.background_image_blurring), + value = ThemeConfig.bgImageBlurring.toFloat(), + defaultValue = 0f, + valueRange = 0f..100f, + steps = 99, + onValueChange = { + ThemeConfig.bgImageBlurring = it.toInt() + } + ) + } + } + + SplicedColumnGroup(title = stringResource(R.string.night)) { + val hasDarkBg = !ThemeConfig.bgImageDark.isNullOrBlank() + ClickableSettingItem( + title = stringResource(R.string.background_image), + description = if (hasDarkBg) stringResource(R.string.click_to_delete) else stringResource( + R.string.select_image + ), + onClick = { manageKey = Pair(PreferKey.bgImageN, true) } + ) + + if (hasDarkBg) { + SliderSettingItem( + title = stringResource(R.string.background_image_blurring), + value = ThemeConfig.bgImageNBlurring.toFloat(), + defaultValue = 0f, + valueRange = 0f..100f, + steps = 99, + onValueChange = { + ThemeConfig.bgImageNBlurring = it.toInt() + } + ) + } + } + } + } + + manageKey?.let { (key, isDark) -> + BackgroundImageManageSheet( + preferenceKey = key, + isDarkTheme = isDark, + onDismissRequest = { manageKey = null } + ) + } +} diff --git a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigViewModel.kt b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigViewModel.kt new file mode 100644 index 000000000..7ee6b4aa1 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigViewModel.kt @@ -0,0 +1,80 @@ +package io.legado.app.ui.config.themeConfig + +import android.net.Uri +import androidx.lifecycle.ViewModel +import io.legado.app.constant.PreferKey +import io.legado.app.utils.FileDoc +import io.legado.app.utils.FileUtils +import io.legado.app.utils.MD5Utils +import io.legado.app.utils.externalFiles +import io.legado.app.utils.inputStream +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import splitties.init.appCtx +import java.io.File +import java.io.FileOutputStream + +class ThemeConfigViewModel : ViewModel() { + + /** + * 设置背景图片 + */ + suspend fun setBackgroundFromUri( + uri: Uri, + preferenceKey: String + ) = withContext(Dispatchers.IO) { + kotlin.runCatching { + val fileDoc = FileDoc.fromUri(uri, false) + val suffix = fileDoc.name.substringAfterLast(".", "jpg") + + // 计算MD5作为文件名 + val md5 = uri.inputStream(appCtx).getOrThrow().use { + MD5Utils.md5Encode(it) + } + val fileName = "$md5.$suffix" + + val folder = File(appCtx.externalFiles, preferenceKey) + val file = File(folder, fileName) + + if (!file.exists()) { + FileUtils.createFileIfNotExist(file.absolutePath) + uri.inputStream(appCtx).getOrThrow().use { input -> + FileOutputStream(file).use { output -> + input.copyTo(output) + } + } + } + + updateBackgroundPath(preferenceKey, file.absolutePath) + }.onFailure { + it.printStackTrace() + } + } + + fun removeBackground(preferenceKey: String) { + updateBackgroundPath(preferenceKey, null) + } + + /** + * 更新背景路径并清理旧文件 + */ + private fun updateBackgroundPath(preferenceKey: String, newPath: String?) { + val oldPath = when (preferenceKey) { + PreferKey.bgImage -> ThemeConfig.bgImageLight + PreferKey.bgImageN -> ThemeConfig.bgImageDark + else -> null + } + + if (oldPath != newPath && oldPath != null) { + val oldFile = File(oldPath) + if (oldFile.absolutePath.contains(appCtx.externalFiles.absolutePath)) { + oldFile.delete() + } + } + + when (preferenceKey) { + PreferKey.bgImage -> ThemeConfig.bgImageLight = newPath + PreferKey.bgImageN -> ThemeConfig.bgImageDark = newPath + } + } +} \ No newline at end of file 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 f9573cfa0..5ef5ec458 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 @@ -14,10 +14,11 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.FileOpen import androidx.compose.material3.AlertDialog -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.SnackbarHostState @@ -50,11 +51,11 @@ import io.legado.app.ui.widget.components.DraggableSelectionHandler import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.filePicker.FilePickerSheet -import io.legado.app.ui.widget.components.filePicker.FilePickerSheetMode import io.legado.app.ui.widget.components.importComponents.BaseImportUiState import io.legado.app.ui.widget.components.importComponents.BatchImportDialog import io.legado.app.ui.widget.components.importComponents.SourceInputDialog import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn +import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.rules.RuleEditFields import io.legado.app.ui.widget.components.rules.RuleEditSheet import io.legado.app.ui.widget.components.rules.RuleListScaffold @@ -82,8 +83,11 @@ fun DictRuleScreen( var editingRule by remember { mutableStateOf(null) } var showDeleteRuleDialog by remember { mutableStateOf(null) } var showUrlInput by remember { mutableStateOf(false) } + var showFilePickerSheet by remember { mutableStateOf(false) } - var filePickerMode by remember { mutableStateOf(FilePickerSheetMode.EXPORT) } + var showImportSheet by remember { mutableStateOf(false) } + var showExportSheet by remember { mutableStateOf(false) } + val reorderableState = rememberReorderableLazyListState(listState) { from, to -> viewModel.moveItemInList(from.index, to.index) @@ -149,21 +153,38 @@ fun DictRuleScreen( ) } - if (showFilePickerSheet) { + if (showExportSheet) { FilePickerSheet( - onDismissRequest = { showFilePickerSheet = false }, + onDismissRequest = { showExportSheet = false }, + title = stringResource(R.string.export), onSelectSysDir = { - showFilePickerSheet = false + showExportSheet = false exportDoc.launch("exportDictRule.json") }, onUpload = { - showFilePickerSheet = false + showExportSheet = false viewModel.uploadSelectedRules(selectedIds, rules) }, allowExtensions = arrayOf("json") ) } + if (showImportSheet) { + FilePickerSheet( + onDismissRequest = { showImportSheet = false }, + title = stringResource(R.string.import_dict_rule), + onSelectSysFile = { types -> + importDoc.launch(types) + showImportSheet = false + }, + onManualInput = { + showUrlInput = true + showImportSheet = false + }, + allowExtensions = arrayOf("json", "txt") + ) + } + (importState as? BaseImportUiState.Success)?.let { state -> BatchImportDialog( title = "导入词典规则", @@ -288,19 +309,11 @@ fun DictRuleScreen( }, snackbarHostState = snackbarHostState, dropDownMenuContent = { dismiss -> - DropdownMenuItem( - text = { Text("在线导入") }, - onClick = { dismiss(); showUrlInput = true }) - DropdownMenuItem( - text = { Text("本地导入") }, - onClick = { - dismiss(); importDoc.launch( - arrayOf( - "text/plain", - "application/json" - ) - ) - }) + RoundDropdownMenuItem( + text = { Text(stringResource(R.string.import_str)) }, + onClick = { showImportSheet = true; dismiss() }, + leadingIcon = { Icon(Icons.Default.FileOpen, null) } + ) } ) { paddingValues -> Box( diff --git a/app/src/main/java/io/legado/app/ui/main/MainScreen.kt b/app/src/main/java/io/legado/app/ui/main/MainScreen.kt index 8cbc3a63b..a15503ce2 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState @@ -27,7 +26,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItemDefaults -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.WideNavigationRail import androidx.compose.material3.WideNavigationRailItem @@ -54,6 +52,7 @@ import io.legado.app.ui.main.explore.ExploreScreen import io.legado.app.ui.main.my.MyScreen import io.legado.app.ui.main.rss.RssScreen import io.legado.app.ui.theme.regularHazeEffect +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.GlassDefaults import io.legado.app.utils.startActivity import io.legado.app.utils.startActivityForBook @@ -159,20 +158,21 @@ fun MainScreen( } } - Scaffold( + AppScaffold( modifier = Modifier.weight(1f), bottomBar = { if (!useRail && MainConfig.showBottomView) { + val labelVisibilityMode = MainConfig.labelVisibilityMode + val isUnlabeled = labelVisibilityMode == "unlabeled" NavigationBar( modifier = Modifier .regularHazeEffect(state = hazeState) - .heightIn(max = 84.dp), + .height(if (isUnlabeled) 64.dp else 80.dp), containerColor = GlassDefaults.glassColor( noBlurColor = BottomAppBarDefaults.containerColor, blurAlpha = GlassDefaults.DefaultBlurAlpha ) ) { - val labelVisibilityMode = MainConfig.labelVisibilityMode val alwaysShowLabel = when (labelVisibilityMode) { "labeled" -> true "selected" -> false 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 5ab7da7ef..ce91c522b 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 @@ -24,14 +24,12 @@ import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Palette import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Source -import androidx.compose.material.icons.filled.Translate import androidx.compose.material.icons.filled.Web import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -52,6 +50,7 @@ import io.legado.app.ui.config.ConfigTag import io.legado.app.ui.dict.rule.DictRuleActivity import io.legado.app.ui.file.FileManageActivity import io.legado.app.ui.replace.ReplaceRuleActivity +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.SplicedColumnGroup @@ -68,7 +67,7 @@ fun MyScreen( val uiState by viewModel.uiState.collectAsState() val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() - Scaffold( + AppScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), contentWindowInsets = WindowInsets.systemBars .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top), @@ -230,7 +229,7 @@ fun MyScreen( ) SettingItem( title = stringResource(R.string.dict_rule), - imageVector = Icons.Default.Translate, + imageVector = Icons.AutoMirrored.Filled.Rule, onClick = { onNavigate( PrefClickEvent.StartActivity(DictRuleActivity::class.java) @@ -281,4 +280,4 @@ fun MyScreen( Spacer(Modifier.height(120.dp)) } } -} \ No newline at end of file +} 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 8284066d9..859988f0a 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 @@ -18,6 +18,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.FileOpen import androidx.compose.material3.AlertDialog import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api @@ -69,7 +70,6 @@ import io.legado.app.ui.widget.components.GroupManageBottomSheet import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.divider.PillDivider import io.legado.app.ui.widget.components.filePicker.FilePickerSheet -import io.legado.app.ui.widget.components.filePicker.FilePickerSheetMode import io.legado.app.ui.widget.components.importComponents.BaseImportUiState import io.legado.app.ui.widget.components.importComponents.BatchImportDialog import io.legado.app.ui.widget.components.importComponents.SourceInputDialog @@ -107,8 +107,9 @@ fun ReplaceRuleScreen( val hapticFeedback = LocalHapticFeedback.current var showUrlInput by remember { mutableStateOf(false) } - var showFilePickerSheet by remember { mutableStateOf(false) } - var filePickerMode by remember { mutableStateOf(FilePickerSheetMode.EXPORT) } + var showImportSheet by remember { mutableStateOf(false) } + var showExportSheet by remember { mutableStateOf(false) } + var showDeleteRuleDialog by remember { mutableStateOf(null) } var showGroupManageSheet by remember { mutableStateOf(false) } @@ -164,15 +165,31 @@ fun ReplaceRuleScreen( ) } - if (showFilePickerSheet) { + if (showImportSheet) { FilePickerSheet( - onDismissRequest = { showFilePickerSheet = false }, + onDismissRequest = { showImportSheet = false }, + title = stringResource(R.string.import_replace_rule), + onSelectSysFile = { types -> + importDoc.launch(types) + showImportSheet = false + }, + onManualInput = { + showUrlInput = true + showImportSheet = false + }, + allowExtensions = arrayOf("json", "txt") + ) + } + + if (showExportSheet) { + FilePickerSheet( + onDismissRequest = { showExportSheet = false }, onSelectSysDir = { - showFilePickerSheet = false + showExportSheet = false exportDoc.launch("exportReplaceRule.json") }, onUpload = { - showFilePickerSheet = false + showExportSheet = false viewModel.uploadSelectedRules(selectedIds, rules) }, allowExtensions = arrayOf("json") @@ -320,7 +337,7 @@ fun ReplaceRuleScreen( ), ActionItem( text = stringResource(R.string.export), - onClick = { showFilePickerSheet = true } + onClick = { showExportSheet = true } ) ), onDeleteSelected = { ids -> @@ -389,15 +406,9 @@ fun ReplaceRuleScreen( snackbarHostState = snackbarHostState, dropDownMenuContent = { dismiss -> RoundDropdownMenuItem( - text = { Text("在线导入") }, - onClick = { - dismiss() - showUrlInput = true // 触发输入框 - } - ) - RoundDropdownMenuItem( - text = { Text("本地导入") }, - onClick = { importDoc.launch(arrayOf("text/plain", "application/json")); dismiss() } + text = { Text(stringResource(R.string.import_str)) }, + onClick = { showImportSheet = true; dismiss() }, + leadingIcon = { Icon(Icons.Default.FileOpen, null) } ) RoundDropdownMenuItem( text = { Text("分组管理") }, 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 f558c4702..cd8164437 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 @@ -52,7 +52,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.PlainTooltip -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TooltipAnchorPosition @@ -78,6 +77,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.button.AlertButton @@ -108,7 +108,7 @@ fun ReplaceEditScreen( val isKeyboardVisible by keyboardAsState() with(sharedTransitionScope) { - Scaffold( + AppScaffold( modifier = Modifier .nestedScroll(scrollBehavior.nestedScrollConnection) .sharedBounds( @@ -487,4 +487,4 @@ fun QuickInputBar( ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/theme/AppBackground.kt b/app/src/main/java/io/legado/app/ui/theme/AppBackground.kt new file mode 100644 index 000000000..694ba71f7 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/theme/AppBackground.kt @@ -0,0 +1,42 @@ +package io.legado.app.ui.theme + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.blur +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import io.legado.app.ui.config.themeConfig.ThemeConfig + +@Composable +fun AppBackground( + darkTheme: Boolean, + content: @Composable () -> Unit +) { + val hasImageBg = ThemeConfig.hasImageBg(darkTheme) + val bgImagePath = if (darkTheme) ThemeConfig.bgImageDark else ThemeConfig.bgImageLight + val blur = if (darkTheme) { + ThemeConfig.bgImageNBlurring + } else { + ThemeConfig.bgImageBlurring + } + + Box(modifier = Modifier.fillMaxSize()) { + + if (hasImageBg && !bgImagePath.isNullOrBlank()) { + AsyncImage( + model = bgImagePath, + contentDescription = null, + modifier = Modifier + .fillMaxSize() + .blur(blur.dp), + //TODO:低版本安卓使用coil-transformations + contentScale = ContentScale.Crop + ) + } + + content() + } +} \ No newline at end of file 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 cf706fedc..a47a60a05 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 @@ -20,17 +20,15 @@ fun AppTheme( val context = LocalContext.current val appThemeMode = ThemeResolver.resolveThemeMode(ThemeConfig.appTheme) val isPureBlack = ThemeConfig.isPureBlack - val hasImageBg = ThemeConfig.hasImageBg(darkTheme) val paletteStyle = ThemeConfig.paletteStyle val colorScheme = - remember(context, appThemeMode, darkTheme, isPureBlack, hasImageBg, paletteStyle) { + remember(context, appThemeMode, darkTheme, isPureBlack, paletteStyle) { ThemeManager.getColorScheme( context = context, mode = appThemeMode, darkTheme = darkTheme, isAmoled = isPureBlack, - isImageBg = hasImageBg, paletteStyle = paletteStyle ) } @@ -39,7 +37,10 @@ fun AppTheme( colorScheme = colorScheme, typography = Typography(), motionScheme = MotionScheme.expressive(), - shapes = Shapes(), - content = content - ) + shapes = Shapes() + ) { + AppBackground(darkTheme = darkTheme) { + content() + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/theme/OpaqueColorScheme.kt b/app/src/main/java/io/legado/app/ui/theme/OpaqueColorScheme.kt index 7b48561a5..429cf7966 100644 --- a/app/src/main/java/io/legado/app/ui/theme/OpaqueColorScheme.kt +++ b/app/src/main/java/io/legado/app/ui/theme/OpaqueColorScheme.kt @@ -23,7 +23,6 @@ fun rememberOpaqueColorScheme(): ColorScheme { mode = appThemeMode, darkTheme = isDark, isAmoled = isPureBlack, - isImageBg = hasImageBg, paletteStyle = paletteStyle, forceOpaque = true ) diff --git a/app/src/main/java/io/legado/app/ui/theme/ThemeManager.kt b/app/src/main/java/io/legado/app/ui/theme/ThemeManager.kt index ebc798374..c4de80e21 100644 --- a/app/src/main/java/io/legado/app/ui/theme/ThemeManager.kt +++ b/app/src/main/java/io/legado/app/ui/theme/ThemeManager.kt @@ -44,7 +44,6 @@ object ThemeManager { mode: AppThemeMode, darkTheme: Boolean, isAmoled: Boolean, - isImageBg: Boolean, paletteStyle: String?, forceOpaque: Boolean = false ): ColorScheme { @@ -82,7 +81,7 @@ object ThemeManager { ) } - if (!forceOpaque && (isImageBg || actualMode == AppThemeMode.Transparent)) { + if (!forceOpaque && actualMode == AppThemeMode.Transparent) { return scheme.copy( surface = Color.Transparent, background = Color.Transparent, diff --git a/app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt b/app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt new file mode 100644 index 000000000..20deec8e1 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt @@ -0,0 +1,39 @@ +package io.legado.app.ui.widget.components + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.material3.FabPosition +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.ScaffoldDefaults +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@Composable +fun AppScaffold( + modifier: Modifier = Modifier, + topBar: @Composable () -> Unit = {}, + bottomBar: @Composable () -> Unit = {}, + snackbarHost: @Composable () -> Unit = {}, + floatingActionButton: @Composable () -> Unit = {}, + floatingActionButtonPosition: FabPosition = FabPosition.End, + containerColor: Color = Color.Transparent, + contentColor: Color = contentColorFor(MaterialTheme.colorScheme.background), + contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, + content: @Composable (PaddingValues) -> Unit +) { + Scaffold( + modifier = modifier, + topBar = topBar, + bottomBar = bottomBar, + snackbarHost = snackbarHost, + floatingActionButton = floatingActionButton, + floatingActionButtonPosition = floatingActionButtonPosition, + containerColor = containerColor, + contentColor = contentColor, + contentWindowInsets = contentWindowInsets, + content = content + ) +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/GlassMediumFlexibleTopAppBar.kt b/app/src/main/java/io/legado/app/ui/widget/components/GlassMediumFlexibleTopAppBar.kt index 729a184da..5fe9c3ccb 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/GlassMediumFlexibleTopAppBar.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/GlassMediumFlexibleTopAppBar.kt @@ -61,10 +61,9 @@ object GlassTopAppBarDefaults { @Composable fun glassColors(): TopAppBarColors { - val opacity = ThemeConfig.containerOpacity / 100f val containerColor = GlassDefaults.glassColor( - noBlurColor = MaterialTheme.colorScheme.surface.copy(alpha = opacity), + noBlurColor = MaterialTheme.colorScheme.surface, blurAlpha = GlassDefaults.TransparentAlpha ) @@ -76,15 +75,17 @@ object GlassTopAppBarDefaults { return TopAppBarDefaults.topAppBarColors( containerColor = containerColor, - scrolledContainerColor = scrolledContainerColor + scrolledContainerColor = scrolledContainerColor, + navigationIconContentColor = MaterialTheme.colorScheme.onSurface, + titleContentColor = MaterialTheme.colorScheme.onSurface, + actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant ) } @Composable fun containerColor(): Color { - val opacity = ThemeConfig.containerOpacity / 100f return GlassDefaults.glassColor( - noBlurColor = MaterialTheme.colorScheme.surface.copy(alpha = opacity), + noBlurColor = MaterialTheme.colorScheme.surface, blurAlpha = GlassDefaults.TransparentAlpha ) } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/SectionHeader.kt b/app/src/main/java/io/legado/app/ui/widget/components/SectionHeader.kt index 3b7bb33d1..efe474c0d 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/SectionHeader.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/SectionHeader.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -24,10 +23,8 @@ fun SectionHeader( // 根容器的 Padding contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp) ) { - Surface( - modifier = Modifier.fillMaxWidth() - ) { - Row( + + Row( modifier = Modifier .fillMaxWidth() .padding(contentPadding), @@ -39,5 +36,5 @@ fun SectionHeader( it() } } - } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/components/SplicedColumnGroup.kt b/app/src/main/java/io/legado/app/ui/widget/components/SplicedColumnGroup.kt index ce775270d..72ea9d303 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/SplicedColumnGroup.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/SplicedColumnGroup.kt @@ -1,5 +1,6 @@ package io.legado.app.ui.widget.components +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope @@ -11,6 +12,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import io.legado.app.ui.widget.components.card.GlassCard @@ -39,10 +41,12 @@ fun SplicedColumnGroup( GlassCard( shape = RoundedCornerShape(16.dp), - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), + colors = CardDefaults.cardColors(containerColor = Color.Transparent), modifier = Modifier.fillMaxWidth() ) { Column( + modifier = Modifier + .animateContentSize(), verticalArrangement = Arrangement.spacedBy(2.dp) ) { content() 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 35b6e43e0..223ae4632 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 @@ -17,32 +17,25 @@ fun GlassCard( modifier: Modifier = Modifier, onClick: (() -> Unit)? = null, shape: Shape = CardDefaults.shape, - colors: CardColors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = ThemeConfig.containerOpacity / 100f), - contentColor = MaterialTheme.colorScheme.onSecondaryContainer - ), + colors: CardColors? = null, elevation: CardElevation = CardDefaults.cardElevation(), border: BorderStroke? = null, content: @Composable ColumnScope.() -> Unit ) { - if (onClick != null) { - Card( - onClick = onClick, - modifier = modifier, - shape = shape, - colors = colors, - elevation = elevation, - border = border, - content = content - ) - } else { - Card( - modifier = modifier, - shape = shape, - colors = colors, - elevation = elevation, - border = border, - content = content - ) - } + val containerAlpha = ThemeConfig.containerOpacity / 100f + + val finalColors = colors ?: CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = containerAlpha), + contentColor = MaterialTheme.colorScheme.onSecondaryContainer + ) + + Card( + onClick = onClick ?: {}, + modifier = modifier, + shape = shape, + colors = finalColors, + elevation = elevation, + border = border, + content = content + ) } 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 912d9961a..735069bba 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 @@ -88,7 +88,7 @@ fun FilePickerSheet( onSelectSysFiles?.let { FilePickerOptionCard( icon = Icons.AutoMirrored.Filled.InsertDriveFile, - text = "系统多选", + text = "多选项目", onClick = { it(typesOfExtensions(allowExtensions)) } ) } 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 27d55f67d..b5ac6c033 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 @@ -40,7 +40,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedToggleButton -import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text @@ -61,6 +60,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import io.legado.app.R import io.legado.app.ui.widget.components.AnimatedText +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import kotlinx.coroutines.launch @@ -141,7 +141,7 @@ fun BatchImportDialog( onDismissRequest = onDismissRequest, containerColor = { it.surfaceContainer } ) { - Scaffold( + AppScaffold( modifier = Modifier .fillMaxWidth() .heightIn(max = LocalConfiguration.current.screenHeightDp.dp * 0.8f), diff --git a/app/src/main/java/io/legado/app/ui/widget/components/list/ListScaffold.kt b/app/src/main/java/io/legado/app/ui/widget/components/list/ListScaffold.kt index 0157518bf..23d7ca88a 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/list/ListScaffold.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/list/ListScaffold.kt @@ -19,7 +19,6 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset import androidx.compose.material3.Icon import androidx.compose.material3.PlainTooltip -import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text @@ -39,6 +38,7 @@ import androidx.compose.ui.zIndex import dev.chrisbanes.haze.HazeState import io.legado.app.ui.theme.responsiveHazeEffect import io.legado.app.ui.theme.responsiveHazeSource +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.SelectionActions import io.legado.app.ui.widget.components.SelectionBottomBar @@ -86,9 +86,15 @@ fun ListScaffold( val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() val hazeState = remember { HazeState() } - Scaffold( + AppScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - snackbarHost = { SnackbarHost(snackbarHostState) }, + snackbarHost = { + SnackbarHost( + hostState = snackbarHostState, + modifier = Modifier + .padding(bottom = 72.dp) + ) + }, topBar = { Box(modifier = Modifier.responsiveHazeEffect(state = hazeState)) { DynamicTopAppBar( @@ -136,4 +142,4 @@ fun ListScaffold( } } } -} \ No newline at end of file +} 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 198e72b26..dd360dfa8 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 @@ -15,6 +15,7 @@ import androidx.compose.material3.Slider import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf @@ -101,6 +102,7 @@ fun CompactSliderSettingItem( var expanded by remember { mutableStateOf(false) } var sliderValue by remember(value) { mutableFloatStateOf(value) } + var displayValue by remember(value) { mutableFloatStateOf(value) } SettingItem( title = title, @@ -126,7 +128,7 @@ fun CompactSliderSettingItem( cornerRadius = 8.dp, horizontalPadding = 8.dp, verticalPadding = 4.dp, - text = value.toInt().toString(), + text = displayValue.toInt().toString(), backgroundColor = MaterialTheme.colorScheme.surfaceContainer, contentColor = MaterialTheme.colorScheme.onSurface ) @@ -144,7 +146,10 @@ fun CompactSliderSettingItem( value = sliderValue, onValueChange = { sliderValue = it - onValueChange(it) + displayValue = it + }, + onValueChangeFinished = { + onValueChange(sliderValue) }, valueRange = valueRange, steps = steps, @@ -152,6 +157,13 @@ fun CompactSliderSettingItem( ) } ) + + LaunchedEffect(value) { + if (!expanded) { + sliderValue = value + displayValue = value + } + } } @Composable diff --git a/app/src/main/java/io/legado/app/ui/widget/components/swipe/SwipeActionContainer.kt b/app/src/main/java/io/legado/app/ui/widget/components/swipe/SwipeActionContainer.kt index 30e7ea11d..b388dced4 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/swipe/SwipeActionContainer.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/swipe/SwipeActionContainer.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.hapticfeedback.HapticFeedbackType @@ -103,7 +104,7 @@ fun SwipeActionContainer( content = { Surface( modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.surface, + color = Color.Transparent, shape = RectangleShape ) { content()