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

This commit is contained in:
HapeLee committed 2026-03-22 02:59:40 +08:00
1 parent dd46484805
commit 6a99e1023f
38 files changed
+745 -182

No files matched your search

@@ -3,15 +3,14 @@ package io.legado.app.base
import android.os.Bundle import android.os.Bundle
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.core.graphics.drawable.toDrawable import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.constant.Theme import io.legado.app.constant.Theme
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.OldThemeConfig import io.legado.app.help.config.OldThemeConfig
import io.legado.app.ui.theme.AppTheme 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.disableAutoFill
import io.legado.app.utils.fullScreen import io.legado.app.utils.fullScreen
import io.legado.app.utils.observeEvent import io.legado.app.utils.observeEvent
@@ -40,9 +39,7 @@ abstract class BaseComposeActivity(
// Compose 入口 // Compose 入口
setContent { setContent {
AppTheme { AppTheme {
Surface() { Content()
Content()
}
} }
} }
@@ -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.coverConfig.CoverConfigViewModel
import io.legado.app.ui.config.otherConfig.OtherConfigViewModel import io.legado.app.ui.config.otherConfig.OtherConfigViewModel
import io.legado.app.ui.config.readConfig.ReadConfigViewModel 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.dict.rule.DictRuleViewModel
import io.legado.app.ui.main.MainViewModel import io.legado.app.ui.main.MainViewModel
import io.legado.app.ui.main.bookshelf.BookshelfViewModel import io.legado.app.ui.main.bookshelf.BookshelfViewModel
@@ -92,6 +93,7 @@ val appModule = module {
viewModelOf(::OtherConfigViewModel) viewModelOf(::OtherConfigViewModel)
viewModelOf(::ReadConfigViewModel) viewModelOf(::ReadConfigViewModel)
viewModelOf(::CoverConfigViewModel) viewModelOf(::CoverConfigViewModel)
viewModelOf(::ThemeConfigViewModel)
viewModelOf(::TocViewModel) viewModelOf(::TocViewModel)
viewModelOf(::RemoteBookViewModel) viewModelOf(::RemoteBookViewModel)
viewModelOf(::BookInfoViewModel) viewModelOf(::BookInfoViewModel)
@@ -17,7 +17,6 @@ import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -32,6 +31,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppConst.appInfo 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.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup import io.legado.app.ui.widget.components.SplicedColumnGroup
@@ -55,7 +55,7 @@ fun AboutScreen(
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
Scaffold( AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
@@ -188,4 +188,4 @@ fun AboutScreen(
} }
} }
} }
} }
@@ -26,7 +26,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@@ -40,6 +39,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.ui.widget.CollapsibleHeader 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.EmptyMessageView
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
@@ -93,7 +93,7 @@ fun AllBookmarkScreen(
} }
} }
Scaffold( AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
Column { Column {
@@ -260,4 +260,4 @@ fun AllBookmarkScreen(
) )
} }
} }
} }
@@ -50,7 +50,6 @@ import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider import androidx.compose.material3.Slider
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text 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.responsiveHazeEffect
import io.legado.app.ui.theme.responsiveHazeSource import io.legado.app.ui.theme.responsiveHazeSource
import io.legado.app.ui.widget.components.AnimatedTextLine 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.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SearchBarSection import io.legado.app.ui.widget.components.SearchBarSection
@@ -286,7 +286,7 @@ fun ExploreShowScreen(
} }
} }
Scaffold( AppScaffold(
modifier = Modifier modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection), .nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
@@ -762,4 +762,4 @@ fun LoadMoreFooter(
) )
} }
} }
} }
@@ -30,7 +30,6 @@ import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -47,6 +46,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.book.changecover.ChangeCoverDialog 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.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.button.TopBarButtonVariant import io.legado.app.ui.widget.components.button.TopBarButtonVariant
@@ -67,7 +67,7 @@ fun BookInfoEditScreen(
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
Scaffold( AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
@@ -278,4 +278,4 @@ fun BookTypeDropdown(
} }
} }
} }
} }
@@ -40,7 +40,6 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface 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.ReadRecord
import io.legado.app.data.entities.readRecord.ReadRecordDetail import io.legado.app.data.entities.readRecord.ReadRecordDetail
import io.legado.app.ui.widget.components.AnimatedTextLine 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.EmptyMessageView
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
@@ -142,7 +142,7 @@ fun ReadRecordScreen(
} }
} }
Scaffold( AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = { topBar = {
@@ -1019,4 +1019,4 @@ fun formatDuring(mss: Long): String {
val m = if (minutes > 0) "${minutes}分钟" else "" val m = if (minutes > 0) "${minutes}分钟" else ""
val s = if (seconds > 0) "${seconds}秒" else "" val s = if (seconds > 0) "${seconds}秒" else ""
return if ("$d$h$m$s".isBlank()) "0秒" else "$d$h$m$s" return if ("$d$h$m$s".isBlank()) "0秒" else "$d$h$m$s"
} }
@@ -44,7 +44,6 @@ import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.PlainTooltip import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipBox
@@ -66,6 +65,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.data.entities.SearchContentHistory import io.legado.app.data.entities.SearchContentHistory
import io.legado.app.ui.widget.components.AnimatedText 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.EmptyMessageView
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
@@ -135,7 +135,7 @@ fun SearchContentScreen(
else -> null else -> null
} }
Scaffold( AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
Column { Column {
@@ -465,4 +465,4 @@ fun SearchResultItem(
} }
} }
} }
} }
@@ -61,7 +61,6 @@ import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryScrollableTabRow import androidx.compose.material3.PrimaryScrollableTabRow
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.Text 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.replace.ReplaceEditRoute
import io.legado.app.ui.widget.CollapsibleHeader import io.legado.app.ui.widget.CollapsibleHeader
import io.legado.app.ui.widget.components.ActionItem 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.EmptyMessageView
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SelectionBottomBar import io.legado.app.ui.widget.components.SelectionBottomBar
@@ -295,7 +295,7 @@ fun TocScreen(
viewModel.clearSelection() viewModel.clearSelection()
} }
Scaffold( AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
DynamicTopAppBar( DynamicTopAppBar(
@@ -948,4 +948,4 @@ private fun StatusIcon(
} }
} }
} }
} }
@@ -14,10 +14,11 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.FileOpen
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHostState 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.button.SmallIconButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem 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.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.BaseImportUiState
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog 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.importComponents.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn 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.RuleEditFields
import io.legado.app.ui.widget.components.rules.RuleEditSheet 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.rules.RuleListScaffold
@@ -84,9 +85,10 @@ fun TxtRuleScreen(
var showEditSheet by remember { mutableStateOf(false) } var showEditSheet by remember { mutableStateOf(false) }
var editingRule by remember { mutableStateOf<TxtTocRule?>(null) } var editingRule by remember { mutableStateOf<TxtTocRule?>(null) }
var showDeleteRuleDialog by remember { mutableStateOf<TxtTocRule?>(null) } var showDeleteRuleDialog by remember { mutableStateOf<TxtTocRule?>(null) }
var showUrlInput by remember { mutableStateOf(false) } var showUrlInput by remember { mutableStateOf(false) }
var showFilePickerSheet by remember { mutableStateOf(false) } var showImportSheet by remember { mutableStateOf(false) }
var filePickerMode by remember { mutableStateOf(FilePickerSheetMode.EXPORT) } var showExportSheet by remember { mutableStateOf(false) }
val reorderableState = rememberReorderableLazyListState(listState) { from, to -> val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
viewModel.moveItemInList(from.index, to.index) viewModel.moveItemInList(from.index, to.index)
@@ -152,21 +154,38 @@ fun TxtRuleScreen(
) )
} }
if (showFilePickerSheet) { if (showExportSheet) {
FilePickerSheet( FilePickerSheet(
onDismissRequest = { showFilePickerSheet = false }, onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export),
onSelectSysDir = { onSelectSysDir = {
showFilePickerSheet = false showExportSheet = false
exportDoc.launch("exportDictRule.json") exportDoc.launch("exportDictRule.json")
}, },
onUpload = { onUpload = {
showFilePickerSheet = false showExportSheet = false
viewModel.uploadSelectedRules(selectedIds, rules) viewModel.uploadSelectedRules(selectedIds, rules)
}, },
allowExtensions = arrayOf("json") allowExtensions = arrayOf("json")
) )
} }
if (showImportSheet) {
FilePickerSheet(
onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_txt_toc_rule),
onSelectSysFile = { types ->
importDoc.launch(types)
showImportSheet = false
},
onManualInput = {
showUrlInput = true
showImportSheet = false
},
allowExtensions = arrayOf("json", "txt")
)
}
(importState as? BaseImportUiState.Success<TxtTocRule>)?.let { state -> (importState as? BaseImportUiState.Success<TxtTocRule>)?.let { state ->
BatchImportDialog( BatchImportDialog(
title = "导入词典规则", title = "导入词典规则",
@@ -279,7 +298,7 @@ fun TxtRuleScreen(
}), }),
ActionItem( ActionItem(
text = stringResource(R.string.export), text = stringResource(R.string.export),
onClick = { showFilePickerSheet = true }) onClick = { showExportSheet = true })
), ),
onDeleteSelected = { ids -> onDeleteSelected = { ids ->
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -292,19 +311,11 @@ fun TxtRuleScreen(
}, },
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
DropdownMenuItem( RoundDropdownMenuItem(
text = { Text("在线导入") }, text = { Text(stringResource(R.string.import_str)) },
onClick = { dismiss(); showUrlInput = true }) onClick = { showImportSheet = true; dismiss() },
DropdownMenuItem( leadingIcon = { Icon(Icons.Default.FileOpen, null) }
text = { Text("本地导入") }, )
onClick = {
dismiss(); importDoc.launch(
arrayOf(
"text/plain",
"application/json"
)
)
})
} }
) { paddingValues -> ) { paddingValues ->
Box( Box(
@@ -53,11 +53,16 @@ fun <T> prefDelegate(
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
private fun readInitialValue(): T { private fun readInitialValue(): T {
return when (defaultValue) { val prefs = appCtx.defaultSharedPreferences
is String -> appCtx.getPrefString(key, defaultValue) as T return when {
is Int -> appCtx.getPrefInt(key, defaultValue) as T defaultValue is String -> appCtx.getPrefString(key, defaultValue) as T
is Boolean -> appCtx.getPrefBoolean(key, defaultValue) as T defaultValue == null && prefs.contains(key) -> {
is Long -> appCtx.getPrefLong(key, defaultValue) as T 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 else -> defaultValue
} }
} }
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier 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.coverConfig.CoverConfigScreen
import io.legado.app.ui.config.otherConfig.OtherConfigScreen import io.legado.app.ui.config.otherConfig.OtherConfigScreen
import io.legado.app.ui.config.readConfig.ReadConfigScreen 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.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup 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.ClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
class TestConfigActivity : BaseComposeActivity() { class TestConfigActivity : BaseComposeActivity() {
@@ -44,6 +43,9 @@ class TestConfigActivity : BaseComposeActivity() {
@Serializable @Serializable
object CoverConfigRoute object CoverConfigRoute
@Serializable
object ThemeConfigRoute
@Composable @Composable
override fun Content() { override fun Content() {
val navController = rememberNavController() val navController = rememberNavController()
@@ -57,7 +59,8 @@ class TestConfigActivity : BaseComposeActivity() {
onBackClick = { finish() }, onBackClick = { finish() },
onNavigateToOther = { navController.navigate(OtherConfigRoute) }, onNavigateToOther = { navController.navigate(OtherConfigRoute) },
onNavigateToRead = { navController.navigate(ReadConfigRoute) }, onNavigateToRead = { navController.navigate(ReadConfigRoute) },
onNavigateToCover = { navController.navigate(CoverConfigRoute) } onNavigateToCover = { navController.navigate(CoverConfigRoute) },
onNavigateToTheme = { navController.navigate(ThemeConfigRoute) }
) )
} }
@@ -72,6 +75,10 @@ class TestConfigActivity : BaseComposeActivity() {
composable<CoverConfigRoute> { composable<CoverConfigRoute> {
CoverConfigScreen(onBackClick = { navController.popBackStack() }) CoverConfigScreen(onBackClick = { navController.popBackStack() })
} }
composable<ThemeConfigRoute> {
ThemeConfigScreen(onBackClick = { navController.popBackStack() })
}
} }
} }
@@ -81,11 +88,12 @@ class TestConfigActivity : BaseComposeActivity() {
onBackClick: () -> Unit, onBackClick: () -> Unit,
onNavigateToOther: () -> Unit, onNavigateToOther: () -> Unit,
onNavigateToRead: () -> Unit, onNavigateToRead: () -> Unit,
onNavigateToCover: () -> Unit onNavigateToCover: () -> Unit,
onNavigateToTheme: () -> Unit
) { ) {
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
Scaffold( AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
@@ -105,10 +113,9 @@ class TestConfigActivity : BaseComposeActivity() {
.padding(16.dp) .padding(16.dp)
) { ) {
SplicedColumnGroup { SplicedColumnGroup {
SwitchSettingItem( ClickableSettingItem(
title = "使用折叠应用栏", title = stringResource(R.string.theme_setting),
checked = ThemeConfig.useFlexibleTopAppBar, onClick = onNavigateToTheme
onCheckedChange = { ThemeConfig.useFlexibleTopAppBar = it }
) )
ClickableSettingItem( ClickableSettingItem(
title = stringResource(R.string.other_setting), title = stringResource(R.string.other_setting),
@@ -12,7 +12,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -27,6 +26,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PreferKey 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.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup import io.legado.app.ui.widget.components.SplicedColumnGroup
@@ -48,7 +48,7 @@ fun CoverConfigScreen(
var manageKey by remember { mutableStateOf<String?>(null) } var manageKey by remember { mutableStateOf<String?>(null) }
var showColorPickerByField by remember { mutableStateOf<String?>(null) } var showColorPickerByField by remember { mutableStateOf<String?>(null) }
Scaffold( AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
@@ -282,4 +282,4 @@ fun CoverConfigScreen(
} }
) )
} }
} }
@@ -13,7 +13,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
@@ -30,6 +29,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.service.WebService 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.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup import io.legado.app.ui.widget.components.SplicedColumnGroup
@@ -79,7 +79,7 @@ fun OtherConfigScreen(
} }
} }
Scaffold( AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
@@ -505,4 +505,4 @@ fun ConfirmDialog(
} }
} }
) )
} }
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue 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.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R 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.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup import io.legado.app.ui.widget.components.SplicedColumnGroup
@@ -41,7 +41,7 @@ fun ReadConfigScreen(
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
var showPageKeySheet by remember { mutableStateOf(false) } var showPageKeySheet by remember { mutableStateOf(false) }
Scaffold( AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
@@ -326,4 +326,4 @@ fun ReadConfigScreen(
if (showPageKeySheet) { if (showPageKeySheet) {
PageKeySheet(onDismissRequest = { showPageKeySheet = false }) PageKeySheet(onDismissRequest = { showPageKeySheet = false })
} }
} }
@@ -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")
)
}
}
@@ -1,7 +1,10 @@
package io.legado.app.ui.config.themeConfig 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.constant.PreferKey
import io.legado.app.ui.config.prefDelegate import io.legado.app.ui.config.prefDelegate
import io.legado.app.utils.postEvent
object ThemeConfig { object ThemeConfig {
@@ -19,16 +22,34 @@ object ThemeConfig {
var isPureBlack by prefDelegate(PreferKey.pureBlack, false) var isPureBlack by prefDelegate(PreferKey.pureBlack, false)
var bgImageLight by prefDelegate<String?>(PreferKey.bgImage, null) var bgImageLight by prefDelegate<String?>(PreferKey.bgImage, null) {
postEvent(EventBus.RECREATE, false)
}
var bgImageDark by prefDelegate<String?>(PreferKey.bgImageN, null) var bgImageDark by prefDelegate<String?>(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 { fun hasImageBg(isDark: Boolean): Boolean {
return if (isDark) { val result = if (isDark) {
!bgImageDark.isNullOrEmpty() !bgImageDark.isNullOrBlank()
} else { } else {
!bgImageLight.isNullOrEmpty() !bgImageLight.isNullOrBlank()
} }
Log.d(
"MainConfig",
"hasImageBg -> isDark=$isDark, " +
"bgImageDark=$bgImageDark, " +
"bgImageLight=$bgImageLight, " +
"result=$result"
)
return result
} }
/* /*
@@ -0,0 +1,170 @@
package io.legado.app.ui.config.themeConfig
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.constant.PreferKey
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ThemeConfigScreen(
onBackClick: () -> Unit,
viewModel: ThemeConfigViewModel = koinViewModel()
) {
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
var manageKey by remember { mutableStateOf<Pair<String, Boolean>?>(null) }
AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
GlassMediumFlexibleTopAppBar(
title = { Text(stringResource(R.string.theme_setting)) },
scrollBehavior = scrollBehavior,
navigationIcon = {
TopbarNavigationButton(onClick = onBackClick)
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
SplicedColumnGroup {
SwitchSettingItem(
title = stringResource(R.string.pure_black),
checked = ThemeConfig.isPureBlack,
onCheckedChange = { ThemeConfig.isPureBlack = it }
)
DropdownListSettingItem(
title = stringResource(R.string.palette_style),
selectedValue = ThemeConfig.paletteStyle,
displayEntries = arrayOf(
"柔和", "中性", "鲜艳", "表现力", "彩虹",
"水果拼盘", "单色", "仿真", "内容取色"
),
entryValues = arrayOf(
"tonalSpot", "neutral", "vibrant", "expressive", "rainbow",
"fruitSalad", "monochrome", "fidelity", "content"
),
onValueChange = { ThemeConfig.paletteStyle = it }
)
}
SplicedColumnGroup(title = "Compose 相关") {
SwitchSettingItem(
title = "使用折叠应用栏",
checked = ThemeConfig.useFlexibleTopAppBar,
onCheckedChange = { ThemeConfig.useFlexibleTopAppBar = it }
)
SliderSettingItem(
title = stringResource(R.string.container_opacity),
description = stringResource(
R.string.container_opacity_summary,
ThemeConfig.containerOpacity
),
value = ThemeConfig.containerOpacity.toFloat(),
defaultValue = 100f,
valueRange = 0f..100f,
steps = 99,
onValueChange = { ThemeConfig.containerOpacity = it.toInt() }
)
SwitchSettingItem(
title = stringResource(R.string.is_blur_enable),
checked = ThemeConfig.enableBlur,
onCheckedChange = { ThemeConfig.enableBlur = it }
)
if (ThemeConfig.enableBlur) {
SwitchSettingItem(
title = stringResource(R.string.is_blur_progressive_enable),
checked = ThemeConfig.enableProgressiveBlur,
onCheckedChange = { ThemeConfig.enableProgressiveBlur = it }
)
}
}
SplicedColumnGroup(title = stringResource(R.string.day)) {
val hasLightBg = !ThemeConfig.bgImageLight.isNullOrBlank()
ClickableSettingItem(
title = stringResource(R.string.background_image),
description = if (hasLightBg) stringResource(R.string.click_to_delete) else stringResource(
R.string.select_image
),
onClick = { manageKey = Pair(PreferKey.bgImage, false) }
)
if (hasLightBg) {
SliderSettingItem(
title = stringResource(R.string.background_image_blurring),
value = ThemeConfig.bgImageBlurring.toFloat(),
defaultValue = 0f,
valueRange = 0f..100f,
steps = 99,
onValueChange = {
ThemeConfig.bgImageBlurring = it.toInt()
}
)
}
}
SplicedColumnGroup(title = stringResource(R.string.night)) {
val hasDarkBg = !ThemeConfig.bgImageDark.isNullOrBlank()
ClickableSettingItem(
title = stringResource(R.string.background_image),
description = if (hasDarkBg) stringResource(R.string.click_to_delete) else stringResource(
R.string.select_image
),
onClick = { manageKey = Pair(PreferKey.bgImageN, true) }
)
if (hasDarkBg) {
SliderSettingItem(
title = stringResource(R.string.background_image_blurring),
value = ThemeConfig.bgImageNBlurring.toFloat(),
defaultValue = 0f,
valueRange = 0f..100f,
steps = 99,
onValueChange = {
ThemeConfig.bgImageNBlurring = it.toInt()
}
)
}
}
}
}
manageKey?.let { (key, isDark) ->
BackgroundImageManageSheet(
preferenceKey = key,
isDarkTheme = isDark,
onDismissRequest = { manageKey = null }
)
}
}
@@ -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
}
}
}
@@ -14,10 +14,11 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.FileOpen
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHostState 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.button.SmallIconButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem 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.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.BaseImportUiState
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog 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.importComponents.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn 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.RuleEditFields
import io.legado.app.ui.widget.components.rules.RuleEditSheet 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.rules.RuleListScaffold
@@ -82,8 +83,11 @@ fun DictRuleScreen(
var editingRule by remember { mutableStateOf<DictRule?>(null) } var editingRule by remember { mutableStateOf<DictRule?>(null) }
var showDeleteRuleDialog by remember { mutableStateOf<DictRule?>(null) } var showDeleteRuleDialog by remember { mutableStateOf<DictRule?>(null) }
var showUrlInput by remember { mutableStateOf(false) } var showUrlInput by remember { mutableStateOf(false) }
var showFilePickerSheet 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 -> val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
viewModel.moveItemInList(from.index, to.index) viewModel.moveItemInList(from.index, to.index)
@@ -149,21 +153,38 @@ fun DictRuleScreen(
) )
} }
if (showFilePickerSheet) { if (showExportSheet) {
FilePickerSheet( FilePickerSheet(
onDismissRequest = { showFilePickerSheet = false }, onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export),
onSelectSysDir = { onSelectSysDir = {
showFilePickerSheet = false showExportSheet = false
exportDoc.launch("exportDictRule.json") exportDoc.launch("exportDictRule.json")
}, },
onUpload = { onUpload = {
showFilePickerSheet = false showExportSheet = false
viewModel.uploadSelectedRules(selectedIds, rules) viewModel.uploadSelectedRules(selectedIds, rules)
}, },
allowExtensions = arrayOf("json") allowExtensions = arrayOf("json")
) )
} }
if (showImportSheet) {
FilePickerSheet(
onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_dict_rule),
onSelectSysFile = { types ->
importDoc.launch(types)
showImportSheet = false
},
onManualInput = {
showUrlInput = true
showImportSheet = false
},
allowExtensions = arrayOf("json", "txt")
)
}
(importState as? BaseImportUiState.Success<DictRule>)?.let { state -> (importState as? BaseImportUiState.Success<DictRule>)?.let { state ->
BatchImportDialog( BatchImportDialog(
title = "导入词典规则", title = "导入词典规则",
@@ -288,19 +309,11 @@ fun DictRuleScreen(
}, },
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
DropdownMenuItem( RoundDropdownMenuItem(
text = { Text("在线导入") }, text = { Text(stringResource(R.string.import_str)) },
onClick = { dismiss(); showUrlInput = true }) onClick = { showImportSheet = true; dismiss() },
DropdownMenuItem( leadingIcon = { Icon(Icons.Default.FileOpen, null) }
text = { Text("本地导入") }, )
onClick = {
dismiss(); importDoc.launch(
arrayOf(
"text/plain",
"application/json"
)
)
})
} }
) { paddingValues -> ) { paddingValues ->
Box( Box(
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
@@ -27,7 +26,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.WideNavigationRail import androidx.compose.material3.WideNavigationRail
import androidx.compose.material3.WideNavigationRailItem 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.my.MyScreen
import io.legado.app.ui.main.rss.RssScreen import io.legado.app.ui.main.rss.RssScreen
import io.legado.app.ui.theme.regularHazeEffect 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.ui.widget.components.GlassDefaults
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import io.legado.app.utils.startActivityForBook import io.legado.app.utils.startActivityForBook
@@ -159,20 +158,21 @@ fun MainScreen(
} }
} }
Scaffold( AppScaffold(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
bottomBar = { bottomBar = {
if (!useRail && MainConfig.showBottomView) { if (!useRail && MainConfig.showBottomView) {
val labelVisibilityMode = MainConfig.labelVisibilityMode
val isUnlabeled = labelVisibilityMode == "unlabeled"
NavigationBar( NavigationBar(
modifier = Modifier modifier = Modifier
.regularHazeEffect(state = hazeState) .regularHazeEffect(state = hazeState)
.heightIn(max = 84.dp), .height(if (isUnlabeled) 64.dp else 80.dp),
containerColor = GlassDefaults.glassColor( containerColor = GlassDefaults.glassColor(
noBlurColor = BottomAppBarDefaults.containerColor, noBlurColor = BottomAppBarDefaults.containerColor,
blurAlpha = GlassDefaults.DefaultBlurAlpha blurAlpha = GlassDefaults.DefaultBlurAlpha
) )
) { ) {
val labelVisibilityMode = MainConfig.labelVisibilityMode
val alwaysShowLabel = when (labelVisibilityMode) { val alwaysShowLabel = when (labelVisibilityMode) {
"labeled" -> true "labeled" -> true
"selected" -> false "selected" -> false
@@ -24,14 +24,12 @@ import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Palette import androidx.compose.material.icons.filled.Palette
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Source import androidx.compose.material.icons.filled.Source
import androidx.compose.material.icons.filled.Translate
import androidx.compose.material.icons.filled.Web import androidx.compose.material.icons.filled.Web
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.dict.rule.DictRuleActivity
import io.legado.app.ui.file.FileManageActivity import io.legado.app.ui.file.FileManageActivity
import io.legado.app.ui.replace.ReplaceRuleActivity 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.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup import io.legado.app.ui.widget.components.SplicedColumnGroup
@@ -68,7 +67,7 @@ fun MyScreen(
val uiState by viewModel.uiState.collectAsState() val uiState by viewModel.uiState.collectAsState()
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
Scaffold( AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
contentWindowInsets = WindowInsets.systemBars contentWindowInsets = WindowInsets.systemBars
.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top), .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top),
@@ -230,7 +229,7 @@ fun MyScreen(
) )
SettingItem( SettingItem(
title = stringResource(R.string.dict_rule), title = stringResource(R.string.dict_rule),
imageVector = Icons.Default.Translate, imageVector = Icons.AutoMirrored.Filled.Rule,
onClick = { onClick = {
onNavigate( onNavigate(
PrefClickEvent.StartActivity(DictRuleActivity::class.java) PrefClickEvent.StartActivity(DictRuleActivity::class.java)
@@ -281,4 +280,4 @@ fun MyScreen(
Spacer(Modifier.height(120.dp)) Spacer(Modifier.height(120.dp))
} }
} }
} }
@@ -18,6 +18,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.FileOpen
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api 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.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.divider.PillDivider 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.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.BaseImportUiState
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog 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.importComponents.SourceInputDialog
@@ -107,8 +107,9 @@ fun ReplaceRuleScreen(
val hapticFeedback = LocalHapticFeedback.current val hapticFeedback = LocalHapticFeedback.current
var showUrlInput by remember { mutableStateOf(false) } var showUrlInput by remember { mutableStateOf(false) }
var showFilePickerSheet by remember { mutableStateOf(false) } var showImportSheet by remember { mutableStateOf(false) }
var filePickerMode by remember { mutableStateOf(FilePickerSheetMode.EXPORT) } var showExportSheet by remember { mutableStateOf(false) }
var showDeleteRuleDialog by remember { mutableStateOf<ReplaceRule?>(null) } var showDeleteRuleDialog by remember { mutableStateOf<ReplaceRule?>(null) }
var showGroupManageSheet by remember { mutableStateOf(false) } var showGroupManageSheet by remember { mutableStateOf(false) }
@@ -164,15 +165,31 @@ fun ReplaceRuleScreen(
) )
} }
if (showFilePickerSheet) { if (showImportSheet) {
FilePickerSheet( 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 = { onSelectSysDir = {
showFilePickerSheet = false showExportSheet = false
exportDoc.launch("exportReplaceRule.json") exportDoc.launch("exportReplaceRule.json")
}, },
onUpload = { onUpload = {
showFilePickerSheet = false showExportSheet = false
viewModel.uploadSelectedRules(selectedIds, rules) viewModel.uploadSelectedRules(selectedIds, rules)
}, },
allowExtensions = arrayOf("json") allowExtensions = arrayOf("json")
@@ -320,7 +337,7 @@ fun ReplaceRuleScreen(
), ),
ActionItem( ActionItem(
text = stringResource(R.string.export), text = stringResource(R.string.export),
onClick = { showFilePickerSheet = true } onClick = { showExportSheet = true }
) )
), ),
onDeleteSelected = { ids -> onDeleteSelected = { ids ->
@@ -389,15 +406,9 @@ fun ReplaceRuleScreen(
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("在线导入") }, text = { Text(stringResource(R.string.import_str)) },
onClick = { onClick = { showImportSheet = true; dismiss() },
dismiss() leadingIcon = { Icon(Icons.Default.FileOpen, null) }
showUrlInput = true // 触发输入框
}
)
RoundDropdownMenuItem(
text = { Text("本地导入") },
onClick = { importDoc.launch(arrayOf("text/plain", "application/json")); dismiss() }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("分组管理") }, text = { Text("分组管理") },
@@ -52,7 +52,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.PlainTooltip import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TooltipAnchorPosition 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.platform.LocalDensity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex 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.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.button.AlertButton import io.legado.app.ui.widget.components.button.AlertButton
@@ -108,7 +108,7 @@ fun ReplaceEditScreen(
val isKeyboardVisible by keyboardAsState() val isKeyboardVisible by keyboardAsState()
with(sharedTransitionScope) { with(sharedTransitionScope) {
Scaffold( AppScaffold(
modifier = Modifier modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection) .nestedScroll(scrollBehavior.nestedScrollConnection)
.sharedBounds( .sharedBounds(
@@ -487,4 +487,4 @@ fun QuickInputBar(
) )
} }
} }
} }
@@ -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()
}
}
@@ -20,17 +20,15 @@ fun AppTheme(
val context = LocalContext.current val context = LocalContext.current
val appThemeMode = ThemeResolver.resolveThemeMode(ThemeConfig.appTheme) val appThemeMode = ThemeResolver.resolveThemeMode(ThemeConfig.appTheme)
val isPureBlack = ThemeConfig.isPureBlack val isPureBlack = ThemeConfig.isPureBlack
val hasImageBg = ThemeConfig.hasImageBg(darkTheme)
val paletteStyle = ThemeConfig.paletteStyle val paletteStyle = ThemeConfig.paletteStyle
val colorScheme = val colorScheme =
remember(context, appThemeMode, darkTheme, isPureBlack, hasImageBg, paletteStyle) { remember(context, appThemeMode, darkTheme, isPureBlack, paletteStyle) {
ThemeManager.getColorScheme( ThemeManager.getColorScheme(
context = context, context = context,
mode = appThemeMode, mode = appThemeMode,
darkTheme = darkTheme, darkTheme = darkTheme,
isAmoled = isPureBlack, isAmoled = isPureBlack,
isImageBg = hasImageBg,
paletteStyle = paletteStyle paletteStyle = paletteStyle
) )
} }
@@ -39,7 +37,10 @@ fun AppTheme(
colorScheme = colorScheme, colorScheme = colorScheme,
typography = Typography(), typography = Typography(),
motionScheme = MotionScheme.expressive(), motionScheme = MotionScheme.expressive(),
shapes = Shapes(), shapes = Shapes()
content = content ) {
) AppBackground(darkTheme = darkTheme) {
content()
}
}
} }
@@ -23,7 +23,6 @@ fun rememberOpaqueColorScheme(): ColorScheme {
mode = appThemeMode, mode = appThemeMode,
darkTheme = isDark, darkTheme = isDark,
isAmoled = isPureBlack, isAmoled = isPureBlack,
isImageBg = hasImageBg,
paletteStyle = paletteStyle, paletteStyle = paletteStyle,
forceOpaque = true forceOpaque = true
) )
@@ -44,7 +44,6 @@ object ThemeManager {
mode: AppThemeMode, mode: AppThemeMode,
darkTheme: Boolean, darkTheme: Boolean,
isAmoled: Boolean, isAmoled: Boolean,
isImageBg: Boolean,
paletteStyle: String?, paletteStyle: String?,
forceOpaque: Boolean = false forceOpaque: Boolean = false
): ColorScheme { ): ColorScheme {
@@ -82,7 +81,7 @@ object ThemeManager {
) )
} }
if (!forceOpaque && (isImageBg || actualMode == AppThemeMode.Transparent)) { if (!forceOpaque && actualMode == AppThemeMode.Transparent) {
return scheme.copy( return scheme.copy(
surface = Color.Transparent, surface = Color.Transparent,
background = Color.Transparent, background = Color.Transparent,
@@ -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
)
}
@@ -61,10 +61,9 @@ object GlassTopAppBarDefaults {
@Composable @Composable
fun glassColors(): TopAppBarColors { fun glassColors(): TopAppBarColors {
val opacity = ThemeConfig.containerOpacity / 100f
val containerColor = GlassDefaults.glassColor( val containerColor = GlassDefaults.glassColor(
noBlurColor = MaterialTheme.colorScheme.surface.copy(alpha = opacity), noBlurColor = MaterialTheme.colorScheme.surface,
blurAlpha = GlassDefaults.TransparentAlpha blurAlpha = GlassDefaults.TransparentAlpha
) )
@@ -76,15 +75,17 @@ object GlassTopAppBarDefaults {
return TopAppBarDefaults.topAppBarColors( return TopAppBarDefaults.topAppBarColors(
containerColor = containerColor, containerColor = containerColor,
scrolledContainerColor = scrolledContainerColor scrolledContainerColor = scrolledContainerColor,
navigationIconContentColor = MaterialTheme.colorScheme.onSurface,
titleContentColor = MaterialTheme.colorScheme.onSurface,
actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
@Composable @Composable
fun containerColor(): Color { fun containerColor(): Color {
val opacity = ThemeConfig.containerOpacity / 100f
return GlassDefaults.glassColor( return GlassDefaults.glassColor(
noBlurColor = MaterialTheme.colorScheme.surface.copy(alpha = opacity), noBlurColor = MaterialTheme.colorScheme.surface,
blurAlpha = GlassDefaults.TransparentAlpha blurAlpha = GlassDefaults.TransparentAlpha
) )
} }
@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -24,10 +23,8 @@ fun SectionHeader(
// 根容器的 Padding // 根容器的 Padding
contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp) contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
) { ) {
Surface(
modifier = Modifier.fillMaxWidth() Row(
) {
Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(contentPadding), .padding(contentPadding),
@@ -39,5 +36,5 @@ fun SectionHeader(
it() it()
} }
} }
}
} }
@@ -1,5 +1,6 @@
package io.legado.app.ui.widget.components package io.legado.app.ui.widget.components
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
@@ -11,6 +12,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.GlassCard
@@ -39,10 +41,12 @@ fun SplicedColumnGroup(
GlassCard( GlassCard(
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), colors = CardDefaults.cardColors(containerColor = Color.Transparent),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Column( Column(
modifier = Modifier
.animateContentSize(),
verticalArrangement = Arrangement.spacedBy(2.dp) verticalArrangement = Arrangement.spacedBy(2.dp)
) { ) {
content() content()
@@ -17,32 +17,25 @@ fun GlassCard(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
shape: Shape = CardDefaults.shape, shape: Shape = CardDefaults.shape,
colors: CardColors = CardDefaults.cardColors( colors: CardColors? = null,
containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = ThemeConfig.containerOpacity / 100f),
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
),
elevation: CardElevation = CardDefaults.cardElevation(), elevation: CardElevation = CardDefaults.cardElevation(),
border: BorderStroke? = null, border: BorderStroke? = null,
content: @Composable ColumnScope.() -> Unit content: @Composable ColumnScope.() -> Unit
) { ) {
if (onClick != null) { val containerAlpha = ThemeConfig.containerOpacity / 100f
Card(
onClick = onClick, val finalColors = colors ?: CardDefaults.cardColors(
modifier = modifier, containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = containerAlpha),
shape = shape, contentColor = MaterialTheme.colorScheme.onSecondaryContainer
colors = colors, )
elevation = elevation,
border = border, Card(
content = content onClick = onClick ?: {},
) modifier = modifier,
} else { shape = shape,
Card( colors = finalColors,
modifier = modifier, elevation = elevation,
shape = shape, border = border,
colors = colors, content = content
elevation = elevation, )
border = border,
content = content
)
}
} }
@@ -88,7 +88,7 @@ fun FilePickerSheet(
onSelectSysFiles?.let { onSelectSysFiles?.let {
FilePickerOptionCard( FilePickerOptionCard(
icon = Icons.AutoMirrored.Filled.InsertDriveFile, icon = Icons.AutoMirrored.Filled.InsertDriveFile,
text = "系统多选", text = "多选项目",
onClick = { it(typesOfExtensions(allowExtensions)) } onClick = { it(typesOfExtensions(allowExtensions)) }
) )
} }
@@ -40,7 +40,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedToggleButton import androidx.compose.material3.OutlinedToggleButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -61,6 +60,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.widget.components.AnimatedText 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 io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -141,7 +141,7 @@ fun <T> BatchImportDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
containerColor = { it.surfaceContainer } containerColor = { it.surfaceContainer }
) { ) {
Scaffold( AppScaffold(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.heightIn(max = LocalConfiguration.current.screenHeightDp.dp * 0.8f), .heightIn(max = LocalConfiguration.current.screenHeightDp.dp * 0.8f),
@@ -19,7 +19,6 @@ import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.PlainTooltip import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -39,6 +38,7 @@ import androidx.compose.ui.zIndex
import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeState
import io.legado.app.ui.theme.responsiveHazeEffect import io.legado.app.ui.theme.responsiveHazeEffect
import io.legado.app.ui.theme.responsiveHazeSource 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.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SelectionActions import io.legado.app.ui.widget.components.SelectionActions
import io.legado.app.ui.widget.components.SelectionBottomBar import io.legado.app.ui.widget.components.SelectionBottomBar
@@ -86,9 +86,15 @@ fun <T> ListScaffold(
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
val hazeState = remember { HazeState() } val hazeState = remember { HazeState() }
Scaffold( AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = {
SnackbarHost(
hostState = snackbarHostState,
modifier = Modifier
.padding(bottom = 72.dp)
)
},
topBar = { topBar = {
Box(modifier = Modifier.responsiveHazeEffect(state = hazeState)) { Box(modifier = Modifier.responsiveHazeEffect(state = hazeState)) {
DynamicTopAppBar( DynamicTopAppBar(
@@ -136,4 +142,4 @@ fun <T> ListScaffold(
} }
} }
} }
} }
@@ -15,6 +15,7 @@ import androidx.compose.material3.Slider
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -101,6 +102,7 @@ fun CompactSliderSettingItem(
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
var sliderValue by remember(value) { mutableFloatStateOf(value) } var sliderValue by remember(value) { mutableFloatStateOf(value) }
var displayValue by remember(value) { mutableFloatStateOf(value) }
SettingItem( SettingItem(
title = title, title = title,
@@ -126,7 +128,7 @@ fun CompactSliderSettingItem(
cornerRadius = 8.dp, cornerRadius = 8.dp,
horizontalPadding = 8.dp, horizontalPadding = 8.dp,
verticalPadding = 4.dp, verticalPadding = 4.dp,
text = value.toInt().toString(), text = displayValue.toInt().toString(),
backgroundColor = MaterialTheme.colorScheme.surfaceContainer, backgroundColor = MaterialTheme.colorScheme.surfaceContainer,
contentColor = MaterialTheme.colorScheme.onSurface contentColor = MaterialTheme.colorScheme.onSurface
) )
@@ -144,7 +146,10 @@ fun CompactSliderSettingItem(
value = sliderValue, value = sliderValue,
onValueChange = { onValueChange = {
sliderValue = it sliderValue = it
onValueChange(it) displayValue = it
},
onValueChangeFinished = {
onValueChange(sliderValue)
}, },
valueRange = valueRange, valueRange = valueRange,
steps = steps, steps = steps,
@@ -152,6 +157,13 @@ fun CompactSliderSettingItem(
) )
} }
) )
LaunchedEffect(value) {
if (!expanded) {
sliderValue = value
displayValue = value
}
}
} }
@Composable @Composable
@@ -21,6 +21,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
@@ -103,7 +104,7 @@ fun SwipeActionContainer(
content = { content = {
Surface( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.surface, color = Color.Transparent,
shape = RectangleShape shape = RectangleShape
) { ) {
content() content()