feat: 将按钮和进度条进行 MD3E 改造

This commit is contained in:
Super12138 2025-11-08 08:21:59 +08:00
parent 96fdb86e91
commit e7b75aadce
12 changed files with 195 additions and 163 deletions

View file

@ -113,7 +113,9 @@ dependencies {
implementation(libs.aboutlibraries.core) implementation(libs.aboutlibraries.core)
implementation(libs.aboutlibraries.compose) implementation(libs.aboutlibraries.compose)
// M3 Color // M3 Color
implementation(libs.com.kyant0.m3color) implementation(libs.kyant0.m3color)
// Capsule
implementation(libs.kyant0.capsule)
// Konfetti // Konfetti
implementation(libs.nl.dionsegijn.konfetti.compose) implementation(libs.nl.dionsegijn.konfetti.compose)
// Lazy Column Scrollbar // Lazy Column Scrollbar

View file

@ -17,9 +17,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.VibrationUtils
import com.kyant.capsule.ContinuousRoundedRectangle
@Composable @Composable
fun ConfirmDialog( fun ConfirmDialog(
@ -87,7 +89,10 @@ fun BasicDialog(
VibrationUtils.performHapticFeedback(view) VibrationUtils.performHapticFeedback(view)
onConfirm() onConfirm()
}, },
shapes = ButtonDefaults.shapes() shapes = ButtonDefaults.shapes(
/*shape = ContinuousRoundedRectangle(50.dp),
pressedShape = ContinuousRoundedRectangle(12.dp)*/
)
) { ) {
Text(confirmButton) Text(confirmButton)
} }

View file

