feat: 优化设置页页面样式,简化布局

This commit is contained in:
Super12138 2026-01-18 11:48:44 +08:00
parent 12285e5be7
commit 667cb152ea
26 changed files with 510 additions and 418 deletions

View file

@ -38,7 +38,7 @@ android {
applicationId = "cn.super12138.todo"
minSdk = 24
targetSdk = 36
versionCode = 923
versionCode = 940
versionName = "2.3.3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

View file

@ -1,5 +1,11 @@
package cn.super12138.todo.ui
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.foundation.shape.ZeroCornerSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.dp
object TodoDefaults {
@ -26,7 +32,9 @@ object TodoDefaults {
/**
* 设置项垂直边距
*/
val settingsItemVerticalPadding = 20.dp
val settingsItemVerticalPadding = 16.dp
val settingsItemPadding = 2.dp
/**
* 待办进度条粗度
@ -47,4 +55,29 @@ object TodoDefaults {
* 待办进度条波幅
*/
const val waveAmplitude = 0.6f
val ScreenContainerShape: Shape
@Composable
get() = MaterialTheme.shapes.large.copy(
bottomStart = ZeroCornerSize,
bottomEnd = ZeroCornerSize
)
val ContainerColor: Color
@Composable
get() = MaterialTheme.colorScheme.surfaceBright
val BackgroundColor: Color
@Composable
get() = MaterialTheme.colorScheme.surfaceContainer
val SettingsItemDefaultShape: CornerBasedShape
@Composable
get() = MaterialTheme.shapes.small
val SettingsItemRoundedShape: CornerBasedShape
@Composable
get() = MaterialTheme.shapes.large
val fadedEdgeWidth = 8.dp
}

View file

@ -73,7 +73,7 @@ class CrashActivity : ComponentActivity() {
) {
CrashPage(
crashLog = crashLogs ?: stringResource(R.string.tip_no_crash_logs),
exitApp = { finishAffinity() },
exitApp = ::finishAffinity,
modifier = Modifier.fillMaxSize()
)
}

View file

@ -8,7 +8,6 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@ -19,6 +18,7 @@ import androidx.core.view.WindowCompat
import androidx.lifecycle.viewmodel.compose.viewModel
import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.Konfetti
import cn.super12138.todo.ui.navigation.TodoNavigation
import cn.super12138.todo.ui.theme.DarkMode
@ -83,7 +83,7 @@ class MainActivity : ComponentActivity() {
contrastLevel = contrastLevel.toDouble(),
dynamicColor = dynamicColor
) {
Surface(color = MaterialTheme.colorScheme.background) {
Surface(color = TodoDefaults.BackgroundColor) {
TodoNavigation(
viewModel = mainViewModel,
modifier = Modifier.fillMaxSize()

View file

@ -4,26 +4,28 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FabPosition
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import cn.super12138.todo.R
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.utils.VibrationUtils
/**
@ -31,16 +33,14 @@ import cn.super12138.todo.utils.VibrationUtils
* * 内容默认由 Box 容器包裹实际使用时推荐配合 Column Row
*
* @param title 标题文本
* @param scrollBehavior 滚动行为用于支持页面内容滚动时标题栏的压缩效果
* @param onBack 当返回按钮被按下时的操作
* @param contentWindowInsets 内容边距通常用于将内容和系统状态栏等隔开可以使用 `WindowInsets.safeContent`
*/
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun LargeTopAppBarScaffold(
fun TopAppBarScaffold(
modifier: Modifier = Modifier,
title: String,
scrollBehavior: TopAppBarScrollBehavior,
onBack: () -> Unit,
snackbarHost: @Composable () -> Unit = {},
floatingActionButton: @Composable () -> Unit = {},
@ -49,7 +49,7 @@ fun LargeTopAppBarScaffold(
content: @Composable (PaddingValues) -> Unit
) {
val view = LocalView.current
LargeTopAppBarScaffold(
TopAppBarScaffold(
title = {
Text(
text = title,
@ -59,7 +59,7 @@ fun LargeTopAppBarScaffold(
},
navigationIcon = {
FilledIconButton(
colors = IconButtonDefaults.filledIconButtonColors(containerColor = MaterialTheme.colorScheme.surfaceContainerHigh),
colors = IconButtonDefaults.filledIconButtonColors(containerColor = MaterialTheme.colorScheme.surfaceContainerHighest),
shapes = IconButtonDefaults.shapes(),
onClick = {
VibrationUtils.performHapticFeedback(view)
@ -72,25 +72,24 @@ fun LargeTopAppBarScaffold(
)
}
},
scrollBehavior = scrollBehavior,
snackbarHost = snackbarHost,
floatingActionButton = floatingActionButton,
floatingActionButtonPosition = floatingActionButtonPosition,
contentWindowInsets = contentWindowInsets,
modifier = modifier
modifier = modifier,
) { innerPadding ->
//TODO: 应用圆角效果
Box { content(innerPadding) }
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LargeTopAppBarScaffold(
fun TopAppBarScaffold(
modifier: Modifier = Modifier,
title: @Composable () -> Unit = {},
navigationIcon: @Composable () -> Unit = {},
actions: @Composable RowScope.() -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior,
snackbarHost: @Composable () -> Unit = {},
floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End,
@ -98,20 +97,22 @@ fun LargeTopAppBarScaffold(
content: @Composable (PaddingValues) -> Unit
) {
Scaffold(
modifier = modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
modifier = modifier,
topBar = {
LargeTopAppBar(
TopAppBar(
title = title,
navigationIcon = navigationIcon,
actions = actions,
scrollBehavior = scrollBehavior
colors = TopAppBarDefaults.topAppBarColors(
containerColor = TodoDefaults.BackgroundColor,
)
)
},
snackbarHost = snackbarHost,
floatingActionButton = floatingActionButton,
floatingActionButtonPosition = floatingActionButtonPosition,
contentWindowInsets = contentWindowInsets
contentWindowInsets = contentWindowInsets,
containerColor = TodoDefaults.BackgroundColor,
) { innerPadding ->
Box { content(innerPadding) }
}

View file

@ -0,0 +1,23 @@
package cn.super12138.todo.ui.components
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import cn.super12138.todo.ui.TodoDefaults
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun RoundedScreenContainer(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
Surface(
modifier = modifier.padding(horizontal = TodoDefaults.screenHorizontalPadding),
color = TodoDefaults.BackgroundColor,
shape = TodoDefaults.ScreenContainerShape,
content = content
)
}

View file

@ -44,7 +44,7 @@ import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.ChipItem
import cn.super12138.todo.ui.components.ConfirmDialog
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.TodoFloatingActionButton
import cn.super12138.todo.ui.pages.editor.components.TodoCategoryChip
import cn.super12138.todo.ui.pages.editor.components.TodoCategoryTextField
@ -69,7 +69,7 @@ fun TodoEditorPage(
// TODO: 本页及其相关组件重组性能检查优化
val view = LocalView.current
val animatedVisibilityScope = LocalNavAnimatedContentScope.current
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val uiState = rememberEditorState(initialTodo = toDo)
@ -112,9 +112,8 @@ fun TodoEditorPage(
BackHandler(onBack = ::checkModifiedBeforeBack)
LargeTopAppBarScaffold(
TopAppBarScaffold(
title = stringResource(if (toDo != null) R.string.title_edit_task else R.string.action_add_task),
scrollBehavior = scrollBehavior,
floatingActionButton = {
with(sharedTransitionScope) {
Row(

View file

@ -1,15 +1,10 @@
package cn.super12138.todo.ui.pages.settings
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
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.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@ -18,17 +13,15 @@ import androidx.compose.runtime.mutableLongStateOf
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.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.RoundedScreenContainer
import cn.super12138.todo.ui.icons.GitHubIcon
import cn.super12138.todo.ui.pages.settings.components.Settings
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsItem
import cn.super12138.todo.utils.SystemUtils
import kotlinx.coroutines.delay
@ -42,80 +35,82 @@ fun SettingsAbout(
onNavigateUp: () -> Unit,
modifier: Modifier = Modifier
) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
LargeTopAppBarScaffold(
TopAppBarScaffold(
title = stringResource(R.string.pref_about),
onBack = onNavigateUp,
scrollBehavior = scrollBehavior,
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
modifier = modifier,
) { innerPadding ->
val context = LocalContext.current
val uriHandler = LocalUriHandler.current
var clickCount by remember { mutableIntStateOf(0) }
var lastClickTime by remember { mutableLongStateOf(0L) }
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = TodoDefaults.screenHorizontalPadding)
.verticalScroll(rememberScrollState())
) {
Settings {
var clickCount by remember { mutableIntStateOf(0) }
var lastClickTime by remember { mutableLongStateOf(0L) }
LaunchedEffect(clickCount) {
if (clickCount > 0) {
lastClickTime = System.currentTimeMillis()
val currentClickTime = lastClickTime
delay(300L)
LaunchedEffect(clickCount) {
if (clickCount > 0) {
lastClickTime = System.currentTimeMillis()
val currentClickTime = lastClickTime
delay(300L)
if (currentClickTime == lastClickTime) {
clickCount = 0
}
}
if (currentClickTime == lastClickTime) {
clickCount = 0
}
}
}
SettingsItem(
leadingIconRes = R.drawable.ic_numbers,
title = stringResource(R.string.pref_app_version),
description = SystemUtils.getAppVersion(context),
onClick = {
clickCount++
if (clickCount == 5) {
if ((System.currentTimeMillis() % 2) == 0.toLong()) {
Toast.makeText(context, "🍨", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "✈️", Toast.LENGTH_SHORT).show()
RoundedScreenContainer(Modifier.padding(innerPadding)) {
SettingsContainer(Modifier.fillMaxSize()) {
item {
SettingsItem(
leadingIconRes = R.drawable.ic_numbers,
title = stringResource(R.string.pref_app_version),
description = SystemUtils.getAppVersion(context),
onClick = {
clickCount++
if (clickCount == 5) {
if ((System.currentTimeMillis() % 2) == 0.toLong()) {
Toast.makeText(context, "🍨", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "✈️", Toast.LENGTH_SHORT).show()
}
clickCount = 0
}
clickCount = 0
}
}
)
SettingsItem(
leadingIconRes = R.drawable.ic_person_4,
title = stringResource(R.string.pref_developer),
description = stringResource(R.string.developer_name),
onClick = { uriHandler.openUri(Constants.DEVELOPER_GITHUB) }
)
SettingsItem(
leadingIcon = GitHubIcon,
title = stringResource(R.string.pref_view_on_github),
description = stringResource(R.string.pref_view_on_github_desc),
onClick = { uriHandler.openUri(Constants.GITHUB_REPO) }
)
SettingsItem(
leadingIconRes = R.drawable.ic_balance,
title = stringResource(R.string.pref_licence),
description = stringResource(R.string.pref_licence_desc),
onClick = toLicencePage
)
SettingsItem(
leadingIconRes = R.drawable.ic_code_blocks,
title = stringResource(R.string.pref_developer_options),
description = stringResource(R.string.pref_developer_options_desc),
onClick = toDevPage
)
},
topRounded = true
)
}
item {
SettingsItem(
leadingIconRes = R.drawable.ic_person_4,
title = stringResource(R.string.pref_developer),
description = stringResource(R.string.developer_name),
onClick = { uriHandler.openUri(Constants.DEVELOPER_GITHUB) },
)
}
item {
SettingsItem(
leadingIcon = GitHubIcon,
title = stringResource(R.string.pref_view_on_github),
description = stringResource(R.string.pref_view_on_github_desc),
onClick = { uriHandler.openUri(Constants.GITHUB_REPO) }
)
}
item {
SettingsItem(
leadingIconRes = R.drawable.ic_balance,
title = stringResource(R.string.pref_licence),
description = stringResource(R.string.pref_licence_desc),
onClick = toLicencePage
)
}
item {
SettingsItem(
leadingIconRes = R.drawable.ic_code_blocks,
title = stringResource(R.string.pref_developer_options),
description = stringResource(R.string.pref_developer_options_desc),
onClick = toDevPage,
bottomRounded = true
)
}
}
}
}

View file

@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
@ -14,7 +13,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import cn.super12138.todo.R
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.licence.LicenceList
import com.mikepenz.aboutlibraries.ui.compose.android.produceLibraries
@ -24,10 +23,8 @@ fun SettingsAboutLicence(
onNavigateUp: () -> Unit,
modifier: Modifier = Modifier
) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
LargeTopAppBarScaffold(
TopAppBarScaffold(
title = stringResource(R.string.pref_licence),
scrollBehavior = scrollBehavior,
onBack = onNavigateUp,
modifier = modifier
) { innerPadding ->

View file

@ -1,25 +1,20 @@
package cn.super12138.todo.ui.pages.settings
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.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.Settings
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.RoundedScreenContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
import cn.super12138.todo.ui.pages.settings.components.SwitchSettingsItem
import cn.super12138.todo.ui.pages.settings.components.appearance.contrast.ContrastPicker
import cn.super12138.todo.ui.pages.settings.components.appearance.darkmode.DarkModePicker
@ -41,46 +36,48 @@ fun SettingsAppearance(
val contrastLevel by DataStoreManager.contrastLevelFlow.collectAsState(initial = Constants.PREF_CONTRAST_LEVEL_DEFAULT)
val scope = rememberCoroutineScope()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
LargeTopAppBarScaffold(
TopAppBarScaffold(
title = stringResource(R.string.pref_appearance),
onBack = onNavigateUp,
scrollBehavior = scrollBehavior,
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
modifier = modifier,
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = TodoDefaults.screenHorizontalPadding)
.verticalScroll(rememberScrollState())
) {
Settings {
SwitchSettingsItem(
checked = dynamicColor,
leadingIconRes = R.drawable.ic_wand_stars,
title = stringResource(R.string.pref_appearance_dynamic_color),
description = stringResource(R.string.pref_appearance_dynamic_color_desc),
onCheckedChange = { scope.launch { DataStoreManager.setDynamicColor(it) } },
)
RoundedScreenContainer(Modifier.padding(innerPadding)) {
SettingsContainer(Modifier.fillMaxSize()) {
item(key = 1) {
SwitchSettingsItem(
checked = dynamicColor,
leadingIconRes = R.drawable.ic_wand_stars,
title = stringResource(R.string.pref_appearance_dynamic_color),
description = stringResource(R.string.pref_appearance_dynamic_color_desc),
onCheckedChange = { scope.launch { DataStoreManager.setDynamicColor(it) } },
topRounded = true
)
}
DarkModePicker(
currentDarkMode = { DarkMode.fromId(darkMode) },
onDarkModeChange = { scope.launch { DataStoreManager.setDarkMode(it.id) } }
)
item(key = 2) {
DarkModePicker(
currentDarkMode = { DarkMode.fromId(darkMode) },
onDarkModeChange = { scope.launch { DataStoreManager.setDarkMode(it.id) } }
)
}
PalettePicker(
currentPalette = { PaletteStyle.fromId(paletteStyle) },
onPaletteChange = { scope.launch { DataStoreManager.setPaletteStyle(it.id) } },
isDynamicColor = dynamicColor,
isDarkMode = DarkMode.fromId(darkMode),
contrastLevel = ContrastLevel.fromFloat(contrastLevel)
)
item(key = 3) {
PalettePicker(
currentPalette = { PaletteStyle.fromId(paletteStyle) },
onPaletteChange = { scope.launch { DataStoreManager.setPaletteStyle(it.id) } },
isDynamicColor = dynamicColor,
isDarkMode = DarkMode.fromId(darkMode),
contrastLevel = ContrastLevel.fromFloat(contrastLevel)
)
}
ContrastPicker(
currentContrast = ContrastLevel.fromFloat(contrastLevel),
onContrastChange = { scope.launch { DataStoreManager.setContrastLevel(it.value) } }
)
item(key = 4) {
ContrastPicker(
currentContrast = ContrastLevel.fromFloat(contrastLevel),
onContrastChange = { scope.launch { DataStoreManager.setContrastLevel(it.value) } },
bottomRounded = true
)
}
}
}
}

View file

@ -4,16 +4,12 @@ import android.content.Context
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -22,17 +18,16 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.window.DialogProperties
import cn.super12138.todo.R
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.activities.MainActivity
import cn.super12138.todo.ui.components.ConfirmDialog
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.Settings
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.RoundedScreenContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsCategory
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsItem
import cn.super12138.todo.ui.viewmodels.MainViewModel
import cn.super12138.todo.utils.SystemUtils
@ -48,9 +43,7 @@ fun SettingsData(
modifier: Modifier = Modifier
) {
val context = LocalContext.current
var showRestoreDialog by rememberSaveable { mutableStateOf(false) }
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
@ -104,47 +97,49 @@ fun SettingsData(
}
)
LargeTopAppBarScaffold(
TopAppBarScaffold(
title = stringResource(R.string.pref_data),
onBack = onNavigateUp,
scrollBehavior = scrollBehavior,
snackbarHost = { SnackbarHost(snackbarHostState) },
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
modifier = modifier,
) { innerPadding ->
Column(
Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = TodoDefaults.screenHorizontalPadding)
.verticalScroll(rememberScrollState())
) {
SettingsCategory(stringResource(R.string.pref_category_data_management))
Settings {
SettingsItem(
leadingIconRes = R.drawable.ic_download,
title = stringResource(R.string.pref_backup),
description = stringResource(R.string.pref_backup_desc),
onClick = {
backupLauncher.launch("Todo-backup-${SystemUtils.getTime()}.zip")
}
)
SettingsItem(
leadingIconRes = R.drawable.ic_upload,
title = stringResource(R.string.pref_restore),
description = stringResource(R.string.pref_restore_desc),
onClick = {
restoreLauncher.launch(arrayOf("application/zip"))
}
)
}
SettingsCategory(stringResource(R.string.pref_category_category_management))
Settings {
SettingsItem(
leadingIconRes = R.drawable.ic_category,
title = stringResource(R.string.pref_category_category_management),
description = stringResource(R.string.pref_category_management_desc),
onClick = toCategoryManager
)
RoundedScreenContainer(Modifier.padding(innerPadding)) {
SettingsContainer(Modifier.fillMaxSize()) {
item {
SettingsCategory(title = stringResource(R.string.pref_category_data_management), first = true)
SettingsItem(
leadingIconRes = R.drawable.ic_download,
title = stringResource(R.string.pref_backup),
description = stringResource(R.string.pref_backup_desc),
onClick = {
backupLauncher.launch("Todo-backup-${SystemUtils.getTime()}.zip")
},
topRounded = true
)
}
item {
SettingsItem(
leadingIconRes = R.drawable.ic_upload,
title = stringResource(R.string.pref_restore),
description = stringResource(R.string.pref_restore_desc),
onClick = {
restoreLauncher.launch(arrayOf("application/zip"))
},
bottomRounded = true
)
}
item {
SettingsCategory(stringResource(R.string.pref_category_category_management))
SettingsItem(
leadingIconRes = R.drawable.ic_category,
title = stringResource(R.string.pref_category_category_management),
description = stringResource(R.string.pref_category_management_desc),
onClick = toCategoryManager,
topRounded = true,
bottomRounded = true
)
}
}
}
}

View file

@ -15,7 +15,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
@ -26,13 +25,12 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
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.text.style.TextAlign
import androidx.compose.ui.unit.IntOffset
import cn.super12138.todo.R
import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.TodoFloatingActionButton
import cn.super12138.todo.ui.pages.settings.components.category.CategoryItem
import cn.super12138.todo.ui.pages.settings.components.category.CategoryPromptDialog
@ -45,7 +43,6 @@ fun SettingsDataCategory(
modifier: Modifier = Modifier
) {
// TODO: 本页及其相关组件重组性能检查优化
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
val listState = rememberLazyListState()
@ -57,10 +54,9 @@ fun SettingsDataCategory(
val isExpanded by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
LargeTopAppBarScaffold(
TopAppBarScaffold(
title = stringResource(R.string.pref_category_category_management),
onBack = onNavigateUp,
scrollBehavior = scrollBehavior,
snackbarHost = { SnackbarHost(snackbarHostState) },
floatingActionButton = {
TodoFloatingActionButton(
@ -73,7 +69,7 @@ fun SettingsDataCategory(
}
)
},
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
modifier = modifier,
) { innerPadding ->
LazyColumn(
state = listState,

View file

@ -1,20 +1,15 @@
package cn.super12138.todo.ui.pages.settings
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.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import cn.super12138.todo.R
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.Settings
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.RoundedScreenContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsItem
@OptIn(ExperimentalMaterial3Api::class)
@ -24,26 +19,22 @@ fun SettingsDeveloperOptions(
onNavigateUp: () -> Unit,
modifier: Modifier = Modifier
) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
LargeTopAppBarScaffold(
TopAppBarScaffold(
title = stringResource(R.string.pref_developer_options),
onBack = onNavigateUp,
scrollBehavior = scrollBehavior,
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
modifier = modifier,
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = TodoDefaults.screenHorizontalPadding)
.verticalScroll(rememberScrollState())
) {
Settings {
SettingsItem(
leadingIconRes = R.drawable.ic_padding,
title = stringResource(R.string.pref_padding),
onClick = toPaddingPage
)
RoundedScreenContainer(Modifier.padding(innerPadding)) {
SettingsContainer(Modifier.fillMaxSize()) {
item {
SettingsItem(
leadingIconRes = R.drawable.ic_padding,
title = stringResource(R.string.pref_padding),
onClick = toPaddingPage,
topRounded = true,
bottomRounded = true
)
}
}
}
}

View file

@ -1,13 +1,9 @@
package cn.super12138.todo.ui.pages.settings
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.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@ -17,17 +13,16 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.logic.model.SortingMethod
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.Settings
import cn.super12138.todo.ui.components.RoundedScreenContainer
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.SettingsCategory
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsItem
import cn.super12138.todo.ui.pages.settings.components.SettingsPlainBox
import cn.super12138.todo.ui.pages.settings.components.SettingsRadioDialog
@ -48,76 +43,79 @@ fun SettingsInterface(
val context = LocalContext.current
val scope = rememberCoroutineScope()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
var showSortingMethodDialog by rememberSaveable { mutableStateOf(false) }
LargeTopAppBarScaffold(
TopAppBarScaffold(
title = stringResource(R.string.pref_interface_interaction),
onBack = onNavigateUp,
scrollBehavior = scrollBehavior,
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
modifier = modifier,
) { innerPadding ->
Column(
Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = TodoDefaults.screenHorizontalPadding)
.verticalScroll(rememberScrollState())
) {
SettingsCategory(stringResource(R.string.pref_category_todo_list))
Settings {
SwitchSettingsItem(
leadingIconRes = R.drawable.ic_checklist,
title = stringResource(R.string.pref_show_completed),
description = stringResource(R.string.pref_show_completed_desc),
checked = showCompleted,
onCheckedChange = { scope.launch { DataStoreManager.setShowCompleted(it) } },
)
SettingsItem(
leadingIconRes = R.drawable.ic_sort,
title = stringResource(R.string.pref_sorting_method),
description = SortingMethod.fromId(sortingMethod).getDisplayName(context),
onClick = { showSortingMethodDialog = true }
)
RoundedScreenContainer(Modifier.padding(innerPadding)) {
SettingsContainer(Modifier.fillMaxSize()) {
item {
SettingsCategory(title = stringResource(R.string.pref_category_todo_list), first = true)
SwitchSettingsItem(
leadingIconRes = R.drawable.ic_checklist,
title = stringResource(R.string.pref_show_completed),
description = stringResource(R.string.pref_show_completed_desc),
checked = showCompleted,
onCheckedChange = { scope.launch { DataStoreManager.setShowCompleted(it) } },
topRounded = true
)
}
item {
SettingsItem(
leadingIconRes = R.drawable.ic_sort,
title = stringResource(R.string.pref_sorting_method),
description = SortingMethod.fromId(sortingMethod).getDisplayName(context),
onClick = { showSortingMethodDialog = true },
bottomRounded = true
)
}
item {
SettingsCategory(stringResource(R.string.pref_category_global))
SwitchSettingsItem(
checked = secureMode,
leadingIconRes = R.drawable.ic_shield,
title = stringResource(R.string.pref_secure_mode),
description = stringResource(R.string.pref_secure_mode_desc),
onCheckedChange = { scope.launch { DataStoreManager.setSecureMode(it) } },
topRounded = true
)
}
item {
SwitchSettingsItem(
checked = hapticFeedback,
leadingIconRes = R.drawable.ic_touch_long,
title = stringResource(R.string.pref_haptic_feedback),
description = stringResource(R.string.pref_haptic_feedback_desc),
onCheckedChange = { scope.launch { DataStoreManager.setHapticFeedback(it) } },
bottomRounded = true
)
SettingsPlainBox(stringResource(R.string.pref_haptic_feedback_more_info))
}
}
SettingsCategory(stringResource(R.string.pref_category_global))
Settings {
SwitchSettingsItem(
checked = secureMode,
leadingIconRes = R.drawable.ic_shield,
title = stringResource(R.string.pref_secure_mode),
description = stringResource(R.string.pref_secure_mode_desc),
onCheckedChange = { scope.launch { DataStoreManager.setSecureMode(it) } }
)
SwitchSettingsItem(
checked = hapticFeedback,
leadingIconRes = R.drawable.ic_touch_long,
title = stringResource(R.string.pref_haptic_feedback),
description = stringResource(R.string.pref_haptic_feedback_desc),
onCheckedChange = { scope.launch { DataStoreManager.setHapticFeedback(it) } }
)
}
SettingsPlainBox(stringResource(R.string.pref_haptic_feedback_more_info))
}
}
val sortingList = remember {
SortingMethod.entries.map {
SettingsRadioOptions(
id = it.id,
text = it.getDisplayName(context)
)
val sortingList = remember {
SortingMethod.entries.map {
SettingsRadioOptions(
id = it.id,
text = it.getDisplayName(context)
)
}
}
SettingsRadioDialog(
visible = showSortingMethodDialog,
title = stringResource(R.string.pref_sorting_method),
currentOptions = SettingsRadioOptions(
id = sortingMethod,
text = SortingMethod.fromId(sortingMethod).getDisplayName(context)
),
options = sortingList,
onSelect = { scope.launch { DataStoreManager.setSortingMethod(it) } },
onDismiss = { showSortingMethodDialog = false }
)
}
SettingsRadioDialog(
visible = showSortingMethodDialog,
title = stringResource(R.string.pref_sorting_method),
currentOptions = SettingsRadioOptions(
id = sortingMethod,
text = SortingMethod.fromId(sortingMethod).getDisplayName(context)
),
options = sortingList,
onSelect = { scope.launch { DataStoreManager.setSortingMethod(it) } },
onDismiss = { showSortingMethodDialog = false }
)
}

View file

@ -1,21 +1,16 @@
package cn.super12138.todo.ui.pages.settings
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.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import cn.super12138.todo.R
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.Settings
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.RoundedScreenContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsItem
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@ -28,46 +23,50 @@ fun SettingsMain(
onNavigateUp: () -> Unit,
modifier: Modifier = Modifier
) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
LargeTopAppBarScaffold(
TopAppBarScaffold(
title = stringResource(R.string.page_settings),
scrollBehavior = scrollBehavior,
onBack = onNavigateUp,
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
modifier = modifier
) { innerPadding ->
Column(
Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = TodoDefaults.screenHorizontalPadding)
.verticalScroll(rememberScrollState())
) {
Settings {
SettingsItem(
leadingIconRes = R.drawable.ic_palette,
title = stringResource(R.string.pref_appearance),
description = stringResource(R.string.pref_appearance_desc),
onClick = toAppearancePage
)
SettingsItem(
leadingIconRes = R.drawable.ic_view_comfy,
title = stringResource(R.string.pref_interface_interaction),
description = stringResource(R.string.pref_interface_interaction_desc),
onClick = toInterfacePage
)
SettingsItem(
leadingIconRes = R.drawable.ic_dns,
title = stringResource(R.string.pref_data),
description = stringResource(R.string.pref_data_desc),
onClick = toDataPage
)
SettingsItem(
leadingIconRes = R.drawable.ic_info,
title = stringResource(R.string.pref_about),
description = stringResource(R.string.pref_about_desc),
onClick = toAboutPage
)
RoundedScreenContainer(Modifier.padding(innerPadding)) {
SettingsContainer(Modifier.fillMaxSize()) {
item {
SettingsItem(
leadingIconRes = R.drawable.ic_palette,
title = stringResource(R.string.pref_appearance),
description = stringResource(R.string.pref_appearance_desc),
onClick = toAppearancePage,
topRounded = true
)
}
item {
SettingsItem(
leadingIconRes = R.drawable.ic_view_comfy,
title = stringResource(R.string.pref_interface_interaction),
description = stringResource(R.string.pref_interface_interaction_desc),
onClick = toInterfacePage
)
}
item {
SettingsItem(
leadingIconRes = R.drawable.ic_dns,
title = stringResource(R.string.pref_data),
description = stringResource(R.string.pref_data_desc),
onClick = toDataPage
)
}
item {
SettingsItem(
leadingIconRes = R.drawable.ic_info,
title = stringResource(R.string.pref_about),
description = stringResource(R.string.pref_about_desc),
onClick = toAboutPage,
bottomRounded = true
)
}
}
}
}

View file

@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -20,19 +21,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.sp
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.utils.drawFadedEdge
import cn.super12138.todo.utils.getPartialRoundedShape
@Composable
fun RowSettingsItem(
@ -41,13 +38,14 @@ fun RowSettingsItem(
title: String,
description: String? = null,
trailingContent: (@Composable () -> Unit)? = null,
shape: Shape = MaterialTheme.shapes.small,
background: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
background: Color = TodoDefaults.ContainerColor,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
scrollState: ScrollState = rememberScrollState(),
fadedEdgeWidth: Dp,
maskColor: Color = MaterialTheme.colorScheme.background,
maskColor: Color = TodoDefaults.ContainerColor,
topRounded: Boolean = false,
bottomRounded: Boolean = false,
content: @Composable RowScope.() -> Unit
) {
MoreContentSettingsItem(
@ -55,8 +53,9 @@ fun RowSettingsItem(
title = title,
description = description,
trailingContent = trailingContent,
shape = shape,
background = background,
topRounded = topRounded,
bottomRounded = bottomRounded,
modifier = modifier
) {
Row(
@ -94,12 +93,13 @@ fun LazyRowSettingsItem(
title: String,
description: String? = null,
trailingContent: (@Composable () -> Unit)? = null,
shape: Shape = MaterialTheme.shapes.small,
background: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
background: Color = TodoDefaults.ContainerColor,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
fadedEdgeWidth: Dp,
maskColor: Color = MaterialTheme.colorScheme.background,
maskColor: Color = TodoDefaults.ContainerColor,
topRounded: Boolean = false,
bottomRounded: Boolean = false,
content: LazyListScope.() -> Unit
) {
MoreContentSettingsItem(
@ -107,7 +107,8 @@ fun LazyRowSettingsItem(
title = title,
description = description,
trailingContent = trailingContent,
shape = shape,
topRounded = topRounded,
bottomRounded = bottomRounded,
background = background,
modifier = modifier
) {
@ -145,15 +146,18 @@ fun MoreContentSettingsItem(
title: String,
description: String? = null,
trailingContent: (@Composable () -> Unit)? = null,
shape: Shape = MaterialTheme.shapes.small,
background: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
background: Color = TodoDefaults.ContainerColor,
shape: CornerBasedShape = TodoDefaults.SettingsItemDefaultShape,
roundedShape: CornerBasedShape = TodoDefaults.SettingsItemRoundedShape,
topRounded: Boolean = false,
bottomRounded: Boolean = false,
content: @Composable () -> Unit
) {
Column(
modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
.clip(shape)
.clip(shape.getPartialRoundedShape(topRounded, bottomRounded, roundedShape))
.background(background)
.padding(
horizontal = TodoDefaults.settingsItemHorizontalPadding,
@ -195,22 +199,3 @@ fun MoreContentSettingsItem(
content()
}
}
fun ContentDrawScope.drawFadedEdge(
edgeWidth: Dp,
maskColor: Color,
leftEdge: Boolean
) {
val edgeWidthPx = edgeWidth.toPx()
drawRect(
topLeft = Offset(if (leftEdge) 0f else size.width - edgeWidthPx, 0f),
size = Size(edgeWidthPx, size.height),
brush =
Brush.horizontalGradient(
colors = listOf(Color.Transparent, maskColor),
startX = if (leftEdge) 0f else size.width,
endX = if (leftEdge) edgeWidthPx else size.width - edgeWidthPx
),
blendMode = BlendMode.DstIn
)
}

View file

@ -8,20 +8,23 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import cn.super12138.todo.ui.TodoDefaults
@Composable
fun SettingsCategory(
title: String,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
first: Boolean = false,
) {
Box(
modifier = modifier
.fillMaxWidth()
.padding(
top = TodoDefaults.settingsItemVerticalPadding / 2,
start = TodoDefaults.settingsItemHorizontalPadding,
end = TodoDefaults.settingsItemHorizontalPadding,
top = if (first) 0.dp else TodoDefaults.settingsItemVerticalPadding,
start = TodoDefaults.settingsItemHorizontalPadding / 2,
end = TodoDefaults.settingsItemHorizontalPadding / 2,
bottom = TodoDefaults.settingsItemVerticalPadding / 2
)
) {
Text(

View file

@ -1,28 +1,35 @@
package cn.super12138.todo.ui.pages.settings.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import cn.super12138.todo.ui.TodoDefaults
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun Settings(
fun SettingsContainer(
modifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit,
state: LazyListState = rememberLazyListState(),
content: LazyListScope.() -> Unit,
) {
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
LazyColumn(
verticalArrangement = Arrangement.spacedBy(TodoDefaults.settingsItemPadding),
state = state,
modifier = modifier
.padding(vertical = TodoDefaults.screenVerticalPadding)
.clip(MaterialTheme.shapes.extraLarge),
content = content
)
) {
item {
Spacer(modifier = Modifier.size(TodoDefaults.settingsItemVerticalPadding))
}
content()
item {
Spacer(modifier = Modifier.size(TodoDefaults.settingsItemVerticalPadding))
}
}
}

View file

@ -76,7 +76,7 @@ fun RadioItem(
modifier
.fillMaxWidth()
.height(56.dp)
.clip(MaterialTheme.shapes.small)
.clip(TodoDefaults.SettingsItemDefaultShape)
.background(MaterialTheme.colorScheme.surfaceContainerHighest)
.selectable(
selected = selected,

View file

@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@ -17,7 +18,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView
@ -26,6 +26,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.sp
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.utils.VibrationUtils
import cn.super12138.todo.utils.getPartialRoundedShape
@Composable
fun SettingsItem(
@ -33,6 +34,8 @@ fun SettingsItem(
@DrawableRes leadingIconRes: Int,
title: String,
description: String? = null,
topRounded: Boolean = false,
bottomRounded: Boolean = false,
enableClick: Boolean = true,
onClick: () -> Unit = {}
) {
@ -41,6 +44,8 @@ fun SettingsItem(
title = title,
description = description,
trailingContent = null,
topRounded = topRounded,
bottomRounded = bottomRounded,
enableClick = enableClick,
onClick = onClick,
modifier = modifier
@ -53,6 +58,8 @@ fun SettingsItem(
leadingIcon: ImageVector? = null,
title: String,
description: String? = null,
topRounded: Boolean = false,
bottomRounded: Boolean = false,
enableClick: Boolean = true,
onClick: () -> Unit = {}
) {
@ -61,6 +68,8 @@ fun SettingsItem(
title = title,
description = description,
trailingContent = null,
topRounded = topRounded,
bottomRounded = bottomRounded,
enableClick = enableClick,
onClick = onClick,
modifier = modifier
@ -74,7 +83,9 @@ fun SettingsItem(
title: String,
description: String? = null,
trailingContent: (@Composable () -> Unit)? = null,
background: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
background: Color = TodoDefaults.ContainerColor,
topRounded: Boolean = false,
bottomRounded: Boolean = false,
enableClick: Boolean = true,
onClick: () -> Unit = {}
) {
@ -93,6 +104,8 @@ fun SettingsItem(
description = description,
trailingContent = trailingContent,
background = background,
topRounded = topRounded,
bottomRounded = bottomRounded,
enableClick = enableClick,
onClick = onClick,
modifier = modifier
@ -106,7 +119,9 @@ fun SettingsItem(
title: String,
description: String? = null,
trailingContent: (@Composable () -> Unit)? = null,
background: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
background: Color = TodoDefaults.ContainerColor,
topRounded: Boolean = false,
bottomRounded: Boolean = false,
enableClick: Boolean = true,
onClick: () -> Unit = {}
) {
@ -125,6 +140,8 @@ fun SettingsItem(
description = description,
trailingContent = trailingContent,
background = background,
topRounded = topRounded,
bottomRounded = bottomRounded,
enableClick = enableClick,
onClick = onClick,
modifier = modifier
@ -139,8 +156,11 @@ fun SettingsItem(
title: String,
description: String? = null,
trailingContent: (@Composable () -> Unit)? = null,
shape: Shape = MaterialTheme.shapes.small,
background: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
background: Color = TodoDefaults.ContainerColor,
shape: CornerBasedShape = TodoDefaults.SettingsItemDefaultShape,
roundedShape: CornerBasedShape = TodoDefaults.SettingsItemRoundedShape,
topRounded: Boolean = false,
bottomRounded: Boolean = false,
enableClick: Boolean = true,
onClick: () -> Unit = {},
) {
@ -149,7 +169,7 @@ fun SettingsItem(
modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
.clip(shape)
.clip(shape.getPartialRoundedShape(topRounded, bottomRounded, roundedShape))
.clickable(
enabled = enableClick,
onClick = {

View file

@ -30,9 +30,8 @@ fun SettingsPlainBox(
.fillMaxWidth()
.wrapContentHeight()
.padding(
start = TodoDefaults.settingsItemHorizontalPadding,
end = TodoDefaults.settingsItemHorizontalPadding,
bottom = TodoDefaults.settingsItemVerticalPadding
vertical = TodoDefaults.settingsItemVerticalPadding,
horizontal = TodoDefaults.settingsItemHorizontalPadding / 2
),
) {
Icon(

View file

@ -17,10 +17,11 @@ fun SwitchSettingsItem(
@DrawableRes leadingIconRes: Int,
title: String,
description: String? = null,
topRounded: Boolean = false,
bottomRounded: Boolean = false,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
val view = LocalView.current
SettingsItem(
leadingIcon = painterResource(leadingIconRes),
title = title,
@ -28,14 +29,13 @@ fun SwitchSettingsItem(
trailingContent = {
Switch(
checked = checked,
onCheckedChange = {
VibrationUtils.performHapticFeedback(view)
onCheckedChange(it)
},
onCheckedChange = null,
modifier = Modifier.padding(start = TodoDefaults.settingsItemHorizontalPadding / 2)
)
},
onClick = { onCheckedChange(!checked) },
topRounded = topRounded,
bottomRounded = bottomRounded,
modifier = modifier,
)
}

View file

@ -35,15 +35,19 @@ import cn.super12138.todo.utils.VibrationUtils
@Composable
fun ContrastPicker(
modifier: Modifier = Modifier,
currentContrast: ContrastLevel,
onContrastChange: (ContrastLevel) -> Unit,
modifier: Modifier = Modifier
topRounded: Boolean = false,
bottomRounded: Boolean = false
) {
val view = LocalView.current
val context = LocalContext.current
MoreContentSettingsItem(
title = stringResource(R.string.pref_contrast_level),
description = stringResource(R.string.pref_contrast_level_desc),
topRounded = topRounded,
bottomRounded = bottomRounded,
modifier = modifier
) {
val contrastLevelName =

View file

@ -13,14 +13,17 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cn.super12138.todo.R
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.pages.settings.components.LazyRowSettingsItem
import cn.super12138.todo.ui.theme.DarkMode
@Composable
fun DarkModePicker(
modifier: Modifier = Modifier,
currentDarkMode: () -> DarkMode,
onDarkModeChange: (darkMode: DarkMode) -> Unit,
modifier: Modifier = Modifier
topRounded: Boolean = false,
bottomRounded: Boolean = false
) {
val context = LocalContext.current
val isInDarkTheme = isSystemInDarkTheme()
@ -31,7 +34,9 @@ fun DarkModePicker(
title = stringResource(R.string.pref_dark_mode),
description = stringResource(R.string.pref_dark_mode_desc),
horizontalArrangement = Arrangement.spacedBy(5.dp),
fadedEdgeWidth = 8.dp,
fadedEdgeWidth = TodoDefaults.fadedEdgeWidth,
topRounded = topRounded,
bottomRounded = bottomRounded,
modifier = modifier
) {
items(items = darkModeList, key = { it.id }) {

View file

@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cn.super12138.todo.R
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.pages.settings.components.LazyRowSettingsItem
import cn.super12138.todo.ui.theme.ContrastLevel
import cn.super12138.todo.ui.theme.DarkMode
@ -18,12 +19,14 @@ import cn.super12138.todo.ui.theme.PaletteStyle
@Composable
fun PalettePicker(
modifier: Modifier = Modifier,
currentPalette: () -> PaletteStyle,
onPaletteChange: (paletteStyle: PaletteStyle) -> Unit,
isDynamicColor: Boolean,
isDarkMode: DarkMode,
contrastLevel: ContrastLevel,
modifier: Modifier = Modifier
topRounded: Boolean = false,
bottomRounded: Boolean = false
) {
val paletteOptions = remember { PaletteStyle.entries.toList() }
@ -31,7 +34,9 @@ fun PalettePicker(
title = stringResource(R.string.pref_palette_style),
description = stringResource(R.string.pref_palette_style_desc),
horizontalArrangement = Arrangement.spacedBy(5.dp),
fadedEdgeWidth = 8.dp,
fadedEdgeWidth = TodoDefaults.fadedEdgeWidth,
topRounded = topRounded,
bottomRounded = bottomRounded,
modifier = modifier
) {
items(items = paletteOptions, key = { it.id }) {

View file

@ -1,9 +1,16 @@
package cn.super12138.todo.utils
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.unit.Dp
import cn.super12138.todo.logic.model.Priority
@Composable
@ -15,4 +22,37 @@ fun Priority.containerColor(): Color =
Priority.Default -> MaterialTheme.colorScheme.secondary
Priority.Important -> MaterialTheme.colorScheme.tertiary
Priority.Urgent -> MaterialTheme.colorScheme.error
}
}
@Composable
fun CornerBasedShape.getPartialRoundedShape(
topRounded: Boolean,
bottomRounded: Boolean,
roundedShape: CornerBasedShape
): CornerBasedShape =
this.copy(
topStart = if (topRounded) roundedShape.topStart else this.topStart,
topEnd = if (topRounded) roundedShape.topEnd else this.topEnd,
bottomEnd = if (bottomRounded) roundedShape.bottomEnd else this.bottomEnd,
bottomStart = if (bottomRounded) roundedShape.bottomStart else this.bottomStart,
)
fun ContentDrawScope.drawFadedEdge(
edgeWidth: Dp,
maskColor: Color,
leftEdge: Boolean
) {
val edgeWidthPx = edgeWidth.toPx()
drawRect(
topLeft = Offset(if (leftEdge) 0f else size.width - edgeWidthPx, 0f),
size = Size(edgeWidthPx, size.height),
brush =
Brush.horizontalGradient(
colors = listOf(Color.Transparent, maskColor),
startX = if (leftEdge) 0f else size.width,
endX = if (leftEdge) edgeWidthPx else size.width - edgeWidthPx
),
blendMode = BlendMode.DstIn
)
}