@ -1,14 +1,9 @@
package cn.super12138.todo.ui.pages.main package cn.super12138.todo.ui.pages.main
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.expandIn
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkOut
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
@ -24,6 +19,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
@ -32,12 +28,12 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.window.core.layout.WindowWidthSizeClass import androidx.window.core.layout.WindowSizeClass
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.database.TodoEntity
import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.ConfirmDialog
import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar
@ -46,17 +42,17 @@ import cn.super12138.todo.ui.viewmodels.MainViewModel
@OptIn(ExperimentalSharedTransitionApi::class, ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalSharedTransitionApi::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun MainPage( fun MainPage(
modifier: Modifier = Modifier,
viewModel: MainViewModel, viewModel: MainViewModel,
toTodoEditPage: () -> Unit, toTodoEditPage: () -> Unit,
toSettingsPage: () -> Unit, toSettingsPage: () -> Unit,
sharedTransitionScope: SharedTransitionScope, sharedTransitionScope: SharedTransitionScope,
animatedVisibilityScope: AnimatedVisibilityScope, animatedVisibilityScope: AnimatedVisibilityScope,
modifier: Modifier = Modifier windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass,
) { ) {
val toDos = viewModel.sortedTodos.collectAsState(initial = emptyList()) val toDos = viewModel.sortedTodos.collectAsState(initial = emptyList())
val selectedTodos = viewModel.selectedTodoIds.collectAsState() val selectedTodos = viewModel.selectedTodoIds.collectAsState()
val showCompleted by DataStoreManager.showCompletedFlow.collectAsState(initial = Constants.PREF_SHOW_COMPLETED_DEFAULT) val showCompleted by DataStoreManager.showCompletedFlow.collectAsState(initial = Constants.PREF_SHOW_COMPLETED_DEFAULT)
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
val listState = rememberLazyListState() val listState = rememberLazyListState()
var showDeleteConfirmDialog by rememberSaveable { mutableStateOf(false) } var showDeleteConfirmDialog by rememberSaveable { mutableStateOf(false) }
@ -86,99 +82,37 @@ fun MainPage(
}, },
floatingActionButton = { floatingActionButton = {
with(sharedTransitionScope) { with(sharedTransitionScope) {
AnimatedVisibility( SmallExtendedFloatingActionButton(
visible = !inSelectedMode, text = { Text(stringResource(R.string.action_add_task)) },
enter = fadeIn() + expandIn(), icon = {
exit = shrinkOut() + fadeOut() Icon(
) { imageVector = Icons.Outlined.Add,
// TODO: 修复在滑动列表时FAB位移导致的动画不连贯临时方案为底部加padding contentDescription = null
/*AnimatedExtendedFloatingActionButton( )
icon = Icons.Outlined.Add, },
text = stringResource(R.string.action_add_task), expanded = expandedFab,
expanded = true, onClick = {
onClick = { viewModel.setEditTodoItem(null) // 每次添加待办前清除上一次已选待办
viewModel.setEditTodoItem(null) // 每次添加待办前清除上一次已选待办 toTodoEditPage()
toTodoEditPage() },
}, modifier = Modifier
modifier = Modifier.sharedElement( .sharedElement(
sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION), sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION),
animatedVisibilityScope = animatedVisibilityScope animatedVisibilityScope = animatedVisibilityScope
) )
)*/ .animateFloatingActionButton(
SmallExtendedFloatingActionButton( visible = !inSelectedMode,
text = { Text(stringResource(R.string.action_add_task)) }, alignment = Alignment.BottomEnd,
icon = {
Icon(
imageVector = Icons.Outlined.Add,
contentDescription = null
)
},
expanded = expandedFab,
onClick = {
viewModel.setEditTodoItem(null) // 每次添加待办前清除上一次已选待办
toTodoEditPage()
},
modifier = Modifier.sharedElement(
sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION),
animatedVisibilityScope = animatedVisibilityScope
) )
) )
}
} }
}, },
contentWindowInsets = WindowInsets(0, 0, 0, 0), contentWindowInsets = WindowInsets(0, 0, 0, 0),
modifier = modifier modifier = modifier
) { innerPadding -> ) { innerPadding ->
if (windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT) { val isMediumScreen =
Column( windowSizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND)
modifier = Modifier.padding( if (isMediumScreen) {
top = innerPadding.calculateTopPadding(),
bottom = innerPadding.calculateBottomPadding()
)
) {
ProgressFragment(
totalTasks = totalTasks,
completedTasks = completedTasks,
modifier = Modifier
.weight(2f)
.fillMaxSize()
)
ManagerFragment(
state = listState,
list = filteredTodoList,
onItemClick = { item ->
if (inSelectedMode) {
viewModel.toggleTodoSelection(item)
} else {
viewModel.setEditTodoItem(item)
toTodoEditPage()
}
},
onItemLongClick = { viewModel.toggleTodoSelection(it) },
onItemChecked = { item ->
item.apply {
viewModel.updateTodo(
TodoEntity(
content = content,
category = category,
isCompleted = true,
priority = priority,
id = id
)
)
viewModel.playConfetti()
}
},
selectedTodoIds = selectedTodoIds,
// sharedTransitionScope = sharedTransitionScope,
// animatedVisibilityScope = animatedVisibilityScope,
modifier = Modifier
.weight(3f)
.fillMaxSize()
)
}
} else {
Row( Row(
modifier = Modifier.padding( modifier = Modifier.padding(
top = innerPadding.calculateTopPadding(), top = innerPadding.calculateTopPadding(),
@ -204,20 +138,48 @@ fun MainPage(
} }
}, },
onItemLongClick = { viewModel.toggleTodoSelection(it) }, onItemLongClick = { viewModel.toggleTodoSelection(it) },
onItemChecked = { item -> onItemChecked = {
item.apply { viewModel.updateTodo(it.copy(isCompleted = true))
viewModel.updateTodo( viewModel.playConfetti()
TodoEntity( },
content = content, selectedTodoIds = selectedTodoIds,
category = category, // sharedTransitionScope = sharedTransitionScope,
isCompleted = true, // animatedVisibilityScope = animatedVisibilityScope,
priority = priority, modifier = Modifier
id = id .weight(3f)
) .fillMaxSize()
) )
viewModel.playConfetti() }
} else {
Column(
modifier = Modifier.padding(
top = innerPadding.calculateTopPadding(),
bottom = innerPadding.calculateBottomPadding()
)
) {
ProgressFragment(
totalTasks = totalTasks,
completedTasks = completedTasks,
modifier = Modifier
.weight(2f)
.fillMaxSize()
)
ManagerFragment(
state = listState,
list = filteredTodoList,
onItemClick = { item ->
if (inSelectedMode) {
viewModel.toggleTodoSelection(item)
} else {
viewModel.setEditTodoItem(item)
toTodoEditPage()
} }
}, },
onItemLongClick = { viewModel.toggleTodoSelection(it) },
onItemChecked = {
viewModel.updateTodo(it.copy(isCompleted = true))
viewModel.playConfetti()
},
selectedTodoIds = selectedTodoIds, selectedTodoIds = selectedTodoIds,
// sharedTransitionScope = sharedTransitionScope, // sharedTransitionScope = sharedTransitionScope,
// animatedVisibilityScope = animatedVisibilityScope, // animatedVisibilityScope = animatedVisibilityScope,

View file

@ -45,6 +45,14 @@ fun ManagerFragment(
bottom = TodoDefaults.toDoCardHeight / 2, bottom = TodoDefaults.toDoCardHeight / 2,
end = TodoDefaults.screenPadding end = TodoDefaults.screenPadding
), ),
/*modifier = Modifier
.clip(
MaterialTheme.shapes.extraLarge.copy(
bottomEnd = CornerSize(0.dp),
bottomStart = CornerSize(0.dp)
)
)
.background(MaterialTheme.colorScheme.surfaceContainerHighest)*/
// verticalArrangement = Arrangement.spacedBy(10.dp) // verticalArrangement = Arrangement.spacedBy(10.dp)
) { ) {
if (list.isEmpty()) { if (list.isEmpty()) {

View file

@ -1,7 +1,9 @@
package cn.super12138.todo.ui.pages.settings package cn.super12138.todo.ui.pages.settings
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -10,6 +12,8 @@ import androidx.compose.material.icons.outlined.DeveloperMode
import androidx.compose.material.icons.outlined.Numbers import androidx.compose.material.icons.outlined.Numbers
import androidx.compose.material.icons.outlined.Person4 import androidx.compose.material.icons.outlined.Person4
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -19,19 +23,22 @@ import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants 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.LargeTopAppBarScaffold
import cn.super12138.todo.ui.icons.GitHubIcon import cn.super12138.todo.ui.icons.GitHubIcon
import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.ui.pages.settings.components.SettingsItem
import cn.super12138.todo.utils.SystemUtils import cn.super12138.todo.utils.SystemUtils
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun SettingsAbout( fun SettingsAbout(
//toSpecialPage: () -> Unit, //toSpecialPage: () -> Unit,
@ -51,9 +58,12 @@ fun SettingsAbout(
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
LazyColumn( LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxWidth()
.padding(innerPadding) .padding(innerPadding)
.padding(horizontal = TodoDefaults.screenPadding)
.clip(MaterialTheme.shapes.extraExtraLarge)
) { ) {
item { item {
var clickCount by remember { mutableIntStateOf(0) } var clickCount by remember { mutableIntStateOf(0) }

View file

@ -4,15 +4,18 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Category import androidx.compose.material.icons.outlined.Category
import androidx.compose.material.icons.outlined.FileDownload import androidx.compose.material.icons.outlined.FileDownload
import androidx.compose.material.icons.outlined.FileUpload import androidx.compose.material.icons.outlined.FileUpload
import androidx.compose.material.icons.outlined.RestartAlt import androidx.compose.material.icons.outlined.RestartAlt
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
@ -29,9 +32,11 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.activities.MainActivity import cn.super12138.todo.ui.activities.MainActivity
import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.ConfirmDialog
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.Settings
import cn.super12138.todo.ui.pages.settings.components.SettingsCategory import cn.super12138.todo.ui.pages.settings.components.SettingsCategory
import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.ui.pages.settings.components.SettingsItem
import cn.super12138.todo.ui.viewmodels.MainViewModel import cn.super12138.todo.ui.viewmodels.MainViewModel
@ -39,7 +44,7 @@ import cn.super12138.todo.utils.SystemUtils
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.system.exitProcess import kotlin.system.exitProcess
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun SettingsData( fun SettingsData(
viewModel: MainViewModel, viewModel: MainViewModel,
@ -111,13 +116,15 @@ fun SettingsData(
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = { SnackbarHost(snackbarHostState) },
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { innerPadding -> ) { innerPadding ->
LazyColumn( Column(
modifier = Modifier Modifier
.fillMaxSize() .fillMaxSize()
.padding(innerPadding) .padding(innerPadding)
.padding(horizontal = TodoDefaults.screenPadding)
.verticalScroll(rememberScrollState())
) { ) {
item { SettingsCategory(stringResource(R.string.pref_category_data_management))
SettingsCategory(stringResource(R.string.pref_category_data_management)) Settings {
SettingsItem( SettingsItem(
leadingIcon = Icons.Outlined.FileDownload, leadingIcon = Icons.Outlined.FileDownload,
title = stringResource(R.string.pref_backup), title = stringResource(R.string.pref_backup),
@ -126,8 +133,6 @@ fun SettingsData(
backupLauncher.launch("Todo-backup-${SystemUtils.getTime()}.zip") backupLauncher.launch("Todo-backup-${SystemUtils.getTime()}.zip")
} }
) )
}
item {
SettingsItem( SettingsItem(
leadingIcon = Icons.Outlined.FileUpload, leadingIcon = Icons.Outlined.FileUpload,
title = stringResource(R.string.pref_restore), title = stringResource(R.string.pref_restore),
@ -137,8 +142,8 @@ fun SettingsData(
} }
) )
} }
item { SettingsCategory(stringResource(R.string.pref_category_category_management))
SettingsCategory(stringResource(R.string.pref_category_category_management)) Settings {
SettingsItem( SettingsItem(
leadingIcon = Icons.Outlined.Category, leadingIcon = Icons.Outlined.Category,
title = stringResource(R.string.pref_category_category_management), title = stringResource(R.string.pref_category_category_management),
@ -147,17 +152,17 @@ fun SettingsData(
) )
} }
} }
ConfirmDialog(
visible = showRestoreDialog,
icon = Icons.Outlined.RestartAlt,
title = stringResource(R.string.tip_tips),
text = stringResource(R.string.tip_restore_success),
showDismissButton = false,
onConfirm = { restartApp(context) },
onDismiss = { showRestoreDialog = false },
properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
)
} }
ConfirmDialog(
visible = showRestoreDialog,
icon = Icons.Outlined.RestartAlt,
title = stringResource(R.string.tip_tips),
text = stringResource(R.string.tip_restore_success),
showDismissButton = false,
onConfirm = { restartApp(context) },
onDismiss = { showRestoreDialog = false },
properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
)
} }
/** /**

View file

@ -1,14 +1,17 @@
package cn.super12138.todo.ui.pages.settings package cn.super12138.todo.ui.pages.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Sort import androidx.compose.material.icons.automirrored.outlined.Sort
import androidx.compose.material.icons.outlined.Checklist import androidx.compose.material.icons.outlined.Checklist
import androidx.compose.material.icons.outlined.Shield import androidx.compose.material.icons.outlined.Shield
import androidx.compose.material.icons.outlined.Vibration import androidx.compose.material.icons.outlined.Vibration
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@ -26,7 +29,9 @@ import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.logic.model.SortingMethod 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.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.Settings
import cn.super12138.todo.ui.pages.settings.components.SettingsCategory import cn.super12138.todo.ui.pages.settings.components.SettingsCategory
import cn.super12138.todo.ui.pages.settings.components.SettingsItem 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.SettingsPlainBox
@ -35,7 +40,7 @@ import cn.super12138.todo.ui.pages.settings.components.SettingsRadioOptions
import cn.super12138.todo.ui.pages.settings.components.SwitchSettingsItem import cn.super12138.todo.ui.pages.settings.components.SwitchSettingsItem
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun SettingsInterface( fun SettingsInterface(
onNavigateUp: () -> Unit, onNavigateUp: () -> Unit,
@ -56,16 +61,15 @@ fun SettingsInterface(
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { innerPadding -> ) { innerPadding ->
Column(
LazyColumn( Modifier
modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(innerPadding) .padding(innerPadding)
.padding(horizontal = TodoDefaults.screenPadding)
.verticalScroll(rememberScrollState())
) { ) {
item { SettingsCategory(stringResource(R.string.pref_category_todo_list))
SettingsCategory(stringResource(R.string.pref_category_todo_list)) Settings {
}
item {
SwitchSettingsItem( SwitchSettingsItem(
checked = showCompleted, checked = showCompleted,
leadingIcon = Icons.Outlined.Checklist, leadingIcon = Icons.Outlined.Checklist,
@ -73,8 +77,6 @@ fun SettingsInterface(
description = stringResource(R.string.pref_show_completed_desc), description = stringResource(R.string.pref_show_completed_desc),
onCheckedChange = { scope.launch { DataStoreManager.setShowCompleted(it) } }, onCheckedChange = { scope.launch { DataStoreManager.setShowCompleted(it) } },
) )
}
item {
SettingsItem( SettingsItem(
leadingIcon = Icons.AutoMirrored.Outlined.Sort, leadingIcon = Icons.AutoMirrored.Outlined.Sort,
title = stringResource(R.string.pref_sorting_method), title = stringResource(R.string.pref_sorting_method),
@ -82,11 +84,8 @@ fun SettingsInterface(
onClick = { showSortingMethodDialog = true } onClick = { showSortingMethodDialog = true }
) )
} }
SettingsCategory(stringResource(R.string.pref_category_global))
item { Settings {
SettingsCategory(stringResource(R.string.pref_category_global))
}
item {
SwitchSettingsItem( SwitchSettingsItem(
checked = secureMode, checked = secureMode,
leadingIcon = Icons.Outlined.Shield, leadingIcon = Icons.Outlined.Shield,
@ -94,8 +93,6 @@ fun SettingsInterface(
description = stringResource(R.string.pref_secure_mode_desc), description = stringResource(R.string.pref_secure_mode_desc),
onCheckedChange = { scope.launch { DataStoreManager.setSecureMode(it) } } onCheckedChange = { scope.launch { DataStoreManager.setSecureMode(it) } }
) )
}
item {
SwitchSettingsItem( SwitchSettingsItem(
checked = hapticFeedback, checked = hapticFeedback,
leadingIcon = Icons.Outlined.Vibration, leadingIcon = Icons.Outlined.Vibration,
@ -103,9 +100,10 @@ fun SettingsInterface(
description = stringResource(R.string.pref_haptic_feedback_desc), description = stringResource(R.string.pref_haptic_feedback_desc),
onCheckedChange = { scope.launch { DataStoreManager.setHapticFeedback(it) } } onCheckedChange = { scope.launch { DataStoreManager.setHapticFeedback(it) } }
) )
SettingsPlainBox(stringResource(R.string.pref_haptic_feedback_more_info))
} }
SettingsPlainBox(stringResource(R.string.pref_haptic_feedback_more_info))
} }
} }
val sortingList = remember { val sortingList = remember {

View file

@ -1,24 +1,29 @@
package cn.super12138.todo.ui.pages.settings package cn.super12138.todo.ui.pages.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ColorLens import androidx.compose.material.icons.outlined.ColorLens
import androidx.compose.material.icons.outlined.Dns import androidx.compose.material.icons.outlined.Dns
import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.ViewComfy import androidx.compose.material.icons.outlined.ViewComfy
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.Settings
import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.ui.pages.settings.components.SettingsItem
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun SettingsMain( fun SettingsMain(
toAppearancePage: () -> Unit, toAppearancePage: () -> Unit,
@ -35,42 +40,39 @@ fun SettingsMain(
onBack = onNavigateUp, onBack = onNavigateUp,
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection) modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) { innerPadding -> ) { innerPadding ->
LazyColumn( Column(
modifier = Modifier Modifier
.fillMaxSize() .fillMaxSize()
.padding(innerPadding) .padding(innerPadding)
.padding(horizontal = TodoDefaults.screenPadding)
.verticalScroll(rememberScrollState())
) { ) {
item { Settings {
SettingsItem( SettingsItem(
leadingIcon = Icons.Outlined.ColorLens, leadingIcon = Icons.Outlined.ColorLens,
title = stringResource(R.string.pref_appearance), title = stringResource(R.string.pref_appearance),
description = stringResource(R.string.pref_appearance_desc), description = stringResource(R.string.pref_appearance_desc),
onClick = toAppearancePage onClick = toAppearancePage
) )
}
item {
SettingsItem( SettingsItem(
leadingIcon = Icons.Outlined.ViewComfy, leadingIcon = Icons.Outlined.ViewComfy,
title = stringResource(R.string.pref_interface_interaction), title = stringResource(R.string.pref_interface_interaction),
description = stringResource(R.string.pref_interface_interaction_desc), description = stringResource(R.string.pref_interface_interaction_desc),
onClick = toInterfacePage onClick = toInterfacePage
) )
}
item {
SettingsItem( SettingsItem(
leadingIcon = Icons.Outlined.Dns, leadingIcon = Icons.Outlined.Dns,
title = stringResource(R.string.pref_data), title = stringResource(R.string.pref_data),
description = stringResource(R.string.pref_data_desc), description = stringResource(R.string.pref_data_desc),
onClick = toDataPage onClick = toDataPage
) )
}
item {
SettingsItem( SettingsItem(
leadingIcon = Icons.Outlined.Info, leadingIcon = Icons.Outlined.Info,
title = stringResource(R.string.pref_about), title = stringResource(R.string.pref_about),
description = stringResource(R.string.pref_about_desc), description = stringResource(R.string.pref_about_desc),
onClick = toAboutPage onClick = toAboutPage
) )
} }
} }
} }

View file

@ -0,0 +1,24 @@
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.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun Settings(
modifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit,
) {
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
modifier = modifier.clip(MaterialTheme.shapes.extraExtraLarge),
content = content
)
}

View file

@ -1,11 +1,13 @@
package cn.super12138.todo.ui.pages.settings.components package cn.super12138.todo.ui.pages.settings.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
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.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -13,6 +15,7 @@ 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
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
@ -48,6 +51,7 @@ fun SettingsItem(
title: String, title: String,
description: String? = null, description: String? = null,
trailingContent: (@Composable () -> Unit)? = null, trailingContent: (@Composable () -> Unit)? = null,
background: Color = MaterialTheme.colorScheme.surfaceContainerHighest,
enableClick: Boolean = true, enableClick: Boolean = true,
onClick: () -> Unit = {} onClick: () -> Unit = {}
) { ) {
@ -65,12 +69,14 @@ fun SettingsItem(
title = title, title = title,
description = description, description = description,
trailingContent = trailingContent, trailingContent = trailingContent,
background = background,
enableClick = enableClick, enableClick = enableClick,
onClick = onClick, onClick = onClick,
modifier = modifier modifier = modifier
) )
} }
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun SettingsItem( fun SettingsItem(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -78,9 +84,10 @@ fun SettingsItem(
title: String, title: String,
description: String? = null, description: String? = null,
trailingContent: (@Composable () -> Unit)? = null, trailingContent: (@Composable () -> Unit)? = null,
shape: Shape = MaterialTheme.shapes.large, shape: Shape = MaterialTheme.shapes.small,
background: Color = MaterialTheme.colorScheme.surfaceContainerHighest,
enableClick: Boolean = true, enableClick: Boolean = true,
onClick: () -> Unit = {} onClick: () -> Unit = {},
) { ) {
val view = LocalView.current val view = LocalView.current
Row( Row(
@ -95,6 +102,7 @@ fun SettingsItem(
onClick() onClick()
} }
) )
.background(background)
.padding( .padding(
horizontal = TodoDefaults.settingsItemHorizontalPadding, horizontal = TodoDefaults.settingsItemHorizontalPadding,
vertical = TodoDefaults.settingsItemVerticalPadding vertical = TodoDefaults.settingsItemVerticalPadding

View file

@ -8,6 +8,8 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -26,6 +28,7 @@ import cn.super12138.todo.utils.VibrationUtils
import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun LicenceList( fun LicenceList(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -80,7 +83,8 @@ fun LicenceList(
onClick = { onClick = {
openDialog = false openDialog = false
VibrationUtils.performHapticFeedback(view) VibrationUtils.performHapticFeedback(view)
} },
shapes = ButtonDefaults.shapes()
) { Text(stringResource(R.string.action_confirm)) } ) { Text(stringResource(R.string.action_confirm)) }
}, },
onDismissRequest = { openDialog = false } onDismissRequest = { openDialog = false }

View file

@ -18,6 +18,8 @@ aboutLibsRelease = "13.1.0"
aboutLibsReleasePlugin = "13.1.0" aboutLibsReleasePlugin = "13.1.0"
# M3 Color # M3 Color
m3color = "2025.4" m3color = "2025.4"
# Capsule
capsule = "2.1.1"
# Konfetti # Konfetti
konfetti = "2.0.5" konfetti = "2.0.5"
# Lazy Column Scrollbar # Lazy Column Scrollbar
@ -70,7 +72,9 @@ aboutlibraries-core = { group = "com.mikepenz", name = "aboutlibraries-core", ve
aboutlibraries-compose = { group = "com.mikepenz", name = "aboutlibraries-compose-m3", version.ref = "aboutLibsRelease" } aboutlibraries-compose = { group = "com.mikepenz", name = "aboutlibraries-compose-m3", version.ref = "aboutLibsRelease" }
# M3 Color # M3 Color
com-kyant0-m3color = { group = "com.github.Kyant0", name = "m3color", version.ref = "m3color" } kyant0-m3color = { group = "com.github.Kyant0", name = "m3color", version.ref = "m3color" }
# Capsule
kyant0-capsule = { group = "io.github.kyant0", name = "capsule", version.ref = "capsule" }
# Konfetti # Konfetti
nl-dionsegijn-konfetti-compose = { group = "nl.dionsegijn", name = "konfetti-compose", version.ref = "konfetti" } nl-dionsegijn-konfetti-compose = { group = "nl.dionsegijn", name = "konfetti-compose", version.ref = "konfetti" }