From 96fdb86e917f724609b0267d6f4f56b6a4b9638c Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sat, 25 Oct 2025 16:47:11 +0800 Subject: [PATCH 01/21] =?UTF-8?q?refactor:=20=E5=AF=B9=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=20Material=203=20Expressive=20=E9=80=82?= =?UTF-8?q?=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo/logic/model/SortingMethod.kt | 5 ++ .../cn/super12138/todo/ui/TodoDefaults.kt | 20 ++++++++ .../todo/ui/components/ChipGroup.kt | 34 +++++--------- .../super12138/todo/ui/components/Dialogs.kt | 9 +++- .../todo/ui/components/ProgressIndicator.kt | 34 ++++++++------ .../todo/ui/pages/crash/CrashPage.kt | 21 ++++++--- .../todo/ui/pages/editor/TodoEditorPage.kt | 33 +++++++++---- .../super12138/todo/ui/pages/main/MainPage.kt | 28 +++++++++-- .../todo/ui/pages/main/ProgressFragment.kt | 26 +++++++++-- .../todo/ui/pages/main/components/TodoCard.kt | 46 +++++++++---------- .../cn/super12138/todo/ui/theme/Theme.kt | 6 ++- 11 files changed, 177 insertions(+), 85 deletions(-) diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt index ab2c03e..2c73a4c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt @@ -6,14 +6,19 @@ import cn.super12138.todo.R enum class SortingMethod(val id: Int) { // 按添加先后顺序 Sequential(1), + // 按学科 Category(2), + // 按优先级 Priority(3), + // 按完成情况 Completion(4), + // 按字母升序 AlphabeticalAscending(5), + // 按字母降序 AlphabeticalDescending(6); diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt index 2a66eb9..8fac742 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt @@ -22,4 +22,24 @@ object TodoDefaults { * 设置项垂直边距 */ val settingsItemVerticalPadding = 20.dp + + /** + * 待办进度条粗度 + */ + val trackThickness = 7.0.dp + + /** + * 待办进度条波长 + */ + val waveLength = 35.dp + + /** + * 待办进度条波速 + */ + val waveSpeed = 3.dp + + /** + * 待办进度条波幅 + */ + const val waveAmplitude = 0.6f } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt index ab5bffb..779ff3e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt @@ -1,11 +1,5 @@ package cn.super12138.todo.ui.components -import android.util.Log -import androidx.compose.animation.AnimatedVisibility -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.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.padding @@ -18,19 +12,16 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import cn.super12138.todo.R import cn.super12138.todo.utils.VibrationUtils -import kotlin.math.log /** * 部分参考:https://github.com/Rhythamtech/FilterChipGroup-Compose-Android/blob/main/FilterChipGroup.kt @@ -75,20 +66,19 @@ private fun FilterChipItem( FilterChip( selected = selected, onClick = onClick, - leadingIcon = { - AnimatedVisibility( - visible = selected, - enter = expandIn(expandFrom = Alignment.CenterStart) + fadeIn(), - exit = shrinkOut(shrinkTowards = Alignment.CenterStart) + fadeOut() - ) { - Icon( - imageVector = Icons.Outlined.Check, - contentDescription = stringResource(R.string.tip_select_this), - modifier = Modifier.size(FilterChipDefaults.IconSize) - ) - } - }, label = { Text(text) }, + leadingIcon = + if (selected) { + { + Icon( + imageVector = Icons.Outlined.Check, + contentDescription = stringResource(R.string.tip_select_this), + modifier = Modifier.size(FilterChipDefaults.IconSize) + ) + } + } else { + null + }, modifier = modifier.padding(end = 10.dp) ) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt index baf89df..adf81e4 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt @@ -6,6 +6,8 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ErrorOutline import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -50,6 +52,7 @@ fun ConfirmDialog( ) } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun BasicDialog( modifier: Modifier = Modifier, @@ -83,7 +86,8 @@ fun BasicDialog( onClick = { VibrationUtils.performHapticFeedback(view) onConfirm() - } + }, + shapes = ButtonDefaults.shapes() ) { Text(confirmButton) } @@ -94,7 +98,8 @@ fun BasicDialog( onClick = { VibrationUtils.performHapticFeedback(view) onDismiss() - } + }, + shapes = ButtonDefaults.shapes() ) { Text(it) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/ProgressIndicator.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/ProgressIndicator.kt index de34004..3349d36 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/ProgressIndicator.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/ProgressIndicator.kt @@ -1,39 +1,47 @@ package cn.super12138.todo.ui.components import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.CircularWavyProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ProgressIndicatorDefaults +import androidx.compose.material3.WavyProgressIndicatorDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.unit.Dp -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable -fun AnimatedCircularProgressIndicator( +fun AnimatedCircularWavyProgressIndicator( progress: Float, modifier: Modifier = Modifier, - color: Color = ProgressIndicatorDefaults.circularColor, - strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth, - trackColor: Color = ProgressIndicatorDefaults.circularDeterminateTrackColor, - strokeCap: StrokeCap = ProgressIndicatorDefaults.CircularDeterminateStrokeCap, - gapSize: Dp = ProgressIndicatorDefaults.CircularIndicatorTrackGapSize + color: Color = WavyProgressIndicatorDefaults.indicatorColor, + trackColor: Color = WavyProgressIndicatorDefaults.trackColor, + stroke: Stroke = WavyProgressIndicatorDefaults.circularIndicatorStroke, + trackStroke: Stroke = WavyProgressIndicatorDefaults.circularTrackStroke, + gapSize: Dp = WavyProgressIndicatorDefaults.CircularIndicatorTrackGapSize, + amplitude: (progress: Float) -> Float = WavyProgressIndicatorDefaults.indicatorAmplitude, + wavelength: Dp = WavyProgressIndicatorDefaults.CircularWavelength, + waveSpeed: Dp = wavelength, ) { val animatedProgress by animateFloatAsState( targetValue = progress, animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec ) - CircularProgressIndicator( + CircularWavyProgressIndicator( progress = { animatedProgress }, modifier = modifier, color = color, - strokeWidth = strokeWidth, trackColor = trackColor, - strokeCap = strokeCap, - gapSize = gapSize + stroke = stroke, + trackStroke = trackStroke, + gapSize = gapSize, + amplitude = amplitude, + wavelength = wavelength, + waveSpeed = waveSpeed, ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt index 547e84e..f64295e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt @@ -13,9 +13,12 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ExitToApp import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -40,12 +43,11 @@ import cn.super12138.todo.ui.activities.CrashActivity.Companion.BRAND_PREFIX import cn.super12138.todo.ui.activities.CrashActivity.Companion.CRASH_TIME_PREFIX import cn.super12138.todo.ui.activities.CrashActivity.Companion.DEVICE_SDK_PREFIX import cn.super12138.todo.ui.activities.CrashActivity.Companion.MODEL_PREFIX -import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun CrashPage( crashLog: String, @@ -77,11 +79,16 @@ fun CrashPage( ) }, floatingActionButton = { - AnimatedExtendedFloatingActionButton( - onClick = exitApp, - icon = Icons.AutoMirrored.Outlined.ExitToApp, - text = stringResource(R.string.action_exit_app), - expanded = isExpanded + SmallExtendedFloatingActionButton( + text = { Text(stringResource(R.string.action_exit_app)) }, + icon = { + Icon( + imageVector = Icons.AutoMirrored.Outlined.ExitToApp, + contentDescription = null + ) + }, + expanded = isExpanded, + onClick = exitApp ) }, contentWindowInsets = WindowInsets(0, 0, 0, 0) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index 2ea70bf..4b62680 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -23,7 +23,10 @@ import androidx.compose.material.icons.automirrored.outlined.Undo import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Save import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults @@ -45,7 +48,6 @@ import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.ui.TodoDefaults -import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton import cn.super12138.todo.ui.components.ChipItem import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.LargeTopAppBarScaffold @@ -56,7 +58,10 @@ import cn.super12138.todo.ui.pages.editor.components.TodoPrioritySlider import cn.super12138.todo.ui.pages.editor.state.rememberEditorState import cn.super12138.todo.utils.VibrationUtils -@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) +@OptIn( + ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class, + ExperimentalMaterial3ExpressiveApi::class +) @Composable fun TodoEditorPage( modifier: Modifier = Modifier, @@ -119,22 +124,32 @@ fun TodoEditorPage( with(sharedTransitionScope) { Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { if (toDo !== null) { - AnimatedExtendedFloatingActionButton( - icon = Icons.Outlined.Delete, - text = stringResource(R.string.action_delete), + SmallExtendedFloatingActionButton( + text = { Text(stringResource(R.string.action_delete)) }, + icon = { + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = null + ) + }, expanded = true, containerColor = MaterialTheme.colorScheme.errorContainer, onClick = { uiState.showDeleteConfirmDialog = true }, modifier = Modifier.imePadding() ) } - AnimatedExtendedFloatingActionButton( - icon = Icons.Outlined.Save, - text = stringResource(R.string.action_save), + SmallExtendedFloatingActionButton( + text = { Text(stringResource(R.string.action_save)) }, + icon = { + Icon( + imageVector = Icons.Outlined.Save, + contentDescription = null + ) + }, expanded = true, onClick = { if (uiState.setErrorIfNotValid()) { - return@AnimatedExtendedFloatingActionButton + return@SmallExtendedFloatingActionButton } else { uiState.clearError() val newTodo = TodoEntity( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index a2ec7c9..bdf86f8 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -18,7 +18,11 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold +import androidx.compose.material3.SmallExtendedFloatingActionButton +import androidx.compose.material3.Text import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -35,12 +39,11 @@ import cn.super12138.todo.R import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.datastore.DataStoreManager -import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar import cn.super12138.todo.ui.viewmodels.MainViewModel -@OptIn(ExperimentalSharedTransitionApi::class) +@OptIn(ExperimentalSharedTransitionApi::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun MainPage( viewModel: MainViewModel, @@ -65,6 +68,7 @@ fun MainPage( val completedTasks by remember { derivedStateOf { toDoList.count { it.isCompleted } } } val filteredTodoList = if (showCompleted) toDoList else toDoList.filter { item -> !item.isCompleted } + val expandedFab by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } } // 当按下返回键(或进行返回操作)时清空选择,仅在非选择模式下生效 BackHandler(inSelectedMode) { viewModel.clearAllTodoSelection() } @@ -88,7 +92,7 @@ fun MainPage( exit = shrinkOut() + fadeOut() ) { // TODO: 修复在滑动列表时FAB位移导致的动画不连贯(临时方案为底部加padding) - AnimatedExtendedFloatingActionButton( + /*AnimatedExtendedFloatingActionButton( icon = Icons.Outlined.Add, text = stringResource(R.string.action_add_task), expanded = true, @@ -100,6 +104,24 @@ fun MainPage( sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION), animatedVisibilityScope = animatedVisibilityScope ) + )*/ + SmallExtendedFloatingActionButton( + text = { Text(stringResource(R.string.action_add_task)) }, + 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 + ) ) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt index ee059a9..98934b0 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt @@ -10,14 +10,18 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import cn.super12138.todo.R -import cn.super12138.todo.ui.components.AnimatedCircularProgressIndicator +import cn.super12138.todo.ui.TodoDefaults +import cn.super12138.todo.ui.components.AnimatedCircularWavyProgressIndicator @Composable fun ProgressFragment( @@ -25,6 +29,11 @@ fun ProgressFragment( completedTasks: Int, modifier: Modifier = Modifier ) { + val stroke = Stroke( + width = with(LocalDensity.current) { TodoDefaults.trackThickness.toPx() }, + cap = StrokeCap.Round, + ) + val context = LocalContext.current val remainTasks = totalTasks - completedTasks @@ -39,10 +48,19 @@ fun ProgressFragment( modifier = modifier, contentAlignment = Alignment.Center ) { - AnimatedCircularProgressIndicator( + AnimatedCircularWavyProgressIndicator( progress = progress, - strokeWidth = 10.dp, - gapSize = 10.dp, + stroke = stroke, + trackStroke = stroke, + amplitude = { progress -> + if (progress <= 0.1f || progress >= 0.95f) { + 0f + } else { + TodoDefaults.waveAmplitude + } + }, + wavelength = TodoDefaults.waveLength, + waveSpeed = TodoDefaults.waveSpeed, modifier = Modifier .size(175.dp) .clearAndSetSemantics {} diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt index 079ed00..dd22418 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt @@ -120,33 +120,33 @@ fun TodoCard( } ) { // with(sharedTransitionScope) { - Text( - text = content, - style = MaterialTheme.typography.titleLarge, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, - modifier = Modifier - /*.sharedBounds( - sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CONTENT_TRANSITION}_$id"), - animatedVisibilityScope = animatedVisibilityScope - )*/ - .basicMarquee() // TODO: 后续评估性能影响 - ) + Text( + text = content, + style = MaterialTheme.typography.titleLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, + modifier = Modifier + /*.sharedBounds( + sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CONTENT_TRANSITION}_$id"), + animatedVisibilityScope = animatedVisibilityScope + )*/ + .basicMarquee() // TODO: 后续评估性能影响 + ) // } } // with(sharedTransitionScope) { - Text( - text = category.ifEmpty { stringResource(R.string.tip_default_category) }, - style = MaterialTheme.typography.labelMedium, - textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, - maxLines = 1, - /*modifier = Modifier.sharedBounds( - sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CATEGORY_TRANSITION}_$id"), - animatedVisibilityScope = animatedVisibilityScope - )*/ - ) + Text( + text = category.ifEmpty { stringResource(R.string.tip_default_category) }, + style = MaterialTheme.typography.labelMedium, + textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, + maxLines = 1, + /*modifier = Modifier.sharedBounds( + sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CATEGORY_TRANSITION}_$id"), + animatedVisibilityScope = animatedVisibilityScope + )*/ + ) // } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt b/app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt index cfbd8c7..25505ab 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt @@ -2,11 +2,13 @@ package cn.super12138.todo.ui.theme import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.MaterialExpressiveTheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun ToDoTheme( color: Color? = null, @@ -33,7 +35,7 @@ fun ToDoTheme( contrastLevel = contrastLevel ) - MaterialTheme( + MaterialExpressiveTheme( colorScheme = colorScheme, typography = Typography, content = content From e7b75aadce3f0e133b8abffcb99935cb1391957d Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sat, 8 Nov 2025 08:21:59 +0800 Subject: [PATCH 02/21] =?UTF-8?q?feat:=20=E5=B0=86=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E5=92=8C=E8=BF=9B=E5=BA=A6=E6=9D=A1=E8=BF=9B=E8=A1=8C=20MD3E?= =?UTF-8?q?=20=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 +- .../super12138/todo/ui/components/Dialogs.kt | 7 +- .../super12138/todo/ui/pages/main/MainPage.kt | 172 +++++++----------- .../todo/ui/pages/main/ManagerFragment.kt | 8 + .../todo/ui/pages/settings/SettingsAbout.kt | 14 +- .../todo/ui/pages/settings/SettingsData.kt | 45 +++-- .../settings/SettingsInterfaceInteraction.kt | 36 ++-- .../todo/ui/pages/settings/SettingsMain.kt | 24 +-- .../settings/components/SettingsContainer.kt | 24 +++ .../pages/settings/components/SettingsItem.kt | 12 +- .../components/licence/LicenceList.kt | 6 +- gradle/libs.versions.toml | 6 +- 12 files changed, 195 insertions(+), 163 deletions(-) create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d350124..509e92f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -113,7 +113,9 @@ dependencies { implementation(libs.aboutlibraries.core) implementation(libs.aboutlibraries.compose) // M3 Color - implementation(libs.com.kyant0.m3color) + implementation(libs.kyant0.m3color) + // Capsule + implementation(libs.kyant0.capsule) // Konfetti implementation(libs.nl.dionsegijn.konfetti.compose) // Lazy Column Scrollbar diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt index adf81e4..a22a290 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt @@ -17,9 +17,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties import cn.super12138.todo.R import cn.super12138.todo.utils.VibrationUtils +import com.kyant.capsule.ContinuousRoundedRectangle @Composable fun ConfirmDialog( @@ -87,7 +89,10 @@ fun BasicDialog( VibrationUtils.performHapticFeedback(view) onConfirm() }, - shapes = ButtonDefaults.shapes() + shapes = ButtonDefaults.shapes( + /*shape = ContinuousRoundedRectangle(50.dp), + pressedShape = ContinuousRoundedRectangle(12.dp)*/ + ) ) { Text(confirmButton) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index bdf86f8..8ec35a1 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -1,14 +1,9 @@ package cn.super12138.todo.ui.pages.main import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalSharedTransitionApi 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.Row import androidx.compose.foundation.layout.WindowInsets @@ -24,6 +19,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.Text import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.material3.animateFloatingActionButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf @@ -32,12 +28,12 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier 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.constants.Constants -import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.ui.components.ConfirmDialog 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) @Composable fun MainPage( + modifier: Modifier = Modifier, viewModel: MainViewModel, toTodoEditPage: () -> Unit, toSettingsPage: () -> Unit, sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope, - modifier: Modifier = Modifier + windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass, ) { val toDos = viewModel.sortedTodos.collectAsState(initial = emptyList()) val selectedTodos = viewModel.selectedTodoIds.collectAsState() val showCompleted by DataStoreManager.showCompletedFlow.collectAsState(initial = Constants.PREF_SHOW_COMPLETED_DEFAULT) - val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass val listState = rememberLazyListState() var showDeleteConfirmDialog by rememberSaveable { mutableStateOf(false) } @@ -86,99 +82,37 @@ fun MainPage( }, floatingActionButton = { with(sharedTransitionScope) { - AnimatedVisibility( - visible = !inSelectedMode, - enter = fadeIn() + expandIn(), - exit = shrinkOut() + fadeOut() - ) { - // TODO: 修复在滑动列表时FAB位移导致的动画不连贯(临时方案为底部加padding) - /*AnimatedExtendedFloatingActionButton( - icon = Icons.Outlined.Add, - text = stringResource(R.string.action_add_task), - expanded = true, - onClick = { - viewModel.setEditTodoItem(null) // 每次添加待办前清除上一次已选待办 - toTodoEditPage() - }, - modifier = Modifier.sharedElement( + SmallExtendedFloatingActionButton( + text = { Text(stringResource(R.string.action_add_task)) }, + 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 ) - )*/ - SmallExtendedFloatingActionButton( - text = { Text(stringResource(R.string.action_add_task)) }, - 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 + .animateFloatingActionButton( + visible = !inSelectedMode, + alignment = Alignment.BottomEnd, ) - ) - } + ) } }, contentWindowInsets = WindowInsets(0, 0, 0, 0), modifier = modifier ) { innerPadding -> - if (windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT) { - 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 = { 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 { + val isMediumScreen = + windowSizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND) + if (isMediumScreen) { Row( modifier = Modifier.padding( top = innerPadding.calculateTopPadding(), @@ -204,20 +138,48 @@ fun MainPage( } }, onItemLongClick = { viewModel.toggleTodoSelection(it) }, - onItemChecked = { item -> - item.apply { - viewModel.updateTodo( - TodoEntity( - content = content, - category = category, - isCompleted = true, - priority = priority, - id = id - ) - ) - viewModel.playConfetti() + onItemChecked = { + viewModel.updateTodo(it.copy(isCompleted = true)) + viewModel.playConfetti() + }, + selectedTodoIds = selectedTodoIds, + // sharedTransitionScope = sharedTransitionScope, + // animatedVisibilityScope = animatedVisibilityScope, + modifier = Modifier + .weight(3f) + .fillMaxSize() + ) + } + } 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, // sharedTransitionScope = sharedTransitionScope, // animatedVisibilityScope = animatedVisibilityScope, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt index 0449b24..1517d59 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt @@ -45,6 +45,14 @@ fun ManagerFragment( bottom = TodoDefaults.toDoCardHeight / 2, 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) ) { if (list.isEmpty()) { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt index 6a2a6e1..e726ec4 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt @@ -1,7 +1,9 @@ package cn.super12138.todo.ui.pages.settings import android.widget.Toast +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn 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.Person4 import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -19,19 +23,22 @@ import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip 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.icons.GitHubIcon import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.utils.SystemUtils import kotlinx.coroutines.delay -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun SettingsAbout( //toSpecialPage: () -> Unit, @@ -51,9 +58,12 @@ fun SettingsAbout( val uriHandler = LocalUriHandler.current LazyColumn( + verticalArrangement = Arrangement.spacedBy(2.dp), modifier = Modifier - .fillMaxSize() + .fillMaxWidth() .padding(innerPadding) + .padding(horizontal = TodoDefaults.screenPadding) + .clip(MaterialTheme.shapes.extraExtraLarge) ) { item { var clickCount by remember { mutableIntStateOf(0) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt index b98852a..15b3400 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt @@ -4,15 +4,18 @@ 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.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Category import androidx.compose.material.icons.outlined.FileDownload import androidx.compose.material.icons.outlined.FileUpload import androidx.compose.material.icons.outlined.RestartAlt 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 @@ -29,9 +32,11 @@ 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.pages.settings.components.SettingsCategory import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.ui.viewmodels.MainViewModel @@ -39,7 +44,7 @@ import cn.super12138.todo.utils.SystemUtils import kotlinx.coroutines.launch import kotlin.system.exitProcess -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun SettingsData( viewModel: MainViewModel, @@ -111,13 +116,15 @@ fun SettingsData( snackbarHost = { SnackbarHost(snackbarHostState) }, modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), ) { innerPadding -> - LazyColumn( - modifier = Modifier + Column( + Modifier .fillMaxSize() .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( leadingIcon = Icons.Outlined.FileDownload, title = stringResource(R.string.pref_backup), @@ -126,8 +133,6 @@ fun SettingsData( backupLauncher.launch("Todo-backup-${SystemUtils.getTime()}.zip") } ) - } - item { SettingsItem( leadingIcon = Icons.Outlined.FileUpload, 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( leadingIcon = Icons.Outlined.Category, 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) + ) } /** diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt index bb6e57b..785ab7b 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt @@ -1,14 +1,17 @@ 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.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.Sort import androidx.compose.material.icons.outlined.Checklist import androidx.compose.material.icons.outlined.Shield import androidx.compose.material.icons.outlined.Vibration 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 @@ -26,7 +29,9 @@ 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.pages.settings.components.SettingsCategory import cn.super12138.todo.ui.pages.settings.components.SettingsItem 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 kotlinx.coroutines.launch -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun SettingsInterface( onNavigateUp: () -> Unit, @@ -56,16 +61,15 @@ fun SettingsInterface( scrollBehavior = scrollBehavior, modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), ) { innerPadding -> - - LazyColumn( - modifier = Modifier + Column( + Modifier .fillMaxSize() .padding(innerPadding) + .padding(horizontal = TodoDefaults.screenPadding) + .verticalScroll(rememberScrollState()) ) { - item { - SettingsCategory(stringResource(R.string.pref_category_todo_list)) - } - item { + SettingsCategory(stringResource(R.string.pref_category_todo_list)) + Settings { SwitchSettingsItem( checked = showCompleted, leadingIcon = Icons.Outlined.Checklist, @@ -73,8 +77,6 @@ fun SettingsInterface( description = stringResource(R.string.pref_show_completed_desc), onCheckedChange = { scope.launch { DataStoreManager.setShowCompleted(it) } }, ) - } - item { SettingsItem( leadingIcon = Icons.AutoMirrored.Outlined.Sort, title = stringResource(R.string.pref_sorting_method), @@ -82,11 +84,8 @@ fun SettingsInterface( onClick = { showSortingMethodDialog = true } ) } - - item { - SettingsCategory(stringResource(R.string.pref_category_global)) - } - item { + SettingsCategory(stringResource(R.string.pref_category_global)) + Settings { SwitchSettingsItem( checked = secureMode, leadingIcon = Icons.Outlined.Shield, @@ -94,8 +93,6 @@ fun SettingsInterface( description = stringResource(R.string.pref_secure_mode_desc), onCheckedChange = { scope.launch { DataStoreManager.setSecureMode(it) } } ) - } - item { SwitchSettingsItem( checked = hapticFeedback, leadingIcon = Icons.Outlined.Vibration, @@ -103,9 +100,10 @@ fun SettingsInterface( description = stringResource(R.string.pref_haptic_feedback_desc), 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 { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt index 55ef93c..27ebe88 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt @@ -1,24 +1,29 @@ 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.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ColorLens import androidx.compose.material.icons.outlined.Dns import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.ViewComfy 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.pages.settings.components.SettingsItem -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun SettingsMain( toAppearancePage: () -> Unit, @@ -35,42 +40,39 @@ fun SettingsMain( onBack = onNavigateUp, modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection) ) { innerPadding -> - LazyColumn( - modifier = Modifier + Column( + Modifier .fillMaxSize() .padding(innerPadding) + .padding(horizontal = TodoDefaults.screenPadding) + .verticalScroll(rememberScrollState()) ) { - item { + Settings { SettingsItem( leadingIcon = Icons.Outlined.ColorLens, title = stringResource(R.string.pref_appearance), description = stringResource(R.string.pref_appearance_desc), onClick = toAppearancePage ) - } - item { SettingsItem( leadingIcon = Icons.Outlined.ViewComfy, title = stringResource(R.string.pref_interface_interaction), description = stringResource(R.string.pref_interface_interaction_desc), onClick = toInterfacePage ) - } - item { SettingsItem( leadingIcon = Icons.Outlined.Dns, title = stringResource(R.string.pref_data), description = stringResource(R.string.pref_data_desc), onClick = toDataPage ) - } - item { SettingsItem( leadingIcon = Icons.Outlined.Info, title = stringResource(R.string.pref_about), description = stringResource(R.string.pref_about_desc), onClick = toAboutPage ) + } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt new file mode 100644 index 0000000..dd746d0 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt @@ -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 + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt index c4f4c35..55e8b75 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt @@ -1,11 +1,13 @@ package cn.super12138.todo.ui.pages.settings.components +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -13,6 +15,7 @@ import androidx.compose.runtime.Composable 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.vector.ImageVector import androidx.compose.ui.platform.LocalView @@ -48,6 +51,7 @@ fun SettingsItem( title: String, description: String? = null, trailingContent: (@Composable () -> Unit)? = null, + background: Color = MaterialTheme.colorScheme.surfaceContainerHighest, enableClick: Boolean = true, onClick: () -> Unit = {} ) { @@ -65,12 +69,14 @@ fun SettingsItem( title = title, description = description, trailingContent = trailingContent, + background = background, enableClick = enableClick, onClick = onClick, modifier = modifier ) } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SettingsItem( modifier: Modifier = Modifier, @@ -78,9 +84,10 @@ fun SettingsItem( title: String, description: String? = null, trailingContent: (@Composable () -> Unit)? = null, - shape: Shape = MaterialTheme.shapes.large, + shape: Shape = MaterialTheme.shapes.small, + background: Color = MaterialTheme.colorScheme.surfaceContainerHighest, enableClick: Boolean = true, - onClick: () -> Unit = {} + onClick: () -> Unit = {}, ) { val view = LocalView.current Row( @@ -95,6 +102,7 @@ fun SettingsItem( onClick() } ) + .background(background) .padding( horizontal = TodoDefaults.settingsItemHorizontalPadding, vertical = TodoDefaults.settingsItemVerticalPadding diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt index eb338c7..fc921d0 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt @@ -8,6 +8,8 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -26,6 +28,7 @@ import cn.super12138.todo.utils.VibrationUtils import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun LicenceList( modifier: Modifier = Modifier, @@ -80,7 +83,8 @@ fun LicenceList( onClick = { openDialog = false VibrationUtils.performHapticFeedback(view) - } + }, + shapes = ButtonDefaults.shapes() ) { Text(stringResource(R.string.action_confirm)) } }, onDismissRequest = { openDialog = false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index def5312..7fb7d26 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,6 +18,8 @@ aboutLibsRelease = "13.1.0" aboutLibsReleasePlugin = "13.1.0" # M3 Color m3color = "2025.4" +# Capsule +capsule = "2.1.1" # Konfetti konfetti = "2.0.5" # 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" } # 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 nl-dionsegijn-konfetti-compose = { group = "nl.dionsegijn", name = "konfetti-compose", version.ref = "konfetti" } From 0fb4192493311846db18ad4bc7269ea51f6964b0 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sat, 8 Nov 2025 14:28:49 +0800 Subject: [PATCH 03/21] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=20Navigation3?= =?UTF-8?q?=20=E5=AF=BC=E8=88=AA=20&=20=E5=AF=B9=20FAB=20=E5=8A=A8?= =?UTF-8?q?=E7=94=BB=E8=BF=9B=E8=A1=8C=20MD3E=20=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 3 +- .../todo/logic/database/TodoEntity.kt | 2 + .../super12138/todo/ui/components/Dialogs.kt | 2 - .../todo/ui/navigation/TodoNavigation.kt | 233 +++++++++--------- .../todo/ui/navigation/TodoScreen.kt | 53 +++- .../todo/ui/pages/editor/TodoEditorPage.kt | 6 +- .../todo/ui/pages/editor/state/EditorState.kt | 4 +- .../super12138/todo/ui/pages/main/MainPage.kt | 19 +- .../todo/ui/pages/settings/SettingsAbout.kt | 1 - .../todo/ui/viewmodels/MainViewModel.kt | 8 - gradle/libs.versions.toml | 5 +- 11 files changed, 172 insertions(+), 164 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 509e92f..243c527 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -100,7 +100,6 @@ dependencies { implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.animation) - implementation(libs.androidx.navigation) implementation(libs.androidx.material3.adaptive) implementation(libs.androidx.ui) implementation(libs.androidx.ui.android) @@ -109,6 +108,8 @@ dependencies { implementation(libs.androidx.material3) implementation(libs.androidx.material.icon.core) implementation(libs.androidx.material.icon.extended) + implementation(libs.androidx.navigation3.runtime) + implementation(libs.androidx.navigation3.ui) // About Libraries implementation(libs.aboutlibraries.core) implementation(libs.aboutlibraries.compose) diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoEntity.kt b/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoEntity.kt index 1174db1..26f386a 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoEntity.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoEntity.kt @@ -4,7 +4,9 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import cn.super12138.todo.constants.Constants +import kotlinx.serialization.Serializable +@Serializable @Entity(tableName = Constants.DB_TABLE_NAME) data class TodoEntity( @ColumnInfo(name = "content") val content: String, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt index a22a290..1b44314 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt @@ -17,11 +17,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties import cn.super12138.todo.R import cn.super12138.todo.utils.VibrationUtils -import com.kyant.capsule.ContinuousRoundedRectangle @Composable fun ConfirmDialog( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt index 7a8d72a..f5804c0 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt @@ -4,10 +4,11 @@ import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionLayout import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController +import androidx.navigation3.runtime.NavBackStack +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.ui.NavDisplay import cn.super12138.todo.ui.pages.editor.TodoEditorPage import cn.super12138.todo.ui.pages.main.MainPage import cn.super12138.todo.ui.pages.settings.SettingsAbout @@ -19,8 +20,7 @@ import cn.super12138.todo.ui.pages.settings.SettingsDeveloperOptions import cn.super12138.todo.ui.pages.settings.SettingsDeveloperOptionsPadding import cn.super12138.todo.ui.pages.settings.SettingsInterface import cn.super12138.todo.ui.pages.settings.SettingsMain -import cn.super12138.todo.ui.theme.materialSharedAxisXIn -import cn.super12138.todo.ui.theme.materialSharedAxisXOut +import cn.super12138.todo.ui.theme.materialSharedAxisX import cn.super12138.todo.ui.viewmodels.MainViewModel private const val INITIAL_OFFSET_FACTOR = 0.10f @@ -29,126 +29,117 @@ private const val INITIAL_OFFSET_FACTOR = 0.10f @Composable fun TodoNavigation( modifier: Modifier = Modifier, - navController: NavHostController = rememberNavController(), - startDestination: String = TodoScreen.Main.name, + backStack: NavBackStack = rememberNavBackStack(TodoScreen.Main), viewModel: MainViewModel ) { + fun onBack() { + backStack.removeAt(backStack.lastIndex) + } + SharedTransitionLayout { - NavHost( - navController = navController, - startDestination = startDestination, - enterTransition = { - materialSharedAxisXIn( - initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() } - ) + NavDisplay( + backStack = backStack, + onBack = ::onBack, + transitionSpec = { + materialSharedAxisX( + initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }, + targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }) }, - exitTransition = { - materialSharedAxisXOut( - targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() } - ) + popTransitionSpec = { + materialSharedAxisX( + initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }, + targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }) }, - popEnterTransition = { - materialSharedAxisXIn( - initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() } - ) + + entryProvider = entryProvider { + entry { + MainPage( + viewModel = viewModel, + toTodoEditPage = { backStack.add(TodoScreen.Editor(it)) }, + toSettingsPage = { backStack.add(TodoScreen.SettingsMain) }, + sharedTransitionScope = this@SharedTransitionLayout + ) + } + entry { editorArgs -> + TodoEditorPage( + toDo = editorArgs.toDo, + onSave = { + viewModel.addTodo(it) + // 如果原来的待办状态为未完成并且修改后状态为完成 + if (editorArgs.toDo?.isCompleted != true && it.isCompleted) { + viewModel.playConfetti() + } + onBack() + }, + onDelete = { + if (editorArgs.toDo !== null) { + viewModel.deleteTodo(editorArgs.toDo) + } + onBack() + }, + onNavigateUp = ::onBack, + sharedTransitionScope = this@SharedTransitionLayout + ) + } + + entry { + SettingsMain( + toAppearancePage = { backStack.add(TodoScreen.SettingsAppearance) }, + toAboutPage = { backStack.add(TodoScreen.SettingsAbout) }, + toInterfacePage = { backStack.add(TodoScreen.SettingsInterface) }, + toDataPage = { backStack.add(TodoScreen.SettingsData) }, + onNavigateUp = ::onBack, + ) + } + + entry { + SettingsAppearance(onNavigateUp = ::onBack) + } + + entry { + SettingsInterface(onNavigateUp = ::onBack) + } + + entry { + SettingsData( + viewModel = viewModel, + toCategoryManager = { backStack.add(TodoScreen.SettingsDataCategory) }, + onNavigateUp = ::onBack + ) + } + + entry { + SettingsDataCategory(onNavigateUp = ::onBack) + } + + entry { + SettingsAbout( + //toSpecialPage = { backStack.add(TodoScreen.SettingsAboutSpecial) }, + toLicencePage = { backStack.add(TodoScreen.SettingsAboutLicence) }, + toDevPage = { backStack.add(TodoScreen.SettingsDev) }, + onNavigateUp = ::onBack, + ) + } + + /*entry { + SettingsAboutSpecial(viewModel = viewModel) + }*/ + + entry { + SettingsAboutLicence(onNavigateUp = ::onBack) + } + + entry { + SettingsDeveloperOptions( + toPaddingPage = { backStack.add(TodoScreen.SettingsDevPadding) }, + onNavigateUp = ::onBack + ) + } + entry { + SettingsDeveloperOptionsPadding(onNavigateUp = ::onBack) + } }, - popExitTransition = { - materialSharedAxisXOut( - targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() } - ) - }, - modifier = modifier - ) { - composable(TodoScreen.Main.name) { - MainPage( - viewModel = viewModel, - toTodoEditPage = { navController.navigate(TodoScreen.TodoEditor.name) }, - toSettingsPage = { navController.navigate(TodoScreen.SettingsMain.name) }, - sharedTransitionScope = this@SharedTransitionLayout, - animatedVisibilityScope = this@composable - ) - } - - composable(TodoScreen.TodoEditor.name) { - TodoEditorPage( - toDo = viewModel.selectedEditTodo, - onSave = { - viewModel.addTodo(it) - // 如果原来的待办状态为未完成并且修改后状态为完成 - if (viewModel.selectedEditTodo?.isCompleted != true && it.isCompleted) { - viewModel.playConfetti() - } - navController.navigateUp() - }, - onDelete = { - if (viewModel.selectedEditTodo !== null) { - viewModel.deleteTodo(viewModel.selectedEditTodo!!) - viewModel.setEditTodoItem(null) - } - navController.navigateUp() - }, - onNavigateUp = { navController.navigateUp() }, - sharedTransitionScope = this@SharedTransitionLayout, - animatedVisibilityScope = this@composable - ) - } - - composable(TodoScreen.SettingsMain.name) { - SettingsMain( - toAppearancePage = { navController.navigate(TodoScreen.SettingsAppearance.name) }, - toAboutPage = { navController.navigate(TodoScreen.SettingsAbout.name) }, - toInterfacePage = { navController.navigate(TodoScreen.SettingsInterface.name) }, - toDataPage = { navController.navigate(TodoScreen.SettingsData.name) }, - onNavigateUp = { navController.navigateUp() }, - ) - } - - composable(TodoScreen.SettingsAppearance.name) { - SettingsAppearance(onNavigateUp = { navController.navigateUp() }) - } - - composable(TodoScreen.SettingsInterface.name) { - SettingsInterface(onNavigateUp = { navController.navigateUp() }) - } - - composable(TodoScreen.SettingsData.name) { - SettingsData( - viewModel = viewModel, - toCategoryManager = { navController.navigate(TodoScreen.SettingsDataCategory.name) }, - onNavigateUp = { navController.navigateUp() } - ) - } - - composable(TodoScreen.SettingsDataCategory.name) { - SettingsDataCategory(onNavigateUp = { navController.navigateUp() }) - } - - composable(TodoScreen.SettingsAbout.name) { - SettingsAbout( - //toSpecialPage = { navController.navigate(TodoScreen.SettingsAboutSpecial.name) }, - toLicencePage = { navController.navigate(TodoScreen.SettingsAboutLicence.name) }, - toDevPage = { navController.navigate(TodoScreen.SettingsDev.name) }, - onNavigateUp = { navController.navigateUp() }, - ) - } - - /*composable(TodoScreen.SettingsAboutSpecial.name) { - SettingsAboutSpecial(viewModel = viewModel) - }*/ - - composable(TodoScreen.SettingsAboutLicence.name) { - SettingsAboutLicence(onNavigateUp = { navController.navigateUp() }) - } - - composable(TodoScreen.SettingsDev.name) { - SettingsDeveloperOptions( - toPaddingPage = { navController.navigate(TodoScreen.SettingsDevPadding.name) }, - onNavigateUp = { navController.navigateUp() } - ) - } - composable(TodoScreen.SettingsDevPadding.name) { - SettingsDeveloperOptionsPadding(onNavigateUp = { navController.navigateUp() }) - } - } + modifier = modifier, + ) } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt index 0ebb9cf..7de325c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt @@ -1,17 +1,44 @@ package cn.super12138.todo.ui.navigation -enum class TodoScreen { - Main, - TodoEditor, - SettingsMain, - SettingsAppearance, - SettingsInterface, - SettingsData, - SettingsDataCategory, - SettingsAbout, +import androidx.navigation3.runtime.NavKey +import cn.super12138.todo.logic.database.TodoEntity +import kotlinx.serialization.Serializable - //SettingsAboutSpecial, - SettingsAboutLicence, - SettingsDev, - SettingsDevPadding +@Serializable +sealed class TodoScreen : NavKey { + @Serializable + data object Main : TodoScreen() + + @Serializable + data class Editor(val toDo: TodoEntity?) : TodoScreen() + + @Serializable + data object SettingsMain : TodoScreen() + + @Serializable + data object SettingsAppearance : TodoScreen() + + @Serializable + data object SettingsInterface : TodoScreen() + + @Serializable + data object SettingsData : TodoScreen() + + @Serializable + data object SettingsDataCategory : TodoScreen() + + @Serializable + data object SettingsAbout : TodoScreen() + + // @Serializable + // data object SettingsAboutSpecial : TodoScreen() + + @Serializable + data object SettingsAboutLicence : TodoScreen() + + @Serializable + data object SettingsDev : TodoScreen() + + @Serializable + data object SettingsDevPadding : TodoScreen() } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index 4b62680..9b40cc3 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -2,7 +2,6 @@ package cn.super12138.todo.ui.pages.editor import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.expandVertically @@ -43,6 +42,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.navigation3.ui.LocalNavAnimatedContentScope import cn.super12138.todo.R import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.database.TodoEntity @@ -69,11 +69,11 @@ fun TodoEditorPage( onSave: (TodoEntity) -> Unit, onDelete: () -> Unit, onNavigateUp: () -> Unit, - sharedTransitionScope: SharedTransitionScope, - animatedVisibilityScope: AnimatedVisibilityScope + sharedTransitionScope: SharedTransitionScope ) { // TODO: 本页及其相关组件重组性能检查优化 val view = LocalView.current + val animatedVisibilityScope = LocalNavAnimatedContentScope.current val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val uiState = rememberEditorState(initialTodo = toDo) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt index 0513e79..99d9824 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt @@ -74,7 +74,7 @@ class EditorState(val initialTodo: TodoEntity? = null) { * 保存状态的 Saver 对象,用于适配 rememberSaveable */ object Saver : androidx.compose.runtime.saveable.Saver { - override fun SaverScope.save(value: EditorState): Any? { + override fun SaverScope.save(value: EditorState): Any { return listOf( value.initialTodo?.id ?: 0, value.toDoContent, @@ -89,7 +89,7 @@ class EditorState(val initialTodo: TodoEntity? = null) { ) } - override fun restore(value: Any): EditorState? { + override fun restore(value: Any): EditorState { val list = value as List<*> val initialTodo = list[0] as? TodoEntity? return EditorState(initialTodo).apply { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index 8ec35a1..caed89c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -1,7 +1,6 @@ package cn.super12138.todo.ui.pages.main import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.layout.Column @@ -31,9 +30,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.navigation3.ui.LocalNavAnimatedContentScope import androidx.window.core.layout.WindowSizeClass import cn.super12138.todo.R import cn.super12138.todo.constants.Constants +import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar @@ -44,12 +45,13 @@ import cn.super12138.todo.ui.viewmodels.MainViewModel fun MainPage( modifier: Modifier = Modifier, viewModel: MainViewModel, - toTodoEditPage: () -> Unit, + toTodoEditPage: (TodoEntity?) -> Unit, toSettingsPage: () -> Unit, sharedTransitionScope: SharedTransitionScope, - animatedVisibilityScope: AnimatedVisibilityScope, windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass, ) { + val animatedVisibilityScope = LocalNavAnimatedContentScope.current + val toDos = viewModel.sortedTodos.collectAsState(initial = emptyList()) val selectedTodos = viewModel.selectedTodoIds.collectAsState() val showCompleted by DataStoreManager.showCompletedFlow.collectAsState(initial = Constants.PREF_SHOW_COMPLETED_DEFAULT) @@ -91,10 +93,7 @@ fun MainPage( ) }, expanded = expandedFab, - onClick = { - viewModel.setEditTodoItem(null) // 每次添加待办前清除上一次已选待办 - toTodoEditPage() - }, + onClick = { toTodoEditPage(null) }, modifier = Modifier .sharedElement( sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION), @@ -133,8 +132,7 @@ fun MainPage( if (inSelectedMode) { viewModel.toggleTodoSelection(item) } else { - viewModel.setEditTodoItem(item) - toTodoEditPage() + toTodoEditPage(item) } }, onItemLongClick = { viewModel.toggleTodoSelection(it) }, @@ -171,8 +169,7 @@ fun MainPage( if (inSelectedMode) { viewModel.toggleTodoSelection(item) } else { - viewModel.setEditTodoItem(item) - toTodoEditPage() + toTodoEditPage(item) } }, onItemLongClick = { viewModel.toggleTodoSelection(it) }, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt index e726ec4..bc533ca 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt @@ -2,7 +2,6 @@ package cn.super12138.todo.ui.pages.settings import android.widget.Toast import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt index 2f9d3b9..4095f91 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt @@ -2,9 +2,7 @@ package cn.super12138.todo.ui.viewmodels import android.content.Context import android.net.Uri -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import cn.super12138.todo.TodoApp @@ -51,8 +49,6 @@ class MainViewModel : ViewModel() { } val showConfetti = mutableStateOf(false) - var selectedEditTodo by mutableStateOf(null) - private set // 多选逻辑参考:https://github.com/X1nto/Mauth private val _selectedTodoIds = MutableStateFlow(listOf()) @@ -82,10 +78,6 @@ class MainViewModel : ViewModel() { } }*/ - fun setEditTodoItem(toDo: TodoEntity?) { - selectedEditTodo = toDo - } - /** * 切换待办的选择状态 */ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 14ef0fa..da7bd7f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,9 +10,9 @@ composeBom = "2025.11.00" liveData = "1.9.4" icons = "1.7.8" room = "2.8.3" -navigation = "2.9.6" material3 = "1.5.0-alpha08" adaptive = "1.2.0" +nav3Core = "1.0.0-rc01" # AboutLibraries aboutLibsRelease = "13.1.0" aboutLibsReleasePlugin = "13.1.0" @@ -49,7 +49,6 @@ androidx-datastore-preferences = { group = "androidx.datastore", name = "datasto # Compose androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-animation = { group = "androidx.compose.animation", name = "animation" } -androidx-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } androidx-material3-adaptive = { group = "androidx.compose.material3.adaptive", name = "adaptive", version.ref = "adaptive" } androidx-ui = { group = "androidx.compose.ui", name = "ui" } androidx-ui-android = { group = "androidx.compose.ui", name = "ui-android" } @@ -61,6 +60,8 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } androidx-material-icon-core = { group = "androidx.compose.material", name = "material-icons-core", version.ref = "icons" } androidx-material-icon-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "icons" } +androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } +androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } # Room androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } From 7fa618a611729ab8c128bad1e9547042f3f776d8 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sun, 9 Nov 2025 10:10:30 +0800 Subject: [PATCH 04/21] =?UTF-8?q?fix:=20FAB=20=E5=9C=A8=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=97=B6=E6=96=87=E5=AD=97=E6=BA=A2=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../ui/components/FloatingActionButton.kt | 70 ++++++++----------- .../todo/ui/pages/editor/TodoEditorPage.kt | 58 +++++---------- .../super12138/todo/ui/pages/main/MainPage.kt | 15 ++-- .../ui/pages/settings/SettingsDataCategory.kt | 6 +- 5 files changed, 55 insertions(+), 96 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 243c527..28a4570 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,7 +37,7 @@ android { applicationId = "cn.super12138.todo" minSdk = 24 targetSdk = 36 - versionCode = 791 + versionCode = 883 versionName = "2.3.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt index fb508e2..a1aa791 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt @@ -1,34 +1,29 @@ package cn.super12138.todo.ui.components -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material3.FloatingActionButton +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.FloatingActionButtonElevation import androidx.compose.material3.Icon +import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.Text import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalView import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp import cn.super12138.todo.utils.VibrationUtils /** - * 带动画且比 Material 3 内置组件动画好看的的可扩展 FAB - * * (内置组件的动画总是卡一下。。。) + * 封装的内置组件 FAB,在点击时支持添加震动反馈 + * * (现在内置组件的动画好像好一点了) * * 将缩放部分转为最简单的`AnimatedVisibility`实现 * @param icon FAB 的前置图标 * @param text FAB 的文本 - * @param textOverflow FAB 文本溢出显示方案 * @param expanded 是否为展开状态 * @param containerColor FAB 容器的颜色 * @param contentColor FAB 文本和图标颜色,通常不需要自己指定,会自动依据容器颜色设置 @@ -36,20 +31,34 @@ import cn.super12138.todo.utils.VibrationUtils * @param onClick 点击 FAB 后的回调 * @param modifier `Modifier` 修改器 */ +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable -fun AnimatedExtendedFloatingActionButton( - modifier: Modifier = Modifier, - icon: ImageVector, +fun TodoFloatingActionButton( text: String, - textOverflow: TextOverflow = TextOverflow.Clip, - expanded: Boolean, + icon: ImageVector, + onClick: () -> Unit, + modifier: Modifier = Modifier, + expanded: Boolean = true, + shape: Shape = FloatingActionButtonDefaults.smallExtendedFabShape, containerColor: Color = FloatingActionButtonDefaults.containerColor, contentColor: Color = contentColorFor(containerColor), elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), - onClick: () -> Unit + interactionSource: MutableInteractionSource? = null, ) { val view = LocalView.current - FloatingActionButton( + SmallExtendedFloatingActionButton( + text = { + Text( + text = text, + overflow = TextOverflow.Clip + ) + }, + icon = { + Icon( + imageVector = icon, + contentDescription = null + ) + }, onClick = { VibrationUtils.performHapticFeedback(view) onClick() @@ -57,27 +66,8 @@ fun AnimatedExtendedFloatingActionButton( elevation = elevation, containerColor = containerColor, contentColor = contentColor, + shape = shape, + interactionSource = interactionSource, modifier = modifier - ) { - Row( - modifier = Modifier.padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = icon, - contentDescription = null - ) - // Spacer(Modifier.width(if (expanded) 8.dp else 0.dp)) - AnimatedVisibility(expanded) { - Row { - Spacer(Modifier.width(8.dp)) - Text( - text = text, - maxLines = 1, - overflow = textOverflow - ) - } - } - } - } + ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index 9b40cc3..9f37c83 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -23,9 +23,7 @@ import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Save import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults @@ -51,6 +49,7 @@ 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.TodoFloatingActionButton import cn.super12138.todo.ui.pages.editor.components.TodoCategoryChip import cn.super12138.todo.ui.pages.editor.components.TodoCategoryTextField import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField @@ -115,41 +114,33 @@ fun TodoEditorPage( } } - BackHandler { checkModifiedBeforeBack() } + BackHandler(onBack = ::checkModifiedBeforeBack) LargeTopAppBarScaffold( title = stringResource(if (toDo != null) R.string.title_edit_task else R.string.action_add_task), scrollBehavior = scrollBehavior, floatingActionButton = { with(sharedTransitionScope) { - Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + Row( + horizontalArrangement = Arrangement.spacedBy(10.dp), + modifier = Modifier.imePadding() + ) { if (toDo !== null) { - SmallExtendedFloatingActionButton( - text = { Text(stringResource(R.string.action_delete)) }, - icon = { - Icon( - imageVector = Icons.Outlined.Delete, - contentDescription = null - ) - }, + TodoFloatingActionButton( + text = stringResource(R.string.action_delete), + icon = Icons.Outlined.Delete, expanded = true, containerColor = MaterialTheme.colorScheme.errorContainer, - onClick = { uiState.showDeleteConfirmDialog = true }, - modifier = Modifier.imePadding() + onClick = { uiState.showDeleteConfirmDialog = true } ) } - SmallExtendedFloatingActionButton( - text = { Text(stringResource(R.string.action_save)) }, - icon = { - Icon( - imageVector = Icons.Outlined.Save, - contentDescription = null - ) - }, + TodoFloatingActionButton( + text = stringResource(R.string.action_save), + icon = Icons.Outlined.Save, expanded = true, onClick = { if (uiState.setErrorIfNotValid()) { - return@SmallExtendedFloatingActionButton + return@TodoFloatingActionButton } else { uiState.clearError() val newTodo = TodoEntity( @@ -163,7 +154,6 @@ fun TodoEditorPage( } }, modifier = Modifier - .imePadding() .sharedElement( sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION), animatedVisibilityScope = animatedVisibilityScope @@ -172,7 +162,7 @@ fun TodoEditorPage( } } }, - onBack = { checkModifiedBeforeBack() }, + onBack = ::checkModifiedBeforeBack, modifier = modifier ) { innerPadding -> LazyColumn( @@ -183,19 +173,12 @@ fun TodoEditorPage( .fillMaxSize() ) { item { - // with(sharedTransitionScope) { TodoContentTextField( value = uiState.toDoContent, onValueChange = { uiState.toDoContent = it }, isError = uiState.isErrorContent, - modifier = Modifier - .fillMaxWidth() - /*.sharedBounds( - sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CONTENT_TRANSITION}_${toDo?.id}"), - animatedVisibilityScope = animatedVisibilityScope - )*/ + modifier = Modifier.fillMaxWidth() ) - // } } item { @@ -217,20 +200,13 @@ fun TodoEditorPage( enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { - // with(sharedTransitionScope) { TodoCategoryTextField( value = uiState.categoryContent, onValueChange = { uiState.categoryContent = it }, isError = uiState.isErrorCategory, supportingText = stringResource(uiState.categorySupportingText), - modifier = Modifier - .fillMaxWidth() - /*.sharedBounds( - sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CATEGORY_TRANSITION}_${toDo?.id}"), - animatedVisibilityScope = animatedVisibilityScope - )*/ + modifier = Modifier.fillMaxWidth() ) - // } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index caed89c..3eb2921 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -13,10 +13,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold -import androidx.compose.material3.SmallExtendedFloatingActionButton -import androidx.compose.material3.Text import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.animateFloatingActionButton import androidx.compose.runtime.Composable @@ -37,6 +34,7 @@ import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.ui.components.ConfirmDialog +import cn.super12138.todo.ui.components.TodoFloatingActionButton import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar import cn.super12138.todo.ui.viewmodels.MainViewModel @@ -84,14 +82,9 @@ fun MainPage( }, floatingActionButton = { with(sharedTransitionScope) { - SmallExtendedFloatingActionButton( - text = { Text(stringResource(R.string.action_add_task)) }, - icon = { - Icon( - imageVector = Icons.Outlined.Add, - contentDescription = null - ) - }, + TodoFloatingActionButton( + text = stringResource(R.string.action_add_task), + icon = Icons.Outlined.Add, expanded = expandedFab, onClick = { toTodoEditPage(null) }, modifier = Modifier diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt index 87dd959..d586fed 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt @@ -34,8 +34,8 @@ 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.AnimatedExtendedFloatingActionButton import cn.super12138.todo.ui.components.LargeTopAppBarScaffold +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 import kotlinx.coroutines.launch @@ -65,7 +65,7 @@ fun SettingsDataCategory( scrollBehavior = scrollBehavior, snackbarHost = { SnackbarHost(snackbarHostState) }, floatingActionButton = { - AnimatedExtendedFloatingActionButton( + TodoFloatingActionButton( icon = Icons.Outlined.Add, text = stringResource(R.string.action_add_category), expanded = isExpanded, @@ -81,7 +81,7 @@ fun SettingsDataCategory( state = listState, modifier = Modifier .fillMaxSize() - .padding(innerPadding) + .padding(innerPadding) //TODO: 可能需要清除屏幕边距 ) { if (categories.isEmpty()) { item { From 84c49542c01dfd140913b45446e67d327458b5a6 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sun, 9 Nov 2025 10:32:44 +0800 Subject: [PATCH 05/21] =?UTF-8?q?fix:=20=E4=B8=BA=20FAB=20=E6=96=87?= =?UTF-8?q?=E5=AD=97=E6=B7=BB=E5=8A=A0=E8=A1=8C=E6=95=B0=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../cn/super12138/todo/ui/components/FloatingActionButton.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 28a4570..a9d07c2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,7 +37,7 @@ android { applicationId = "cn.super12138.todo" minSdk = 24 targetSdk = 36 - versionCode = 883 + versionCode = 884 versionName = "2.3.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt index a1aa791..b47aba2 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt @@ -50,6 +50,7 @@ fun TodoFloatingActionButton( text = { Text( text = text, + maxLines = 1, overflow = TextOverflow.Clip ) }, From a7311bfddf8c6838de1c8737752030e868ff9bd3 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sun, 9 Nov 2025 19:13:42 +0800 Subject: [PATCH 06/21] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E9=A2=9C?= =?UTF-8?q?=E8=89=B2=E6=A8=A1=E5=BC=8F=E5=92=8C=E4=B8=BB=E9=A2=98=E8=89=B2?= =?UTF-8?q?=E7=89=88=E7=9A=84=E6=80=A7=E8=83=BD=E4=B8=8E=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/pages/settings/SettingsAppearance.kt | 4 +- .../appearance/darkmode/DarkModePicker.kt | 50 ++++++++----------- .../appearance/palette/PaletteItem.kt | 5 +- .../appearance/palette/PalettePicker.kt | 40 +++++++-------- 4 files changed, 47 insertions(+), 52 deletions(-) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt index 49eee2f..824291e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt @@ -63,12 +63,12 @@ fun SettingsAppearance( ) DarkModePicker( - currentDarkMode = DarkMode.fromId(darkMode), + currentDarkMode = { DarkMode.fromId(darkMode) }, onDarkModeChange = { scope.launch { DataStoreManager.setDarkMode(it.id) } } ) PalettePicker( - currentPalette = PaletteStyle.fromId(paletteStyle), + currentPalette = { PaletteStyle.fromId(paletteStyle) }, onPaletteChange = { scope.launch { DataStoreManager.setPaletteStyle(it.id) } }, isDynamicColor = dynamicColor, isDarkMode = DarkMode.fromId(darkMode), diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt index 1b3dd47..afaca2e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt @@ -2,7 +2,11 @@ package cn.super12138.todo.ui.pages.settings.components.appearance.darkmode import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -14,12 +18,15 @@ import cn.super12138.todo.ui.theme.DarkMode @Composable fun DarkModePicker( - currentDarkMode: DarkMode, + currentDarkMode: () -> DarkMode, onDarkModeChange: (darkMode: DarkMode) -> Unit, modifier: Modifier = Modifier ) { val context = LocalContext.current val isInDarkTheme = isSystemInDarkTheme() + + val darkModeList = remember { DarkMode.entries.toList() } + LazyRowSettingsItem( title = stringResource(R.string.pref_dark_mode), description = stringResource(R.string.pref_dark_mode_desc), @@ -27,37 +34,22 @@ fun DarkModePicker( fadedEdgeWidth = 8.dp, modifier = modifier ) { - item { - DarkModeItem( - icon = DarkMode.FollowSystem.icon, - name = DarkMode.FollowSystem.getDisplayName(context), - contentColor = if (isInDarkTheme) Color.White else Color.Black, - containerColor = if (isInDarkTheme) Color.Black else Color.White, - selected = currentDarkMode == DarkMode.FollowSystem, - onSelect = { onDarkModeChange(DarkMode.FollowSystem) } - ) - } + items(items = darkModeList, key = { it.id }) { + val (contentColor, containerColor) = when (it) { + DarkMode.FollowSystem -> if (isInDarkTheme) Color.White to Color.Black else Color.Black to Color.White + DarkMode.Light -> Color.Black to Color.White + DarkMode.Dark -> Color.White to Color.Black + } - item { - DarkModeItem( - icon = DarkMode.Light.icon, - name = DarkMode.Light.getDisplayName(context), - contentColor = Color.Black, - containerColor = Color.White, - selected = currentDarkMode == DarkMode.Light, - onSelect = { onDarkModeChange(DarkMode.Light) } - ) - } + val isSelected by remember { derivedStateOf { currentDarkMode() == it } } - item { DarkModeItem( - icon = DarkMode.Dark.icon, - name = DarkMode.Dark.getDisplayName(context), - contentColor = Color.White, - containerColor = Color.Black, - selected = currentDarkMode == DarkMode.Dark, - onSelect = { onDarkModeChange(DarkMode.Dark) } - ) + icon = it.icon, + name = it.getDisplayName(context), + contentColor = contentColor, + containerColor = containerColor, + selected = isSelected, + onSelect = { onDarkModeChange(it) }) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PaletteItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PaletteItem.kt index c95c2ca..4e53edf 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PaletteItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PaletteItem.kt @@ -108,7 +108,10 @@ fun PaletteItem( Text( text = paletteStyle.getDisplayName(context), - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = if (selected) { + MaterialTheme.colorScheme.primary + } else MaterialTheme.colorScheme.onSurface ) } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt index d36ade5..9a0d5b8 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt @@ -2,7 +2,10 @@ package cn.super12138.todo.ui.pages.settings.components.appearance.palette import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -15,17 +18,14 @@ import cn.super12138.todo.ui.theme.PaletteStyle @Composable fun PalettePicker( - currentPalette: PaletteStyle, + currentPalette: () -> PaletteStyle, onPaletteChange: (paletteStyle: PaletteStyle) -> Unit, isDynamicColor: Boolean, isDarkMode: DarkMode, contrastLevel: ContrastLevel, modifier: Modifier = Modifier ) { - - val paletteOptions = remember { - PaletteStyle.entries.toList() - } + val paletteOptions = remember { PaletteStyle.entries.toList() } LazyRowSettingsItem( title = stringResource(R.string.pref_palette_style), @@ -34,21 +34,21 @@ fun PalettePicker( fadedEdgeWidth = 8.dp, modifier = modifier ) { - paletteOptions.forEach { paletteStyle -> - item { - PaletteItem( - isDynamicColor = isDynamicColor, - isDark = when (isDarkMode) { - DarkMode.FollowSystem -> isSystemInDarkTheme() - DarkMode.Light -> false - DarkMode.Dark -> true - }, - paletteStyle = paletteStyle, - selected = currentPalette == paletteStyle, - contrastLevel = contrastLevel, - onSelect = { onPaletteChange(paletteStyle) } - ) - } + items(items = paletteOptions, key = { it.id }) { + val isSelected by remember { derivedStateOf { currentPalette() == it } } + + PaletteItem( + isDynamicColor = isDynamicColor, + isDark = when (isDarkMode) { + DarkMode.FollowSystem -> isSystemInDarkTheme() + DarkMode.Light -> false + DarkMode.Dark -> true + }, + paletteStyle = it, + selected = isSelected, + contrastLevel = contrastLevel, + onSelect = { onPaletteChange(it) } + ) } } } \ No newline at end of file From 3b84fcd1f060d20d0ce0a694a9cf8da7fc309f41 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sun, 9 Nov 2025 22:22:24 +0800 Subject: [PATCH 07/21] =?UTF-8?q?feat:=20=E9=83=A8=E5=88=86=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E9=A1=B5=E9=9D=A2=E7=9A=84=20MD3E=20=E9=80=82?= =?UTF-8?q?=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../cn/super12138/todo/ui/TodoDefaults.kt | 7 ++- .../todo/ui/components/Scaffolds.kt | 11 +++-- .../todo/ui/pages/crash/CrashPage.kt | 2 +- .../todo/ui/pages/editor/TodoEditorPage.kt | 2 +- .../todo/ui/pages/main/ManagerFragment.kt | 4 +- .../todo/ui/pages/settings/SettingsAbout.kt | 27 ++++------ .../ui/pages/settings/SettingsAboutLicence.kt | 17 +++++-- .../ui/pages/settings/SettingsAppearance.kt | 49 ++++++++++--------- .../todo/ui/pages/settings/SettingsData.kt | 2 +- .../settings/SettingsDeveloperOptions.kt | 12 +++-- .../settings/SettingsInterfaceInteraction.kt | 2 +- .../todo/ui/pages/settings/SettingsMain.kt | 2 +- .../settings/components/RowSettingsItem.kt | 13 +++-- .../settings/components/SettingsCategory.kt | 3 +- .../settings/components/SettingsContainer.kt | 8 ++- .../settings/components/SettingsDialogs.kt | 2 +- .../pages/settings/components/SettingsItem.kt | 4 +- .../components/licence/LicenceItem.kt | 10 ++-- .../components/licence/LicenceList.kt | 5 +- 20 files changed, 115 insertions(+), 69 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a9d07c2..0685a42 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,7 +37,7 @@ android { applicationId = "cn.super12138.todo" minSdk = 24 targetSdk = 36 - versionCode = 884 + versionCode = 886 versionName = "2.3.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt index 8fac742..f5f09f4 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt @@ -6,7 +6,12 @@ object TodoDefaults { /** * 屏幕左右两边预留边距(防止内容全部贴边显示过丑) */ - val screenPadding = 16.dp + val screenHorizontalPadding = 16.dp + + /** + * 屏幕上下预留边距(防止内容全部贴边显示过丑) + */ + val screenVerticalPadding = 16.dp /** * 待办卡片默认高度 diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt index c5fbff3..d5f4e80 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt @@ -7,10 +7,13 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack 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.IconButton +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.Text @@ -33,7 +36,7 @@ import cn.super12138.todo.utils.VibrationUtils * @param onBack 当返回按钮被按下时的操作 * @param contentWindowInsets 内容边距,通常用于将内容和系统状态栏等隔开;可以使用 `WindowInsets.safeContent` */ -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun LargeTopAppBarScaffold( modifier: Modifier = Modifier, @@ -56,7 +59,9 @@ fun LargeTopAppBarScaffold( ) }, navigationIcon = { - IconButton( + FilledIconButton( + colors = IconButtonDefaults.filledIconButtonColors(containerColor = MaterialTheme.colorScheme.surfaceContainerHigh), + shapes = IconButtonDefaults.shapes(), onClick = { VibrationUtils.performHapticFeedback(view) onBack() diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt index f64295e..8472b7a 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt @@ -114,7 +114,7 @@ fun CrashPage( Column( modifier = Modifier .padding(innerPadding) - .padding(horizontal = TodoDefaults.screenPadding) + .padding(horizontal = TodoDefaults.screenHorizontalPadding) .fillMaxSize() .verticalScroll(scrollState) ) { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index 9f37c83..2e86810 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -169,7 +169,7 @@ fun TodoEditorPage( verticalArrangement = Arrangement.spacedBy(5.dp), modifier = Modifier .padding(innerPadding) - .padding(horizontal = TodoDefaults.screenPadding) + .padding(horizontal = TodoDefaults.screenHorizontalPadding) .fillMaxSize() ) { item { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt index 1517d59..c223134 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt @@ -41,9 +41,9 @@ fun ManagerFragment( LazyColumn( state = state, contentPadding = PaddingValues( - start = TodoDefaults.screenPadding, + start = TodoDefaults.screenHorizontalPadding, bottom = TodoDefaults.toDoCardHeight / 2, - end = TodoDefaults.screenPadding + end = TodoDefaults.screenHorizontalPadding ), /*modifier = Modifier .clip( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt index bc533ca..816c6c7 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt @@ -2,9 +2,11 @@ package cn.super12138.todo.ui.pages.settings import android.widget.Toast import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize 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.outlined.Balance import androidx.compose.material.icons.outlined.DeveloperMode @@ -12,7 +14,6 @@ import androidx.compose.material.icons.outlined.Numbers import androidx.compose.material.icons.outlined.Person4 import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -22,7 +23,6 @@ import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler @@ -33,6 +33,7 @@ import cn.super12138.todo.constants.Constants import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.components.LargeTopAppBarScaffold import cn.super12138.todo.ui.icons.GitHubIcon +import cn.super12138.todo.ui.pages.settings.components.Settings import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.utils.SystemUtils import kotlinx.coroutines.delay @@ -56,15 +57,15 @@ fun SettingsAbout( val context = LocalContext.current val uriHandler = LocalUriHandler.current - LazyColumn( + Column( verticalArrangement = Arrangement.spacedBy(2.dp), modifier = Modifier - .fillMaxWidth() + .fillMaxSize() .padding(innerPadding) - .padding(horizontal = TodoDefaults.screenPadding) - .clip(MaterialTheme.shapes.extraExtraLarge) + .padding(horizontal = TodoDefaults.screenHorizontalPadding) + .verticalScroll(rememberScrollState()) ) { - item { + Settings { var clickCount by remember { mutableIntStateOf(0) } var lastClickTime by remember { mutableLongStateOf(0L) } @@ -96,32 +97,24 @@ fun SettingsAbout( } } ) - } - item { SettingsItem( leadingIcon = Icons.Outlined.Person4, 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( leadingIcon = Icons.Outlined.Balance, title = stringResource(R.string.pref_licence), description = stringResource(R.string.pref_licence_desc), onClick = toLicencePage ) - } - item { SettingsItem( leadingIcon = Icons.Outlined.DeveloperMode, title = stringResource(R.string.pref_developer_options), diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt index 91e7ddf..fd69873 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt @@ -1,16 +1,20 @@ package cn.super12138.todo.ui.pages.settings -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CornerSize 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 +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.pages.settings.components.licence.LicenceList import com.mikepenz.aboutlibraries.ui.compose.android.produceLibraries @@ -29,10 +33,17 @@ fun SettingsAboutLicence( modifier = modifier ) { innerPadding -> val libraries by produceLibraries(R.raw.aboutlibraries) - Column( + Box( modifier = Modifier - .padding(innerPadding) .fillMaxSize() + .padding(innerPadding) + .padding(horizontal = TodoDefaults.screenHorizontalPadding) + .clip( + MaterialTheme.shapes.extraLarge.copy( + bottomEnd = CornerSize(0f), + bottomStart = CornerSize(0f) + ) + ) ) { val listState = rememberLazyListState() LicenceList( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt index 824291e..f534369 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt @@ -19,7 +19,9 @@ 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.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 @@ -52,33 +54,36 @@ fun SettingsAppearance( modifier = Modifier .fillMaxSize() .padding(innerPadding) + .padding(horizontal = TodoDefaults.screenHorizontalPadding) .verticalScroll(rememberScrollState()) ) { - SwitchSettingsItem( - checked = dynamicColor, - leadingIcon = Icons.Outlined.AutoAwesome, - title = stringResource(R.string.pref_appearance_dynamic_color), - description = stringResource(R.string.pref_appearance_dynamic_color_desc), - onCheckedChange = { scope.launch { DataStoreManager.setDynamicColor(it) } }, - ) + Settings { + SwitchSettingsItem( + checked = dynamicColor, + leadingIcon = Icons.Outlined.AutoAwesome, + title = stringResource(R.string.pref_appearance_dynamic_color), + description = stringResource(R.string.pref_appearance_dynamic_color_desc), + onCheckedChange = { scope.launch { DataStoreManager.setDynamicColor(it) } }, + ) - DarkModePicker( - currentDarkMode = { DarkMode.fromId(darkMode) }, - onDarkModeChange = { scope.launch { DataStoreManager.setDarkMode(it.id) } } - ) + 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) - ) + 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) } } - ) + ContrastPicker( + currentContrast = ContrastLevel.fromFloat(contrastLevel), + onContrastChange = { scope.launch { DataStoreManager.setContrastLevel(it.value) } } + ) + } } } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt index 15b3400..fe8d71d 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt @@ -120,7 +120,7 @@ fun SettingsData( Modifier .fillMaxSize() .padding(innerPadding) - .padding(horizontal = TodoDefaults.screenPadding) + .padding(horizontal = TodoDefaults.screenHorizontalPadding) .verticalScroll(rememberScrollState()) ) { SettingsCategory(stringResource(R.string.pref_category_data_management)) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt index 490fd75..0ac78f2 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt @@ -1,8 +1,10 @@ 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.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Padding import androidx.compose.material3.ExperimentalMaterial3Api @@ -12,7 +14,9 @@ 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.pages.settings.components.SettingsItem @OptIn(ExperimentalMaterial3Api::class) @@ -29,12 +33,14 @@ fun SettingsDeveloperOptions( scrollBehavior = scrollBehavior, modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), ) { innerPadding -> - LazyColumn( + Column( modifier = Modifier .fillMaxSize() .padding(innerPadding) + .padding(horizontal = TodoDefaults.screenHorizontalPadding,) + .verticalScroll(rememberScrollState()) ) { - item { + Settings { SettingsItem( leadingIcon = Icons.Outlined.Padding, title = stringResource(R.string.pref_padding), diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt index 785ab7b..0936a40 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt @@ -65,7 +65,7 @@ fun SettingsInterface( Modifier .fillMaxSize() .padding(innerPadding) - .padding(horizontal = TodoDefaults.screenPadding) + .padding(horizontal = TodoDefaults.screenHorizontalPadding) .verticalScroll(rememberScrollState()) ) { SettingsCategory(stringResource(R.string.pref_category_todo_list)) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt index 27ebe88..e19d509 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt @@ -44,7 +44,7 @@ fun SettingsMain( Modifier .fillMaxSize() .padding(innerPadding) - .padding(horizontal = TodoDefaults.screenPadding) + .padding(horizontal = TodoDefaults.screenHorizontalPadding) .verticalScroll(rememberScrollState()) ) { Settings { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/RowSettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/RowSettingsItem.kt index 1c96e51..f82c2fe 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/RowSettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/RowSettingsItem.kt @@ -1,6 +1,7 @@ package cn.super12138.todo.ui.pages.settings.components import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.background import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -40,7 +41,8 @@ fun RowSettingsItem( title: String, description: String? = null, trailingContent: (@Composable () -> Unit)? = null, - shape: Shape = MaterialTheme.shapes.large, + shape: Shape = MaterialTheme.shapes.small, + background: Color = MaterialTheme.colorScheme.surfaceContainerHigh, horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, verticalAlignment: Alignment.Vertical = Alignment.Top, scrollState: ScrollState = rememberScrollState(), @@ -54,6 +56,7 @@ fun RowSettingsItem( description = description, trailingContent = trailingContent, shape = shape, + background = background, modifier = modifier ) { Row( @@ -91,7 +94,8 @@ fun LazyRowSettingsItem( title: String, description: String? = null, trailingContent: (@Composable () -> Unit)? = null, - shape: Shape = MaterialTheme.shapes.large, + shape: Shape = MaterialTheme.shapes.small, + background: Color = MaterialTheme.colorScheme.surfaceContainerHigh, horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, verticalAlignment: Alignment.Vertical = Alignment.Top, fadedEdgeWidth: Dp, @@ -104,6 +108,7 @@ fun LazyRowSettingsItem( description = description, trailingContent = trailingContent, shape = shape, + background = background, modifier = modifier ) { LazyRow( @@ -140,7 +145,8 @@ fun MoreContentSettingsItem( title: String, description: String? = null, trailingContent: (@Composable () -> Unit)? = null, - shape: Shape = MaterialTheme.shapes.large, + shape: Shape = MaterialTheme.shapes.small, + background: Color = MaterialTheme.colorScheme.surfaceContainerHigh, content: @Composable () -> Unit ) { Column( @@ -148,6 +154,7 @@ fun MoreContentSettingsItem( .fillMaxWidth() .wrapContentHeight() .clip(shape) + .background(background) .padding( horizontal = TodoDefaults.settingsItemHorizontalPadding, vertical = TodoDefaults.settingsItemVerticalPadding diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt index 4ae1d70..3d79d21 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt @@ -21,7 +21,8 @@ fun SettingsCategory( .padding( top = TodoDefaults.settingsItemVerticalPadding / 2, start = TodoDefaults.settingsItemHorizontalPadding, - end = TodoDefaults.settingsItemHorizontalPadding + end = TodoDefaults.settingsItemHorizontalPadding, + bottom = TodoDefaults.settingsItemVerticalPadding / 4, ) ) { Text( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt index dd746d0..18b2ae1 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt @@ -3,12 +3,15 @@ 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.foundation.shape.CornerSize 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 +import cn.super12138.todo.ui.TodoDefaults @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -18,7 +21,10 @@ fun Settings( ) { Column( verticalArrangement = Arrangement.spacedBy(2.dp), - modifier = modifier.clip(MaterialTheme.shapes.extraExtraLarge), + modifier = modifier + .padding(vertical = TodoDefaults.screenVerticalPadding).clip( + shape = MaterialTheme.shapes.extraLarge + ), content = content ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt index 1dcd78e..827e330 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt @@ -81,7 +81,7 @@ fun RadioItem( }, role = Role.RadioButton ) - .padding(horizontal = TodoDefaults.screenPadding), + .padding(horizontal = TodoDefaults.screenHorizontalPadding), verticalAlignment = Alignment.CenterVertically ) { RadioButton( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt index 55e8b75..a5a4fb0 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt @@ -51,7 +51,7 @@ fun SettingsItem( title: String, description: String? = null, trailingContent: (@Composable () -> Unit)? = null, - background: Color = MaterialTheme.colorScheme.surfaceContainerHighest, + background: Color = MaterialTheme.colorScheme.surfaceContainerHigh, enableClick: Boolean = true, onClick: () -> Unit = {} ) { @@ -85,7 +85,7 @@ fun SettingsItem( description: String? = null, trailingContent: (@Composable () -> Unit)? = null, shape: Shape = MaterialTheme.shapes.small, - background: Color = MaterialTheme.colorScheme.surfaceContainerHighest, + background: Color = MaterialTheme.colorScheme.surfaceContainerHigh, enableClick: Boolean = true, onClick: () -> Unit = {}, ) { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceItem.kt index 52c5442..d121879 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceItem.kt @@ -1,5 +1,6 @@ package cn.super12138.todo.ui.pages.settings.components.licence +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -34,11 +35,13 @@ fun LicenceItem( showAuthor: Boolean = true, showVersion: Boolean = true, showLicenseBadges: Boolean = true, - colors: LibraryColors = LibraryDefaults.libraryColors(), + colors: LibraryColors = LibraryDefaults.libraryColors( + libraryBackgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh + ), padding: LibraryPadding = LibraryDefaults.libraryPadding(), libraryPadding: LibraryPadding = LibraryDefaults.libraryPadding(), typography: Typography = MaterialTheme.typography, - shape: Shape = MaterialTheme.shapes.large, + shape: Shape = MaterialTheme.shapes.small, onClick: () -> Unit ) { Column( @@ -46,6 +49,7 @@ fun LicenceItem( .fillMaxWidth() .clip(shape) .clickable { onClick.invoke() } + .background(colors.libraryBackgroundColor) .padding(libraryPadding.contentPadding) ) { Row( @@ -64,7 +68,7 @@ fun LicenceItem( val version = library.artifactVersion if (version != null && showVersion) { Text( - version, + text = version, modifier = Modifier.padding(padding.versionPadding.contentPadding), style = typography.bodyMedium, textAlign = TextAlign.Center diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt index fc921d0..68776b1 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt @@ -1,5 +1,6 @@ package cn.super12138.todo.ui.pages.settings.components.licence +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState @@ -21,6 +22,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import cn.super12138.todo.R import cn.super12138.todo.ui.components.BasicDialog import cn.super12138.todo.ui.components.LazyColumnCustomScrollBar @@ -44,9 +46,10 @@ fun LicenceList( ) { LazyColumn( state = state, + verticalArrangement = Arrangement.spacedBy(2.dp), modifier = modifier ) { - items(libraries?.libraries ?: listOf()) { library -> + items(items = libraries?.libraries ?: listOf(), key = { it.artifactId }) { library -> var openDialog by rememberSaveable { mutableStateOf(false) } LicenceItem( From 24a90ea5f82d269149fee9077cbe5c78fd4b09d3 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Thu, 13 Nov 2025 19:49:05 +0800 Subject: [PATCH 08/21] =?UTF-8?q?feat:=20=E8=AE=A9=E6=9B=B4=E5=A4=9A?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E9=80=82=E9=85=8D=20MD3E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo/ui/components/ProgressIndicator.kt | 47 ----------- .../super12138/todo/ui/pages/main/MainPage.kt | 80 +++++++++---------- .../todo/ui/pages/main/ProgressFragment.kt | 34 +++++--- .../ui/pages/settings/SettingsAboutLicence.kt | 8 +- .../settings/SettingsDeveloperOptions.kt | 2 +- .../settings/components/SettingsCategory.kt | 1 - .../settings/components/SettingsContainer.kt | 6 +- .../settings/components/SettingsDialogs.kt | 7 +- .../components/licence/LicenceList.kt | 3 + 9 files changed, 73 insertions(+), 115 deletions(-) delete mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/components/ProgressIndicator.kt diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/ProgressIndicator.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/ProgressIndicator.kt deleted file mode 100644 index 3349d36..0000000 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/ProgressIndicator.kt +++ /dev/null @@ -1,47 +0,0 @@ -package cn.super12138.todo.ui.components - -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.material3.CircularWavyProgressIndicator -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.ProgressIndicatorDefaults -import androidx.compose.material3.WavyProgressIndicatorDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.unit.Dp - -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) -@Composable -fun AnimatedCircularWavyProgressIndicator( - progress: Float, - modifier: Modifier = Modifier, - color: Color = WavyProgressIndicatorDefaults.indicatorColor, - trackColor: Color = WavyProgressIndicatorDefaults.trackColor, - stroke: Stroke = WavyProgressIndicatorDefaults.circularIndicatorStroke, - trackStroke: Stroke = WavyProgressIndicatorDefaults.circularTrackStroke, - gapSize: Dp = WavyProgressIndicatorDefaults.CircularIndicatorTrackGapSize, - amplitude: (progress: Float) -> Float = WavyProgressIndicatorDefaults.indicatorAmplitude, - wavelength: Dp = WavyProgressIndicatorDefaults.CircularWavelength, - waveSpeed: Dp = wavelength, -) { - val animatedProgress by animateFloatAsState( - targetValue = progress, - animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec - ) - - CircularWavyProgressIndicator( - progress = { animatedProgress }, - modifier = modifier, - color = color, - trackColor = trackColor, - stroke = stroke, - trackStroke = trackStroke, - gapSize = gapSize, - amplitude = amplitude, - wavelength = wavelength, - waveSpeed = waveSpeed, - ) -} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index 3eb2921..f5c3ae5 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -4,9 +4,8 @@ import androidx.activity.compose.BackHandler import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons @@ -14,7 +13,6 @@ import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Scaffold -import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.animateFloatingActionButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -26,9 +24,9 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout import androidx.compose.ui.res.stringResource import androidx.navigation3.ui.LocalNavAnimatedContentScope -import androidx.window.core.layout.WindowSizeClass import cn.super12138.todo.R import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.database.TodoEntity @@ -46,7 +44,7 @@ fun MainPage( toTodoEditPage: (TodoEntity?) -> Unit, toSettingsPage: () -> Unit, sharedTransitionScope: SharedTransitionScope, - windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass, + // windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass, ) { val animatedVisibilityScope = LocalNavAnimatedContentScope.current @@ -102,7 +100,7 @@ fun MainPage( contentWindowInsets = WindowInsets(0, 0, 0, 0), modifier = modifier ) { innerPadding -> - val isMediumScreen = + /*val isMediumScreen = windowSizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND) if (isMediumScreen) { Row( @@ -141,44 +139,40 @@ fun MainPage( .fillMaxSize() ) } - } 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 { - toTodoEditPage(item) - } - }, - onItemLongClick = { viewModel.toggleTodoSelection(it) }, - onItemChecked = { - viewModel.updateTodo(it.copy(isCompleted = true)) - viewModel.playConfetti() - }, - selectedTodoIds = selectedTodoIds, - // sharedTransitionScope = sharedTransitionScope, - // animatedVisibilityScope = animatedVisibilityScope, - modifier = Modifier - .weight(3f) - .fillMaxSize() - ) - } + } else {*/ + Column( + modifier = Modifier.padding( + top = innerPadding.calculateTopPadding(), + bottom = innerPadding.calculateBottomPadding() + ) + ) { + ProgressFragment( + totalTasks = totalTasks, + completedTasks = completedTasks, + modifier = Modifier.fillMaxWidth() + ) + ManagerFragment( + state = listState, + list = filteredTodoList, + onItemClick = { item -> + if (inSelectedMode) { + viewModel.toggleTodoSelection(item) + } else { + toTodoEditPage(item) + } + }, + onItemLongClick = { viewModel.toggleTodoSelection(it) }, + onItemChecked = { + viewModel.updateTodo(it.copy(isCompleted = true)) + viewModel.playConfetti() + }, + selectedTodoIds = selectedTodoIds, + modifier = Modifier.fillMaxWidth() + // sharedTransitionScope = sharedTransitionScope, + // animatedVisibilityScope = animatedVisibilityScope, + ) } + /*}*/ ConfirmDialog( visible = showDeleteConfirmDialog, icon = Icons.Outlined.Delete, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt index 98934b0..e003d63 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt @@ -1,13 +1,20 @@ package cn.super12138.todo.ui.pages.main import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.material3.CircularWavyProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProgressIndicatorDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.StrokeCap @@ -21,21 +28,21 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import cn.super12138.todo.R import cn.super12138.todo.ui.TodoDefaults -import cn.super12138.todo.ui.components.AnimatedCircularWavyProgressIndicator +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun ProgressFragment( totalTasks: Int, completedTasks: Int, modifier: Modifier = Modifier ) { - val stroke = Stroke( - width = with(LocalDensity.current) { TodoDefaults.trackThickness.toPx() }, - cap = StrokeCap.Round, - ) - val context = LocalContext.current + val thickStrokeWidth = with(LocalDensity.current) { TodoDefaults.trackThickness.toPx() } + val thickStroke = remember(thickStrokeWidth) { + Stroke(width = thickStrokeWidth, cap = StrokeCap.Round) + } + val remainTasks = totalTasks - completedTasks val progress = if (totalTasks != 0) { completedTasks / totalTasks.toFloat() @@ -45,13 +52,18 @@ fun ProgressFragment( } Box( - modifier = modifier, + modifier = modifier.padding(20.dp), contentAlignment = Alignment.Center ) { - AnimatedCircularWavyProgressIndicator( - progress = progress, - stroke = stroke, - trackStroke = stroke, + val animatedProgress by animateFloatAsState( + targetValue = progress, + animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec + ) + + CircularWavyProgressIndicator( + progress = { animatedProgress }, + stroke = thickStroke, + trackStroke = thickStroke, amplitude = { progress -> if (progress <= 0.1f || progress >= 0.95f) { 0f diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt index fd69873..4a08ad9 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.CornerSize import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TopAppBarDefaults @@ -38,12 +37,7 @@ fun SettingsAboutLicence( .fillMaxSize() .padding(innerPadding) .padding(horizontal = TodoDefaults.screenHorizontalPadding) - .clip( - MaterialTheme.shapes.extraLarge.copy( - bottomEnd = CornerSize(0f), - bottomStart = CornerSize(0f) - ) - ) + .clip(MaterialTheme.shapes.extraLarge) ) { val listState = rememberLazyListState() LicenceList( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt index 0ac78f2..bc888fb 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt @@ -37,7 +37,7 @@ fun SettingsDeveloperOptions( modifier = Modifier .fillMaxSize() .padding(innerPadding) - .padding(horizontal = TodoDefaults.screenHorizontalPadding,) + .padding(horizontal = TodoDefaults.screenHorizontalPadding) .verticalScroll(rememberScrollState()) ) { Settings { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt index 3d79d21..9d58ae4 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt @@ -22,7 +22,6 @@ fun SettingsCategory( top = TodoDefaults.settingsItemVerticalPadding / 2, start = TodoDefaults.settingsItemHorizontalPadding, end = TodoDefaults.settingsItemHorizontalPadding, - bottom = TodoDefaults.settingsItemVerticalPadding / 4, ) ) { Text( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt index 18b2ae1..36b9062 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt @@ -4,7 +4,6 @@ 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.foundation.shape.CornerSize import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -22,9 +21,8 @@ fun Settings( Column( verticalArrangement = Arrangement.spacedBy(2.dp), modifier = modifier - .padding(vertical = TodoDefaults.screenVerticalPadding).clip( - shape = MaterialTheme.shapes.extraLarge - ), + .padding(vertical = TodoDefaults.screenVerticalPadding) + .clip(MaterialTheme.shapes.extraLarge), content = content ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt index 827e330..d146ccb 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt @@ -1,5 +1,7 @@ package cn.super12138.todo.ui.pages.settings.components +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -39,7 +41,9 @@ fun SettingsRadioDialog( text = { // Modifier.selectableGroup() 用来确保无障碍功能运行正确 Column( + verticalArrangement = Arrangement.spacedBy(2.dp), modifier = Modifier + .clip(MaterialTheme.shapes.extraLarge) .selectableGroup() .verticalScroll(rememberScrollState()) ) { @@ -72,7 +76,8 @@ fun RadioItem( modifier .fillMaxWidth() .height(56.dp) - .clip(MaterialTheme.shapes.large) + .clip(MaterialTheme.shapes.small) + .background(MaterialTheme.colorScheme.surfaceContainerHighest) .selectable( selected = selected, onClick = { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt index 68776b1..f801320 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt @@ -2,6 +2,7 @@ package cn.super12138.todo.ui.pages.settings.components.licence import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items @@ -24,6 +25,7 @@ import androidx.compose.ui.platform.LocalView 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.components.BasicDialog import cn.super12138.todo.ui.components.LazyColumnCustomScrollBar import cn.super12138.todo.utils.VibrationUtils @@ -47,6 +49,7 @@ fun LicenceList( LazyColumn( state = state, verticalArrangement = Arrangement.spacedBy(2.dp), + contentPadding = PaddingValues(vertical = TodoDefaults.screenVerticalPadding), modifier = modifier ) { items(items = libraries?.libraries ?: listOf(), key = { it.artifactId }) { library -> From 6ed4fe7e198045a3741fa4e116cbcfdbd50228bf Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sun, 16 Nov 2025 10:40:48 +0800 Subject: [PATCH 09/21] =?UTF-8?q?feat:=20=E9=92=88=E5=AF=B9=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=9C=BA=E6=99=AF=E4=BD=BF=E7=94=A8=20MD3E=20?= =?UTF-8?q?=E5=8A=A8=E7=94=BB=E6=9B=B2=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../super12138/todo/ui/pages/main/ProgressFragment.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt index e003d63..5a3dcb3 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt @@ -2,6 +2,10 @@ package cn.super12138.todo.ui.pages.main import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -113,7 +117,11 @@ fun ProgressFragment( ) ) } - AnimatedVisibility(remainTasks != 0) { + AnimatedVisibility( + visible = remainTasks != 0, + enter = fadeIn(MaterialTheme.motionScheme.fastSpatialSpec()) + expandVertically(MaterialTheme.motionScheme.fastSpatialSpec()), + exit = fadeOut(MaterialTheme.motionScheme.fastSpatialSpec()) + shrinkVertically(MaterialTheme.motionScheme.fastSpatialSpec()), + ) { Text( text = stringResource(R.string.tip_remain_tasks, remainTasks), style = MaterialTheme.typography.labelMedium, From 3487c64fe5a2c5183f608a484ce14c0db387dc7c Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sun, 16 Nov 2025 15:09:26 +0800 Subject: [PATCH 10/21] =?UTF-8?q?refactor:=20=E6=8A=8A=20Material=20Icon?= =?UTF-8?q?=20=E6=9B=BF=E6=8D=A2=E6=88=90=20Material=20Symbols?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 并优化部分无障碍表述 --- app/build.gradle.kts | 2 - .../todo/ui/components/ChipGroup.kt | 7 +-- .../super12138/todo/ui/components/Dialogs.kt | 14 ++--- .../ui/components/FloatingActionButton.kt | 9 +-- .../todo/ui/components/Scaffolds.kt | 5 +- .../icons/{GithubIcons.kt => GitHubIcons.kt} | 0 .../todo/ui/pages/crash/CrashPage.kt | 5 +- .../todo/ui/pages/editor/TodoEditorPage.kt | 12 ++-- .../super12138/todo/ui/pages/main/MainPage.kt | 8 +-- .../todo/ui/pages/main/ProgressFragment.kt | 8 ++- .../todo/ui/pages/main/components/TodoCard.kt | 9 ++- .../ui/pages/main/components/TodoTopAppBar.kt | 14 ++--- .../todo/ui/pages/settings/SettingsAbout.kt | 13 ++--- .../ui/pages/settings/SettingsAppearance.kt | 4 +- .../todo/ui/pages/settings/SettingsData.kt | 13 ++--- .../ui/pages/settings/SettingsDataCategory.kt | 4 +- .../settings/SettingsDeveloperOptions.kt | 4 +- .../settings/SettingsInterfaceInteraction.kt | 15 ++--- .../todo/ui/pages/settings/SettingsMain.kt | 13 ++--- .../pages/settings/components/SettingsItem.kt | 55 +++++++++++++++++++ .../settings/components/SettingsPlainBox.kt | 5 +- .../settings/components/SwitchSettingsItem.kt | 31 +++++++++++ .../appearance/darkmode/DarkModeItem.kt | 7 ++- .../appearance/darkmode/DarkModePicker.kt | 2 +- .../components/category/CategoryDialog.kt | 5 +- .../components/category/CategoryItem.kt | 5 +- .../cn/super12138/todo/ui/theme/DarkMode.kt | 13 ++--- app/src/main/res/drawable/ic_add.xml | 9 +++ app/src/main/res/drawable/ic_arrow_back.xml | 10 ++++ app/src/main/res/drawable/ic_balance.xml | 9 +++ app/src/main/res/drawable/ic_category.xml | 9 +++ app/src/main/res/drawable/ic_check.xml | 9 +++ app/src/main/res/drawable/ic_checklist.xml | 9 +++ app/src/main/res/drawable/ic_close.xml | 9 +++ app/src/main/res/drawable/ic_code_blocks.xml | 9 +++ app/src/main/res/drawable/ic_dark_mode.xml | 9 +++ app/src/main/res/drawable/ic_delete.xml | 9 +++ app/src/main/res/drawable/ic_dns.xml | 9 +++ app/src/main/res/drawable/ic_download.xml | 9 +++ app/src/main/res/drawable/ic_exit_to_app.xml | 10 ++++ app/src/main/res/drawable/ic_info.xml | 9 +++ app/src/main/res/drawable/ic_light_mode.xml | 9 +++ app/src/main/res/drawable/ic_lightbulb_2.xml | 9 +++ app/src/main/res/drawable/ic_numbers.xml | 9 +++ app/src/main/res/drawable/ic_padding.xml | 9 +++ app/src/main/res/drawable/ic_palette.xml | 9 +++ app/src/main/res/drawable/ic_person_4.xml | 9 +++ app/src/main/res/drawable/ic_restart_alt.xml | 9 +++ app/src/main/res/drawable/ic_save.xml | 9 +++ app/src/main/res/drawable/ic_select_all.xml | 9 +++ app/src/main/res/drawable/ic_settings.xml | 9 +++ app/src/main/res/drawable/ic_shield.xml | 9 +++ app/src/main/res/drawable/ic_sort.xml | 11 ++++ app/src/main/res/drawable/ic_touch_long.xml | 9 +++ app/src/main/res/drawable/ic_undo.xml | 10 ++++ app/src/main/res/drawable/ic_upload.xml | 9 +++ app/src/main/res/drawable/ic_view_comfy.xml | 9 +++ app/src/main/res/drawable/ic_wand_stars.xml | 9 +++ app/src/main/res/values/strings.xml | 1 + gradle/libs.versions.toml | 3 - 60 files changed, 446 insertions(+), 124 deletions(-) rename app/src/main/kotlin/cn/super12138/todo/ui/icons/{GithubIcons.kt => GitHubIcons.kt} (100%) create mode 100644 app/src/main/res/drawable/ic_add.xml create mode 100644 app/src/main/res/drawable/ic_arrow_back.xml create mode 100644 app/src/main/res/drawable/ic_balance.xml create mode 100644 app/src/main/res/drawable/ic_category.xml create mode 100644 app/src/main/res/drawable/ic_check.xml create mode 100644 app/src/main/res/drawable/ic_checklist.xml create mode 100644 app/src/main/res/drawable/ic_close.xml create mode 100644 app/src/main/res/drawable/ic_code_blocks.xml create mode 100644 app/src/main/res/drawable/ic_dark_mode.xml create mode 100644 app/src/main/res/drawable/ic_delete.xml create mode 100644 app/src/main/res/drawable/ic_dns.xml create mode 100644 app/src/main/res/drawable/ic_download.xml create mode 100644 app/src/main/res/drawable/ic_exit_to_app.xml create mode 100644 app/src/main/res/drawable/ic_info.xml create mode 100644 app/src/main/res/drawable/ic_light_mode.xml create mode 100644 app/src/main/res/drawable/ic_lightbulb_2.xml create mode 100644 app/src/main/res/drawable/ic_numbers.xml create mode 100644 app/src/main/res/drawable/ic_padding.xml create mode 100644 app/src/main/res/drawable/ic_palette.xml create mode 100644 app/src/main/res/drawable/ic_person_4.xml create mode 100644 app/src/main/res/drawable/ic_restart_alt.xml create mode 100644 app/src/main/res/drawable/ic_save.xml create mode 100644 app/src/main/res/drawable/ic_select_all.xml create mode 100644 app/src/main/res/drawable/ic_settings.xml create mode 100644 app/src/main/res/drawable/ic_shield.xml create mode 100644 app/src/main/res/drawable/ic_sort.xml create mode 100644 app/src/main/res/drawable/ic_touch_long.xml create mode 100644 app/src/main/res/drawable/ic_undo.xml create mode 100644 app/src/main/res/drawable/ic_upload.xml create mode 100644 app/src/main/res/drawable/ic_view_comfy.xml create mode 100644 app/src/main/res/drawable/ic_wand_stars.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0685a42..b170988 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -106,8 +106,6 @@ dependencies { implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) - implementation(libs.androidx.material.icon.core) - implementation(libs.androidx.material.icon.extended) implementation(libs.androidx.navigation3.runtime) implementation(libs.androidx.navigation3.ui) // About Libraries diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt index 779ff3e..d46199d 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt @@ -4,8 +4,6 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Check import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChipDefaults import androidx.compose.material3.Icon @@ -18,6 +16,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import cn.super12138.todo.R @@ -71,8 +70,8 @@ private fun FilterChipItem( if (selected) { { Icon( - imageVector = Icons.Outlined.Check, - contentDescription = stringResource(R.string.tip_select_this), + painter = painterResource(R.drawable.ic_check), + contentDescription = stringResource(R.string.tip_selected), modifier = Modifier.size(FilterChipDefaults.IconSize) ) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt index 1b44314..ee9e49d 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt @@ -1,10 +1,9 @@ package cn.super12138.todo.ui.components +import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.Column import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ErrorOutline import androidx.compose.material3.AlertDialog import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @@ -14,8 +13,9 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.window.DialogProperties import cn.super12138.todo.R @@ -25,7 +25,7 @@ import cn.super12138.todo.utils.VibrationUtils fun ConfirmDialog( modifier: Modifier = Modifier, visible: Boolean, - icon: ImageVector = Icons.Outlined.ErrorOutline, + @DrawableRes iconRes: Int, title: String = stringResource(R.string.title_warning), text: String, confirmButtonText: String = stringResource(R.string.action_confirm), @@ -37,7 +37,7 @@ fun ConfirmDialog( ) { BasicDialog( visible = visible, - icon = icon, + painter = painterResource(iconRes), title = title, text = { Text(text) }, // 已经实现好滚动了 confirmButton = confirmButtonText, @@ -57,7 +57,7 @@ fun ConfirmDialog( fun BasicDialog( modifier: Modifier = Modifier, visible: Boolean, - icon: ImageVector, + painter: Painter, title: String, text: @Composable (() -> Unit)? = null, confirmButton: String, @@ -71,7 +71,7 @@ fun BasicDialog( visible = visible, icon = { Icon( - imageVector = icon, + painter = painter, contentDescription = null // 会跟下面的文本重复,所以设置为 null ) }, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt index b47aba2..40cf4b2 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt @@ -1,5 +1,6 @@ package cn.super12138.todo.ui.components +import androidx.annotation.DrawableRes import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FloatingActionButtonDefaults @@ -12,8 +13,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import cn.super12138.todo.utils.VibrationUtils @@ -22,7 +23,7 @@ import cn.super12138.todo.utils.VibrationUtils * * (现在内置组件的动画好像好一点了) * * 将缩放部分转为最简单的`AnimatedVisibility`实现 - * @param icon FAB 的前置图标 + * @param iconRes FAB 的前置图标 * @param text FAB 的文本 * @param expanded 是否为展开状态 * @param containerColor FAB 容器的颜色 @@ -35,7 +36,7 @@ import cn.super12138.todo.utils.VibrationUtils @Composable fun TodoFloatingActionButton( text: String, - icon: ImageVector, + @DrawableRes iconRes: Int, onClick: () -> Unit, modifier: Modifier = Modifier, expanded: Boolean = true, @@ -56,7 +57,7 @@ fun TodoFloatingActionButton( }, icon = { Icon( - imageVector = icon, + painter = painterResource(iconRes), contentDescription = null ) }, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt index d5f4e80..158fe2a 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt @@ -4,8 +4,6 @@ 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.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FabPosition @@ -22,6 +20,7 @@ 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 @@ -68,7 +67,7 @@ fun LargeTopAppBarScaffold( } ) { Icon( - imageVector = Icons.AutoMirrored.Outlined.ArrowBack, + painter = painterResource(R.drawable.ic_arrow_back), contentDescription = stringResource(R.string.action_back) ) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/icons/GithubIcons.kt b/app/src/main/kotlin/cn/super12138/todo/ui/icons/GitHubIcons.kt similarity index 100% rename from app/src/main/kotlin/cn/super12138/todo/ui/icons/GithubIcons.kt rename to app/src/main/kotlin/cn/super12138/todo/ui/icons/GitHubIcons.kt diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt index 8472b7a..2a8da2f 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt @@ -10,8 +10,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.ExitToApp import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon @@ -28,6 +26,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -83,7 +82,7 @@ fun CrashPage( text = { Text(stringResource(R.string.action_exit_app)) }, icon = { Icon( - imageVector = Icons.AutoMirrored.Outlined.ExitToApp, + painter = painterResource(R.drawable.ic_exit_to_app), contentDescription = null ) }, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index 2e86810..f2904e2 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -17,10 +17,6 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.Undo -import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material.icons.outlined.Save import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme @@ -128,7 +124,7 @@ fun TodoEditorPage( if (toDo !== null) { TodoFloatingActionButton( text = stringResource(R.string.action_delete), - icon = Icons.Outlined.Delete, + iconRes = R.drawable.ic_delete, expanded = true, containerColor = MaterialTheme.colorScheme.errorContainer, onClick = { uiState.showDeleteConfirmDialog = true } @@ -136,7 +132,7 @@ fun TodoEditorPage( } TodoFloatingActionButton( text = stringResource(R.string.action_save), - icon = Icons.Outlined.Save, + iconRes = R.drawable.ic_save, expanded = true, onClick = { if (uiState.setErrorIfNotValid()) { @@ -256,7 +252,7 @@ fun TodoEditorPage( ConfirmDialog( visible = uiState.showExitConfirmDialog, - icon = Icons.AutoMirrored.Outlined.Undo, + iconRes = R.drawable.ic_undo, text = stringResource(R.string.tip_discard_changes), onConfirm = { uiState.showExitConfirmDialog = false @@ -267,7 +263,7 @@ fun TodoEditorPage( ConfirmDialog( visible = uiState.showDeleteConfirmDialog, - icon = Icons.Outlined.Delete, + iconRes = R.drawable.ic_delete, text = stringResource(R.string.tip_delete_task, 1), onConfirm = onDelete, onDismiss = { uiState.showDeleteConfirmDialog = false } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index f5c3ae5..af8eadc 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -8,9 +8,6 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Add -import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Scaffold import androidx.compose.material3.animateFloatingActionButton @@ -24,7 +21,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout import androidx.compose.ui.res.stringResource import androidx.navigation3.ui.LocalNavAnimatedContentScope import cn.super12138.todo.R @@ -82,7 +78,7 @@ fun MainPage( with(sharedTransitionScope) { TodoFloatingActionButton( text = stringResource(R.string.action_add_task), - icon = Icons.Outlined.Add, + iconRes = R.drawable.ic_add, expanded = expandedFab, onClick = { toTodoEditPage(null) }, modifier = Modifier @@ -175,7 +171,7 @@ fun MainPage( /*}*/ ConfirmDialog( visible = showDeleteConfirmDialog, - icon = Icons.Outlined.Delete, + iconRes = R.drawable.ic_delete, text = stringResource(R.string.tip_delete_task, selectedTodoIds.size), onConfirm = { viewModel.deleteSelectedTodo() }, onDismiss = { showDeleteConfirmDialog = false } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt index 5a3dcb3..10f5d77 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt @@ -119,8 +119,12 @@ fun ProgressFragment( } AnimatedVisibility( visible = remainTasks != 0, - enter = fadeIn(MaterialTheme.motionScheme.fastSpatialSpec()) + expandVertically(MaterialTheme.motionScheme.fastSpatialSpec()), - exit = fadeOut(MaterialTheme.motionScheme.fastSpatialSpec()) + shrinkVertically(MaterialTheme.motionScheme.fastSpatialSpec()), + enter = fadeIn(MaterialTheme.motionScheme.fastSpatialSpec()) + expandVertically( + MaterialTheme.motionScheme.fastSpatialSpec() + ), + exit = fadeOut(MaterialTheme.motionScheme.fastSpatialSpec()) + shrinkVertically( + MaterialTheme.motionScheme.fastSpatialSpec() + ), ) { Text( text = stringResource(R.string.tip_remain_tasks, remainTasks), diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt index dd22418..a232388 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt @@ -15,8 +15,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Check import androidx.compose.material3.Badge import androidx.compose.material3.BadgedBox import androidx.compose.material3.ElevatedCard @@ -31,6 +29,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow @@ -87,9 +86,9 @@ fun TodoCard( .padding(5.dp) ) { Icon( - imageVector = Icons.Outlined.Check, + painter = painterResource(R.drawable.ic_check), tint = contentColorFor(MaterialTheme.colorScheme.secondary), - contentDescription = stringResource(R.string.tip_select_this) + contentDescription = stringResource(R.string.tip_selected) ) } } @@ -158,7 +157,7 @@ fun TodoCard( } ) { Icon( - imageVector = Icons.Outlined.Check, + painter = painterResource(R.drawable.ic_check), tint = MaterialTheme.colorScheme.primary, contentDescription = stringResource(R.string.tip_mark_completed) ) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt index 24f9901..2f00f64 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt @@ -11,11 +11,6 @@ import androidx.compose.animation.scaleIn import androidx.compose.animation.shrinkOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Row -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Close -import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material.icons.outlined.SelectAll -import androidx.compose.material.icons.outlined.Settings import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -30,6 +25,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier 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 androidx.compose.ui.tooling.preview.Preview @@ -70,7 +66,7 @@ fun TodoTopAppBar( } ) { Icon( - imageVector = Icons.Outlined.Close, + painter = painterResource(R.drawable.ic_close), contentDescription = stringResource(R.string.tip_clear_selected_items) ) } @@ -125,7 +121,7 @@ fun TodoTopAppBar( } ) { Icon( - imageVector = Icons.Outlined.Settings, + painter = painterResource(R.drawable.ic_settings), contentDescription = stringResource(R.string.page_settings) ) } @@ -138,7 +134,7 @@ fun TodoTopAppBar( } ) { Icon( - imageVector = Icons.Outlined.SelectAll, + painter = painterResource(R.drawable.ic_select_all), contentDescription = stringResource(R.string.tip_select_all) ) } @@ -149,7 +145,7 @@ fun TodoTopAppBar( } ) { Icon( - imageVector = Icons.Outlined.Delete, + painter = painterResource(R.drawable.ic_delete), contentDescription = stringResource(R.string.action_delete) ) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt index 816c6c7..898ba75 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt @@ -7,11 +7,6 @@ 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.material.icons.Icons -import androidx.compose.material.icons.outlined.Balance -import androidx.compose.material.icons.outlined.DeveloperMode -import androidx.compose.material.icons.outlined.Numbers -import androidx.compose.material.icons.outlined.Person4 import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.TopAppBarDefaults @@ -82,7 +77,7 @@ fun SettingsAbout( } SettingsItem( - leadingIcon = Icons.Outlined.Numbers, + leadingIconRes = R.drawable.ic_numbers, title = stringResource(R.string.pref_app_version), description = SystemUtils.getAppVersion(context), onClick = { @@ -98,7 +93,7 @@ fun SettingsAbout( } ) SettingsItem( - leadingIcon = Icons.Outlined.Person4, + leadingIconRes = R.drawable.ic_person_4, title = stringResource(R.string.pref_developer), description = stringResource(R.string.developer_name), onClick = { uriHandler.openUri(Constants.DEVELOPER_GITHUB) } @@ -110,13 +105,13 @@ fun SettingsAbout( onClick = { uriHandler.openUri(Constants.GITHUB_REPO) } ) SettingsItem( - leadingIcon = Icons.Outlined.Balance, + leadingIconRes = R.drawable.ic_balance, title = stringResource(R.string.pref_licence), description = stringResource(R.string.pref_licence_desc), onClick = toLicencePage ) SettingsItem( - leadingIcon = Icons.Outlined.DeveloperMode, + leadingIconRes = R.drawable.ic_code_blocks, title = stringResource(R.string.pref_developer_options), description = stringResource(R.string.pref_developer_options_desc), onClick = toDevPage diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt index f534369..f881f59 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt @@ -5,8 +5,6 @@ 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.material.icons.Icons -import androidx.compose.material.icons.outlined.AutoAwesome import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -60,7 +58,7 @@ fun SettingsAppearance( Settings { SwitchSettingsItem( checked = dynamicColor, - leadingIcon = Icons.Outlined.AutoAwesome, + 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) } }, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt index fe8d71d..227c668 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt @@ -9,11 +9,6 @@ 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.material.icons.Icons -import androidx.compose.material.icons.outlined.Category -import androidx.compose.material.icons.outlined.FileDownload -import androidx.compose.material.icons.outlined.FileUpload -import androidx.compose.material.icons.outlined.RestartAlt import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.SnackbarHost @@ -126,7 +121,7 @@ fun SettingsData( SettingsCategory(stringResource(R.string.pref_category_data_management)) Settings { SettingsItem( - leadingIcon = Icons.Outlined.FileDownload, + leadingIconRes = R.drawable.ic_download, title = stringResource(R.string.pref_backup), description = stringResource(R.string.pref_backup_desc), onClick = { @@ -134,7 +129,7 @@ fun SettingsData( } ) SettingsItem( - leadingIcon = Icons.Outlined.FileUpload, + leadingIconRes = R.drawable.ic_upload, title = stringResource(R.string.pref_restore), description = stringResource(R.string.pref_restore_desc), onClick = { @@ -145,7 +140,7 @@ fun SettingsData( SettingsCategory(stringResource(R.string.pref_category_category_management)) Settings { SettingsItem( - leadingIcon = Icons.Outlined.Category, + leadingIconRes = R.drawable.ic_category, title = stringResource(R.string.pref_category_category_management), description = stringResource(R.string.pref_category_management_desc), onClick = toCategoryManager @@ -155,7 +150,7 @@ fun SettingsData( } ConfirmDialog( visible = showRestoreDialog, - icon = Icons.Outlined.RestartAlt, + iconRes = R.drawable.ic_restart_alt, title = stringResource(R.string.tip_tips), text = stringResource(R.string.tip_restore_success), showDismissButton = false, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt index d586fed..38f0e35 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt @@ -10,8 +10,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Add import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHost @@ -66,7 +64,7 @@ fun SettingsDataCategory( snackbarHost = { SnackbarHost(snackbarHostState) }, floatingActionButton = { TodoFloatingActionButton( - icon = Icons.Outlined.Add, + iconRes = R.drawable.ic_add, text = stringResource(R.string.action_add_category), expanded = isExpanded, onClick = { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt index bc888fb..76507d2 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt @@ -5,8 +5,6 @@ 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.material.icons.Icons -import androidx.compose.material.icons.outlined.Padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -42,7 +40,7 @@ fun SettingsDeveloperOptions( ) { Settings { SettingsItem( - leadingIcon = Icons.Outlined.Padding, + leadingIconRes = R.drawable.ic_padding, title = stringResource(R.string.pref_padding), onClick = toPaddingPage ) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt index 0936a40..96251d1 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt @@ -5,11 +5,6 @@ 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.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.Sort -import androidx.compose.material.icons.outlined.Checklist -import androidx.compose.material.icons.outlined.Shield -import androidx.compose.material.icons.outlined.Vibration import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.TopAppBarDefaults @@ -71,14 +66,14 @@ fun SettingsInterface( SettingsCategory(stringResource(R.string.pref_category_todo_list)) Settings { SwitchSettingsItem( - checked = showCompleted, - leadingIcon = Icons.Outlined.Checklist, + 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( - leadingIcon = Icons.AutoMirrored.Outlined.Sort, + leadingIconRes = R.drawable.ic_sort, title = stringResource(R.string.pref_sorting_method), description = SortingMethod.fromId(sortingMethod).getDisplayName(context), onClick = { showSortingMethodDialog = true } @@ -88,14 +83,14 @@ fun SettingsInterface( Settings { SwitchSettingsItem( checked = secureMode, - leadingIcon = Icons.Outlined.Shield, + 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, - leadingIcon = Icons.Outlined.Vibration, + 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) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt index e19d509..6299153 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt @@ -5,11 +5,6 @@ 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.material.icons.Icons -import androidx.compose.material.icons.outlined.ColorLens -import androidx.compose.material.icons.outlined.Dns -import androidx.compose.material.icons.outlined.Info -import androidx.compose.material.icons.outlined.ViewComfy import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.TopAppBarDefaults @@ -49,25 +44,25 @@ fun SettingsMain( ) { Settings { SettingsItem( - leadingIcon = Icons.Outlined.ColorLens, + leadingIconRes = R.drawable.ic_palette, title = stringResource(R.string.pref_appearance), description = stringResource(R.string.pref_appearance_desc), onClick = toAppearancePage ) SettingsItem( - leadingIcon = Icons.Outlined.ViewComfy, + leadingIconRes = R.drawable.ic_view_comfy, title = stringResource(R.string.pref_interface_interaction), description = stringResource(R.string.pref_interface_interaction_desc), onClick = toInterfacePage ) SettingsItem( - leadingIcon = Icons.Outlined.Dns, + leadingIconRes = R.drawable.ic_dns, title = stringResource(R.string.pref_data), description = stringResource(R.string.pref_data_desc), onClick = toDataPage ) SettingsItem( - leadingIcon = Icons.Outlined.Info, + leadingIconRes = R.drawable.ic_info, title = stringResource(R.string.pref_about), description = stringResource(R.string.pref_about_desc), onClick = toAboutPage diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt index a5a4fb0..2a9381e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt @@ -1,5 +1,6 @@ package cn.super12138.todo.ui.pages.settings.components +import androidx.annotation.DrawableRes import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -17,13 +18,35 @@ 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 +import androidx.compose.ui.res.painterResource 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 +@Composable +fun SettingsItem( + modifier: Modifier = Modifier, + @DrawableRes leadingIconRes: Int, + title: String, + description: String? = null, + enableClick: Boolean = true, + onClick: () -> Unit = {} +) { + SettingsItem( + leadingIcon = painterResource(leadingIconRes), + title = title, + description = description, + trailingContent = null, + enableClick = enableClick, + onClick = onClick, + modifier = modifier + ) +} + @Composable fun SettingsItem( modifier: Modifier = Modifier, @@ -44,6 +67,38 @@ fun SettingsItem( ) } +@Composable +fun SettingsItem( + modifier: Modifier = Modifier, + leadingIcon: Painter? = null, + title: String, + description: String? = null, + trailingContent: (@Composable () -> Unit)? = null, + background: Color = MaterialTheme.colorScheme.surfaceContainerHigh, + enableClick: Boolean = true, + onClick: () -> Unit = {} +) { + SettingsItem( + leadingIcon = { + leadingIcon?.let { + Icon( + painter = leadingIcon, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(end = TodoDefaults.settingsItemHorizontalPadding), + ) + } + }, + title = title, + description = description, + trailingContent = trailingContent, + background = background, + enableClick = enableClick, + onClick = onClick, + modifier = modifier + ) +} + @Composable fun SettingsItem( modifier: Modifier = Modifier, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsPlainBox.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsPlainBox.kt index 3e3112c..630a754 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsPlainBox.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsPlainBox.kt @@ -6,13 +6,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription @@ -37,7 +36,7 @@ fun SettingsPlainBox( ), ) { Icon( - imageVector = Icons.Outlined.Info, + painter = painterResource(R.drawable.ic_info), contentDescription = null ) Spacer(Modifier.size(20.dp)) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt index b0db4d8..dba0e6f 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt @@ -1,14 +1,45 @@ package cn.super12138.todo.ui.pages.settings.components +import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.padding import androidx.compose.material3.Switch import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.utils.VibrationUtils +@Composable +fun SwitchSettingsItem( + modifier: Modifier = Modifier, + @DrawableRes leadingIconRes: Int, + title: String, + description: String? = null, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit +) { + val view = LocalView.current + SettingsItem( + leadingIcon = painterResource(leadingIconRes), + title = title, + description = description, + trailingContent = { + Switch( + checked = checked, + onCheckedChange = { + VibrationUtils.performHapticFeedback(view) + onCheckedChange(it) + }, + modifier = Modifier.padding(start = TodoDefaults.settingsItemHorizontalPadding / 2) + ) + }, + onClick = { onCheckedChange(!checked) }, + modifier = modifier, + ) +} + @Composable fun SwitchSettingsItem( modifier: Modifier = Modifier, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModeItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModeItem.kt index 53b7843..77e4778 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModeItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModeItem.kt @@ -1,5 +1,6 @@ package cn.super12138.todo.ui.pages.settings.components.appearance.darkmode +import androidx.annotation.DrawableRes import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -18,15 +19,15 @@ 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.vector.ImageVector import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import cn.super12138.todo.utils.VibrationUtils @Composable fun DarkModeItem( - icon: ImageVector, + @DrawableRes iconRes: Int, name: String, contentColor: Color, containerColor: Color, @@ -61,7 +62,7 @@ fun DarkModeItem( ), ) { Icon( - imageVector = icon, + painter = painterResource(iconRes), contentDescription = null, tint = contentColor, modifier = Modifier diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt index afaca2e..8cdec0c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt @@ -44,7 +44,7 @@ fun DarkModePicker( val isSelected by remember { derivedStateOf { currentDarkMode() == it } } DarkModeItem( - icon = it.icon, + iconRes = it.iconRes, name = it.getDisplayName(context), contentColor = contentColor, containerColor = containerColor, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryDialog.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryDialog.kt index d4ae27f..aa59817 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryDialog.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryDialog.kt @@ -7,8 +7,6 @@ import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -19,6 +17,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties @@ -53,7 +52,7 @@ fun CategoryPromptDialog( BasicDialog( visible = visible, - icon = Icons.Outlined.Info, + painter = painterResource(R.drawable.ic_info), title = stringResource(R.string.tip_tips), text = { // 已经是实现好滚动的Column布局 diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt index 0ca8269..c858d9f 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt @@ -5,8 +5,6 @@ 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.material.icons.Icons -import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -16,6 +14,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip 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 @@ -64,7 +63,7 @@ fun CategoryItem( } ) { Icon( - imageVector = Icons.Outlined.Delete, + painter = painterResource(R.drawable.ic_delete), contentDescription = stringResource(R.string.action_delete) ) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/theme/DarkMode.kt b/app/src/main/kotlin/cn/super12138/todo/ui/theme/DarkMode.kt index 6eabe08..d63eb4b 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/theme/DarkMode.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/theme/DarkMode.kt @@ -1,20 +1,15 @@ package cn.super12138.todo.ui.theme import android.content.Context -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.DarkMode -import androidx.compose.material.icons.outlined.LightMode -import androidx.compose.material.icons.outlined.SettingsSuggest -import androidx.compose.ui.graphics.vector.ImageVector import cn.super12138.todo.R enum class DarkMode( val id: Int, - val icon: ImageVector + val iconRes: Int ) { - FollowSystem(-1, Icons.Outlined.SettingsSuggest), - Light(1, Icons.Outlined.LightMode), - Dark(2, Icons.Outlined.DarkMode); + FollowSystem(-1, R.drawable.ic_lightbulb_2), + Light(1, R.drawable.ic_light_mode), + Dark(2, R.drawable.ic_dark_mode); fun getDisplayName(context: Context): String { val resId = when (this) { diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 0000000..cebf3ad --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_back.xml b/app/src/main/res/drawable/ic_arrow_back.xml new file mode 100644 index 0000000..6c86b2c --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_back.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_balance.xml b/app/src/main/res/drawable/ic_balance.xml new file mode 100644 index 0000000..25d6c7e --- /dev/null +++ b/app/src/main/res/drawable/ic_balance.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_category.xml b/app/src/main/res/drawable/ic_category.xml new file mode 100644 index 0000000..93b5ce7 --- /dev/null +++ b/app/src/main/res/drawable/ic_category.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 0000000..93cea1a --- /dev/null +++ b/app/src/main/res/drawable/ic_check.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_checklist.xml b/app/src/main/res/drawable/ic_checklist.xml new file mode 100644 index 0000000..99c104f --- /dev/null +++ b/app/src/main/res/drawable/ic_checklist.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000..cbffd83 --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_code_blocks.xml b/app/src/main/res/drawable/ic_code_blocks.xml new file mode 100644 index 0000000..7206c81 --- /dev/null +++ b/app/src/main/res/drawable/ic_code_blocks.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_dark_mode.xml b/app/src/main/res/drawable/ic_dark_mode.xml new file mode 100644 index 0000000..8f2b331 --- /dev/null +++ b/app/src/main/res/drawable/ic_dark_mode.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml new file mode 100644 index 0000000..2565afc --- /dev/null +++ b/app/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_dns.xml b/app/src/main/res/drawable/ic_dns.xml new file mode 100644 index 0000000..e6369d5 --- /dev/null +++ b/app/src/main/res/drawable/ic_dns.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_download.xml b/app/src/main/res/drawable/ic_download.xml new file mode 100644 index 0000000..dc9f21c --- /dev/null +++ b/app/src/main/res/drawable/ic_download.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_exit_to_app.xml b/app/src/main/res/drawable/ic_exit_to_app.xml new file mode 100644 index 0000000..df0bec0 --- /dev/null +++ b/app/src/main/res/drawable/ic_exit_to_app.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml new file mode 100644 index 0000000..f4bbc0a --- /dev/null +++ b/app/src/main/res/drawable/ic_info.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_light_mode.xml b/app/src/main/res/drawable/ic_light_mode.xml new file mode 100644 index 0000000..8710c7d --- /dev/null +++ b/app/src/main/res/drawable/ic_light_mode.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lightbulb_2.xml b/app/src/main/res/drawable/ic_lightbulb_2.xml new file mode 100644 index 0000000..fc88628 --- /dev/null +++ b/app/src/main/res/drawable/ic_lightbulb_2.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_numbers.xml b/app/src/main/res/drawable/ic_numbers.xml new file mode 100644 index 0000000..87edfe2 --- /dev/null +++ b/app/src/main/res/drawable/ic_numbers.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_padding.xml b/app/src/main/res/drawable/ic_padding.xml new file mode 100644 index 0000000..cfb2acb --- /dev/null +++ b/app/src/main/res/drawable/ic_padding.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_palette.xml b/app/src/main/res/drawable/ic_palette.xml new file mode 100644 index 0000000..719d6a7 --- /dev/null +++ b/app/src/main/res/drawable/ic_palette.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_person_4.xml b/app/src/main/res/drawable/ic_person_4.xml new file mode 100644 index 0000000..8ac355c --- /dev/null +++ b/app/src/main/res/drawable/ic_person_4.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_restart_alt.xml b/app/src/main/res/drawable/ic_restart_alt.xml new file mode 100644 index 0000000..666ce5b --- /dev/null +++ b/app/src/main/res/drawable/ic_restart_alt.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_save.xml b/app/src/main/res/drawable/ic_save.xml new file mode 100644 index 0000000..74a6c61 --- /dev/null +++ b/app/src/main/res/drawable/ic_save.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_select_all.xml b/app/src/main/res/drawable/ic_select_all.xml new file mode 100644 index 0000000..c8fdc8c --- /dev/null +++ b/app/src/main/res/drawable/ic_select_all.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..7c2b6bc --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_shield.xml b/app/src/main/res/drawable/ic_shield.xml new file mode 100644 index 0000000..b1a91f7 --- /dev/null +++ b/app/src/main/res/drawable/ic_shield.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sort.xml b/app/src/main/res/drawable/ic_sort.xml new file mode 100644 index 0000000..40a4cc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_sort.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_touch_long.xml b/app/src/main/res/drawable/ic_touch_long.xml new file mode 100644 index 0000000..4c96006 --- /dev/null +++ b/app/src/main/res/drawable/ic_touch_long.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_undo.xml b/app/src/main/res/drawable/ic_undo.xml new file mode 100644 index 0000000..72804c5 --- /dev/null +++ b/app/src/main/res/drawable/ic_undo.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_upload.xml b/app/src/main/res/drawable/ic_upload.xml new file mode 100644 index 0000000..456537a --- /dev/null +++ b/app/src/main/res/drawable/ic_upload.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_view_comfy.xml b/app/src/main/res/drawable/ic_view_comfy.xml new file mode 100644 index 0000000..3ef8519 --- /dev/null +++ b/app/src/main/res/drawable/ic_view_comfy.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wand_stars.xml b/app/src/main/res/drawable/ic_wand_stars.xml new file mode 100644 index 0000000..b89f58d --- /dev/null +++ b/app/src/main/res/drawable/ic_wand_stars.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0c3f4bc..ec1df71 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -116,4 +116,5 @@ Developer Options Some debugging options, only for DEVELOPMENT use Padding + Selected \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index da7bd7f..6ea1cf7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,6 @@ datastore = "1.1.7" activityCompose = "1.11.0" composeBom = "2025.11.00" liveData = "1.9.4" -icons = "1.7.8" room = "2.8.3" material3 = "1.5.0-alpha08" adaptive = "1.2.0" @@ -58,8 +57,6 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } -androidx-material-icon-core = { group = "androidx.compose.material", name = "material-icons-core", version.ref = "icons" } -androidx-material-icon-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "icons" } androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } From 63a7a830715223d54ccef2becbf04ad6ca00f347 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sat, 22 Nov 2025 20:25:57 +0800 Subject: [PATCH 11/21] =?UTF-8?q?feat:=20=E9=80=82=E9=85=8D=20MD3E=20?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../super12138/todo/logic/model/Priority.kt | 2 +- .../editor/components/TodoPrioritySlider.kt | 2 +- .../super12138/todo/ui/pages/main/MainPage.kt | 2 - .../todo/ui/pages/main/ManagerFragment.kt | 15 +++--- .../todo/ui/pages/main/components/TodoCard.kt | 52 +++++++++---------- .../cn/super12138/todo/utils/TodoExt.kt | 18 +++++++ 6 files changed, 52 insertions(+), 39 deletions(-) create mode 100644 app/src/main/kotlin/cn/super12138/todo/utils/TodoExt.kt diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/Priority.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/Priority.kt index 395cbe1..b2eb31e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/model/Priority.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/Priority.kt @@ -10,7 +10,7 @@ enum class Priority(val value: Float) { NotImportant(-1f), NotUrgent(-2f); - fun getDisplayName(context: Context): String { + fun displayName(context: Context): String { val resId = when (this) { Urgent -> R.string.priority_urgent Important -> R.string.priority_important diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoPrioritySlider.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoPrioritySlider.kt index 4b416bc..008a182 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoPrioritySlider.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoPrioritySlider.kt @@ -35,7 +35,7 @@ fun TodoPrioritySlider( val view = LocalView.current val context = LocalContext.current - val priorityName = remember { Priority.entries.map { it.getDisplayName(context) } } + val priorityName = remember { Priority.entries.map { it.displayName(context) } } val interactionSource = remember { MutableInteractionSource() } Slider( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index af8eadc..3bc62b1 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -164,8 +164,6 @@ fun MainPage( }, selectedTodoIds = selectedTodoIds, modifier = Modifier.fillMaxWidth() - // sharedTransitionScope = sharedTransitionScope, - // animatedVisibilityScope = animatedVisibilityScope, ) } /*}*/ diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt index c223134..04c95b5 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -21,7 +22,7 @@ import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.components.LazyColumnCustomScrollBar import cn.super12138.todo.ui.pages.main.components.TodoCard -@OptIn(ExperimentalSharedTransitionApi::class) +@OptIn(ExperimentalSharedTransitionApi::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun ManagerFragment( modifier: Modifier = Modifier, @@ -30,9 +31,7 @@ fun ManagerFragment( onItemClick: (TodoEntity) -> Unit = {}, onItemLongClick: (TodoEntity) -> Unit = {}, onItemChecked: (TodoEntity) -> Unit = {}, - selectedTodoIds: List, - // sharedTransitionScope: SharedTransitionScope, - // animatedVisibilityScope: AnimatedVisibilityScope + selectedTodoIds: List ) { LazyColumnCustomScrollBar( state = state, @@ -79,11 +78,13 @@ fun ManagerFragment( onCardClick = { onItemClick(item) }, onCardLongClick = { onItemLongClick(item) }, onChecked = { onItemChecked(item) }, - // sharedTransitionScope = sharedTransitionScope, - // animatedVisibilityScope = animatedVisibilityScope, modifier = Modifier .padding(vertical = 5.dp) - .animateItem() // TODO: 设置动画时间 + .animateItem( + fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(), + placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(), + fadeOutSpec = MaterialTheme.motionScheme.fastEffectsSpec() + ) ) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt index a232388..2c7c960 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt @@ -2,6 +2,10 @@ package cn.super12138.todo.ui.pages.main.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.expandHorizontally +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.basicMarquee @@ -18,6 +22,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Badge import androidx.compose.material3.BadgedBox import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -38,8 +43,12 @@ import cn.super12138.todo.R import cn.super12138.todo.logic.model.Priority import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.utils.VibrationUtils +import cn.super12138.todo.utils.containerColor -@OptIn(ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class) +@OptIn( + ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class, + ExperimentalMaterial3ExpressiveApi::class +) @Composable fun TodoCard( modifier: Modifier = Modifier, @@ -51,9 +60,7 @@ fun TodoCard( selected: Boolean, onCardClick: () -> Unit = {}, onCardLongClick: () -> Unit = {}, - onChecked: () -> Unit = {}, - // sharedTransitionScope: SharedTransitionScope, - // animatedVisibilityScope: AnimatedVisibilityScope + onChecked: () -> Unit = {} ) { val view = LocalView.current val context = LocalContext.current @@ -77,7 +84,15 @@ fun TodoCard( ) .padding(horizontal = 15.dp) ) { - AnimatedVisibility(selected) { + AnimatedVisibility( + visible = selected, + enter = fadeIn(MaterialTheme.motionScheme.fastSpatialSpec()) + expandHorizontally( + MaterialTheme.motionScheme.fastSpatialSpec() + ), + exit = fadeOut(MaterialTheme.motionScheme.fastSpatialSpec()) + shrinkHorizontally( + MaterialTheme.motionScheme.fastSpatialSpec() + ) + ) { Box( Modifier .padding(end = 15.dp) @@ -102,51 +117,32 @@ fun TodoCard( BadgedBox( badge = { Badge( - containerColor = when (priority) { - Priority.NotUrgent -> MaterialTheme.colorScheme.surfaceContainerHighest - Priority.NotImportant -> MaterialTheme.colorScheme.surfaceContainerHighest - Priority.Default -> MaterialTheme.colorScheme.secondary - Priority.Important -> MaterialTheme.colorScheme.tertiary - Priority.Urgent -> MaterialTheme.colorScheme.error - }, + containerColor = priority.containerColor(), modifier = Modifier.padding(start = 5.dp) ) { Text( - text = priority.getDisplayName(context), + text = priority.displayName(context), textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, ) } } ) { - // with(sharedTransitionScope) { Text( text = content, style = MaterialTheme.typography.titleLarge, maxLines = 1, overflow = TextOverflow.Ellipsis, textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, - modifier = Modifier - /*.sharedBounds( - sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CONTENT_TRANSITION}_$id"), - animatedVisibilityScope = animatedVisibilityScope - )*/ - .basicMarquee() // TODO: 后续评估性能影响 + modifier = Modifier.basicMarquee() // TODO: 后续评估性能影响 ) - // } } - // with(sharedTransitionScope) { Text( text = category.ifEmpty { stringResource(R.string.tip_default_category) }, style = MaterialTheme.typography.labelMedium, textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, - maxLines = 1, - /*modifier = Modifier.sharedBounds( - sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CATEGORY_TRANSITION}_$id"), - animatedVisibilityScope = animatedVisibilityScope - )*/ + maxLines = 1 ) - // } } AnimatedVisibility(!selected && !completed) { diff --git a/app/src/main/kotlin/cn/super12138/todo/utils/TodoExt.kt b/app/src/main/kotlin/cn/super12138/todo/utils/TodoExt.kt new file mode 100644 index 0000000..65d5c37 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/utils/TodoExt.kt @@ -0,0 +1,18 @@ +package cn.super12138.todo.utils + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.Color +import cn.super12138.todo.logic.model.Priority + +@Composable +@Stable +fun Priority.containerColor(): Color = + when (this) { + Priority.NotUrgent -> MaterialTheme.colorScheme.surfaceContainerHighest + Priority.NotImportant -> MaterialTheme.colorScheme.surfaceContainerHighest + Priority.Default -> MaterialTheme.colorScheme.secondary + Priority.Important -> MaterialTheme.colorScheme.tertiary + Priority.Urgent -> MaterialTheme.colorScheme.error + } \ No newline at end of file From cdbbdb9fc0f5a0fb1dde645de3f8888d09e34b75 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sun, 23 Nov 2025 19:57:54 +0800 Subject: [PATCH 12/21] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E4=BE=9D?= =?UTF-8?q?=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 +++- gradle/libs.versions.toml | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b170988..f2e947e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -19,7 +19,9 @@ val verCode = "git rev-list --count HEAD".exec().toInt() */ android { namespace = "cn.super12138.todo" - compileSdk = 36 + compileSdk { + version = release(36) + } // 获取 Release 签名 val releaseSigning = if (project.hasProperty("releaseStoreFile")) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0000f26..88eb7d9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,12 +8,10 @@ datastore = "1.2.0" activityCompose = "1.12.0" composeBom = "2025.11.01" liveData = "1.9.5" -icons = "1.7.8" room = "2.8.4" -navigation = "2.9.6" material3 = "1.5.0-alpha09" adaptive = "1.2.0" -nav3Core = "1.0.0-rc01" +nav3Core = "1.0.0" # AboutLibraries aboutLibsRelease = "13.1.0" aboutLibsReleasePlugin = "13.1.0" From 667cb152ea94989f2f9d32b4d92c94f51e6cee97 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sun, 18 Jan 2026 11:48:44 +0800 Subject: [PATCH 13/21] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E9=A1=B5=E9=A1=B5=E9=9D=A2=E6=A0=B7=E5=BC=8F=EF=BC=8C?= =?UTF-8?q?=E7=AE=80=E5=8C=96=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../cn/super12138/todo/ui/TodoDefaults.kt | 35 ++++- .../todo/ui/activities/CrashActivity.kt | 2 +- .../todo/ui/activities/MainActivity.kt | 4 +- .../todo/ui/components/Scaffolds.kt | 35 ++--- .../todo/ui/components/ScreenContainer.kt | 23 +++ .../todo/ui/pages/editor/TodoEditorPage.kt | 7 +- .../todo/ui/pages/settings/SettingsAbout.kt | 143 +++++++++--------- .../ui/pages/settings/SettingsAboutLicence.kt | 7 +- .../ui/pages/settings/SettingsAppearance.kt | 81 +++++----- .../todo/ui/pages/settings/SettingsData.kt | 89 +++++------ .../ui/pages/settings/SettingsDataCategory.kt | 10 +- .../settings/SettingsDeveloperOptions.kt | 41 ++--- .../settings/SettingsInterfaceInteraction.kt | 140 +++++++++-------- .../todo/ui/pages/settings/SettingsMain.kt | 87 ++++++----- .../settings/components/RowSettingsItem.kt | 57 +++---- .../settings/components/SettingsCategory.kt | 11 +- .../settings/components/SettingsContainer.kt | 39 +++-- .../settings/components/SettingsDialogs.kt | 2 +- .../pages/settings/components/SettingsItem.kt | 32 +++- .../settings/components/SettingsPlainBox.kt | 5 +- .../settings/components/SwitchSettingsItem.kt | 10 +- .../appearance/contrast/ContrastPicker.kt | 6 +- .../appearance/darkmode/DarkModePicker.kt | 9 +- .../appearance/palette/PalettePicker.kt | 9 +- .../cn/super12138/todo/utils/TodoExt.kt | 42 ++++- 26 files changed, 510 insertions(+), 418 deletions(-) create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/components/ScreenContainer.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7acc6f2..6d4e2d2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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" diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt index f5f09f4..c3b4264 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt @@ -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 } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt b/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt index 11724d2..0bd774e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt @@ -73,7 +73,7 @@ class CrashActivity : ComponentActivity() { ) { CrashPage( crashLog = crashLogs ?: stringResource(R.string.tip_no_crash_logs), - exitApp = { finishAffinity() }, + exitApp = ::finishAffinity, modifier = Modifier.fillMaxSize() ) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt index a31cf2a..c2d7f72 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt @@ -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() diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt index 158fe2a..895cbf5 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt @@ -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) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/ScreenContainer.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/ScreenContainer.kt new file mode 100644 index 0000000..68a8f9e --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/ScreenContainer.kt @@ -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 + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index f2904e2..36e453c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -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( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt index 898ba75..717c55d 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt @@ -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 + ) + } } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt index 4a08ad9..402a307 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt @@ -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 -> diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt index f881f59..afdc329 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt @@ -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 + ) + } } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt index 227c668..24c0d06 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt @@ -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 + ) + } } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt index 38f0e35..ab3db90 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt @@ -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, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt index 76507d2..9e539c9 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt @@ -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 + ) + } } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt index 96251d1..80677ea 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt @@ -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 } - ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt index 6299153..45f90aa 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt @@ -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 + ) + } } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/RowSettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/RowSettingsItem.kt index f82c2fe..ff1c36e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/RowSettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/RowSettingsItem.kt @@ -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 - ) -} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt index 9d58ae4..099ec10 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt @@ -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( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt index 36b9062..5f4cce1 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt @@ -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)) + } + } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt index d146ccb..5d0d8bb 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt @@ -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, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt index 2a9381e..e9f2093 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt @@ -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 = { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsPlainBox.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsPlainBox.kt index 630a754..af68ab6 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsPlainBox.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsPlainBox.kt @@ -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( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt index dba0e6f..3ab8e77 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt @@ -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, ) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/contrast/ContrastPicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/contrast/ContrastPicker.kt index ef9a0e9..3ba95bc 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/contrast/ContrastPicker.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/contrast/ContrastPicker.kt @@ -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 = diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt index 8cdec0c..b777ade 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt @@ -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 }) { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt index 9a0d5b8..c0b266d 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt @@ -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 }) { diff --git a/app/src/main/kotlin/cn/super12138/todo/utils/TodoExt.kt b/app/src/main/kotlin/cn/super12138/todo/utils/TodoExt.kt index 65d5c37..d04dcda 100644 --- a/app/src/main/kotlin/cn/super12138/todo/utils/TodoExt.kt +++ b/app/src/main/kotlin/cn/super12138/todo/utils/TodoExt.kt @@ -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 - } \ No newline at end of file + } + +@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 + ) +} \ No newline at end of file From 562293fd17a2bb4df29838c0fec4fb8d390b8c45 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sun, 18 Jan 2026 19:19:50 +0800 Subject: [PATCH 14/21] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E8=AE=B8=E5=8F=AF=E8=AF=81=E9=A1=B5=E9=9D=A2=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit remove: 全部的滚动条 --- app/build.gradle.kts | 7 +- .../todo/ui/components/ScrollBar.kt | 25 -- .../super12138/todo/ui/pages/main/MainPage.kt | 3 +- .../todo/ui/pages/main/ManagerFragment.kt | 101 ++++--- .../ui/pages/settings/SettingsAboutLicence.kt | 150 ++++++++-- .../settings/components/SettingsDialogs.kt | 5 +- .../pages/settings/components/SettingsItem.kt | 261 ++++++++++-------- .../components/licence/LicenceItem.kt | 108 -------- .../components/licence/LicenceList.kt | 101 ------- gradle/libs.versions.toml | 6 +- 10 files changed, 333 insertions(+), 434 deletions(-) delete mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/components/ScrollBar.kt delete mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceItem.kt delete mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6d4e2d2..eb1594a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,7 +38,7 @@ android { applicationId = "cn.super12138.todo" minSdk = 24 targetSdk = 36 - versionCode = 940 + versionCode = 941 versionName = "2.3.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -101,12 +101,13 @@ dependencies { implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.animation) - implementation(libs.androidx.material3.adaptive) implementation(libs.androidx.ui) implementation(libs.androidx.ui.android) implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) + implementation(libs.androidx.material3.windowsizeclass) + implementation(libs.androidx.material3.adaptive) implementation(libs.androidx.navigation3.runtime) implementation(libs.androidx.navigation3.ui) // About Libraries @@ -118,8 +119,6 @@ dependencies { implementation(libs.kyant0.capsule) // Konfetti implementation(libs.nl.dionsegijn.konfetti.compose) - // Lazy Column Scrollbar - implementation(libs.lazycolumnscrollbar) // Kotlin implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.android) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/ScrollBar.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/ScrollBar.kt deleted file mode 100644 index 0b5007b..0000000 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/ScrollBar.kt +++ /dev/null @@ -1,25 +0,0 @@ -package cn.super12138.todo.ui.components - -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import my.nanihadesuka.compose.LazyColumnScrollbar -import my.nanihadesuka.compose.ScrollbarSettings - -@Composable -fun LazyColumnCustomScrollBar( - state: LazyListState, - modifier: Modifier = Modifier, - content: @Composable () -> Unit -) { - LazyColumnScrollbar( - state = state, - modifier = modifier, - settings = ScrollbarSettings( - thumbUnselectedColor = MaterialTheme.colorScheme.secondary, - thumbSelectedColor = MaterialTheme.colorScheme.primary - ), - content = content - ) -} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index 3bc62b1..e9788a0 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -1,7 +1,6 @@ package cn.super12138.todo.ui.pages.main import androidx.activity.compose.BackHandler -import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.WindowInsets @@ -32,7 +31,7 @@ import cn.super12138.todo.ui.components.TodoFloatingActionButton import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar import cn.super12138.todo.ui.viewmodels.MainViewModel -@OptIn(ExperimentalSharedTransitionApi::class, ExperimentalMaterial3ExpressiveApi::class) +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun MainPage( modifier: Modifier = Modifier, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt index 04c95b5..fbb07ca 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt @@ -1,6 +1,5 @@ package cn.super12138.todo.ui.pages.main -import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -19,10 +18,9 @@ import cn.super12138.todo.R import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.model.Priority import cn.super12138.todo.ui.TodoDefaults -import cn.super12138.todo.ui.components.LazyColumnCustomScrollBar import cn.super12138.todo.ui.pages.main.components.TodoCard -@OptIn(ExperimentalSharedTransitionApi::class, ExperimentalMaterial3ExpressiveApi::class) +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun ManagerFragment( modifier: Modifier = Modifier, @@ -33,60 +31,55 @@ fun ManagerFragment( onItemChecked: (TodoEntity) -> Unit = {}, selectedTodoIds: List ) { - LazyColumnCustomScrollBar( + LazyColumn( state = state, + contentPadding = PaddingValues( + start = TodoDefaults.screenHorizontalPadding, + bottom = TodoDefaults.toDoCardHeight / 2, + end = TodoDefaults.screenHorizontalPadding + ), modifier = modifier - ) { - LazyColumn( - state = state, - contentPadding = PaddingValues( - start = TodoDefaults.screenHorizontalPadding, - bottom = TodoDefaults.toDoCardHeight / 2, - end = TodoDefaults.screenHorizontalPadding - ), - /*modifier = Modifier - .clip( - MaterialTheme.shapes.extraLarge.copy( - bottomEnd = CornerSize(0.dp), - bottomStart = CornerSize(0.dp) - ) + /*.clip( + MaterialTheme.shapes.extraLarge.copy( + bottomEnd = CornerSize(0.dp), + bottomStart = CornerSize(0.dp) ) - .background(MaterialTheme.colorScheme.surfaceContainerHighest)*/ - // verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - if (list.isEmpty()) { - item { - Text( - text = stringResource(R.string.tip_no_task), - style = MaterialTheme.typography.titleMedium, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() - ) - } - } else { - items( - items = list, - key = { it.id } - ) { item -> - TodoCard( - // id = item.id, - content = item.content, - category = item.category, - completed = item.isCompleted, - priority = Priority.fromFloat(item.priority), - selected = selectedTodoIds.contains(item.id), - onCardClick = { onItemClick(item) }, - onCardLongClick = { onItemLongClick(item) }, - onChecked = { onItemChecked(item) }, - modifier = Modifier - .padding(vertical = 5.dp) - .animateItem( - fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(), - placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(), - fadeOutSpec = MaterialTheme.motionScheme.fastEffectsSpec() - ) - ) - } + ) + .background(MaterialTheme.colorScheme.surfaceContainerHighest)*/ + // verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + if (list.isEmpty()) { + item { + Text( + text = stringResource(R.string.tip_no_task), + style = MaterialTheme.typography.titleMedium, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + } + } else { + items( + items = list, + key = { it.id } + ) { item -> + TodoCard( + // id = item.id, + content = item.content, + category = item.category, + completed = item.isCompleted, + priority = Priority.fromFloat(item.priority), + selected = selectedTodoIds.contains(item.id), + onCardClick = { onItemClick(item) }, + onCardLongClick = { onItemLongClick(item) }, + onChecked = { onItemChecked(item) }, + modifier = Modifier + .padding(vertical = 5.dp) + .animateItem( + fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(), + placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(), + fadeOutSpec = MaterialTheme.motionScheme.fastEffectsSpec() + ) + ) } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt index 402a307..a5b0e73 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt @@ -1,46 +1,156 @@ package cn.super12138.todo.ui.pages.settings -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Badge +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import cn.super12138.todo.R -import cn.super12138.todo.ui.TodoDefaults +import cn.super12138.todo.ui.components.BasicDialog +import cn.super12138.todo.ui.components.RoundedScreenContainer import cn.super12138.todo.ui.components.TopAppBarScaffold -import cn.super12138.todo.ui.pages.settings.components.licence.LicenceList +import cn.super12138.todo.ui.pages.settings.components.SettingsContainer +import cn.super12138.todo.ui.pages.settings.components.SettingsItem +import cn.super12138.todo.utils.VibrationUtils import com.mikepenz.aboutlibraries.ui.compose.android.produceLibraries +import com.mikepenz.aboutlibraries.ui.compose.util.author +import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SettingsAboutLicence( onNavigateUp: () -> Unit, modifier: Modifier = Modifier ) { + val libraries by produceLibraries(R.raw.aboutlibraries) + val view = LocalView.current + val uriHandler = LocalUriHandler.current + TopAppBarScaffold( title = stringResource(R.string.pref_licence), onBack = onNavigateUp, modifier = modifier ) { innerPadding -> - val libraries by produceLibraries(R.raw.aboutlibraries) - Box( - modifier = Modifier - .fillMaxSize() - .padding(innerPadding) - .padding(horizontal = TodoDefaults.screenHorizontalPadding) - .clip(MaterialTheme.shapes.extraLarge) - ) { - val listState = rememberLazyListState() - LicenceList( - libraries = libraries, - state = listState - ) + + RoundedScreenContainer(Modifier.padding(innerPadding)) { + SettingsContainer(Modifier.fillMaxSize()) { + itemsIndexed( + items = libraries?.libraries ?: listOf(), + key = { _, library -> library.artifactId } + ) { index, library -> + var openDialog by rememberSaveable { mutableStateOf(false) } + + SettingsItem( + headlineContent = { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = library.name, + modifier = Modifier.weight(1f), + style = MaterialTheme.typography.titleLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + val version = library.artifactVersion + if (version != null) { + Text( + text = version, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center + ) + } + } + val author = library.author + if (author.isNotBlank()) { + Text( + text = author, + style = MaterialTheme.typography.bodyMedium + ) + } + }, + supportingContent = { + if (library.licenses.isNotEmpty()) { + FlowRow { + library.licenses.forEach { + Badge(containerColor = MaterialTheme.colorScheme.primary) { + Text( + maxLines = 1, + text = it.name, + // style = MaterialTheme.typography.labelSmall, + textAlign = TextAlign.Center, + overflow = TextOverflow.Ellipsis, + ) + } + } + } + } + }, + onClick = { + val license = library.licenses.firstOrNull() + if (!license?.htmlReadyLicenseContent.isNullOrBlank()) { + openDialog = true + } else if (!license?.url.isNullOrBlank()) { + license.url?.also { + try { + uriHandler.openUri(it) + } catch (t: Throwable) { + throw Exception("Failed to open licence URL: $it", t) + } + } + } + }, + topRounded = index == 0, + bottomRounded = index == (libraries?.libraries?.size ?: 1) - 1 + ) + + BasicDialog( + visible = openDialog, + title = { Text(library.name) }, + text = { + library.licenses.firstOrNull()?.licenseContent?.let { + Column(Modifier.verticalScroll(rememberScrollState())) { + SelectionContainer { Text(text = it) } + } + } + }, + confirmButton = { + FilledTonalButton( + onClick = { + openDialog = false + VibrationUtils.performHapticFeedback(view) + }, + shapes = ButtonDefaults.shapes() + ) { Text(stringResource(R.string.action_confirm)) } + }, + onDismissRequest = { openDialog = false } + ) + } + } } } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt index 5d0d8bb..0e1b4a1 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt @@ -1,6 +1,5 @@ package cn.super12138.todo.ui.pages.settings.components -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -43,7 +42,7 @@ fun SettingsRadioDialog( Column( verticalArrangement = Arrangement.spacedBy(2.dp), modifier = Modifier - .clip(MaterialTheme.shapes.extraLarge) + .clip(TodoDefaults.SettingsItemRoundedShape) .selectableGroup() .verticalScroll(rememberScrollState()) ) { @@ -77,7 +76,7 @@ fun RadioItem( .fillMaxWidth() .height(56.dp) .clip(TodoDefaults.SettingsItemDefaultShape) - .background(MaterialTheme.colorScheme.surfaceContainerHighest) + // .background(MaterialTheme.colorScheme.surfaceContainerHighest) .selectable( selected = selected, onClick = { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt index e9f2093..c40c851 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt @@ -28,6 +28,7 @@ import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.getPartialRoundedShape +// Leading icon as drawable resource @Composable fun SettingsItem( modifier: Modifier = Modifier, @@ -38,44 +39,20 @@ fun SettingsItem( bottomRounded: Boolean = false, enableClick: Boolean = true, onClick: () -> Unit = {} -) { - SettingsItem( - leadingIcon = painterResource(leadingIconRes), - title = title, - description = description, - trailingContent = null, - topRounded = topRounded, - bottomRounded = bottomRounded, - enableClick = enableClick, - onClick = onClick, - modifier = modifier - ) -} +) = SettingsItem( + leadingIcon = painterResource(leadingIconRes), + title = title, + description = description, + trailingContent = null, + topRounded = topRounded, + bottomRounded = bottomRounded, + enableClick = enableClick, + onClick = onClick, + modifier = modifier +) -@Composable -fun SettingsItem( - modifier: Modifier = Modifier, - leadingIcon: ImageVector? = null, - title: String, - description: String? = null, - topRounded: Boolean = false, - bottomRounded: Boolean = false, - enableClick: Boolean = true, - onClick: () -> Unit = {} -) { - SettingsItem( - leadingIcon = leadingIcon, - title = title, - description = description, - trailingContent = null, - topRounded = topRounded, - bottomRounded = bottomRounded, - enableClick = enableClick, - onClick = onClick, - modifier = modifier - ) -} +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SettingsItem( modifier: Modifier = Modifier, @@ -88,29 +65,61 @@ fun SettingsItem( bottomRounded: Boolean = false, enableClick: Boolean = true, onClick: () -> Unit = {} -) { - SettingsItem( - leadingIcon = { - leadingIcon?.let { - Icon( - painter = leadingIcon, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(end = TodoDefaults.settingsItemHorizontalPadding), - ) - } - }, - title = title, - description = description, - trailingContent = trailingContent, - background = background, - topRounded = topRounded, - bottomRounded = bottomRounded, - enableClick = enableClick, - onClick = onClick, - modifier = modifier - ) -} +) = SettingsItem( + leadingIcon = { + leadingIcon?.let { + /*Box( + modifier = Modifier + .padding(end = TodoDefaults.settingsItemHorizontalPadding) + .background( + color = MaterialTheme.colorScheme.secondaryContainer, + shape = MaterialShapes.Cookie6Sided.toShape() + ) + .size(35.dp), + contentAlignment = Alignment.Center + ) {*/ + Icon( + painter = leadingIcon, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(end = TodoDefaults.settingsItemHorizontalPadding) + ) + // } + } + }, + title = title, + description = description, + trailingContent = trailingContent, + background = background, + topRounded = topRounded, + bottomRounded = bottomRounded, + enableClick = enableClick, + onClick = onClick, + modifier = modifier +) + +// Leading icon as ImageVector +@Composable +fun SettingsItem( + modifier: Modifier = Modifier, + leadingIcon: ImageVector? = null, + title: String, + description: String? = null, + topRounded: Boolean = false, + bottomRounded: Boolean = false, + enableClick: Boolean = true, + onClick: () -> Unit = {} +) = SettingsItem( + leadingIcon = leadingIcon, + title = title, + description = description, + trailingContent = null, + topRounded = topRounded, + bottomRounded = bottomRounded, + enableClick = enableClick, + onClick = onClick, + modifier = modifier +) @Composable fun SettingsItem( @@ -124,29 +133,28 @@ fun SettingsItem( bottomRounded: Boolean = false, enableClick: Boolean = true, onClick: () -> Unit = {} -) { - SettingsItem( - leadingIcon = { - leadingIcon?.let { - Icon( - imageVector = it, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(end = TodoDefaults.settingsItemHorizontalPadding), - ) - } - }, - title = title, - description = description, - trailingContent = trailingContent, - background = background, - topRounded = topRounded, - bottomRounded = bottomRounded, - enableClick = enableClick, - onClick = onClick, - modifier = modifier - ) -} +) = SettingsItem( + leadingIcon = { + leadingIcon?.let { + Icon( + imageVector = it, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(end = TodoDefaults.settingsItemHorizontalPadding), + ) + } + }, + title = title, + description = description, + trailingContent = trailingContent, + background = background, + topRounded = topRounded, + bottomRounded = bottomRounded, + enableClick = enableClick, + onClick = onClick, + modifier = modifier +) + @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -163,6 +171,57 @@ fun SettingsItem( bottomRounded: Boolean = false, enableClick: Boolean = true, onClick: () -> Unit = {}, +) = SettingsItem( + modifier = modifier, + leadingIcon = leadingIcon, + headlineContent = { + Text( + text = title, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleLarge.copy( + color = MaterialTheme.colorScheme.onSurface, + fontSize = 20.sp + ) + ) + }, + supportingContent = { + description?.let { + Text( + text = it, + // maxLines = 2, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + ) + } + }, + trailingContent = trailingContent, + background = background, + shape = shape, + roundedShape = roundedShape, + topRounded = topRounded, + bottomRounded = bottomRounded, + enableClick = enableClick, + onClick = onClick +) + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun SettingsItem( + modifier: Modifier = Modifier, + leadingIcon: (@Composable () -> Unit)? = null, + headlineContent: (@Composable () -> Unit)? = null, + supportingContent: (@Composable () -> Unit)? = null, + trailingContent: (@Composable () -> Unit)? = null, + background: Color = TodoDefaults.ContainerColor, + shape: CornerBasedShape = TodoDefaults.SettingsItemDefaultShape, + roundedShape: CornerBasedShape = TodoDefaults.SettingsItemRoundedShape, + topRounded: Boolean = false, + bottomRounded: Boolean = false, + enableClick: Boolean = true, + onClick: () -> Unit = {}, ) { val view = LocalView.current Row( @@ -171,47 +230,23 @@ fun SettingsItem( .wrapContentHeight() .clip(shape.getPartialRoundedShape(topRounded, bottomRounded, roundedShape)) .clickable( - enabled = enableClick, - onClick = { + enabled = enableClick, onClick = { VibrationUtils.performHapticFeedback(view) onClick() - } - ) + }) .background(background) .padding( horizontal = TodoDefaults.settingsItemHorizontalPadding, vertical = TodoDefaults.settingsItemVerticalPadding - ), - verticalAlignment = Alignment.CenterVertically + ), verticalAlignment = Alignment.CenterVertically ) { - leadingIcon?.let { - it() - } + leadingIcon?.let { it() } Column(modifier = Modifier.weight(1f)) { - Text( - text = title, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleLarge.copy( - color = MaterialTheme.colorScheme.onSurface, - fontSize = 20.sp - ) - ) - description?.let { - Text( - text = it, - // maxLines = 2, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - ) - } + headlineContent?.let { it() } + supportingContent?.let { it() } } - trailingContent?.let { - it() - } + trailingContent?.let { it() } } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceItem.kt deleted file mode 100644 index d121879..0000000 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceItem.kt +++ /dev/null @@ -1,108 +0,0 @@ -package cn.super12138.todo.ui.pages.settings.components.licence - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.Typography -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import com.mikepenz.aboutlibraries.entity.Library -import com.mikepenz.aboutlibraries.ui.compose.LibraryColors -import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults -import com.mikepenz.aboutlibraries.ui.compose.LibraryPadding -import com.mikepenz.aboutlibraries.ui.compose.m3.component.LibraryChip -import com.mikepenz.aboutlibraries.ui.compose.m3.libraryColors -import com.mikepenz.aboutlibraries.ui.compose.util.author - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun LicenceItem( - modifier: Modifier = Modifier, - library: Library, - showAuthor: Boolean = true, - showVersion: Boolean = true, - showLicenseBadges: Boolean = true, - colors: LibraryColors = LibraryDefaults.libraryColors( - libraryBackgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh - ), - padding: LibraryPadding = LibraryDefaults.libraryPadding(), - libraryPadding: LibraryPadding = LibraryDefaults.libraryPadding(), - typography: Typography = MaterialTheme.typography, - shape: Shape = MaterialTheme.shapes.small, - onClick: () -> Unit -) { - Column( - modifier = modifier - .fillMaxWidth() - .clip(shape) - .clickable { onClick.invoke() } - .background(colors.libraryBackgroundColor) - .padding(libraryPadding.contentPadding) - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = library.name, - modifier = Modifier - .padding(padding.namePadding) - .weight(1f), - style = typography.titleLarge, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - val version = library.artifactVersion - if (version != null && showVersion) { - Text( - text = version, - modifier = Modifier.padding(padding.versionPadding.contentPadding), - style = typography.bodyMedium, - textAlign = TextAlign.Center - ) - } - } - val author = library.author - if (showAuthor && author.isNotBlank()) { - Text( - text = author, - style = typography.bodyMedium - ) - } - if (showLicenseBadges && library.licenses.isNotEmpty()) { - FlowRow { - library.licenses.forEach { - LibraryChip( - modifier = Modifier.padding(padding.licensePadding.containerPadding), - minHeight = LibraryDefaults.libraryDimensions().chipMinHeight, - containerColor = colors.licenseChipColors.containerColor, - contentColor = colors.licenseChipColors.contentColor, - shape = LibraryDefaults.libraryShapes().chipShape, - ) { - Text( - modifier = Modifier.padding(padding.licensePadding.contentPadding), - maxLines = 1, - text = it.name, - // style = MaterialTheme.typography.labelSmall, - textAlign = TextAlign.Center, - overflow = LibraryDefaults.libraryTextStyles().defaultOverflow, - ) - } - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt deleted file mode 100644 index f801320..0000000 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/licence/LicenceList.kt +++ /dev/null @@ -1,101 +0,0 @@ -package cn.super12138.todo.ui.pages.settings.components.licence - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.platform.LocalView -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.components.BasicDialog -import cn.super12138.todo.ui.components.LazyColumnCustomScrollBar -import cn.super12138.todo.utils.VibrationUtils -import com.mikepenz.aboutlibraries.Libs -import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent - -@OptIn(ExperimentalMaterial3ExpressiveApi::class) -@Composable -fun LicenceList( - modifier: Modifier = Modifier, - libraries: Libs?, - state: LazyListState = rememberLazyListState() -) { - val view = LocalView.current - val uriHandler = LocalUriHandler.current - - LazyColumnCustomScrollBar( - state = state, - modifier = modifier - ) { - LazyColumn( - state = state, - verticalArrangement = Arrangement.spacedBy(2.dp), - contentPadding = PaddingValues(vertical = TodoDefaults.screenVerticalPadding), - modifier = modifier - ) { - items(items = libraries?.libraries ?: listOf(), key = { it.artifactId }) { library -> - var openDialog by rememberSaveable { mutableStateOf(false) } - - LicenceItem( - library = library, - onClick = { - val license = library.licenses.firstOrNull() - if (!license?.htmlReadyLicenseContent.isNullOrBlank()) { - openDialog = true - } else if (!license?.url.isNullOrBlank()) { - license.url?.also { - try { - uriHandler.openUri(it) - } catch (t: Throwable) { - throw Exception("Failed to open licence URL: $it", t) - } - } - } - VibrationUtils.performHapticFeedback(view) - }, - ) - - BasicDialog( - visible = openDialog, - title = { Text(library.name) }, - text = { - library.licenses.firstOrNull()?.licenseContent?.let { - Column(Modifier.verticalScroll(rememberScrollState())) { - SelectionContainer { Text(text = it) } - } - } - }, - confirmButton = { - FilledTonalButton( - onClick = { - openDialog = false - VibrationUtils.performHapticFeedback(view) - }, - shapes = ButtonDefaults.shapes() - ) { Text(stringResource(R.string.action_confirm)) } - }, - onDismissRequest = { openDialog = false } - ) - } - } - } -} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5638bf0..d4b6466 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -48,7 +48,6 @@ androidx-datastore-preferences = { group = "androidx.datastore", name = "datasto # Compose androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-animation = { group = "androidx.compose.animation", name = "animation" } -androidx-material3-adaptive = { group = "androidx.compose.material3.adaptive", name = "adaptive", version.ref = "adaptive" } androidx-ui = { group = "androidx.compose.ui", name = "ui" } androidx-ui-android = { group = "androidx.compose.ui", name = "ui-android" } androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } @@ -57,6 +56,8 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } +androidx-material3-windowsizeclass = { group = "androidx.compose.material3", name = "material3-window-size-class", version.ref = "material3" } +androidx-material3-adaptive = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite", version.ref = "material3" } androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } @@ -77,9 +78,6 @@ kyant0-capsule = { group = "io.github.kyant0", name = "capsule", version.ref = " # Konfetti nl-dionsegijn-konfetti-compose = { group = "nl.dionsegijn", name = "konfetti-compose", version.ref = "konfetti" } -# Lazy Column Scrollbar -lazycolumnscrollbar = { group = "com.github.nanihadesuka", name = "LazyColumnScrollbar", version.ref = "lazyColumnScrollbar" } - # Kotlin kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinCoroutines" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinCoroutines" } From a6e12f49816592a11d6b617891cfcc659c86cd7f Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sat, 24 Jan 2026 10:36:03 +0800 Subject: [PATCH 15/21] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=E8=A7=84?= =?UTF-8?q?=E8=8C=83=E5=8C=96=E5=B8=83=E5=B1=80=EF=BC=88=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E6=96=B9=E5=BC=8F1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo/logic/model/ContrastLevel.kt | 19 ++ .../super12138/todo/logic/model/DarkMode.kt | 19 ++ .../todo/logic/model/PaletteStyle.kt | 23 +++ .../super12138/todo/logic/model/Priority.kt | 28 +-- .../todo/logic/model/SortingMethod.kt | 31 ++-- .../cn/super12138/todo/ui/TodoDefaults.kt | 5 +- .../todo/ui/activities/CrashActivity.kt | 12 +- .../todo/ui/activities/MainActivity.kt | 50 +++-- .../todo/ui/components/Scaffolds.kt | 85 +++++++-- .../todo/ui/navigation/SettingsNavigation.kt | 2 + .../todo/ui/navigation/TodoDestinations.kt | 32 ++++ .../todo/ui/navigation/TodoNavigation.kt | 66 +++++-- .../todo/ui/navigation/TodoScreen.kt | 9 +- .../todo/ui/navigation/TopLevelBackStack.kt | 62 +++++++ .../todo/ui/pages/editor/TodoEditorPage.kt | 10 +- .../editor/components/TodoPrioritySlider.kt | 3 +- .../todo/ui/pages/main/ManagerFragment.kt | 86 --------- .../pages/main/{MainPage.kt => TasksPage.kt} | 166 ++++++++--------- .../todo/ui/pages/main/components/TodoCard.kt | 2 +- .../ui/pages/main/components/TodoTopAppBar.kt | 61 ++----- .../todo/ui/pages/overview/OverviewPage.kt | 27 +++ .../todo/ui/pages/settings/SettingsAbout.kt | 108 ++++++----- .../ui/pages/settings/SettingsAboutLicence.kt | 171 +++++++++--------- .../ui/pages/settings/SettingsAppearance.kt | 76 ++++---- .../todo/ui/pages/settings/SettingsData.kt | 77 ++++---- .../ui/pages/settings/SettingsDataCategory.kt | 9 +- .../settings/SettingsDeveloperOptions.kt | 24 +-- .../settings/SettingsInterfaceInteraction.kt | 108 ++++++----- .../todo/ui/pages/settings/SettingsMain.kt | 76 ++++---- .../appearance/contrast/ContrastPicker.kt | 5 +- .../appearance/darkmode/DarkModePicker.kt | 6 +- .../appearance/palette/PaletteItem.kt | 9 +- .../appearance/palette/PalettePicker.kt | 6 +- .../super12138/todo/ui/theme/ContrastLevel.kt | 27 --- .../cn/super12138/todo/ui/theme/DarkMode.kt | 26 --- .../super12138/todo/ui/theme/PaletteStyle.kt | 35 ---- .../cn/super12138/todo/ui/theme/Theme.kt | 1 + .../cn/super12138/todo/ui/theme/ThemeExt.kt | 6 +- .../todo/ui/viewmodels/MainViewModel.kt | 5 + .../cn/super12138/todo/utils/SystemUtils.kt | 11 ++ app/src/main/res/drawable/ic_ballot.xml | 9 + .../main/res/drawable/ic_ballot_filled.xml | 9 + .../main/res/drawable/ic_settings_filled.xml | 9 + app/src/main/res/drawable/ic_view_kanban.xml | 9 + .../res/drawable/ic_view_kanban_filled.xml | 9 + app/src/main/res/values-zh-rCN/strings.xml | 3 + app/src/main/res/values/strings.xml | 2 + 47 files changed, 867 insertions(+), 767 deletions(-) create mode 100644 app/src/main/kotlin/cn/super12138/todo/logic/model/ContrastLevel.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/logic/model/DarkMode.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/logic/model/PaletteStyle.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/navigation/SettingsNavigation.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoDestinations.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/navigation/TopLevelBackStack.kt delete mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt rename app/src/main/kotlin/cn/super12138/todo/ui/pages/main/{MainPage.kt => TasksPage.kt} (54%) create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/overview/OverviewPage.kt delete mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/theme/ContrastLevel.kt delete mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/theme/DarkMode.kt delete mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/theme/PaletteStyle.kt create mode 100644 app/src/main/res/drawable/ic_ballot.xml create mode 100644 app/src/main/res/drawable/ic_ballot_filled.xml create mode 100644 app/src/main/res/drawable/ic_settings_filled.xml create mode 100644 app/src/main/res/drawable/ic_view_kanban.xml create mode 100644 app/src/main/res/drawable/ic_view_kanban_filled.xml diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/ContrastLevel.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/ContrastLevel.kt new file mode 100644 index 0000000..eb6ae6c --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/ContrastLevel.kt @@ -0,0 +1,19 @@ +package cn.super12138.todo.logic.model + +import androidx.annotation.StringRes +import cn.super12138.todo.R + +enum class ContrastLevel( + val value: Float, + @StringRes val nameRes: Int +) { + VeryLow(value = -1f, nameRes = R.string.contrast_very_low), + Low(value = -0.5f, nameRes = R.string.contrast_low), + Default(value = 0f, nameRes = R.string.contrast_default), + Medium(value = 0.5f, nameRes = R.string.contrast_high), + High(value = 1f, nameRes = R.string.contrast_very_high); + + companion object { + fun fromFloat(float: Float) = entries.find { it.value == float } ?: Default + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/DarkMode.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/DarkMode.kt new file mode 100644 index 0000000..55b5392 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/DarkMode.kt @@ -0,0 +1,19 @@ +package cn.super12138.todo.logic.model + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import cn.super12138.todo.R + +enum class DarkMode( + val id: Int, + @DrawableRes val iconRes: Int, + @StringRes val nameRes: Int +) { + FollowSystem(id = -1, iconRes = R.drawable.ic_lightbulb_2, nameRes = R.string.dark_mode_system), + Light(id = 1, iconRes = R.drawable.ic_light_mode, nameRes = R.string.dark_mode_light), + Dark(id = 2, iconRes = R.drawable.ic_dark_mode, nameRes = R.string.dark_mode_dark); + + companion object { + fun fromId(id: Int) = entries.find { it.id == id } ?: FollowSystem + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/PaletteStyle.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/PaletteStyle.kt new file mode 100644 index 0000000..8f0341b --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/PaletteStyle.kt @@ -0,0 +1,23 @@ +package cn.super12138.todo.logic.model + +import androidx.annotation.StringRes +import cn.super12138.todo.R + +enum class PaletteStyle( + val id: Int, + @StringRes val nameRes: Int +) { + TonalSpot(id = 1, nameRes = R.string.palette_tonal_spot), + Neutral(id = 2, nameRes = R.string.palette_neutral), + Vibrant(id = 3, nameRes = R.string.palette_vibrant), + Expressive(id = 4, nameRes = R.string.palette_expressive), + Rainbow(id = 5, nameRes = R.string.palette_rainbow), + FruitSalad(id = 6, nameRes = R.string.palette_fruit_salad), + Monochrome(id = 7, nameRes = R.string.palette_monochrome), + Fidelity(id = 8, nameRes = R.string.palette_fidelity), + Content(id = 9, nameRes = R.string.palette_content); + + companion object { + fun fromId(id: Int) = entries.firstOrNull { it.id == id } ?: TonalSpot + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/Priority.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/Priority.kt index b2eb31e..50378f2 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/model/Priority.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/Priority.kt @@ -1,25 +1,17 @@ package cn.super12138.todo.logic.model -import android.content.Context +import androidx.annotation.StringRes import cn.super12138.todo.R -enum class Priority(val value: Float) { - Urgent(2f), - Important(1f), - Default(0f), - NotImportant(-1f), - NotUrgent(-2f); - - fun displayName(context: Context): String { - val resId = when (this) { - Urgent -> R.string.priority_urgent - Important -> R.string.priority_important - Default -> R.string.priority_default - NotImportant -> R.string.priority_not_important - NotUrgent -> R.string.priority_not_urgent - } - return context.getString(resId) - } +enum class Priority( + val value: Float, + @StringRes val nameRes: Int +) { + Urgent(value = 2f, nameRes = R.string.priority_urgent), + Important(value = 1f, nameRes = R.string.priority_important), + Default(value = 0f, nameRes = R.string.priority_default), + NotImportant(value = -1f, nameRes = R.string.priority_not_important), + NotUrgent(value = -2f, nameRes = R.string.priority_not_urgent); companion object { fun fromFloat(float: Float) = entries.find { it.value == float } ?: Default diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt index 2c73a4c..81dadf0 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt @@ -1,38 +1,29 @@ package cn.super12138.todo.logic.model -import android.content.Context +import androidx.annotation.StringRes import cn.super12138.todo.R -enum class SortingMethod(val id: Int) { +enum class SortingMethod( + val id: Int, + @StringRes val nameRes: Int +) { // 按添加先后顺序 - Sequential(1), + Sequential(id = 1, nameRes = R.string.sorting_sequential), // 按学科 - Category(2), + Category(id = 2, nameRes = R.string.sorting_category), // 按优先级 - Priority(3), + Priority(id = 3, nameRes = R.string.sorting_priority), // 按完成情况 - Completion(4), + Completion(id = 4, nameRes = R.string.sorting_completion), // 按字母升序 - AlphabeticalAscending(5), + AlphabeticalAscending(id = 5, nameRes = R.string.sorting_alphabetical_ascending), // 按字母降序 - AlphabeticalDescending(6); - - fun getDisplayName(context: Context): String { - val resId = when (this) { - Sequential -> R.string.sorting_sequential - Category -> R.string.sorting_category - Priority -> R.string.sorting_priority - Completion -> R.string.sorting_completion - AlphabeticalAscending -> R.string.sorting_alphabetical_ascending - AlphabeticalDescending -> R.string.sorting_alphabetical_descending - } - return context.getString(resId) - } + AlphabeticalDescending(id = 6, nameRes = R.string.sorting_alphabetical_descending); companion object { fun fromId(id: Int) = entries.find { it.id == id } ?: Sequential diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt index c3b4264..c326260 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt @@ -1,7 +1,6 @@ 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 @@ -58,10 +57,10 @@ object TodoDefaults { val ScreenContainerShape: Shape @Composable - get() = MaterialTheme.shapes.large.copy( + get() = MaterialTheme.shapes.large/*.copy( bottomStart = ZeroCornerSize, bottomEnd = ZeroCornerSize - ) + )*/ val ContainerColor: Color @Composable diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt b/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt index 0bd774e..1c4f57f 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt @@ -1,10 +1,8 @@ package cn.super12138.todo.ui.activities -import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.LaunchedEffect @@ -16,11 +14,12 @@ import androidx.core.view.WindowCompat import cn.super12138.todo.R import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.datastore.DataStoreManager +import cn.super12138.todo.logic.model.DarkMode +import cn.super12138.todo.logic.model.PaletteStyle import cn.super12138.todo.ui.pages.crash.CrashPage -import cn.super12138.todo.ui.theme.DarkMode -import cn.super12138.todo.ui.theme.PaletteStyle import cn.super12138.todo.ui.theme.ToDoTheme import cn.super12138.todo.utils.VibrationUtils +import cn.super12138.todo.utils.configureEdgeToEdge class CrashActivity : ComponentActivity() { companion object { @@ -32,10 +31,7 @@ class CrashActivity : ComponentActivity() { } override fun onCreate(savedInstanceState: Bundle?) { - enableEdgeToEdge() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - window.isNavigationBarContrastEnforced = false - } + configureEdgeToEdge() super.onCreate(savedInstanceState) val crashLogs = intent.getStringExtra("crash_logs") diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt index c2d7f72..32bd9dd 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt @@ -1,42 +1,45 @@ package cn.super12138.todo.ui.activities -import android.os.Build import android.os.Bundle import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Icon import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen 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.logic.model.DarkMode +import cn.super12138.todo.logic.model.PaletteStyle import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.components.Konfetti +import cn.super12138.todo.ui.navigation.TodoDestinations import cn.super12138.todo.ui.navigation.TodoNavigation -import cn.super12138.todo.ui.theme.DarkMode -import cn.super12138.todo.ui.theme.PaletteStyle import cn.super12138.todo.ui.theme.ToDoTheme import cn.super12138.todo.ui.viewmodels.MainViewModel import cn.super12138.todo.utils.VibrationUtils +import cn.super12138.todo.utils.configureEdgeToEdge class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() - enableEdgeToEdge() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - window.isNavigationBarContrastEnforced = false - } + configureEdgeToEdge() super.onCreate(savedInstanceState) setContent { val mainViewModel: MainViewModel = viewModel() + val navigationBackStack = mainViewModel.topLevelBackStack val showConfetti = mainViewModel.showConfetti // 主题 val dynamicColor by DataStoreManager.dynamicColorFlow.collectAsState(initial = Constants.PREF_DYNAMIC_COLOR_DEFAULT) @@ -84,10 +87,33 @@ class MainActivity : ComponentActivity() { dynamicColor = dynamicColor ) { Surface(color = TodoDefaults.BackgroundColor) { - TodoNavigation( - viewModel = mainViewModel, - modifier = Modifier.fillMaxSize() - ) + NavigationSuiteScaffold( + navigationSuiteItems = { + TodoDestinations.entries.forEach { + val selected = it.route == navigationBackStack.topLevelKey + item( + icon = { + Icon( + painter = if (selected) painterResource(it.selectedIcon) else painterResource( + it.icon + ), + contentDescription = null + ) + }, + label = { Text(stringResource(it.label)) }, + selected = selected, + onClick = { navigationBackStack.addTopLevel(it.route) } + ) + } + }, + containerColor = TodoDefaults.BackgroundColor + ) { + TodoNavigation( + backStack = navigationBackStack, + viewModel = mainViewModel, + modifier = Modifier.fillMaxSize() + ) + } Konfetti(state = showConfetti) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt index 895cbf5..bb7b125 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt @@ -1,7 +1,5 @@ package cn.super12138.todo.ui.components -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 @@ -20,6 +18,8 @@ import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -33,10 +33,9 @@ import cn.super12138.todo.utils.VibrationUtils * * 内容默认由 Box 容器包裹;实际使用时推荐配合 Column 或 Row * * @param title 标题文本 - * @param onBack 当返回按钮被按下时的操作 * @param contentWindowInsets 内容边距,通常用于将内容和系统状态栏等隔开;可以使用 `WindowInsets.safeContent` */ -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun TopAppBarScaffold( modifier: Modifier = Modifier, @@ -46,7 +45,7 @@ fun TopAppBarScaffold( floatingActionButton: @Composable () -> Unit = {}, floatingActionButtonPosition: FabPosition = FabPosition.End, contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, - content: @Composable (PaddingValues) -> Unit + content: @Composable () -> Unit ) { val view = LocalView.current TopAppBarScaffold( @@ -77,10 +76,37 @@ fun TopAppBarScaffold( floatingActionButtonPosition = floatingActionButtonPosition, contentWindowInsets = contentWindowInsets, modifier = modifier, - ) { innerPadding -> - //TODO: 应用圆角效果 - Box { content(innerPadding) } - } + content = content + ) +} + +@Composable +fun TopAppBarScaffold( + modifier: Modifier = Modifier, + title: String, + navigationIcon: @Composable () -> Unit = {}, + snackbarHost: @Composable () -> Unit = {}, + floatingActionButton: @Composable () -> Unit = {}, + floatingActionButtonPosition: FabPosition = FabPosition.End, + contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, + content: @Composable () -> Unit +) { + TopAppBarScaffold( + title = { + Text( + text = title, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + navigationIcon = navigationIcon, + snackbarHost = snackbarHost, + floatingActionButton = floatingActionButton, + floatingActionButtonPosition = floatingActionButtonPosition, + contentWindowInsets = contentWindowInsets, + modifier = modifier, + content = content + ) } @OptIn(ExperimentalMaterial3Api::class) @@ -94,9 +120,9 @@ fun TopAppBarScaffold( floatingActionButton: @Composable () -> Unit = {}, floatingActionButtonPosition: FabPosition = FabPosition.End, contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, - content: @Composable (PaddingValues) -> Unit + content: @Composable () -> Unit ) { - Scaffold( + TopAppBarScaffold( modifier = modifier, topBar = { TopAppBar( @@ -112,8 +138,37 @@ fun TopAppBarScaffold( floatingActionButton = floatingActionButton, floatingActionButtonPosition = floatingActionButtonPosition, contentWindowInsets = contentWindowInsets, - containerColor = TodoDefaults.BackgroundColor, - ) { innerPadding -> - Box { content(innerPadding) } - } + content = content, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TopAppBarScaffold( + modifier: Modifier = Modifier, + topBar: @Composable () -> Unit = {}, + snackbarHost: @Composable () -> Unit = {}, + floatingActionButton: @Composable () -> Unit = {}, + floatingActionButtonPosition: FabPosition = FabPosition.End, + contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, + containerColor: Color = TodoDefaults.BackgroundColor, + screenShape: Shape = TodoDefaults.ScreenContainerShape, + content: @Composable () -> Unit +) = Scaffold( + modifier = modifier, + topBar = topBar, + snackbarHost = snackbarHost, + floatingActionButton = floatingActionButton, + floatingActionButtonPosition = floatingActionButtonPosition, + contentWindowInsets = contentWindowInsets, + containerColor = containerColor, +) { innerPadding -> + Surface( + modifier = Modifier + .padding(paddingValues = innerPadding) + .padding(horizontal = TodoDefaults.screenHorizontalPadding), + color = containerColor, + shape = screenShape, + content = content + ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/SettingsNavigation.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/SettingsNavigation.kt new file mode 100644 index 0000000..0b31233 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/SettingsNavigation.kt @@ -0,0 +1,2 @@ +package cn.super12138.todo.ui.navigation + diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoDestinations.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoDestinations.kt new file mode 100644 index 0000000..732f77a --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoDestinations.kt @@ -0,0 +1,32 @@ +package cn.super12138.todo.ui.navigation + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.navigation3.runtime.NavKey + +enum class TodoDestinations( + val route: NavKey, + @StringRes val label: Int, + @DrawableRes val icon: Int, + @DrawableRes val selectedIcon: Int +) { + Overview( + route = TodoScreen.Overview, + label = cn.super12138.todo.R.string.page_overview, + icon = cn.super12138.todo.R.drawable.ic_view_kanban, + selectedIcon = cn.super12138.todo.R.drawable.ic_view_kanban_filled + ), + Tasks( + route = TodoScreen.Tasks, + label = cn.super12138.todo.R.string.page_tasks, + icon = cn.super12138.todo.R.drawable.ic_ballot, + selectedIcon = cn.super12138.todo.R.drawable.ic_ballot_filled + ), + Settings( + route = TodoScreen.Settings, + label = cn.super12138.todo.R.string.page_settings, + icon = cn.super12138.todo.R.drawable.ic_settings, + selectedIcon = cn.super12138.todo.R.drawable.ic_settings_filled + ) +} + diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt index f5804c0..fd8f2c6 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt @@ -1,16 +1,23 @@ package cn.super12138.todo.ui.navigation +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entryProvider -import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.ui.NavDisplay import cn.super12138.todo.ui.pages.editor.TodoEditorPage import cn.super12138.todo.ui.pages.main.MainPage +import cn.super12138.todo.ui.pages.overview.OverviewPage import cn.super12138.todo.ui.pages.settings.SettingsAbout import cn.super12138.todo.ui.pages.settings.SettingsAboutLicence import cn.super12138.todo.ui.pages.settings.SettingsAppearance @@ -28,17 +35,39 @@ private const val INITIAL_OFFSET_FACTOR = 0.10f @OptIn(ExperimentalSharedTransitionApi::class) @Composable fun TodoNavigation( + backStack: TopLevelBackStack, modifier: Modifier = Modifier, - backStack: NavBackStack = rememberNavBackStack(TodoScreen.Main), viewModel: MainViewModel ) { fun onBack() { - backStack.removeAt(backStack.lastIndex) + backStack.removeLast() + } + + val anim = NavDisplay.transitionSpec { + // Slide new content up, keeping the old content in place underneath + slideInVertically( + initialOffsetY = { it / 5 }, + animationSpec = tween(200) + ) + fadeIn(tween(200)) togetherWith ExitTransition.KeepUntilTransitionsFinished + } + NavDisplay.popTransitionSpec { + // Slide old content down, revealing the new content in place underneath + EnterTransition.None togetherWith + slideOutVertically( + targetOffsetY = { it / 5 }, + animationSpec = tween(200) + ) + fadeOut(tween(200)) + } + NavDisplay.predictivePopTransitionSpec { + // Slide old content down, revealing the new content in place underneath + EnterTransition.None togetherWith + slideOutVertically( + targetOffsetY = { it / 5 }, + animationSpec = tween(200) + ) + fadeOut(tween(200)) } SharedTransitionLayout { NavDisplay( - backStack = backStack, + backStack = backStack.backStack, onBack = ::onBack, transitionSpec = { materialSharedAxisX( @@ -50,16 +79,27 @@ fun TodoNavigation( initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }, targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }) }, - entryProvider = entryProvider { - entry { + entry(metadata = anim) { + OverviewPage() + } + entry(metadata = anim) { MainPage( viewModel = viewModel, toTodoEditPage = { backStack.add(TodoScreen.Editor(it)) }, - toSettingsPage = { backStack.add(TodoScreen.SettingsMain) }, sharedTransitionScope = this@SharedTransitionLayout ) } + + entry(metadata = anim) { + SettingsMain( + toAppearancePage = { backStack.add(TodoScreen.SettingsAppearance) }, + toAboutPage = { backStack.add(TodoScreen.SettingsAbout) }, + toInterfacePage = { backStack.add(TodoScreen.SettingsInterface) }, + toDataPage = { backStack.add(TodoScreen.SettingsData) }, + ) + } + entry { editorArgs -> TodoEditorPage( toDo = editorArgs.toDo, @@ -82,16 +122,6 @@ fun TodoNavigation( ) } - entry { - SettingsMain( - toAppearancePage = { backStack.add(TodoScreen.SettingsAppearance) }, - toAboutPage = { backStack.add(TodoScreen.SettingsAbout) }, - toInterfacePage = { backStack.add(TodoScreen.SettingsInterface) }, - toDataPage = { backStack.add(TodoScreen.SettingsData) }, - onNavigateUp = ::onBack, - ) - } - entry { SettingsAppearance(onNavigateUp = ::onBack) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt index 7de325c..298959c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt @@ -7,13 +7,16 @@ import kotlinx.serialization.Serializable @Serializable sealed class TodoScreen : NavKey { @Serializable - data object Main : TodoScreen() + data object Overview : TodoScreen() + + @Serializable + data object Tasks : TodoScreen() @Serializable data class Editor(val toDo: TodoEntity?) : TodoScreen() @Serializable - data object SettingsMain : TodoScreen() + data object Settings : TodoScreen() @Serializable data object SettingsAppearance : TodoScreen() @@ -41,4 +44,4 @@ sealed class TodoScreen : NavKey { @Serializable data object SettingsDevPadding : TodoScreen() -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TopLevelBackStack.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TopLevelBackStack.kt new file mode 100644 index 0000000..3fb2f42 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TopLevelBackStack.kt @@ -0,0 +1,62 @@ +package cn.super12138.todo.ui.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshots.SnapshotStateList + +@Composable +fun rememberTopLevelBackStack(startKey: T): TopLevelBackStack = + remember { TopLevelBackStack(startKey = startKey) } + +class TopLevelBackStack(startKey: T) { + // Maintain a stack for each top level route + private var topLevelStacks: LinkedHashMap> = linkedMapOf( + startKey to mutableStateListOf(startKey) + ) + + // Expose the current top level route for consumers + var topLevelKey: T by mutableStateOf(startKey) + private set + + // Expose the back stack so it can be rendered by the NavDisplay + val backStack = mutableStateListOf(startKey) + + private fun updateBackStack() = + backStack.apply { + clear() + addAll(topLevelStacks.flatMap { it.value }) + } + + fun addTopLevel(key: T) { + // If the top level doesn't exist, add it + if (topLevelStacks[key] == null) { + topLevelStacks.put(key, mutableStateListOf(key)) + } else { + // Otherwise just move it to the end of the stacks + topLevelStacks.apply { + remove(key)?.let { + put(key, it) + } + } + } + topLevelKey = key + updateBackStack() + } + + fun add(key: T) { + topLevelStacks[topLevelKey]?.add(key) + updateBackStack() + } + + fun removeLast() { + val removedKey = topLevelStacks[topLevelKey]?.removeLastOrNull() + // If the removed key was a top level key, remove the associated top level stack + topLevelStacks.remove(removedKey) + topLevelKey = topLevelStacks.keys.last() + updateBackStack() + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index 36e453c..fb1bbae 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -41,11 +41,10 @@ import cn.super12138.todo.R import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.database.TodoEntity 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.TopAppBarScaffold import cn.super12138.todo.ui.components.TodoFloatingActionButton +import cn.super12138.todo.ui.components.TopAppBarScaffold import cn.super12138.todo.ui.pages.editor.components.TodoCategoryChip import cn.super12138.todo.ui.pages.editor.components.TodoCategoryTextField import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField @@ -159,13 +158,10 @@ fun TodoEditorPage( }, onBack = ::checkModifiedBeforeBack, modifier = modifier - ) { innerPadding -> + ) { LazyColumn( verticalArrangement = Arrangement.spacedBy(5.dp), - modifier = Modifier - .padding(innerPadding) - .padding(horizontal = TodoDefaults.screenHorizontalPadding) - .fillMaxSize() + modifier = Modifier.fillMaxSize() ) { item { TodoContentTextField( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoPrioritySlider.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoPrioritySlider.kt index 008a182..1e0981b 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoPrioritySlider.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoPrioritySlider.kt @@ -15,6 +15,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.LiveRegionMode import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.liveRegion @@ -35,7 +36,7 @@ fun TodoPrioritySlider( val view = LocalView.current val context = LocalContext.current - val priorityName = remember { Priority.entries.map { it.displayName(context) } } + val priorityName = Priority.entries.map { stringResource(it.nameRes) } val interactionSource = remember { MutableInteractionSource() } Slider( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt deleted file mode 100644 index fbb07ca..0000000 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt +++ /dev/null @@ -1,86 +0,0 @@ -package cn.super12138.todo.ui.pages.main - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import cn.super12138.todo.R -import cn.super12138.todo.logic.database.TodoEntity -import cn.super12138.todo.logic.model.Priority -import cn.super12138.todo.ui.TodoDefaults -import cn.super12138.todo.ui.pages.main.components.TodoCard - -@OptIn(ExperimentalMaterial3ExpressiveApi::class) -@Composable -fun ManagerFragment( - modifier: Modifier = Modifier, - state: LazyListState, - list: List, - onItemClick: (TodoEntity) -> Unit = {}, - onItemLongClick: (TodoEntity) -> Unit = {}, - onItemChecked: (TodoEntity) -> Unit = {}, - selectedTodoIds: List -) { - LazyColumn( - state = state, - contentPadding = PaddingValues( - start = TodoDefaults.screenHorizontalPadding, - bottom = TodoDefaults.toDoCardHeight / 2, - end = TodoDefaults.screenHorizontalPadding - ), - modifier = modifier - /*.clip( - MaterialTheme.shapes.extraLarge.copy( - bottomEnd = CornerSize(0.dp), - bottomStart = CornerSize(0.dp) - ) - ) - .background(MaterialTheme.colorScheme.surfaceContainerHighest)*/ - // verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - if (list.isEmpty()) { - item { - Text( - text = stringResource(R.string.tip_no_task), - style = MaterialTheme.typography.titleMedium, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() - ) - } - } else { - items( - items = list, - key = { it.id } - ) { item -> - TodoCard( - // id = item.id, - content = item.content, - category = item.category, - completed = item.isCompleted, - priority = Priority.fromFloat(item.priority), - selected = selectedTodoIds.contains(item.id), - onCardClick = { onItemClick(item) }, - onCardLongClick = { onItemLongClick(item) }, - onChecked = { onItemChecked(item) }, - modifier = Modifier - .padding(vertical = 5.dp) - .animateItem( - fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(), - placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(), - fadeOutSpec = MaterialTheme.motionScheme.fastEffectsSpec() - ) - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/TasksPage.kt similarity index 54% rename from app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt rename to app/src/main/kotlin/cn/super12138/todo/ui/pages/main/TasksPage.kt index e9788a0..2efff83 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/TasksPage.kt @@ -2,13 +2,18 @@ package cn.super12138.todo.ui.pages.main import androidx.activity.compose.BackHandler import androidx.compose.animation.SharedTransitionScope -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.Scaffold +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.material3.animateFloatingActionButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -21,13 +26,19 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp import androidx.navigation3.ui.LocalNavAnimatedContentScope import cn.super12138.todo.R 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.model.Priority +import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.TodoFloatingActionButton +import cn.super12138.todo.ui.components.TopAppBarScaffold +import cn.super12138.todo.ui.pages.main.components.TodoCard import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar import cn.super12138.todo.ui.viewmodels.MainViewModel @@ -37,9 +48,7 @@ fun MainPage( modifier: Modifier = Modifier, viewModel: MainViewModel, toTodoEditPage: (TodoEntity?) -> Unit, - toSettingsPage: () -> Unit, sharedTransitionScope: SharedTransitionScope, - // windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass, ) { val animatedVisibilityScope = LocalNavAnimatedContentScope.current @@ -53,8 +62,8 @@ fun MainPage( val selectedTodoIds by remember { derivedStateOf { selectedTodos.value } } val inSelectedMode by remember { derivedStateOf { !selectedTodoIds.isEmpty() } } val toDoList by remember { derivedStateOf { toDos.value } } - val totalTasks by remember { derivedStateOf { toDoList.size } } - val completedTasks by remember { derivedStateOf { toDoList.count { it.isCompleted } } } + /*val totalTasks by remember { derivedStateOf { toDoList.size } } + val completedTasks by remember { derivedStateOf { toDoList.count { it.isCompleted } } }*/ val filteredTodoList = if (showCompleted) toDoList else toDoList.filter { item -> !item.isCompleted } val expandedFab by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } } @@ -62,15 +71,14 @@ fun MainPage( // 当按下返回键(或进行返回操作)时清空选择,仅在非选择模式下生效 BackHandler(inSelectedMode) { viewModel.clearAllTodoSelection() } - Scaffold( + TopAppBarScaffold( topBar = { TodoTopAppBar( selectedTodoIds = selectedTodoIds, selectedMode = inSelectedMode, onCancelSelect = { viewModel.clearAllTodoSelection() }, onSelectAll = { viewModel.selectAllTodos() }, - onDeleteSelectedTodo = { showDeleteConfirmDialog = true }, - toSettingsPage = toSettingsPage + onDeleteSelectedTodo = { showDeleteConfirmDialog = true } ) }, floatingActionButton = { @@ -94,84 +102,68 @@ fun MainPage( }, contentWindowInsets = WindowInsets(0, 0, 0, 0), modifier = modifier - ) { innerPadding -> - /*val isMediumScreen = - windowSizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND) - if (isMediumScreen) { - Row( - 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 { - toTodoEditPage(item) - } - }, - onItemLongClick = { viewModel.toggleTodoSelection(it) }, - onItemChecked = { - viewModel.updateTodo(it.copy(isCompleted = true)) - viewModel.playConfetti() - }, - selectedTodoIds = selectedTodoIds, - // sharedTransitionScope = sharedTransitionScope, - // animatedVisibilityScope = animatedVisibilityScope, - modifier = Modifier - .weight(3f) - .fillMaxSize() - ) - } - } else {*/ - Column( - modifier = Modifier.padding( - top = innerPadding.calculateTopPadding(), - bottom = innerPadding.calculateBottomPadding() - ) + ) { + LazyColumn( + state = listState, + verticalArrangement = Arrangement.spacedBy(5.dp), + modifier = Modifier.fillMaxSize() ) { - ProgressFragment( - totalTasks = totalTasks, - completedTasks = completedTasks, - modifier = Modifier.fillMaxWidth() - ) - ManagerFragment( - state = listState, - list = filteredTodoList, - onItemClick = { item -> - if (inSelectedMode) { - viewModel.toggleTodoSelection(item) - } else { - toTodoEditPage(item) - } - }, - onItemLongClick = { viewModel.toggleTodoSelection(it) }, - onItemChecked = { - viewModel.updateTodo(it.copy(isCompleted = true)) - viewModel.playConfetti() - }, - selectedTodoIds = selectedTodoIds, - modifier = Modifier.fillMaxWidth() - ) + item { + Spacer(modifier = Modifier.size(TodoDefaults.settingsItemVerticalPadding)) + } + + if (filteredTodoList.isEmpty()) { + item { + Text( + text = stringResource(R.string.tip_no_task), + style = MaterialTheme.typography.titleMedium, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + } + } else { + items( + items = filteredTodoList, + key = { it.id } + ) { + TodoCard( + // id = item.id, + content = it.content, + category = it.category, + completed = it.isCompleted, + priority = Priority.fromFloat(it.priority), + selected = selectedTodoIds.contains(it.id), + onCardClick = { + if (inSelectedMode) { + viewModel.toggleTodoSelection(it) + } else { + toTodoEditPage(it) + } + }, + onCardLongClick = { viewModel.toggleTodoSelection(it) }, + onChecked = { + viewModel.updateTodo(it.copy(isCompleted = true)) + viewModel.playConfetti() + }, + modifier = Modifier + .animateItem( + fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(), + placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(), + fadeOutSpec = MaterialTheme.motionScheme.fastEffectsSpec() + ) + ) + } + item { + Spacer(modifier = Modifier.size(TodoDefaults.settingsItemVerticalPadding)) + } + } } - /*}*/ - ConfirmDialog( - visible = showDeleteConfirmDialog, - iconRes = R.drawable.ic_delete, - text = stringResource(R.string.tip_delete_task, selectedTodoIds.size), - onConfirm = { viewModel.deleteSelectedTodo() }, - onDismiss = { showDeleteConfirmDialog = false } - ) } + ConfirmDialog( + visible = showDeleteConfirmDialog, + iconRes = R.drawable.ic_delete, + text = stringResource(R.string.tip_delete_task, selectedTodoIds.size), + onConfirm = { viewModel.deleteSelectedTodo() }, + onDismiss = { showDeleteConfirmDialog = false } + ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt index 2c7c960..57db1c5 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt @@ -121,7 +121,7 @@ fun TodoCard( modifier = Modifier.padding(start = 5.dp) ) { Text( - text = priority.displayName(context), + text = stringResource(priority.nameRes), textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, ) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt index 2f00f64..d570b0b 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults @@ -30,6 +31,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import cn.super12138.todo.R +import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.utils.VibrationUtils @OptIn(ExperimentalMaterial3Api::class) @@ -40,15 +42,14 @@ fun TodoTopAppBar( onCancelSelect: () -> Unit, onSelectAll: () -> Unit, onDeleteSelectedTodo: () -> Unit, - toSettingsPage: () -> Unit, modifier: Modifier = Modifier ) { val view = LocalView.current val animatedTopAppBarColors by animateColorAsState( targetValue = if (selectedMode) { - TopAppBarDefaults.topAppBarColors().scrolledContainerColor + MaterialTheme.colorScheme.surfaceContainerHighest } else { - TopAppBarDefaults.topAppBarColors().containerColor + TodoDefaults.BackgroundColor } ) @@ -103,52 +104,29 @@ fun TodoTopAppBar( } }, actions = { - AnimatedContent( - targetState = selectedMode, - transitionSpec = { - ( - fadeIn() + scaleIn(initialScale = 0.92f) - ).togetherWith( - fadeOut(animationSpec = tween(90)) - ) - } - ) { inSelectedMode -> - if (!inSelectedMode) { + AnimatedVisibility(visible = selectedMode) { + Row { IconButton( onClick = { VibrationUtils.performHapticFeedback(view) - toSettingsPage() + onSelectAll() } ) { Icon( - painter = painterResource(R.drawable.ic_settings), - contentDescription = stringResource(R.string.page_settings) + painter = painterResource(R.drawable.ic_select_all), + contentDescription = stringResource(R.string.tip_select_all) ) } - } else { - Row { - IconButton( - onClick = { - VibrationUtils.performHapticFeedback(view) - onSelectAll() - } - ) { - Icon( - painter = painterResource(R.drawable.ic_select_all), - contentDescription = stringResource(R.string.tip_select_all) - ) - } - IconButton( - onClick = { - VibrationUtils.performHapticFeedback(view) - onDeleteSelectedTodo() - } - ) { - Icon( - painter = painterResource(R.drawable.ic_delete), - contentDescription = stringResource(R.string.action_delete) - ) + IconButton( + onClick = { + VibrationUtils.performHapticFeedback(view) + onDeleteSelectedTodo() } + ) { + Icon( + painter = painterResource(R.drawable.ic_delete), + contentDescription = stringResource(R.string.action_delete) + ) } } } @@ -170,7 +148,6 @@ private fun TodoTopAppBarPreview() { selectedMode = selectedMode, onCancelSelect = { selectedMode = !selectedMode }, onSelectAll = { }, - onDeleteSelectedTodo = { }, - toSettingsPage = { selectedMode = !selectedMode } + onDeleteSelectedTodo = { } ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/overview/OverviewPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/overview/OverviewPage.kt new file mode 100644 index 0000000..106e7bb --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/overview/OverviewPage.kt @@ -0,0 +1,27 @@ +package cn.super12138.todo.ui.pages.overview + +import androidx.compose.foundation.layout.Row +import androidx.compose.material3.Card +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import cn.super12138.todo.R +import cn.super12138.todo.ui.components.TopAppBarScaffold + +@Composable +fun OverviewPage(modifier: Modifier = Modifier) { + TopAppBarScaffold( + title = stringResource(R.string.page_overview), + modifier = modifier + ) { + Row { + Card { + Icon(painter = painterResource(R.drawable.ic_ballot), contentDescription = null) + Text("总任务") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt index 717c55d..b090140 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt @@ -2,7 +2,6 @@ package cn.super12138.todo.ui.pages.settings import android.widget.Toast import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.runtime.Composable @@ -19,7 +18,6 @@ import androidx.compose.ui.res.stringResource import cn.super12138.todo.R import cn.super12138.todo.constants.Constants 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.SettingsContainer import cn.super12138.todo.ui.pages.settings.components.SettingsItem @@ -39,7 +37,7 @@ fun SettingsAbout( title = stringResource(R.string.pref_about), onBack = onNavigateUp, modifier = modifier, - ) { innerPadding -> + ) { val context = LocalContext.current val uriHandler = LocalUriHandler.current var clickCount by remember { mutableIntStateOf(0) } @@ -57,60 +55,58 @@ fun SettingsAbout( } } - 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 + 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() } - }, - 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 - ) - } + clickCount = 0 + } + }, + 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 + ) } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt index a5b0e73..b79d198 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.SelectionContainer @@ -30,7 +29,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import cn.super12138.todo.R import cn.super12138.todo.ui.components.BasicDialog -import cn.super12138.todo.ui.components.RoundedScreenContainer import cn.super12138.todo.ui.components.TopAppBarScaffold import cn.super12138.todo.ui.pages.settings.components.SettingsContainer import cn.super12138.todo.ui.pages.settings.components.SettingsItem @@ -53,103 +51,100 @@ fun SettingsAboutLicence( title = stringResource(R.string.pref_licence), onBack = onNavigateUp, modifier = modifier - ) { innerPadding -> + ) { + SettingsContainer(Modifier.fillMaxSize()) { + itemsIndexed( + items = libraries?.libraries ?: listOf(), + key = { _, library -> library.artifactId } + ) { index, library -> + var openDialog by rememberSaveable { mutableStateOf(false) } - RoundedScreenContainer(Modifier.padding(innerPadding)) { - SettingsContainer(Modifier.fillMaxSize()) { - itemsIndexed( - items = libraries?.libraries ?: listOf(), - key = { _, library -> library.artifactId } - ) { index, library -> - var openDialog by rememberSaveable { mutableStateOf(false) } - - SettingsItem( - headlineContent = { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { + SettingsItem( + headlineContent = { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = library.name, + modifier = Modifier.weight(1f), + style = MaterialTheme.typography.titleLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + val version = library.artifactVersion + if (version != null) { Text( - text = library.name, - modifier = Modifier.weight(1f), - style = MaterialTheme.typography.titleLarge, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - val version = library.artifactVersion - if (version != null) { - Text( - text = version, - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center - ) - } - } - val author = library.author - if (author.isNotBlank()) { - Text( - text = author, - style = MaterialTheme.typography.bodyMedium + text = version, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center ) } - }, - supportingContent = { - if (library.licenses.isNotEmpty()) { - FlowRow { - library.licenses.forEach { - Badge(containerColor = MaterialTheme.colorScheme.primary) { - Text( - maxLines = 1, - text = it.name, - // style = MaterialTheme.typography.labelSmall, - textAlign = TextAlign.Center, - overflow = TextOverflow.Ellipsis, - ) - } + } + val author = library.author + if (author.isNotBlank()) { + Text( + text = author, + style = MaterialTheme.typography.bodyMedium + ) + } + }, + supportingContent = { + if (library.licenses.isNotEmpty()) { + FlowRow { + library.licenses.forEach { + Badge(containerColor = MaterialTheme.colorScheme.primary) { + Text( + maxLines = 1, + text = it.name, + // style = MaterialTheme.typography.labelSmall, + textAlign = TextAlign.Center, + overflow = TextOverflow.Ellipsis, + ) } } } - }, - onClick = { - val license = library.licenses.firstOrNull() - if (!license?.htmlReadyLicenseContent.isNullOrBlank()) { - openDialog = true - } else if (!license?.url.isNullOrBlank()) { - license.url?.also { - try { - uriHandler.openUri(it) - } catch (t: Throwable) { - throw Exception("Failed to open licence URL: $it", t) - } + } + }, + onClick = { + val license = library.licenses.firstOrNull() + if (!license?.htmlReadyLicenseContent.isNullOrBlank()) { + openDialog = true + } else if (!license?.url.isNullOrBlank()) { + license.url?.also { + try { + uriHandler.openUri(it) + } catch (t: Throwable) { + throw Exception("Failed to open licence URL: $it", t) } } - }, - topRounded = index == 0, - bottomRounded = index == (libraries?.libraries?.size ?: 1) - 1 - ) + } + }, + topRounded = index == 0, + bottomRounded = index == (libraries?.libraries?.size ?: 1) - 1 + ) - BasicDialog( - visible = openDialog, - title = { Text(library.name) }, - text = { - library.licenses.firstOrNull()?.licenseContent?.let { - Column(Modifier.verticalScroll(rememberScrollState())) { - SelectionContainer { Text(text = it) } - } + BasicDialog( + visible = openDialog, + title = { Text(library.name) }, + text = { + library.licenses.firstOrNull()?.licenseContent?.let { + Column(Modifier.verticalScroll(rememberScrollState())) { + SelectionContainer { Text(text = it) } } - }, - confirmButton = { - FilledTonalButton( - onClick = { - openDialog = false - VibrationUtils.performHapticFeedback(view) - }, - shapes = ButtonDefaults.shapes() - ) { Text(stringResource(R.string.action_confirm)) } - }, - onDismissRequest = { openDialog = false } - ) - } + } + }, + confirmButton = { + FilledTonalButton( + onClick = { + openDialog = false + VibrationUtils.performHapticFeedback(view) + }, + shapes = ButtonDefaults.shapes() + ) { Text(stringResource(R.string.action_confirm)) } + }, + onDismissRequest = { openDialog = false } + ) } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt index afdc329..7eac076 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt @@ -1,7 +1,6 @@ package cn.super12138.todo.ui.pages.settings import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -12,16 +11,15 @@ 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.ContrastLevel +import cn.super12138.todo.logic.model.DarkMode +import cn.super12138.todo.logic.model.PaletteStyle 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 import cn.super12138.todo.ui.pages.settings.components.appearance.palette.PalettePicker -import cn.super12138.todo.ui.theme.ContrastLevel -import cn.super12138.todo.ui.theme.DarkMode -import cn.super12138.todo.ui.theme.PaletteStyle import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -40,44 +38,42 @@ fun SettingsAppearance( title = stringResource(R.string.pref_appearance), onBack = onNavigateUp, modifier = modifier, - ) { innerPadding -> - 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 - ) - } + ) { + 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 + ) + } - item(key = 2) { - 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) } } + ) + } - 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) - ) - } + 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) + ) + } - item(key = 4) { - ContrastPicker( - currentContrast = ContrastLevel.fromFloat(contrastLevel), - onContrastChange = { scope.launch { DataStoreManager.setContrastLevel(it.value) } }, - bottomRounded = true - ) - } + item(key = 4) { + ContrastPicker( + currentContrast = ContrastLevel.fromFloat(contrastLevel), + onContrastChange = { scope.launch { DataStoreManager.setContrastLevel(it.value) } }, + bottomRounded = true + ) } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt index 24c0d06..c42671d 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt @@ -5,7 +5,6 @@ import android.content.Intent import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.SnackbarHost @@ -25,7 +24,6 @@ import cn.super12138.todo.R import cn.super12138.todo.ui.activities.MainActivity import cn.super12138.todo.ui.components.ConfirmDialog 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 @@ -102,44 +100,45 @@ fun SettingsData( onBack = onNavigateUp, snackbarHost = { SnackbarHost(snackbarHostState) }, modifier = modifier, - ) { innerPadding -> - 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 - ) - } + ) { + 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 - ) - } + 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 + ) } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt index ab3db90..162834c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt @@ -6,7 +6,6 @@ import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState @@ -30,8 +29,8 @@ 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.TopAppBarScaffold import cn.super12138.todo.ui.components.TodoFloatingActionButton +import cn.super12138.todo.ui.components.TopAppBarScaffold import cn.super12138.todo.ui.pages.settings.components.category.CategoryItem import cn.super12138.todo.ui.pages.settings.components.category.CategoryPromptDialog import kotlinx.coroutines.launch @@ -70,12 +69,10 @@ fun SettingsDataCategory( ) }, modifier = modifier, - ) { innerPadding -> + ) { LazyColumn( state = listState, - modifier = Modifier - .fillMaxSize() - .padding(innerPadding) //TODO: 可能需要清除屏幕边距 + modifier = Modifier.fillMaxSize() ) { if (categories.isEmpty()) { item { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt index 9e539c9..f57ff34 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt @@ -1,14 +1,12 @@ package cn.super12138.todo.ui.pages.settings import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import cn.super12138.todo.R 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 @@ -23,18 +21,16 @@ fun SettingsDeveloperOptions( title = stringResource(R.string.pref_developer_options), onBack = onNavigateUp, modifier = modifier, - ) { innerPadding -> - 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 - ) - } + ) { + SettingsContainer(Modifier.fillMaxSize()) { + item { + SettingsItem( + leadingIconRes = R.drawable.ic_padding, + title = stringResource(R.string.pref_padding), + onClick = toPaddingPage, + topRounded = true, + bottomRounded = true + ) } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt index 80677ea..afbd75b 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt @@ -1,14 +1,12 @@ package cn.super12138.todo.ui.pages.settings import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -19,7 +17,6 @@ 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.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 @@ -48,70 +45,69 @@ fun SettingsInterface( title = stringResource(R.string.pref_interface_interaction), onBack = onNavigateUp, modifier = modifier, - ) { innerPadding -> - 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 - ) - } + ) { + 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 = stringResource(SortingMethod.fromId(sortingMethod).nameRes), + 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 { + 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)) - } + 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)) } } - val sortingList = remember { - SortingMethod.entries.map { - SettingsRadioOptions( - id = it.id, - text = it.getDisplayName(context) - ) - } + val sortingList = SortingMethod.entries.map { + SettingsRadioOptions( + id = it.id, + text = stringResource(it.nameRes) + ) } SettingsRadioDialog( visible = showSortingMethodDialog, title = stringResource(R.string.pref_sorting_method), currentOptions = SettingsRadioOptions( id = sortingMethod, - text = SortingMethod.fromId(sortingMethod).getDisplayName(context) + text = stringResource(SortingMethod.fromId(sortingMethod).nameRes) ), options = sortingList, onSelect = { scope.launch { DataStoreManager.setSortingMethod(it) } }, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt index 45f90aa..1ee7715 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt @@ -1,7 +1,6 @@ package cn.super12138.todo.ui.pages.settings import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.runtime.Composable @@ -9,7 +8,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import cn.super12138.todo.R 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 @@ -20,53 +18,49 @@ fun SettingsMain( toInterfacePage: () -> Unit, toDataPage: () -> Unit, toAboutPage: () -> Unit, - onNavigateUp: () -> Unit, modifier: Modifier = Modifier ) { TopAppBarScaffold( title = stringResource(R.string.page_settings), - onBack = onNavigateUp, modifier = modifier - ) { innerPadding -> - 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 - ) - } + ) { + 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_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_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 - ) - } + item { + SettingsItem( + leadingIconRes = R.drawable.ic_info, + title = stringResource(R.string.pref_about), + description = stringResource(R.string.pref_about_desc), + onClick = toAboutPage, + bottomRounded = true + ) } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/contrast/ContrastPicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/contrast/ContrastPicker.kt index 3ba95bc..2edea55 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/contrast/ContrastPicker.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/contrast/ContrastPicker.kt @@ -29,8 +29,8 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.dp import cn.super12138.todo.R +import cn.super12138.todo.logic.model.ContrastLevel import cn.super12138.todo.ui.pages.settings.components.MoreContentSettingsItem -import cn.super12138.todo.ui.theme.ContrastLevel import cn.super12138.todo.utils.VibrationUtils @Composable @@ -50,8 +50,7 @@ fun ContrastPicker( bottomRounded = bottomRounded, modifier = modifier ) { - val contrastLevelName = - ContrastLevel.entries.map { it.getDisplayName(context) } + val contrastLevelName = ContrastLevel.entries.map { stringResource(it.nameRes) } var lastVibratedLevel by remember { mutableFloatStateOf(currentContrast.value) } Slider( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt index b777ade..1d5f177 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt @@ -9,13 +9,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import cn.super12138.todo.R +import cn.super12138.todo.logic.model.DarkMode 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( @@ -25,7 +24,6 @@ fun DarkModePicker( topRounded: Boolean = false, bottomRounded: Boolean = false ) { - val context = LocalContext.current val isInDarkTheme = isSystemInDarkTheme() val darkModeList = remember { DarkMode.entries.toList() } @@ -50,7 +48,7 @@ fun DarkModePicker( DarkModeItem( iconRes = it.iconRes, - name = it.getDisplayName(context), + name = stringResource(it.nameRes), contentColor = contentColor, containerColor = containerColor, selected = isSelected, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PaletteItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PaletteItem.kt index 4e53edf..93a26ac 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PaletteItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PaletteItem.kt @@ -22,14 +22,14 @@ 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.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach -import cn.super12138.todo.ui.theme.ContrastLevel -import cn.super12138.todo.ui.theme.PaletteStyle +import cn.super12138.todo.logic.model.ContrastLevel +import cn.super12138.todo.logic.model.PaletteStyle import cn.super12138.todo.ui.theme.dynamicColorScheme import cn.super12138.todo.utils.VibrationUtils @@ -44,7 +44,6 @@ fun PaletteItem( modifier: Modifier = Modifier ) { val view = LocalView.current - val context = LocalContext.current Column( modifier = modifier @@ -107,7 +106,7 @@ fun PaletteItem( Spacer(Modifier.size(8.dp)) Text( - text = paletteStyle.getDisplayName(context), + text = stringResource(paletteStyle.nameRes), style = MaterialTheme.typography.bodyMedium, color = if (selected) { MaterialTheme.colorScheme.primary diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt index c0b266d..d0dc5ec 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt @@ -11,11 +11,11 @@ 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.logic.model.ContrastLevel +import cn.super12138.todo.logic.model.DarkMode +import cn.super12138.todo.logic.model.PaletteStyle 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 -import cn.super12138.todo.ui.theme.PaletteStyle @Composable fun PalettePicker( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/theme/ContrastLevel.kt b/app/src/main/kotlin/cn/super12138/todo/ui/theme/ContrastLevel.kt deleted file mode 100644 index 5446f75..0000000 --- a/app/src/main/kotlin/cn/super12138/todo/ui/theme/ContrastLevel.kt +++ /dev/null @@ -1,27 +0,0 @@ -package cn.super12138.todo.ui.theme - -import android.content.Context -import cn.super12138.todo.R - -enum class ContrastLevel(val value: Float) { - VeryLow(-1f), - Low(-0.5f), - Default(0f), - Medium(0.5f), - High(1f); - - fun getDisplayName(context: Context): String { - val resId = when (this) { - VeryLow -> R.string.contrast_very_low - Low -> R.string.contrast_low - Default -> R.string.contrast_default - Medium -> R.string.contrast_high - High -> R.string.contrast_very_high - } - return context.getString(resId) - } - - companion object { - fun fromFloat(float: Float) = entries.find { it.value == float } ?: Default - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/theme/DarkMode.kt b/app/src/main/kotlin/cn/super12138/todo/ui/theme/DarkMode.kt deleted file mode 100644 index d63eb4b..0000000 --- a/app/src/main/kotlin/cn/super12138/todo/ui/theme/DarkMode.kt +++ /dev/null @@ -1,26 +0,0 @@ -package cn.super12138.todo.ui.theme - -import android.content.Context -import cn.super12138.todo.R - -enum class DarkMode( - val id: Int, - val iconRes: Int -) { - FollowSystem(-1, R.drawable.ic_lightbulb_2), - Light(1, R.drawable.ic_light_mode), - Dark(2, R.drawable.ic_dark_mode); - - fun getDisplayName(context: Context): String { - val resId = when (this) { - FollowSystem -> R.string.dark_mode_system - Light -> R.string.dark_mode_light - Dark -> R.string.dark_mode_dark - } - return context.getString(resId) - } - - companion object { - fun fromId(id: Int) = entries.find { it.id == id } ?: FollowSystem - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/theme/PaletteStyle.kt b/app/src/main/kotlin/cn/super12138/todo/ui/theme/PaletteStyle.kt deleted file mode 100644 index c112917..0000000 --- a/app/src/main/kotlin/cn/super12138/todo/ui/theme/PaletteStyle.kt +++ /dev/null @@ -1,35 +0,0 @@ -package cn.super12138.todo.ui.theme - -import android.content.Context -import cn.super12138.todo.R - -enum class PaletteStyle(val id: Int) { - TonalSpot(1), - Neutral(2), - Vibrant(3), - Expressive(4), - Rainbow(5), - FruitSalad(6), - Monochrome(7), - Fidelity(8), - Content(9); - - fun getDisplayName(context: Context): String { - val resId = when (this) { - TonalSpot -> R.string.palette_tonal_spot - Neutral -> R.string.palette_neutral - Vibrant -> R.string.palette_vibrant - Expressive -> R.string.palette_expressive - Rainbow -> R.string.palette_rainbow - FruitSalad -> R.string.palette_fruit_salad - Monochrome -> R.string.palette_monochrome - Fidelity -> R.string.palette_fidelity - Content -> R.string.palette_content - } - return context.getString(resId) - } - - companion object { - fun fromId(id: Int) = entries.firstOrNull { it.id == id } ?: TonalSpot - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt b/app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt index 25505ab..0b0bda7 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt @@ -7,6 +7,7 @@ import androidx.compose.material3.MaterialExpressiveTheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource +import cn.super12138.todo.logic.model.PaletteStyle @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/theme/ThemeExt.kt b/app/src/main/kotlin/cn/super12138/todo/ui/theme/ThemeExt.kt index dba667a..947d34e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/theme/ThemeExt.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/theme/ThemeExt.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb +import cn.super12138.todo.logic.model.PaletteStyle import com.kyant.m3color.hct.Hct import com.kyant.m3color.scheme.SchemeContent import com.kyant.m3color.scheme.SchemeExpressive @@ -98,6 +99,5 @@ private inline fun Int.toColor(): Color = Color(this) // https://github.com/jordond/MaterialKolor/blob/main/material-kolor/src/commonMain/kotlin/com/materialkolor/DynamicMaterialTheme.kt @Composable -private fun Color.animate(animationSpec: AnimationSpec = spring()): Color { - return animateColorAsState(this, animationSpec).value -} \ No newline at end of file +private fun Color.animate(animationSpec: AnimationSpec = spring()): Color = + animateColorAsState(this, animationSpec).value \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt index 4095f91..c97ace1 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt @@ -5,12 +5,15 @@ import android.net.Uri import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.navigation3.runtime.NavKey import cn.super12138.todo.TodoApp import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.Repository import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.logic.model.SortingMethod +import cn.super12138.todo.ui.navigation.TodoScreen +import cn.super12138.todo.ui.navigation.TopLevelBackStack import cn.super12138.todo.utils.FileUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -32,6 +35,8 @@ import java.util.zip.ZipOutputStream @OptIn(ExperimentalCoroutinesApi::class) class MainViewModel : ViewModel() { + val topLevelBackStack = TopLevelBackStack(startKey = TodoScreen.Overview) + // 待办 private val toDos: Flow> = Repository.getAllTodos() val sortedTodos: Flow> = diff --git a/app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt b/app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt index b594f69..cf3b318 100644 --- a/app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt +++ b/app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt @@ -2,6 +2,8 @@ package cn.super12138.todo.utils import android.content.Context import android.os.Build +import androidx.activity.ComponentActivity +import androidx.activity.enableEdgeToEdge import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -39,4 +41,13 @@ object SystemUtils { return sdf.format(currentTime) } +} + +fun ComponentActivity.configureEdgeToEdge() { + enableEdgeToEdge() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Force the 3-button navigation bar to be transparent + // See: https://developer.android.com/develop/ui/views/layout/edge-to-edge#create-transparent + window.isNavigationBarContrastEnforced = false + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_ballot.xml b/app/src/main/res/drawable/ic_ballot.xml new file mode 100644 index 0000000..caadbc3 --- /dev/null +++ b/app/src/main/res/drawable/ic_ballot.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_ballot_filled.xml b/app/src/main/res/drawable/ic_ballot_filled.xml new file mode 100644 index 0000000..618d530 --- /dev/null +++ b/app/src/main/res/drawable/ic_ballot_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings_filled.xml b/app/src/main/res/drawable/ic_settings_filled.xml new file mode 100644 index 0000000..de2bf2a --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_view_kanban.xml b/app/src/main/res/drawable/ic_view_kanban.xml new file mode 100644 index 0000000..68826de --- /dev/null +++ b/app/src/main/res/drawable/ic_view_kanban.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_view_kanban_filled.xml b/app/src/main/res/drawable/ic_view_kanban_filled.xml new file mode 100644 index 0000000..79137ec --- /dev/null +++ b/app/src/main/res/drawable/ic_view_kanban_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 862ecdf..99dc1c9 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -115,4 +115,7 @@ 开发者选项 一些调试选项,仅供开发使用 边距 + 已选择 + 待办 + 概览 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ec1df71..5aa16fe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -117,4 +117,6 @@ Some debugging options, only for DEVELOPMENT use Padding Selected + Tasks + Overview \ No newline at end of file From 2b28b446eb66c478cc72b991a91a0965049da874 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:34:39 +0800 Subject: [PATCH 16/21] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E6=96=B9=E5=BC=8F2=20&=20=E6=B7=BB=E5=8A=A0=E8=BD=AC?= =?UTF-8?q?=E5=9C=BA=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../cn/super12138/todo/constants/Constants.kt | 3 +- .../cn/super12138/todo/logic/IRepository.kt | 18 ++ .../cn/super12138/todo/logic/Repository.kt | 14 +- .../todo/logic/model/ContrastLevel.kt | 2 +- .../super12138/todo/logic/model/DarkMode.kt | 4 +- .../todo/logic/model/PaletteStyle.kt | 2 +- .../super12138/todo/logic/model/Priority.kt | 2 +- .../todo/logic/model/SortingMethod.kt | 2 +- .../cn/super12138/todo/ui/TodoDefaults.kt | 2 +- .../todo/ui/activities/MainActivity.kt | 36 ++-- .../todo/ui/navigation/SettingsNavigation.kt | 103 +++++++++++ .../todo/ui/navigation/TodoDestinations.kt | 8 +- .../todo/ui/navigation/TodoNavigation.kt | 175 ------------------ .../todo/ui/navigation/TodoScreen.kt | 65 ++++--- .../todo/ui/navigation/TopNavigation.kt | 122 ++++++++++++ .../todo/ui/pages/editor/TodoEditorPage.kt | 65 ++++--- .../todo/ui/pages/overview/OverviewPage.kt | 6 +- .../settings/components/SettingsCategory.kt | 8 +- .../settings/components/SettingsContainer.kt | 4 +- .../pages/{main => tasks}/ProgressFragment.kt | 2 +- .../ui/pages/{main => tasks}/TasksPage.kt | 52 +++--- .../{main => tasks}/components/TodoCard.kt | 17 +- .../components/TodoTopAppBar.kt | 2 +- .../todo/ui/viewmodels/MainViewModel.kt | 4 +- 25 files changed, 413 insertions(+), 307 deletions(-) create mode 100644 app/src/main/kotlin/cn/super12138/todo/logic/IRepository.kt delete mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/navigation/TopNavigation.kt rename app/src/main/kotlin/cn/super12138/todo/ui/pages/{main => tasks}/ProgressFragment.kt (99%) rename app/src/main/kotlin/cn/super12138/todo/ui/pages/{main => tasks}/TasksPage.kt (81%) rename app/src/main/kotlin/cn/super12138/todo/ui/pages/{main => tasks}/components/TodoCard.kt (93%) rename app/src/main/kotlin/cn/super12138/todo/ui/pages/{main => tasks}/components/TodoTopAppBar.kt (99%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index eb1594a..c291fdc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,7 +38,7 @@ android { applicationId = "cn.super12138.todo" minSdk = 24 targetSdk = 36 - versionCode = 941 + versionCode = 950 versionName = "2.3.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt index 8745294..1b7d8ee 100644 --- a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt +++ b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt @@ -5,8 +5,7 @@ object Constants { const val GITHUB_REPO = "https://github.com/Super12138/ToDo/" const val KEY_TODO_FAB_TRANSITION = "todo_fab" - // const val KEY_TODO_CONTENT_TRANSITION = "todo_content" - // const val KEY_TODO_CATEGORY_TRANSITION = "todo_category" + const val KEY_TODO_ITEM_TRANSITION = "todo_item_id" const val DB_NAME = "todo" const val DB_TABLE_NAME = "todo" diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/IRepository.kt b/app/src/main/kotlin/cn/super12138/todo/logic/IRepository.kt new file mode 100644 index 0000000..ab6bfd4 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/logic/IRepository.kt @@ -0,0 +1,18 @@ +package cn.super12138.todo.logic + +import cn.super12138.todo.logic.database.TodoEntity +import kotlinx.coroutines.flow.Flow + +interface IRepository { + suspend fun insertTodo(toDo: TodoEntity) + + fun getAllTodos(): Flow> + + suspend fun updateTodo(toDo: TodoEntity) + + suspend fun deleteTodo(toDo: TodoEntity) + + suspend fun deleteTodoFromIds(toDoItems: List) + + // suspend fun deleteAllTodo() +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/Repository.kt b/app/src/main/kotlin/cn/super12138/todo/logic/Repository.kt index 034a5c2..2aa71cc 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/Repository.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/Repository.kt @@ -4,29 +4,29 @@ import cn.super12138.todo.TodoApp import cn.super12138.todo.logic.database.TodoEntity import kotlinx.coroutines.flow.Flow -object Repository { +object Repository : IRepository { private val db get() = TodoApp.db private val toDoDao = db.toDoDao() - suspend fun insertTodo(toDo: TodoEntity) { + override suspend fun insertTodo(toDo: TodoEntity) { toDoDao.insert(toDo) } - fun getAllTodos(): Flow> = toDoDao.getAll() + override fun getAllTodos(): Flow> = toDoDao.getAll() - suspend fun updateTodo(toDo: TodoEntity) { + override suspend fun updateTodo(toDo: TodoEntity) { toDoDao.update(toDo) } - suspend fun deleteTodo(toDo: TodoEntity) { + override suspend fun deleteTodo(toDo: TodoEntity) { toDoDao.delete(toDo) } - suspend fun deleteTodoFromIds(toDoItems: List) { + override suspend fun deleteTodoFromIds(toDoItems: List) { toDoDao.deleteFromIds(toDoItems.toSet()) } - /*suspend fun deleteAllTodo() { + /*override suspend fun deleteAllTodo() { toDoDao.deleteAllTodo() }*/ } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/ContrastLevel.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/ContrastLevel.kt index eb6ae6c..4f65193 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/model/ContrastLevel.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/ContrastLevel.kt @@ -5,7 +5,7 @@ import cn.super12138.todo.R enum class ContrastLevel( val value: Float, - @StringRes val nameRes: Int + @param:StringRes val nameRes: Int ) { VeryLow(value = -1f, nameRes = R.string.contrast_very_low), Low(value = -0.5f, nameRes = R.string.contrast_low), diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/DarkMode.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/DarkMode.kt index 55b5392..4df36dd 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/model/DarkMode.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/DarkMode.kt @@ -6,8 +6,8 @@ import cn.super12138.todo.R enum class DarkMode( val id: Int, - @DrawableRes val iconRes: Int, - @StringRes val nameRes: Int + @param:DrawableRes val iconRes: Int, + @param:StringRes val nameRes: Int ) { FollowSystem(id = -1, iconRes = R.drawable.ic_lightbulb_2, nameRes = R.string.dark_mode_system), Light(id = 1, iconRes = R.drawable.ic_light_mode, nameRes = R.string.dark_mode_light), diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/PaletteStyle.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/PaletteStyle.kt index 8f0341b..d22fa38 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/model/PaletteStyle.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/PaletteStyle.kt @@ -5,7 +5,7 @@ import cn.super12138.todo.R enum class PaletteStyle( val id: Int, - @StringRes val nameRes: Int + @param:StringRes val nameRes: Int ) { TonalSpot(id = 1, nameRes = R.string.palette_tonal_spot), Neutral(id = 2, nameRes = R.string.palette_neutral), diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/Priority.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/Priority.kt index 50378f2..ace33e9 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/model/Priority.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/Priority.kt @@ -5,7 +5,7 @@ import cn.super12138.todo.R enum class Priority( val value: Float, - @StringRes val nameRes: Int + @param:StringRes val nameRes: Int ) { Urgent(value = 2f, nameRes = R.string.priority_urgent), Important(value = 1f, nameRes = R.string.priority_important), diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt index 81dadf0..d88614b 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt @@ -5,7 +5,7 @@ import cn.super12138.todo.R enum class SortingMethod( val id: Int, - @StringRes val nameRes: Int + @param:StringRes val nameRes: Int ) { // 按添加先后顺序 Sequential(id = 1, nameRes = R.string.sorting_sequential), diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt index c326260..839f2ea 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt @@ -16,7 +16,7 @@ object TodoDefaults { /** * 屏幕上下预留边距(防止内容全部贴边显示过丑) */ - val screenVerticalPadding = 16.dp + val screenVerticalPadding = 8.dp /** * 待办卡片默认高度 diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt index 32bd9dd..d63b6a4 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.animation.Crossfade import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Icon @@ -26,7 +27,7 @@ import cn.super12138.todo.logic.model.PaletteStyle import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.components.Konfetti import cn.super12138.todo.ui.navigation.TodoDestinations -import cn.super12138.todo.ui.navigation.TodoNavigation +import cn.super12138.todo.ui.navigation.TopNavigation import cn.super12138.todo.ui.theme.ToDoTheme import cn.super12138.todo.ui.viewmodels.MainViewModel import cn.super12138.todo.utils.VibrationUtils @@ -39,7 +40,7 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { val mainViewModel: MainViewModel = viewModel() - val navigationBackStack = mainViewModel.topLevelBackStack + val mainBackStack = mainViewModel.mainBackStack val showConfetti = mainViewModel.showConfetti // 主题 val dynamicColor by DataStoreManager.dynamicColorFlow.collectAsState(initial = Constants.PREF_DYNAMIC_COLOR_DEFAULT) @@ -89,27 +90,34 @@ class MainActivity : ComponentActivity() { Surface(color = TodoDefaults.BackgroundColor) { NavigationSuiteScaffold( navigationSuiteItems = { - TodoDestinations.entries.forEach { - val selected = it.route == navigationBackStack.topLevelKey + TodoDestinations.entries.forEach { destination -> + val selected = destination.route == mainBackStack.topLevelKey item( icon = { - Icon( - painter = if (selected) painterResource(it.selectedIcon) else painterResource( - it.icon - ), - contentDescription = null - ) + Crossfade(selected) { + if (it) { + Icon( + painter = painterResource(destination.selectedIcon), + contentDescription = null + ) + } else { + Icon( + painter = painterResource(destination.icon), + contentDescription = null + ) + } + } }, - label = { Text(stringResource(it.label)) }, + label = { Text(stringResource(destination.label)) }, selected = selected, - onClick = { navigationBackStack.addTopLevel(it.route) } + onClick = { mainBackStack.addTopLevel(destination.route) } ) } }, containerColor = TodoDefaults.BackgroundColor ) { - TodoNavigation( - backStack = navigationBackStack, + TopNavigation( + backStack = mainBackStack, viewModel = mainViewModel, modifier = Modifier.fillMaxSize() ) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/SettingsNavigation.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/SettingsNavigation.kt index 0b31233..11c38c9 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/SettingsNavigation.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/SettingsNavigation.kt @@ -1,2 +1,105 @@ package cn.super12138.todo.ui.navigation +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation3.runtime.NavBackStack +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.ui.NavDisplay +import cn.super12138.todo.ui.pages.settings.SettingsAbout +import cn.super12138.todo.ui.pages.settings.SettingsAboutLicence +import cn.super12138.todo.ui.pages.settings.SettingsAppearance +import cn.super12138.todo.ui.pages.settings.SettingsData +import cn.super12138.todo.ui.pages.settings.SettingsDataCategory +import cn.super12138.todo.ui.pages.settings.SettingsDeveloperOptions +import cn.super12138.todo.ui.pages.settings.SettingsDeveloperOptionsPadding +import cn.super12138.todo.ui.pages.settings.SettingsInterface +import cn.super12138.todo.ui.pages.settings.SettingsMain +import cn.super12138.todo.ui.theme.materialSharedAxisX +import cn.super12138.todo.ui.viewmodels.MainViewModel + +private const val INITIAL_OFFSET_FACTOR = 0.10f + +@Composable +fun SettingsNavigation( + backStack: NavBackStack, + viewModel: MainViewModel, + modifier: Modifier = Modifier +) { + fun onBack() { + backStack.removeLastOrNull() + } + + NavDisplay( + backStack = backStack, + onBack = ::onBack, + transitionSpec = { + materialSharedAxisX( + initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }, + targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }) + }, + popTransitionSpec = { + materialSharedAxisX( + initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }, + targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }) + }, + entryProvider = entryProvider { + entry { + SettingsMain( + toAppearancePage = { backStack.add(TodoScreen.Settings.Appearance) }, + toAboutPage = { backStack.add(TodoScreen.Settings.About) }, + toInterfacePage = { backStack.add(TodoScreen.Settings.Interface) }, + toDataPage = { backStack.add(TodoScreen.Settings.Data) }, + ) + } + + entry { + SettingsAppearance(onNavigateUp = ::onBack) + } + + entry { + SettingsInterface(onNavigateUp = ::onBack) + } + + entry { + SettingsData( + viewModel = viewModel, + toCategoryManager = { backStack.add(TodoScreen.Settings.DataCategory) }, + onNavigateUp = ::onBack + ) + } + + entry { + SettingsDataCategory(onNavigateUp = ::onBack) + } + + entry { + SettingsAbout( + //toSpecialPage = { backStack.add(TodoScreen.Settings.AboutSpecial) }, + toLicencePage = { backStack.add(TodoScreen.Settings.AboutLicence) }, + toDevPage = { backStack.add(TodoScreen.Settings.DeveloperOptions) }, + onNavigateUp = ::onBack, + ) + } + + /*entry { + SettingsAboutSpecial(viewModel = viewModel) + }*/ + + entry { + SettingsAboutLicence(onNavigateUp = ::onBack) + } + + entry { + SettingsDeveloperOptions( + toPaddingPage = { backStack.add(TodoScreen.Settings.DeveloperOptionsPadding) }, + onNavigateUp = ::onBack + ) + } + entry { + SettingsDeveloperOptionsPadding(onNavigateUp = ::onBack) + } + }, + modifier = modifier, + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoDestinations.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoDestinations.kt index 732f77a..096f2eb 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoDestinations.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoDestinations.kt @@ -6,9 +6,9 @@ import androidx.navigation3.runtime.NavKey enum class TodoDestinations( val route: NavKey, - @StringRes val label: Int, - @DrawableRes val icon: Int, - @DrawableRes val selectedIcon: Int + @param:StringRes val label: Int, + @param:DrawableRes val icon: Int, + @param:DrawableRes val selectedIcon: Int ) { Overview( route = TodoScreen.Overview, @@ -23,7 +23,7 @@ enum class TodoDestinations( selectedIcon = cn.super12138.todo.R.drawable.ic_ballot_filled ), Settings( - route = TodoScreen.Settings, + route = TodoScreen.Settings.Main, label = cn.super12138.todo.R.string.page_settings, icon = cn.super12138.todo.R.drawable.ic_settings, selectedIcon = cn.super12138.todo.R.drawable.ic_settings_filled diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt deleted file mode 100644 index fd8f2c6..0000000 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt +++ /dev/null @@ -1,175 +0,0 @@ -package cn.super12138.todo.ui.navigation - -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionLayout -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically -import androidx.compose.animation.togetherWith -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.navigation3.runtime.NavKey -import androidx.navigation3.runtime.entryProvider -import androidx.navigation3.ui.NavDisplay -import cn.super12138.todo.ui.pages.editor.TodoEditorPage -import cn.super12138.todo.ui.pages.main.MainPage -import cn.super12138.todo.ui.pages.overview.OverviewPage -import cn.super12138.todo.ui.pages.settings.SettingsAbout -import cn.super12138.todo.ui.pages.settings.SettingsAboutLicence -import cn.super12138.todo.ui.pages.settings.SettingsAppearance -import cn.super12138.todo.ui.pages.settings.SettingsData -import cn.super12138.todo.ui.pages.settings.SettingsDataCategory -import cn.super12138.todo.ui.pages.settings.SettingsDeveloperOptions -import cn.super12138.todo.ui.pages.settings.SettingsDeveloperOptionsPadding -import cn.super12138.todo.ui.pages.settings.SettingsInterface -import cn.super12138.todo.ui.pages.settings.SettingsMain -import cn.super12138.todo.ui.theme.materialSharedAxisX -import cn.super12138.todo.ui.viewmodels.MainViewModel - -private const val INITIAL_OFFSET_FACTOR = 0.10f - -@OptIn(ExperimentalSharedTransitionApi::class) -@Composable -fun TodoNavigation( - backStack: TopLevelBackStack, - modifier: Modifier = Modifier, - viewModel: MainViewModel -) { - fun onBack() { - backStack.removeLast() - } - - val anim = NavDisplay.transitionSpec { - // Slide new content up, keeping the old content in place underneath - slideInVertically( - initialOffsetY = { it / 5 }, - animationSpec = tween(200) - ) + fadeIn(tween(200)) togetherWith ExitTransition.KeepUntilTransitionsFinished - } + NavDisplay.popTransitionSpec { - // Slide old content down, revealing the new content in place underneath - EnterTransition.None togetherWith - slideOutVertically( - targetOffsetY = { it / 5 }, - animationSpec = tween(200) - ) + fadeOut(tween(200)) - } + NavDisplay.predictivePopTransitionSpec { - // Slide old content down, revealing the new content in place underneath - EnterTransition.None togetherWith - slideOutVertically( - targetOffsetY = { it / 5 }, - animationSpec = tween(200) - ) + fadeOut(tween(200)) - } - - SharedTransitionLayout { - NavDisplay( - backStack = backStack.backStack, - onBack = ::onBack, - transitionSpec = { - materialSharedAxisX( - initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }, - targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }) - }, - popTransitionSpec = { - materialSharedAxisX( - initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }, - targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }) - }, - entryProvider = entryProvider { - entry(metadata = anim) { - OverviewPage() - } - entry(metadata = anim) { - MainPage( - viewModel = viewModel, - toTodoEditPage = { backStack.add(TodoScreen.Editor(it)) }, - sharedTransitionScope = this@SharedTransitionLayout - ) - } - - entry(metadata = anim) { - SettingsMain( - toAppearancePage = { backStack.add(TodoScreen.SettingsAppearance) }, - toAboutPage = { backStack.add(TodoScreen.SettingsAbout) }, - toInterfacePage = { backStack.add(TodoScreen.SettingsInterface) }, - toDataPage = { backStack.add(TodoScreen.SettingsData) }, - ) - } - - entry { editorArgs -> - TodoEditorPage( - toDo = editorArgs.toDo, - onSave = { - viewModel.addTodo(it) - // 如果原来的待办状态为未完成并且修改后状态为完成 - if (editorArgs.toDo?.isCompleted != true && it.isCompleted) { - viewModel.playConfetti() - } - onBack() - }, - onDelete = { - if (editorArgs.toDo !== null) { - viewModel.deleteTodo(editorArgs.toDo) - } - onBack() - }, - onNavigateUp = ::onBack, - sharedTransitionScope = this@SharedTransitionLayout - ) - } - - entry { - SettingsAppearance(onNavigateUp = ::onBack) - } - - entry { - SettingsInterface(onNavigateUp = ::onBack) - } - - entry { - SettingsData( - viewModel = viewModel, - toCategoryManager = { backStack.add(TodoScreen.SettingsDataCategory) }, - onNavigateUp = ::onBack - ) - } - - entry { - SettingsDataCategory(onNavigateUp = ::onBack) - } - - entry { - SettingsAbout( - //toSpecialPage = { backStack.add(TodoScreen.SettingsAboutSpecial) }, - toLicencePage = { backStack.add(TodoScreen.SettingsAboutLicence) }, - toDevPage = { backStack.add(TodoScreen.SettingsDev) }, - onNavigateUp = ::onBack, - ) - } - - /*entry { - SettingsAboutSpecial(viewModel = viewModel) - }*/ - - entry { - SettingsAboutLicence(onNavigateUp = ::onBack) - } - - entry { - SettingsDeveloperOptions( - toPaddingPage = { backStack.add(TodoScreen.SettingsDevPadding) }, - onNavigateUp = ::onBack - ) - } - entry { - SettingsDeveloperOptionsPadding(onNavigateUp = ::onBack) - } - }, - modifier = modifier, - ) - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt index 298959c..701464d 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt @@ -13,35 +13,44 @@ sealed class TodoScreen : NavKey { data object Tasks : TodoScreen() @Serializable - data class Editor(val toDo: TodoEntity?) : TodoScreen() + sealed class Settings : TodoScreen() { + @Serializable + data object Main : Settings() + + @Serializable + data object Appearance : Settings() + + @Serializable + data object Interface : Settings() + + @Serializable + data object Data : Settings() + + @Serializable + data object DataCategory : Settings() + + @Serializable + data object About : Settings() + + // @Serializable + // data object AboutEasterEgg : Settings() + + @Serializable + data object AboutLicence : Settings() + + @Serializable + data object DeveloperOptions : Settings() + + @Serializable + data object DeveloperOptionsPadding : Settings() + } @Serializable - data object Settings : TodoScreen() + sealed class Editor : TodoScreen() { + @Serializable + data object Add : Editor() - @Serializable - data object SettingsAppearance : TodoScreen() - - @Serializable - data object SettingsInterface : TodoScreen() - - @Serializable - data object SettingsData : TodoScreen() - - @Serializable - data object SettingsDataCategory : TodoScreen() - - @Serializable - data object SettingsAbout : TodoScreen() - - // @Serializable - // data object SettingsAboutSpecial : TodoScreen() - - @Serializable - data object SettingsAboutLicence : TodoScreen() - - @Serializable - data object SettingsDev : TodoScreen() - - @Serializable - data object SettingsDevPadding : TodoScreen() + @Serializable + data class Edit(val toDo: TodoEntity) : Editor() + } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TopNavigation.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TopNavigation.kt new file mode 100644 index 0000000..b71834e --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TopNavigation.kt @@ -0,0 +1,122 @@ +package cn.super12138.todo.ui.navigation + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith +import androidx.compose.animation.unveilIn +import androidx.compose.animation.veilOut +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.ui.NavDisplay +import cn.super12138.todo.ui.pages.editor.TodoAddPage +import cn.super12138.todo.ui.pages.editor.TodoEditPage +import cn.super12138.todo.ui.pages.overview.OverviewPage +import cn.super12138.todo.ui.pages.tasks.TasksPage +import cn.super12138.todo.ui.viewmodels.MainViewModel + +@OptIn(ExperimentalAnimationApi::class) +@Composable +fun TopNavigation( + backStack: TopLevelBackStack, + modifier: Modifier = Modifier, + viewModel: MainViewModel +) { + fun onBack() { + backStack.removeLast() + } + /* + * NavDisplay.transitionSpec { + * // Slide new content up, keeping the old content in place underneath + * slideInVertically( + * initialOffsetY = { it / 5 }, + * animationSpec = tween(200) + * ) + fadeIn(tween(200)) togetherWith ExitTransition.KeepUntilTransitionsFinished + * } + NavDisplay.popTransitionSpec { + * // Slide old content down, revealing the new content in place underneath + * EnterTransition.None togetherWith + * slideOutVertically( + * targetOffsetY = { it / 5 }, + * animationSpec = tween(200) + * ) + fadeOut(tween(200)) + * } + NavDisplay.predictivePopTransitionSpec { + * // Slide old content down, revealing the new content in place underneath + * EnterTransition.None togetherWith + * slideOutVertically( + * targetOffsetY = { it / 5 }, + * animationSpec = tween(200) + * ) + fadeOut(tween(200)) + * } + */ + + val veilColor = MaterialTheme.colorScheme.surfaceDim + SharedTransitionLayout { + NavDisplay( + backStack = backStack.backStack, + onBack = ::onBack, + transitionSpec = { + fadeIn() togetherWith veilOut(targetColor = veilColor) + }, + popTransitionSpec = { + unveilIn(initialColor = veilColor) togetherWith fadeOut() + }, + predictivePopTransitionSpec = { + unveilIn(initialColor = veilColor) togetherWith fadeOut() + }, + entryProvider = entryProvider { + entry { + OverviewPage() + } + + entry { + TasksPage( + viewModel = viewModel, + toTodoAddPage = { backStack.add(TodoScreen.Editor.Add) }, + toTodoEditPage = { backStack.add(TodoScreen.Editor.Edit(it)) } + ) + } + + entry { + TodoAddPage( + onSave = { + viewModel.addTodo(it) + onBack() + }, + onNavigateUp = ::onBack + ) + } + + entry { editorArgs -> + TodoEditPage( + toDo = editorArgs.toDo, + onSave = { + viewModel.addTodo(it) + // 如果原来的待办状态为未完成并且修改后状态为完成 + if (!editorArgs.toDo.isCompleted && it.isCompleted) { + viewModel.playConfetti() + } + onBack() + }, + onDelete = { + viewModel.deleteTodo(editorArgs.toDo) + onBack() + }, + onNavigateUp = ::onBack + ) + } + + entry { + SettingsNavigation( + backStack = viewModel.settingsBackStack, + viewModel = viewModel + ) + } + }, + modifier = modifier, + ) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index fb1bbae..51ed747 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -2,7 +2,6 @@ package cn.super12138.todo.ui.pages.editor import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn @@ -17,12 +16,9 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -41,6 +37,7 @@ import cn.super12138.todo.R import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.database.TodoEntity 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.TodoFloatingActionButton @@ -52,23 +49,50 @@ import cn.super12138.todo.ui.pages.editor.components.TodoPrioritySlider import cn.super12138.todo.ui.pages.editor.state.rememberEditorState import cn.super12138.todo.utils.VibrationUtils -@OptIn( - ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class, - ExperimentalMaterial3ExpressiveApi::class -) @Composable -fun TodoEditorPage( +fun SharedTransitionScope.TodoAddPage( + modifier: Modifier = Modifier, + onSave: (TodoEntity) -> Unit, + onNavigateUp: () -> Unit +) = TodoEditorPage( + toDo = null, + modifier = modifier.sharedBounds( + sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION), + animatedVisibilityScope = LocalNavAnimatedContentScope.current + ), + onSave = onSave, + onDelete = {}, + onNavigateUp = onNavigateUp +) + +@Composable +fun SharedTransitionScope.TodoEditPage( + modifier: Modifier = Modifier, + toDo: TodoEntity, + onSave: (TodoEntity) -> Unit, + onDelete: () -> Unit, + onNavigateUp: () -> Unit +) = TodoEditorPage( + toDo = toDo, + modifier = modifier.sharedBounds( + sharedContentState = rememberSharedContentState(key = "${Constants.KEY_TODO_ITEM_TRANSITION}_${toDo.id}"), + animatedVisibilityScope = LocalNavAnimatedContentScope.current + ), + onSave = onSave, + onDelete = onDelete, + onNavigateUp = onNavigateUp +) + +@Composable +private fun TodoEditorPage( modifier: Modifier = Modifier, toDo: TodoEntity? = null, onSave: (TodoEntity) -> Unit, onDelete: () -> Unit, - onNavigateUp: () -> Unit, - sharedTransitionScope: SharedTransitionScope + onNavigateUp: () -> Unit ) { // TODO: 本页及其相关组件重组性能检查优化 val view = LocalView.current - val animatedVisibilityScope = LocalNavAnimatedContentScope.current - TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val uiState = rememberEditorState(initialTodo = toDo) @@ -114,7 +138,6 @@ fun TodoEditorPage( TopAppBarScaffold( title = stringResource(if (toDo != null) R.string.title_edit_task else R.string.action_add_task), floatingActionButton = { - with(sharedTransitionScope) { Row( horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.imePadding() @@ -146,15 +169,9 @@ fun TodoEditorPage( ) onSave(newTodo) } - }, - modifier = Modifier - .sharedElement( - sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION), - animatedVisibilityScope = animatedVisibilityScope - ) + } ) } - } }, onBack = ::checkModifiedBeforeBack, modifier = modifier @@ -163,6 +180,10 @@ fun TodoEditorPage( verticalArrangement = Arrangement.spacedBy(5.dp), modifier = Modifier.fillMaxSize() ) { + item { + Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding)) + } + item { TodoContentTextField( value = uiState.toDoContent, @@ -241,7 +262,7 @@ fun TodoEditorPage( } item { - Spacer(modifier = Modifier.size(80.dp)) + Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding)) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/overview/OverviewPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/overview/OverviewPage.kt index 106e7bb..65dfca8 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/overview/OverviewPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/overview/OverviewPage.kt @@ -1,6 +1,7 @@ package cn.super12138.todo.ui.pages.overview -import androidx.compose.foundation.layout.Row +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.foundation.layout.Column import androidx.compose.material3.Card import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -11,13 +12,14 @@ import androidx.compose.ui.res.stringResource import cn.super12138.todo.R import cn.super12138.todo.ui.components.TopAppBarScaffold +@OptIn(ExperimentalAnimationApi::class) @Composable fun OverviewPage(modifier: Modifier = Modifier) { TopAppBarScaffold( title = stringResource(R.string.page_overview), modifier = modifier ) { - Row { + Column { Card { Icon(painter = painterResource(R.drawable.ic_ballot), contentDescription = null) Text("总任务") diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt index 099ec10..3d940e3 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt @@ -21,10 +21,10 @@ fun SettingsCategory( modifier = modifier .fillMaxWidth() .padding( - top = if (first) 0.dp else TodoDefaults.settingsItemVerticalPadding, - start = TodoDefaults.settingsItemHorizontalPadding / 2, - end = TodoDefaults.settingsItemHorizontalPadding / 2, - bottom = TodoDefaults.settingsItemVerticalPadding / 2 + top = if (first) 0.dp else TodoDefaults.screenVerticalPadding, + start = TodoDefaults.screenHorizontalPadding / 2, + end = TodoDefaults.screenHorizontalPadding / 2, + bottom = TodoDefaults.screenVerticalPadding ) ) { Text( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt index 5f4cce1..d14ee41 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsContainer.kt @@ -23,13 +23,13 @@ fun SettingsContainer( modifier = modifier ) { item { - Spacer(modifier = Modifier.size(TodoDefaults.settingsItemVerticalPadding)) + Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding)) } content() item { - Spacer(modifier = Modifier.size(TodoDefaults.settingsItemVerticalPadding)) + Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding)) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/ProgressFragment.kt similarity index 99% rename from app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt rename to app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/ProgressFragment.kt index 10f5d77..d38259b 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ProgressFragment.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/ProgressFragment.kt @@ -1,4 +1,4 @@ -package cn.super12138.todo.ui.pages.main +package cn.super12138.todo.ui.pages.tasks import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/TasksPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt similarity index 81% rename from app/src/main/kotlin/cn/super12138/todo/ui/pages/main/TasksPage.kt rename to app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt index 2efff83..f750e16 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/TasksPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt @@ -1,4 +1,4 @@ -package cn.super12138.todo.ui.pages.main +package cn.super12138.todo.ui.pages.tasks import androidx.activity.compose.BackHandler import androidx.compose.animation.SharedTransitionScope @@ -38,17 +38,17 @@ import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.TodoFloatingActionButton import cn.super12138.todo.ui.components.TopAppBarScaffold -import cn.super12138.todo.ui.pages.main.components.TodoCard -import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar +import cn.super12138.todo.ui.pages.tasks.components.TodoCard +import cn.super12138.todo.ui.pages.tasks.components.TodoTopAppBar import cn.super12138.todo.ui.viewmodels.MainViewModel @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable -fun MainPage( +fun SharedTransitionScope.TasksPage( modifier: Modifier = Modifier, viewModel: MainViewModel, - toTodoEditPage: (TodoEntity?) -> Unit, - sharedTransitionScope: SharedTransitionScope, + toTodoAddPage: () -> Unit, + toTodoEditPage: (TodoEntity) -> Unit, ) { val animatedVisibilityScope = LocalNavAnimatedContentScope.current @@ -82,23 +82,21 @@ fun MainPage( ) }, floatingActionButton = { - with(sharedTransitionScope) { - TodoFloatingActionButton( - text = stringResource(R.string.action_add_task), - iconRes = R.drawable.ic_add, - expanded = expandedFab, - onClick = { toTodoEditPage(null) }, - modifier = Modifier - .sharedElement( - sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION), - animatedVisibilityScope = animatedVisibilityScope - ) - .animateFloatingActionButton( - visible = !inSelectedMode, - alignment = Alignment.BottomEnd, - ) - ) - } + TodoFloatingActionButton( + text = stringResource(R.string.action_add_task), + iconRes = R.drawable.ic_add, + expanded = expandedFab, + onClick = { toTodoAddPage() }, + modifier = Modifier + .sharedBounds( + sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION), + animatedVisibilityScope = animatedVisibilityScope + ) + .animateFloatingActionButton( + visible = !inSelectedMode, + alignment = Alignment.BottomEnd, + ) + ) }, contentWindowInsets = WindowInsets(0, 0, 0, 0), modifier = modifier @@ -109,7 +107,7 @@ fun MainPage( modifier = Modifier.fillMaxSize() ) { item { - Spacer(modifier = Modifier.size(TodoDefaults.settingsItemVerticalPadding)) + Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding)) } if (filteredTodoList.isEmpty()) { @@ -146,6 +144,10 @@ fun MainPage( viewModel.playConfetti() }, modifier = Modifier + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "${Constants.KEY_TODO_ITEM_TRANSITION}_${it.id}"), + animatedVisibilityScope = LocalNavAnimatedContentScope.current + ) .animateItem( fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(), placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(), @@ -154,7 +156,7 @@ fun MainPage( ) } item { - Spacer(modifier = Modifier.size(TodoDefaults.settingsItemVerticalPadding)) + Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding)) } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoCard.kt similarity index 93% rename from app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt rename to app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoCard.kt index 57db1c5..9910509 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoCard.kt @@ -1,12 +1,10 @@ -package cn.super12138.todo.ui.pages.main.components +package cn.super12138.todo.ui.pages.tasks.components import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkHorizontally -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.combinedClickable @@ -21,7 +19,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Badge import androidx.compose.material3.BadgedBox -import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -32,7 +31,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -45,10 +43,7 @@ import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.containerColor -@OptIn( - ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class, - ExperimentalMaterial3ExpressiveApi::class -) +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun TodoCard( modifier: Modifier = Modifier, @@ -63,8 +58,8 @@ fun TodoCard( onChecked: () -> Unit = {} ) { val view = LocalView.current - val context = LocalContext.current - ElevatedCard( + Card( + colors = CardDefaults.cardColors(containerColor = TodoDefaults.ContainerColor), modifier = modifier .fillMaxWidth() .height(TodoDefaults.toDoCardHeight) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoTopAppBar.kt similarity index 99% rename from app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt rename to app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoTopAppBar.kt index d570b0b..20d6c54 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoTopAppBar.kt @@ -1,4 +1,4 @@ -package cn.super12138.todo.ui.pages.main.components +package cn.super12138.todo.ui.pages.tasks.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt index c97ace1..76525c9 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt @@ -5,6 +5,7 @@ import android.net.Uri import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey import cn.super12138.todo.TodoApp import cn.super12138.todo.constants.Constants @@ -35,7 +36,8 @@ import java.util.zip.ZipOutputStream @OptIn(ExperimentalCoroutinesApi::class) class MainViewModel : ViewModel() { - val topLevelBackStack = TopLevelBackStack(startKey = TodoScreen.Overview) + val mainBackStack = TopLevelBackStack(TodoScreen.Overview) + val settingsBackStack = NavBackStack(TodoScreen.Settings.Main) // 待办 private val toDos: Flow> = Repository.getAllTodos() From 5b87a17a820e239e72d46cee1ae7f72f5a7ea246 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:43:03 +0800 Subject: [PATCH 17/21] =?UTF-8?q?feat:=20=E8=BD=AC=E5=9C=BA=E5=8A=A8?= =?UTF-8?q?=E7=94=BB=E4=BC=98=E5=8C=96=20&=20=E4=BB=BB=E5=8A=A1=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=20MD3E=20=E9=80=82=E9=85=8D=20&=20=E5=BE=85=E5=8A=9E?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E6=A0=B7=E5=BC=8F=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../cn/super12138/todo/ui/TodoDefaults.kt | 2 +- .../todo/ui/navigation/TopNavigation.kt | 23 +++-- .../todo/ui/pages/editor/TodoEditorPage.kt | 41 +++------ .../components/TodoMarkAsCompletedCheckbox.kt | 59 +++++++++++++ .../todo/ui/pages/tasks/TasksPage.kt | 33 +++---- .../ui/pages/tasks/components/TodoCard.kt | 85 ++++++++++--------- .../cn/super12138/todo/ui/theme/Motion.kt | 30 +++++++ 8 files changed, 182 insertions(+), 93 deletions(-) create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoMarkAsCompletedCheckbox.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c291fdc..18c4764 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,7 +38,7 @@ android { applicationId = "cn.super12138.todo" minSdk = 24 targetSdk = 36 - versionCode = 950 + versionCode = 951 versionName = "2.3.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt index 839f2ea..7a3de92 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt @@ -21,7 +21,7 @@ object TodoDefaults { /** * 待办卡片默认高度 */ - val toDoCardHeight = 80.dp + val toDoCardHeight = 86.dp /** * 设置项水平边距 diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TopNavigation.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TopNavigation.kt index b71834e..18b01f6 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TopNavigation.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TopNavigation.kt @@ -2,11 +2,6 @@ package cn.super12138.todo.ui.navigation import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.SharedTransitionLayout -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.togetherWith -import androidx.compose.animation.unveilIn -import androidx.compose.animation.veilOut import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -17,8 +12,13 @@ import cn.super12138.todo.ui.pages.editor.TodoAddPage import cn.super12138.todo.ui.pages.editor.TodoEditPage import cn.super12138.todo.ui.pages.overview.OverviewPage import cn.super12138.todo.ui.pages.tasks.TasksPage +import cn.super12138.todo.ui.theme.fadeThrough import cn.super12138.todo.ui.viewmodels.MainViewModel +/** + * 来自:https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/transition/MaterialFadeThrough.java#L33 + */ + @OptIn(ExperimentalAnimationApi::class) @Composable fun TopNavigation( @@ -53,12 +53,12 @@ fun TopNavigation( * } */ - val veilColor = MaterialTheme.colorScheme.surfaceDim +// val veilColor = MaterialTheme.colorScheme.surfaceDim SharedTransitionLayout { NavDisplay( backStack = backStack.backStack, onBack = ::onBack, - transitionSpec = { + /*transitionSpec = { fadeIn() togetherWith veilOut(targetColor = veilColor) }, popTransitionSpec = { @@ -66,6 +66,15 @@ fun TopNavigation( }, predictivePopTransitionSpec = { unveilIn(initialColor = veilColor) togetherWith fadeOut() + },*/ + transitionSpec = { + fadeThrough() + }, + popTransitionSpec = { + fadeThrough() + }, + predictivePopTransitionSpec = { + fadeThrough() }, entryProvider = entryProvider { entry { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index 51ed747..7294b3a 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -13,23 +13,20 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation3.ui.LocalNavAnimatedContentScope @@ -45,9 +42,9 @@ import cn.super12138.todo.ui.components.TopAppBarScaffold import cn.super12138.todo.ui.pages.editor.components.TodoCategoryChip import cn.super12138.todo.ui.pages.editor.components.TodoCategoryTextField import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField +import cn.super12138.todo.ui.pages.editor.components.TodoMarkAsCompletedCheckbox import cn.super12138.todo.ui.pages.editor.components.TodoPrioritySlider import cn.super12138.todo.ui.pages.editor.state.rememberEditorState -import cn.super12138.todo.utils.VibrationUtils @Composable fun SharedTransitionScope.TodoAddPage( @@ -92,8 +89,6 @@ private fun TodoEditorPage( onNavigateUp: () -> Unit ) { // TODO: 本页及其相关组件重组性能检查优化 - val view = LocalView.current - val uiState = rememberEditorState(initialTodo = toDo) val originalCategories by DataStoreManager.categoriesFlow.collectAsState(initial = emptyList()) @@ -180,11 +175,11 @@ private fun TodoEditorPage( verticalArrangement = Arrangement.spacedBy(5.dp), modifier = Modifier.fillMaxSize() ) { - item { + item(key = 0) { Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding)) } - item { + item(key=1) { TodoContentTextField( value = uiState.toDoContent, onValueChange = { uiState.toDoContent = it }, @@ -193,7 +188,7 @@ private fun TodoEditorPage( ) } - item { + item(key = 2) { Text( text = stringResource(R.string.label_category), style = MaterialTheme.typography.titleMedium @@ -222,7 +217,7 @@ private fun TodoEditorPage( } } - item { + item (key = 3){ Text( text = stringResource(R.string.label_priority), style = MaterialTheme.typography.titleMedium @@ -234,34 +229,22 @@ private fun TodoEditorPage( ) } - item { + item(key = 4) { if (toDo != null) { Text( text = stringResource(R.string.label_more), style = MaterialTheme.typography.titleMedium ) - Row( - verticalAlignment = Alignment.CenterVertically, + TodoMarkAsCompletedCheckbox( + checked = uiState.isCompleted, + onCheckedChange = { uiState.isCompleted = it }, modifier = Modifier.fillMaxWidth() - ) { - Text( - text = stringResource(R.string.tip_mark_completed), - style = MaterialTheme.typography.labelLarge, - modifier = Modifier.padding(end = 10.dp) - ) - Switch( - checked = uiState.isCompleted, - onCheckedChange = { - VibrationUtils.performHapticFeedback(view) - uiState.isCompleted = it - } - ) - } + ) } } - item { + item (key = 5){ Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding)) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoMarkAsCompletedCheckbox.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoMarkAsCompletedCheckbox.kt new file mode 100644 index 0000000..01279de --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoMarkAsCompletedCheckbox.kt @@ -0,0 +1,59 @@ +package cn.super12138.todo.ui.pages.editor.components + +import android.view.HapticFeedbackConstants +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.selection.toggleable +import androidx.compose.material3.Checkbox +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import cn.super12138.todo.R +import cn.super12138.todo.utils.VibrationUtils + +@Composable +fun TodoMarkAsCompletedCheckbox( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier +) { + val view = LocalView.current + val interactionSource = remember { MutableInteractionSource() } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = modifier + .toggleable( + value = checked, + onValueChange = { + VibrationUtils.performHapticFeedback(view, HapticFeedbackConstants.LONG_PRESS) + onCheckedChange(it) + }, + role = Role.Checkbox, + indication = null, + interactionSource = interactionSource, + ) + ) { + Text( + text = stringResource(R.string.tip_mark_completed), + style = MaterialTheme.typography.labelLarge + ) + + Checkbox( + checked = checked, + onCheckedChange = { + VibrationUtils.performHapticFeedback(view, HapticFeedbackConstants.LONG_PRESS) + onCheckedChange(it) + }, + interactionSource = interactionSource + ) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt index f750e16..f4922f8 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt @@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme @@ -27,7 +27,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import androidx.navigation3.ui.LocalNavAnimatedContentScope import cn.super12138.todo.R import cn.super12138.todo.constants.Constants @@ -103,7 +102,7 @@ fun SharedTransitionScope.TasksPage( ) { LazyColumn( state = listState, - verticalArrangement = Arrangement.spacedBy(5.dp), + verticalArrangement = Arrangement.spacedBy(TodoDefaults.settingsItemPadding), modifier = Modifier.fillMaxSize() ) { item { @@ -120,32 +119,34 @@ fun SharedTransitionScope.TasksPage( ) } } else { - items( + itemsIndexed( items = filteredTodoList, - key = { it.id } - ) { + key = { _, task -> task.id } + ) { index, task -> TodoCard( // id = item.id, - content = it.content, - category = it.category, - completed = it.isCompleted, - priority = Priority.fromFloat(it.priority), - selected = selectedTodoIds.contains(it.id), + content = task.content, + category = task.category, + completed = task.isCompleted, + priority = Priority.fromFloat(task.priority), + selected = selectedTodoIds.contains(task.id), onCardClick = { if (inSelectedMode) { - viewModel.toggleTodoSelection(it) + viewModel.toggleTodoSelection(task) } else { - toTodoEditPage(it) + toTodoEditPage(task) } }, - onCardLongClick = { viewModel.toggleTodoSelection(it) }, + onCardLongClick = { viewModel.toggleTodoSelection(task) }, onChecked = { - viewModel.updateTodo(it.copy(isCompleted = true)) + viewModel.updateTodo(task.copy(isCompleted = true)) viewModel.playConfetti() }, + topRounded = index == 0, + bottomRounded = index == filteredTodoList.size - 1, modifier = Modifier .sharedBounds( - sharedContentState = rememberSharedContentState(key = "${Constants.KEY_TODO_ITEM_TRANSITION}_${it.id}"), + sharedContentState = rememberSharedContentState(key = "${Constants.KEY_TODO_ITEM_TRANSITION}_${task.id}"), animatedVisibilityScope = LocalNavAnimatedContentScope.current ) .animateItem( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoCard.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoCard.kt index 9910509..12d7a15 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoCard.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoCard.kt @@ -1,6 +1,7 @@ package cn.super12138.todo.ui.pages.tasks.components import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateColorAsState import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -17,10 +18,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.material3.Badge -import androidx.compose.material3.BadgedBox -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -28,6 +27,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -42,6 +42,7 @@ import cn.super12138.todo.logic.model.Priority import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.containerColor +import cn.super12138.todo.utils.getPartialRoundedShape @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -55,19 +56,22 @@ fun TodoCard( selected: Boolean, onCardClick: () -> Unit = {}, onCardLongClick: () -> Unit = {}, - onChecked: () -> Unit = {} + onChecked: () -> Unit = {}, + shape: CornerBasedShape = TodoDefaults.SettingsItemDefaultShape, + roundedShape: CornerBasedShape = TodoDefaults.SettingsItemRoundedShape, + topRounded: Boolean = false, + bottomRounded: Boolean = false ) { val view = LocalView.current - Card( - colors = CardDefaults.cardColors(containerColor = TodoDefaults.ContainerColor), - modifier = modifier - .fillMaxWidth() - .height(TodoDefaults.toDoCardHeight) - ) { + // TODO: 滑动删除 + val animatedContainerColor by animateColorAsState(if (selected) MaterialTheme.colorScheme.secondaryContainer else TodoDefaults.ContainerColor) + Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxSize() + modifier = modifier + .fillMaxWidth() + .height(TodoDefaults.toDoCardHeight) + .clip(shape.getPartialRoundedShape(topRounded, bottomRounded, roundedShape)) .combinedClickable( onClick = { VibrationUtils.performHapticFeedback(view) @@ -77,7 +81,8 @@ fun TodoCard( // 因为 combinedClickable 在更新的 Compose 里已经处理好了触感反馈 onLongClick = onCardLongClick ) - .padding(horizontal = 15.dp) + .background(animatedContainerColor) + .padding(horizontal = TodoDefaults.screenHorizontalPadding) ) { AnimatedVisibility( visible = selected, @@ -104,40 +109,42 @@ fun TodoCard( } Column( - verticalArrangement = Arrangement.Center, + verticalArrangement = Arrangement.spacedBy( + space = 5.dp, + alignment = Alignment.CenterVertically + ), modifier = Modifier .weight(1f) .fillMaxSize() ) { - BadgedBox( - badge = { - Badge( - containerColor = priority.containerColor(), - modifier = Modifier.padding(start = 5.dp) - ) { - Text( - text = stringResource(priority.nameRes), - textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, - ) - } - } + Text( + text = content, + style = MaterialTheme.typography.titleLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, + modifier = Modifier.basicMarquee() // TODO: 后续评估性能影响 + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(5.dp) ) { + Badge(containerColor = MaterialTheme.colorScheme.primary) { + Text( + text = category.ifEmpty { stringResource(R.string.tip_default_category) }, + style = MaterialTheme.typography.labelMedium, + textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, + maxLines = 1 + ) + } + Text( - text = content, - style = MaterialTheme.typography.titleLarge, - maxLines = 1, - overflow = TextOverflow.Ellipsis, + text = stringResource(priority.nameRes), + style = MaterialTheme.typography.labelMedium.copy(priority.containerColor()), textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, - modifier = Modifier.basicMarquee() // TODO: 后续评估性能影响 ) } - - Text( - text = category.ifEmpty { stringResource(R.string.tip_default_category) }, - style = MaterialTheme.typography.labelMedium, - textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, - maxLines = 1 - ) } AnimatedVisibility(!selected && !completed) { @@ -172,7 +179,7 @@ fun TodoCard( ) }*/ } - } + } /* diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/theme/Motion.kt b/app/src/main/kotlin/cn/super12138/todo/ui/theme/Motion.kt index cdaf9c4..a8bcc64 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/theme/Motion.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/theme/Motion.kt @@ -9,6 +9,7 @@ import androidx.compose.animation.core.LinearOutSlowInEasing import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally @@ -17,6 +18,8 @@ import androidx.compose.animation.slideOutHorizontally */ private const val ProgressThreshold = 0.35f +private const val DEFAULT_START_SCALE = 0.95f //0.92f +private const val THRESHOLD_ALPHA = 0.5f private val Int.ForOutgoing: Int get() = (this * ProgressThreshold).toInt() @@ -84,4 +87,31 @@ fun materialSharedAxisXOut( delayMillis = 0, easing = FastOutLinearInEasing ) +) + +fun fadeThrough( + durationMillis: Int = 200, +): ContentTransform = ContentTransform( + fadeThroughIn(durationMillis = durationMillis), + fadeThroughOut(durationMillis = durationMillis) +) + +fun fadeThroughIn( + durationMillis: Int = DefaultMotionDuration, +): EnterTransition = + fadeIn( + animationSpec = tween(durationMillis), + initialAlpha = THRESHOLD_ALPHA + ) + scaleIn( + animationSpec = tween(durationMillis), + initialScale = DEFAULT_START_SCALE + ) + +fun fadeThroughOut( + durationMillis: Int = DefaultMotionDuration, +): ExitTransition = fadeOut( + animationSpec = tween( + durationMillis = durationMillis, + easing = FastOutLinearInEasing + ) ) \ No newline at end of file From 7f248802d56eb77ed11acaaee408518672a05645 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:43:29 +0800 Subject: [PATCH 18/21] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../ui/pages/settings/components/SettingsItem.kt | 3 ++- .../kotlin/cn/super12138/todo/utils/TodoExt.kt | 15 ++++++++++++++- .../cn/super12138/todo/utils/VibrationUtils.kt | 11 +++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 18c4764..0c22098 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,7 +38,7 @@ android { applicationId = "cn.super12138.todo" minSdk = 24 targetSdk = 36 - versionCode = 951 + versionCode = 952 versionName = "2.3.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt index c40c851..bf4f4e6 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt @@ -238,7 +238,8 @@ fun SettingsItem( .padding( horizontal = TodoDefaults.settingsItemHorizontalPadding, vertical = TodoDefaults.settingsItemVerticalPadding - ), verticalAlignment = Alignment.CenterVertically + ), + verticalAlignment = Alignment.CenterVertically ) { leadingIcon?.let { it() } diff --git a/app/src/main/kotlin/cn/super12138/todo/utils/TodoExt.kt b/app/src/main/kotlin/cn/super12138/todo/utils/TodoExt.kt index d04dcda..8297445 100644 --- a/app/src/main/kotlin/cn/super12138/todo/utils/TodoExt.kt +++ b/app/src/main/kotlin/cn/super12138/todo/utils/TodoExt.kt @@ -24,6 +24,13 @@ fun Priority.containerColor(): Color = Priority.Urgent -> MaterialTheme.colorScheme.error } +/** + * 获取部分圆角的形状 + * + * @param topRounded 顶部是否圆角 + * @param bottomRounded 底部是否圆角 + * @param roundedShape 所需圆角形状 + */ @Composable fun CornerBasedShape.getPartialRoundedShape( topRounded: Boolean, @@ -37,7 +44,13 @@ fun CornerBasedShape.getPartialRoundedShape( bottomStart = if (bottomRounded) roundedShape.bottomStart else this.bottomStart, ) - +/** + * 绘制渐变边缘遮罩 + * + * @param edgeWidth 渐变边缘宽度 + * @param maskColor 遮罩颜色 + * @param leftEdge 是否在左侧边缘添加遮罩(否即在右侧边缘添加) + */ fun ContentDrawScope.drawFadedEdge( edgeWidth: Dp, maskColor: Color, diff --git a/app/src/main/kotlin/cn/super12138/todo/utils/VibrationUtils.kt b/app/src/main/kotlin/cn/super12138/todo/utils/VibrationUtils.kt index 2c5cb30..f99a9ba 100644 --- a/app/src/main/kotlin/cn/super12138/todo/utils/VibrationUtils.kt +++ b/app/src/main/kotlin/cn/super12138/todo/utils/VibrationUtils.kt @@ -7,10 +7,21 @@ import cn.super12138.todo.constants.Constants object VibrationUtils { private var isEnabled: Boolean = Constants.PREF_HAPTIC_FEEDBACK_DEFAULT + /** + * 启用或禁用触感反馈 + */ fun setEnabled(enabled: Boolean) { isEnabled = enabled } + /** + * **依据触感反馈的启用状态**为用户提供当前视图的触感反馈 + * + * 注:触感反馈 **当且仅当 `isHapticFeedbackEnabled()` 为 `true` 时(须在系统设置中开启)** 才会被出发 + * + * @param view 触发触感反馈的视图 + * @param feedbackConstants 触感反馈类型,默认值为 `HapticFeedbackConstants.CONTEXT_CLICK` + */ fun performHapticFeedback( view: View, feedbackConstants: Int = HapticFeedbackConstants.CONTEXT_CLICK From 0fc2383e6c030f20057170cab988299ee6c1e5dc Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:48:22 +0800 Subject: [PATCH 19/21] =?UTF-8?q?chore:=20=E5=90=8C=E6=AD=A5=E4=B8=BB?= =?UTF-8?q?=E5=88=86=E6=94=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android_ci.yml | 2 +- gradle/libs.versions.toml | 12 ++++++------ gradle/wrapper/gradle-wrapper.properties | 4 ++-- script/pnpm-lock.yaml | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/android_ci.yml b/.github/workflows/android_ci.yml index 03c0cca..29746b8 100644 --- a/.github/workflows/android_ci.yml +++ b/.github/workflows/android_ci.yml @@ -26,7 +26,7 @@ jobs: # 缓存NDK - name: Cache NDK - uses: actions/cache@v5.0.2 + uses: actions/cache@v5.0.3 with: path: | ${{ env.ANDROID_HOME }}/ndk diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8af2ae5..2b9a4e7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,11 +5,11 @@ splashScreen = "1.2.0" lifecycleRuntimeKtx = "2.10.0" datastore = "1.2.0" # Compose -activityCompose = "1.12.2" -composeBom = "2026.01.00" -liveData = "1.10.1" +activityCompose = "1.12.3" +composeBom = "2026.01.01" +liveData = "1.10.2" room = "2.8.4" -material3 = "1.5.0-alpha12" +material3 = "1.5.0-alpha13" adaptive = "1.2.0" nav3Core = "1.0.0" # AboutLibraries @@ -32,8 +32,8 @@ junitVersion = "1.3.0" espressoCore = "3.7.0" # Plugins agp = "9.0.0" -kotlin = "2.3.0" -ksp = "2.3.4" +kotlin = "2.3.10" +ksp = "2.3.5" [libraries] # Android X diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 44ae953..4146564 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=0d585f69da091fc5b2beced877feab55a3064d43b8a1d46aeb07996b0915e0e0 -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip +distributionSha256Sum=b266d5ff6b90eada6dc3b20cb090e3731302e553a27c5d3e4df1f0d76beaff06 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/script/pnpm-lock.yaml b/script/pnpm-lock.yaml index c03ce15..417a347 100644 --- a/script/pnpm-lock.yaml +++ b/script/pnpm-lock.yaml @@ -10,15 +10,15 @@ importers: devDependencies: '@types/node': specifier: ^24.6.2 - version: 24.10.9 + version: 24.10.10 typescript: specifier: ^5.9.3 version: 5.9.3 packages: - '@types/node@24.10.9': - resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==} + '@types/node@24.10.10': + resolution: {integrity: sha512-+0/4J266CBGPUq/ELg7QUHhN25WYjE0wYTPSQJn1xeu8DOlIOPxXxrNGiLmfAWl7HMMgWFWXpt9IDjMWrF5Iow==} typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} @@ -30,7 +30,7 @@ packages: snapshots: - '@types/node@24.10.9': + '@types/node@24.10.10': dependencies: undici-types: 7.16.0 From a487e4b890528a5cf74f6a1802107961198b0eba Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Thu, 5 Feb 2026 23:44:37 +0800 Subject: [PATCH 20/21] =?UTF-8?q?feat:=20=E5=88=86=E7=B1=BB=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=A1=B5=E9=9D=A2=20MD3E=20=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/pages/settings/SettingsAboutLicence.kt | 13 ++- .../ui/pages/settings/SettingsDataCategory.kt | 80 +++++++++++++------ .../pages/settings/components/SettingsItem.kt | 6 +- .../components/category/CategoryItem.kt | 71 ---------------- .../todo/ui/pages/tasks/TasksPage.kt | 6 +- 5 files changed, 73 insertions(+), 103 deletions(-) delete mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt index b79d198..4797528 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt @@ -16,8 +16,10 @@ import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -58,7 +60,12 @@ fun SettingsAboutLicence( key = { _, library -> library.artifactId } ) { index, library -> var openDialog by rememberSaveable { mutableStateOf(false) } - + val topRounded by remember { derivedStateOf { index == 0 } } + val bottomRounded by remember { + derivedStateOf { + index == (libraries?.libraries?.size ?: 1) - 1 + } + } SettingsItem( headlineContent = { Row( @@ -120,8 +127,8 @@ fun SettingsAboutLicence( } } }, - topRounded = index == 0, - bottomRounded = index == (libraries?.libraries?.size ?: 1) - 1 + topRounded = topRounded, + bottomRounded = bottomRounded ) BasicDialog( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt index 162834c..fc6e788 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt @@ -1,15 +1,15 @@ package cn.super12138.todo.ui.pages.settings -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.VisibilityThreshold -import androidx.compose.animation.core.spring -import androidx.compose.animation.core.tween +import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FilledTonalIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -24,24 +24,29 @@ 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.platform.LocalView +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.text.style.TextOverflow import cn.super12138.todo.R import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.ui.components.TodoFloatingActionButton import cn.super12138.todo.ui.components.TopAppBarScaffold -import cn.super12138.todo.ui.pages.settings.components.category.CategoryItem +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.category.CategoryPromptDialog +import cn.super12138.todo.utils.VibrationUtils import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun SettingsDataCategory( onNavigateUp: () -> Unit, modifier: Modifier = Modifier ) { // TODO: 本页及其相关组件重组性能检查优化 + val view = LocalView.current val snackbarHostState = remember { SnackbarHostState() } val scope = rememberCoroutineScope() val listState = rememberLazyListState() @@ -53,6 +58,7 @@ fun SettingsDataCategory( val isExpanded by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } } + // TODO: 取消5字分类限制 TopAppBarScaffold( title = stringResource(R.string.pref_category_category_management), onBack = onNavigateUp, @@ -70,10 +76,7 @@ fun SettingsDataCategory( }, modifier = modifier, ) { - LazyColumn( - state = listState, - modifier = Modifier.fillMaxSize() - ) { + SettingsContainer(Modifier.fillMaxSize()) { if (categories.isEmpty()) { item { Text( @@ -84,23 +87,50 @@ fun SettingsDataCategory( ) } } else { - items(items = categories, key = { it }) { - CategoryItem( - name = it, - onClick = { category -> + // Keep stable content key (category) for animations, but compute rounding based on content + items( + items = categories, + key = { it } + ) { category -> + // compute rounding by content rather than by index so insertion/moves + // correctly update rounded corners while keeping content key for animations + val topRounded = category == categories.first() + val bottomRounded = category == categories.last() + + SettingsItem( + headlineContent = { + Text( + text = category, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.basicMarquee() + ) + }, + trailingContent = { + FilledTonalIconButton( + shapes = IconButtonDefaults.shapes(), + onClick = { + VibrationUtils.performHapticFeedback(view) + scope.launch { DataStoreManager.setCategories(categories - category) } + } + ) { + Icon( + painter = painterResource(R.drawable.ic_delete), + contentDescription = stringResource(R.string.action_delete) + ) + } + }, + onClick = { initialCategory = category showDialog = true }, - onDelete = { category -> - scope.launch { DataStoreManager.setCategories(categories - category) } - }, + topRounded = topRounded, + bottomRounded = bottomRounded, modifier = Modifier.animateItem( - fadeInSpec = tween(100), - placementSpec = spring( - stiffness = Spring.StiffnessMediumLow, - visibilityThreshold = IntOffset.VisibilityThreshold - ), - fadeOutSpec = tween(100) + fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(), + placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(), + fadeOutSpec = MaterialTheme.motionScheme.defaultEffectsSpec() ) ) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt index bf4f4e6..9be0799 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt @@ -230,10 +230,12 @@ fun SettingsItem( .wrapContentHeight() .clip(shape.getPartialRoundedShape(topRounded, bottomRounded, roundedShape)) .clickable( - enabled = enableClick, onClick = { + enabled = enableClick, + onClick = { VibrationUtils.performHapticFeedback(view) onClick() - }) + } + ) .background(background) .padding( horizontal = TodoDefaults.settingsItemHorizontalPadding, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt deleted file mode 100644 index c858d9f..0000000 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt +++ /dev/null @@ -1,71 +0,0 @@ -package cn.super12138.todo.ui.pages.settings.components.category - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -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 - -@Composable -fun CategoryItem( - modifier: Modifier = Modifier, - name: String, - onClick: (String) -> Unit = {}, - onDelete: (String) -> Unit = {} -) { - val view = LocalView.current - Row( - modifier = modifier - .fillMaxWidth() - .wrapContentHeight() - .clip(MaterialTheme.shapes.large) - .clickable( - onClick = { - VibrationUtils.performHapticFeedback(view) - onClick(name) - } - ) - .padding( - horizontal = TodoDefaults.settingsItemHorizontalPadding, - vertical = TodoDefaults.settingsItemVerticalPadding / 2 - ), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = name, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onSurface, - ), - modifier = Modifier.weight(1f) - ) - - IconButton( - onClick = { - VibrationUtils.performHapticFeedback(view) - onDelete(name) - } - ) { - Icon( - painter = painterResource(R.drawable.ic_delete), - contentDescription = stringResource(R.string.action_delete) - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt index f4922f8..83a5b10 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt @@ -123,6 +123,8 @@ fun SharedTransitionScope.TasksPage( items = filteredTodoList, key = { _, task -> task.id } ) { index, task -> + val topRounded by remember { derivedStateOf { index == 0 } } + val bottomRounded by remember { derivedStateOf { index == filteredTodoList.size - 1 } } TodoCard( // id = item.id, content = task.content, @@ -142,8 +144,8 @@ fun SharedTransitionScope.TasksPage( viewModel.updateTodo(task.copy(isCompleted = true)) viewModel.playConfetti() }, - topRounded = index == 0, - bottomRounded = index == filteredTodoList.size - 1, + topRounded = topRounded, + bottomRounded = bottomRounded, modifier = Modifier .sharedBounds( sharedContentState = rememberSharedContentState(key = "${Constants.KEY_TODO_ITEM_TRANSITION}_${task.id}"), From bddc9af2f7d15fc197cbcf4ddede438c6fa04368 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Fri, 6 Feb 2026 09:39:05 +0800 Subject: [PATCH 21/21] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E5=8A=A8=E7=94=BB=E5=92=8C=E9=A1=B5=E9=9D=A2=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../cn/super12138/todo/ui/TodoDefaults.kt | 29 ++- .../todo/ui/pages/settings/SettingsAbout.kt | 6 +- .../ui/pages/settings/SettingsAboutLicence.kt | 20 +- .../ui/pages/settings/SettingsAppearance.kt | 6 +- .../todo/ui/pages/settings/SettingsData.kt | 10 +- .../ui/pages/settings/SettingsDataCategory.kt | 7 - .../settings/SettingsDeveloperOptions.kt | 4 +- .../settings/SettingsInterfaceInteraction.kt | 14 +- .../todo/ui/pages/settings/SettingsMain.kt | 6 +- .../settings/components/RowSettingsItem.kt | 16 +- .../settings/components/SettingsDialogs.kt | 20 +- .../pages/settings/components/SettingsItem.kt | 54 +++-- .../settings/components/SwitchSettingsItem.kt | 7 +- .../appearance/contrast/ContrastPicker.kt | 4 - .../appearance/darkmode/DarkModePicker.kt | 4 - .../appearance/palette/PalettePicker.kt | 4 - .../todo/ui/pages/tasks/TasksPage.kt | 12 +- .../ui/pages/tasks/components/TodoCard.kt | 189 +++++++++--------- .../cn/super12138/todo/ui/theme/Shape.kt | 175 ++++++++++++++++ 20 files changed, 366 insertions(+), 223 deletions(-) create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/theme/Shape.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0c22098..1497ff9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,7 +38,7 @@ android { applicationId = "cn.super12138.todo" minSdk = 24 targetSdk = 36 - versionCode = 952 + versionCode = 955 versionName = "2.3.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt index 7a3de92..ad31b96 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/TodoDefaults.kt @@ -1,6 +1,9 @@ package cn.super12138.todo.ui +import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.foundation.shape.CornerBasedShape +import androidx.compose.material3.ButtonShapes +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color @@ -33,7 +36,7 @@ object TodoDefaults { */ val settingsItemVerticalPadding = 16.dp - val settingsItemPadding = 2.dp + val settingsItemPadding = 4.dp /** * 待办进度条粗度 @@ -70,13 +73,27 @@ object TodoDefaults { @Composable get() = MaterialTheme.colorScheme.surfaceContainer - val SettingsItemDefaultShape: CornerBasedShape - @Composable - get() = MaterialTheme.shapes.small - val SettingsItemRoundedShape: CornerBasedShape + val fadedEdgeWidth = 8.dp + + val defaultShape: CornerBasedShape @Composable get() = MaterialTheme.shapes.large - val fadedEdgeWidth = 8.dp + val pressedShape: CornerBasedShape + @Composable + get() = MaterialTheme.shapes.small + + + @OptIn(ExperimentalMaterial3ExpressiveApi::class) + @Composable + fun shapes() = ButtonShapes( + shape = defaultShape, + pressedShape = pressedShape + ) + + @OptIn(ExperimentalMaterial3ExpressiveApi::class) + val shapesDefaultAnimationSpec: FiniteAnimationSpec + @Composable + get() = MaterialTheme.motionScheme.defaultEffectsSpec() } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt index b090140..7b63d23 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt @@ -71,8 +71,7 @@ fun SettingsAbout( } clickCount = 0 } - }, - topRounded = true + } ) } item { @@ -104,8 +103,7 @@ fun SettingsAbout( 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 + onClick = toDevPage ) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt index 4797528..5fa612f 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt @@ -5,7 +5,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll @@ -16,10 +16,8 @@ import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -55,17 +53,11 @@ fun SettingsAboutLicence( modifier = modifier ) { SettingsContainer(Modifier.fillMaxSize()) { - itemsIndexed( + items( items = libraries?.libraries ?: listOf(), - key = { _, library -> library.artifactId } - ) { index, library -> + key = { it.artifactId } + ) { library -> var openDialog by rememberSaveable { mutableStateOf(false) } - val topRounded by remember { derivedStateOf { index == 0 } } - val bottomRounded by remember { - derivedStateOf { - index == (libraries?.libraries?.size ?: 1) - 1 - } - } SettingsItem( headlineContent = { Row( @@ -126,9 +118,7 @@ fun SettingsAboutLicence( } } } - }, - topRounded = topRounded, - bottomRounded = bottomRounded + } ) BasicDialog( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt index 7eac076..b451fef 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt @@ -46,8 +46,7 @@ fun SettingsAppearance( 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 + onCheckedChange = { scope.launch { DataStoreManager.setDynamicColor(it) } } ) } @@ -71,8 +70,7 @@ fun SettingsAppearance( item(key = 4) { ContrastPicker( currentContrast = ContrastLevel.fromFloat(contrastLevel), - onContrastChange = { scope.launch { DataStoreManager.setContrastLevel(it.value) } }, - bottomRounded = true + onContrastChange = { scope.launch { DataStoreManager.setContrastLevel(it.value) } } ) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt index c42671d..f7eb820 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt @@ -113,8 +113,7 @@ fun SettingsData( description = stringResource(R.string.pref_backup_desc), onClick = { backupLauncher.launch("Todo-backup-${SystemUtils.getTime()}.zip") - }, - topRounded = true + } ) } item { @@ -124,8 +123,7 @@ fun SettingsData( description = stringResource(R.string.pref_restore_desc), onClick = { restoreLauncher.launch(arrayOf("application/zip")) - }, - bottomRounded = true + } ) } @@ -135,9 +133,7 @@ fun SettingsData( 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 + onClick = toCategoryManager ) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt index fc6e788..197b649 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt @@ -92,11 +92,6 @@ fun SettingsDataCategory( items = categories, key = { it } ) { category -> - // compute rounding by content rather than by index so insertion/moves - // correctly update rounded corners while keeping content key for animations - val topRounded = category == categories.first() - val bottomRounded = category == categories.last() - SettingsItem( headlineContent = { Text( @@ -125,8 +120,6 @@ fun SettingsDataCategory( initialCategory = category showDialog = true }, - topRounded = topRounded, - bottomRounded = bottomRounded, modifier = Modifier.animateItem( fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(), placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(), diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt index f57ff34..e7cedce 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDeveloperOptions.kt @@ -27,9 +27,7 @@ fun SettingsDeveloperOptions( SettingsItem( leadingIconRes = R.drawable.ic_padding, title = stringResource(R.string.pref_padding), - onClick = toPaddingPage, - topRounded = true, - bottomRounded = true + onClick = toPaddingPage ) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt index afbd75b..f7ab29c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt @@ -11,7 +11,6 @@ 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.platform.LocalContext import androidx.compose.ui.res.stringResource import cn.super12138.todo.R import cn.super12138.todo.constants.Constants @@ -38,7 +37,6 @@ fun SettingsInterface( val sortingMethod by DataStoreManager.sortingMethodFlow.collectAsState(initial = Constants.PREF_SORTING_METHOD_DEFAULT) val hapticFeedback by DataStoreManager.hapticFeedbackFlow.collectAsState(initial = Constants.PREF_HAPTIC_FEEDBACK_DEFAULT) - val context = LocalContext.current val scope = rememberCoroutineScope() var showSortingMethodDialog by rememberSaveable { mutableStateOf(false) } TopAppBarScaffold( @@ -57,8 +55,7 @@ fun SettingsInterface( 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 + onCheckedChange = { scope.launch { DataStoreManager.setShowCompleted(it) } } ) } item { @@ -66,8 +63,7 @@ fun SettingsInterface( leadingIconRes = R.drawable.ic_sort, title = stringResource(R.string.pref_sorting_method), description = stringResource(SortingMethod.fromId(sortingMethod).nameRes), - onClick = { showSortingMethodDialog = true }, - bottomRounded = true + onClick = { showSortingMethodDialog = true } ) } @@ -78,8 +74,7 @@ fun SettingsInterface( 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 + onCheckedChange = { scope.launch { DataStoreManager.setSecureMode(it) } } ) } @@ -89,8 +84,7 @@ fun SettingsInterface( 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 + onCheckedChange = { scope.launch { DataStoreManager.setHapticFeedback(it) } } ) SettingsPlainBox(stringResource(R.string.pref_haptic_feedback_more_info)) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt index 1ee7715..c513d26 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt @@ -30,8 +30,7 @@ fun SettingsMain( leadingIconRes = R.drawable.ic_palette, title = stringResource(R.string.pref_appearance), description = stringResource(R.string.pref_appearance_desc), - onClick = toAppearancePage, - topRounded = true + onClick = toAppearancePage ) } @@ -58,8 +57,7 @@ fun SettingsMain( leadingIconRes = R.drawable.ic_info, title = stringResource(R.string.pref_about), description = stringResource(R.string.pref_about_desc), - onClick = toAboutPage, - bottomRounded = true + onClick = toAboutPage ) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/RowSettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/RowSettingsItem.kt index ff1c36e..8121e8c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/RowSettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/RowSettingsItem.kt @@ -29,7 +29,6 @@ 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( @@ -44,8 +43,6 @@ fun RowSettingsItem( scrollState: ScrollState = rememberScrollState(), fadedEdgeWidth: Dp, maskColor: Color = TodoDefaults.ContainerColor, - topRounded: Boolean = false, - bottomRounded: Boolean = false, content: @Composable RowScope.() -> Unit ) { MoreContentSettingsItem( @@ -54,8 +51,6 @@ fun RowSettingsItem( description = description, trailingContent = trailingContent, background = background, - topRounded = topRounded, - bottomRounded = bottomRounded, modifier = modifier ) { Row( @@ -98,8 +93,6 @@ fun LazyRowSettingsItem( verticalAlignment: Alignment.Vertical = Alignment.Top, fadedEdgeWidth: Dp, maskColor: Color = TodoDefaults.ContainerColor, - topRounded: Boolean = false, - bottomRounded: Boolean = false, content: LazyListScope.() -> Unit ) { MoreContentSettingsItem( @@ -107,8 +100,6 @@ fun LazyRowSettingsItem( title = title, description = description, trailingContent = trailingContent, - topRounded = topRounded, - bottomRounded = bottomRounded, background = background, modifier = modifier ) { @@ -147,17 +138,14 @@ fun MoreContentSettingsItem( description: String? = null, trailingContent: (@Composable () -> Unit)? = null, background: Color = TodoDefaults.ContainerColor, - shape: CornerBasedShape = TodoDefaults.SettingsItemDefaultShape, - roundedShape: CornerBasedShape = TodoDefaults.SettingsItemRoundedShape, - topRounded: Boolean = false, - bottomRounded: Boolean = false, + shape: CornerBasedShape = TodoDefaults.defaultShape, content: @Composable () -> Unit ) { Column( modifier = modifier .fillMaxWidth() .wrapContentHeight() - .clip(shape.getPartialRoundedShape(topRounded, bottomRounded, roundedShape)) + .clip(shape) .background(background) .padding( horizontal = TodoDefaults.settingsItemHorizontalPadding, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt index 0e1b4a1..82f3363 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt @@ -1,5 +1,7 @@ package cn.super12138.todo.ui.pages.settings.components +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -10,10 +12,13 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectableGroup import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -22,6 +27,7 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.components.BasicDialog +import cn.super12138.todo.ui.theme.shapeByInteraction import cn.super12138.todo.utils.VibrationUtils @Composable @@ -42,7 +48,6 @@ fun SettingsRadioDialog( Column( verticalArrangement = Arrangement.spacedBy(2.dp), modifier = Modifier - .clip(TodoDefaults.SettingsItemRoundedShape) .selectableGroup() .verticalScroll(rememberScrollState()) ) { @@ -63,6 +68,7 @@ fun SettingsRadioDialog( ) } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun RadioItem( selected: Boolean, @@ -71,13 +77,23 @@ fun RadioItem( modifier: Modifier = Modifier ) { val view = LocalView.current + val interactionSource = remember { MutableInteractionSource() } + val pressed by interactionSource.collectIsPressedAsState() + Row( modifier .fillMaxWidth() .height(56.dp) - .clip(TodoDefaults.SettingsItemDefaultShape) + .clip( + shapeByInteraction( + shapes = TodoDefaults.shapes(), + pressed = pressed, + animationSpec = TodoDefaults.shapesDefaultAnimationSpec + ) + ) // .background(MaterialTheme.colorScheme.surfaceContainerHighest) .selectable( + interactionSource = interactionSource, selected = selected, onClick = { VibrationUtils.performHapticFeedback(view) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt index 9be0799..7087695 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt @@ -3,17 +3,21 @@ package cn.super12138.todo.ui.pages.settings.components import androidx.annotation.DrawableRes import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.shape.CornerBasedShape +import androidx.compose.material3.ButtonShapes import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -25,18 +29,17 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.sp import cn.super12138.todo.ui.TodoDefaults +import cn.super12138.todo.ui.theme.shapeByInteraction import cn.super12138.todo.utils.VibrationUtils -import cn.super12138.todo.utils.getPartialRoundedShape // Leading icon as drawable resource +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SettingsItem( modifier: Modifier = Modifier, @DrawableRes leadingIconRes: Int, title: String, description: String? = null, - topRounded: Boolean = false, - bottomRounded: Boolean = false, enableClick: Boolean = true, onClick: () -> Unit = {} ) = SettingsItem( @@ -44,8 +47,6 @@ fun SettingsItem( title = title, description = description, trailingContent = null, - topRounded = topRounded, - bottomRounded = bottomRounded, enableClick = enableClick, onClick = onClick, modifier = modifier @@ -61,8 +62,7 @@ fun SettingsItem( description: String? = null, trailingContent: (@Composable () -> Unit)? = null, background: Color = TodoDefaults.ContainerColor, - topRounded: Boolean = false, - bottomRounded: Boolean = false, + shapes: ButtonShapes = TodoDefaults.shapes(), enableClick: Boolean = true, onClick: () -> Unit = {} ) = SettingsItem( @@ -91,22 +91,20 @@ fun SettingsItem( description = description, trailingContent = trailingContent, background = background, - topRounded = topRounded, - bottomRounded = bottomRounded, + shapes = shapes, enableClick = enableClick, onClick = onClick, modifier = modifier ) // Leading icon as ImageVector +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SettingsItem( modifier: Modifier = Modifier, leadingIcon: ImageVector? = null, title: String, description: String? = null, - topRounded: Boolean = false, - bottomRounded: Boolean = false, enableClick: Boolean = true, onClick: () -> Unit = {} ) = SettingsItem( @@ -114,13 +112,12 @@ fun SettingsItem( title = title, description = description, trailingContent = null, - topRounded = topRounded, - bottomRounded = bottomRounded, enableClick = enableClick, onClick = onClick, modifier = modifier ) +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SettingsItem( modifier: Modifier = Modifier, @@ -129,8 +126,7 @@ fun SettingsItem( description: String? = null, trailingContent: (@Composable () -> Unit)? = null, background: Color = TodoDefaults.ContainerColor, - topRounded: Boolean = false, - bottomRounded: Boolean = false, + shapes: ButtonShapes = TodoDefaults.shapes(), enableClick: Boolean = true, onClick: () -> Unit = {} ) = SettingsItem( @@ -148,9 +144,8 @@ fun SettingsItem( description = description, trailingContent = trailingContent, background = background, - topRounded = topRounded, - bottomRounded = bottomRounded, enableClick = enableClick, + shapes = shapes, onClick = onClick, modifier = modifier ) @@ -165,10 +160,7 @@ fun SettingsItem( description: String? = null, trailingContent: (@Composable () -> Unit)? = null, background: Color = TodoDefaults.ContainerColor, - shape: CornerBasedShape = TodoDefaults.SettingsItemDefaultShape, - roundedShape: CornerBasedShape = TodoDefaults.SettingsItemRoundedShape, - topRounded: Boolean = false, - bottomRounded: Boolean = false, + shapes: ButtonShapes = TodoDefaults.shapes(), enableClick: Boolean = true, onClick: () -> Unit = {}, ) = SettingsItem( @@ -199,10 +191,7 @@ fun SettingsItem( }, trailingContent = trailingContent, background = background, - shape = shape, - roundedShape = roundedShape, - topRounded = topRounded, - bottomRounded = bottomRounded, + shapes = shapes, enableClick = enableClick, onClick = onClick ) @@ -216,20 +205,23 @@ fun SettingsItem( supportingContent: (@Composable () -> Unit)? = null, trailingContent: (@Composable () -> Unit)? = null, background: Color = TodoDefaults.ContainerColor, - shape: CornerBasedShape = TodoDefaults.SettingsItemDefaultShape, - roundedShape: CornerBasedShape = TodoDefaults.SettingsItemRoundedShape, - topRounded: Boolean = false, - bottomRounded: Boolean = false, + shapes: ButtonShapes = TodoDefaults.shapes(), + interactionSource: MutableInteractionSource? = null, enableClick: Boolean = true, onClick: () -> Unit = {}, ) { val view = LocalView.current + val userInteractionSource = interactionSource ?: remember { MutableInteractionSource() } + val pressed by userInteractionSource.collectIsPressedAsState() + val animatedShape = shapeByInteraction(shapes, pressed, TodoDefaults.shapesDefaultAnimationSpec) + Row( modifier = modifier .fillMaxWidth() .wrapContentHeight() - .clip(shape.getPartialRoundedShape(topRounded, bottomRounded, roundedShape)) + .clip(animatedShape) .clickable( + interactionSource = userInteractionSource, enabled = enableClick, onClick = { VibrationUtils.performHapticFeedback(view) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt index 3ab8e77..f81ca80 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt @@ -2,6 +2,7 @@ package cn.super12138.todo.ui.pages.settings.components import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Switch import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -11,14 +12,13 @@ import androidx.compose.ui.res.painterResource import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.utils.VibrationUtils +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SwitchSettingsItem( modifier: Modifier = Modifier, @DrawableRes leadingIconRes: Int, title: String, description: String? = null, - topRounded: Boolean = false, - bottomRounded: Boolean = false, checked: Boolean, onCheckedChange: (Boolean) -> Unit ) { @@ -34,12 +34,11 @@ fun SwitchSettingsItem( ) }, onClick = { onCheckedChange(!checked) }, - topRounded = topRounded, - bottomRounded = bottomRounded, modifier = modifier, ) } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SwitchSettingsItem( modifier: Modifier = Modifier, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/contrast/ContrastPicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/contrast/ContrastPicker.kt index 2edea55..95a1496 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/contrast/ContrastPicker.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/contrast/ContrastPicker.kt @@ -38,16 +38,12 @@ fun ContrastPicker( modifier: Modifier = Modifier, currentContrast: ContrastLevel, onContrastChange: (ContrastLevel) -> Unit, - 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 = ContrastLevel.entries.map { stringResource(it.nameRes) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt index 1d5f177..9f6ceac 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/darkmode/DarkModePicker.kt @@ -21,8 +21,6 @@ fun DarkModePicker( modifier: Modifier = Modifier, currentDarkMode: () -> DarkMode, onDarkModeChange: (darkMode: DarkMode) -> Unit, - topRounded: Boolean = false, - bottomRounded: Boolean = false ) { val isInDarkTheme = isSystemInDarkTheme() @@ -33,8 +31,6 @@ fun DarkModePicker( description = stringResource(R.string.pref_dark_mode_desc), horizontalArrangement = Arrangement.spacedBy(5.dp), fadedEdgeWidth = TodoDefaults.fadedEdgeWidth, - topRounded = topRounded, - bottomRounded = bottomRounded, modifier = modifier ) { items(items = darkModeList, key = { it.id }) { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt index d0dc5ec..ac4997e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/appearance/palette/PalettePicker.kt @@ -25,8 +25,6 @@ fun PalettePicker( isDynamicColor: Boolean, isDarkMode: DarkMode, contrastLevel: ContrastLevel, - topRounded: Boolean = false, - bottomRounded: Boolean = false ) { val paletteOptions = remember { PaletteStyle.entries.toList() } @@ -35,8 +33,6 @@ fun PalettePicker( description = stringResource(R.string.pref_palette_style_desc), horizontalArrangement = Arrangement.spacedBy(5.dp), fadedEdgeWidth = TodoDefaults.fadedEdgeWidth, - topRounded = topRounded, - bottomRounded = bottomRounded, modifier = modifier ) { items(items = paletteOptions, key = { it.id }) { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt index 83a5b10..7443bff 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/TasksPage.kt @@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme @@ -119,12 +119,10 @@ fun SharedTransitionScope.TasksPage( ) } } else { - itemsIndexed( + items( items = filteredTodoList, - key = { _, task -> task.id } - ) { index, task -> - val topRounded by remember { derivedStateOf { index == 0 } } - val bottomRounded by remember { derivedStateOf { index == filteredTodoList.size - 1 } } + key = { it.id } + ) { task -> TodoCard( // id = item.id, content = task.content, @@ -144,8 +142,6 @@ fun SharedTransitionScope.TasksPage( viewModel.updateTodo(task.copy(isCompleted = true)) viewModel.playConfetti() }, - topRounded = topRounded, - bottomRounded = bottomRounded, modifier = Modifier .sharedBounds( sharedContentState = rememberSharedContentState(key = "${Constants.KEY_TODO_ITEM_TRANSITION}_${task.id}"), diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoCard.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoCard.kt index 12d7a15..5aaa0d4 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoCard.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/tasks/components/TodoCard.kt @@ -9,6 +9,8 @@ import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.background import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -18,8 +20,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.material3.Badge +import androidx.compose.material3.ButtonShapes import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -28,6 +30,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -40,9 +43,9 @@ import androidx.compose.ui.unit.dp import cn.super12138.todo.R import cn.super12138.todo.logic.model.Priority import cn.super12138.todo.ui.TodoDefaults +import cn.super12138.todo.ui.theme.shapeByInteraction import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.containerColor -import cn.super12138.todo.utils.getPartialRoundedShape @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -57,111 +60,115 @@ fun TodoCard( onCardClick: () -> Unit = {}, onCardLongClick: () -> Unit = {}, onChecked: () -> Unit = {}, - shape: CornerBasedShape = TodoDefaults.SettingsItemDefaultShape, - roundedShape: CornerBasedShape = TodoDefaults.SettingsItemRoundedShape, - topRounded: Boolean = false, - bottomRounded: Boolean = false + shapes: ButtonShapes = TodoDefaults.shapes(), ) { val view = LocalView.current // TODO: 滑动删除 val animatedContainerColor by animateColorAsState(if (selected) MaterialTheme.colorScheme.secondaryContainer else TodoDefaults.ContainerColor) - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = modifier - .fillMaxWidth() - .height(TodoDefaults.toDoCardHeight) - .clip(shape.getPartialRoundedShape(topRounded, bottomRounded, roundedShape)) - .combinedClickable( - onClick = { - VibrationUtils.performHapticFeedback(view) - onCardClick() - }, - // 不再需要使用:VibrationUtils.performHapticFeedback(view, HapticFeedbackConstants.LONG_PRESS) - // 因为 combinedClickable 在更新的 Compose 里已经处理好了触感反馈 - onLongClick = onCardLongClick - ) - .background(animatedContainerColor) - .padding(horizontal = TodoDefaults.screenHorizontalPadding) + val interactionSource = remember { MutableInteractionSource() } + val pressed by interactionSource.collectIsPressedAsState() + val animatedShape = shapeByInteraction( + shapes = shapes, + pressed = if (selected) true else pressed, + animationSpec = TodoDefaults.shapesDefaultAnimationSpec + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .fillMaxWidth() + .height(TodoDefaults.toDoCardHeight) + .clip(animatedShape) + .combinedClickable( + interactionSource = interactionSource, + onClick = { + VibrationUtils.performHapticFeedback(view) + onCardClick() + }, + // 不再需要使用:VibrationUtils.performHapticFeedback(view, HapticFeedbackConstants.LONG_PRESS) + // 因为 combinedClickable 在更新的 Compose 里已经处理好了触感反馈 + onLongClick = onCardLongClick + ) + .background(animatedContainerColor) + .padding(horizontal = TodoDefaults.screenHorizontalPadding) + ) { + AnimatedVisibility( + visible = selected, + enter = fadeIn(MaterialTheme.motionScheme.fastSpatialSpec()) + expandHorizontally( + MaterialTheme.motionScheme.fastSpatialSpec() + ), + exit = fadeOut(MaterialTheme.motionScheme.fastSpatialSpec()) + shrinkHorizontally( + MaterialTheme.motionScheme.fastSpatialSpec() + ) ) { - AnimatedVisibility( - visible = selected, - enter = fadeIn(MaterialTheme.motionScheme.fastSpatialSpec()) + expandHorizontally( - MaterialTheme.motionScheme.fastSpatialSpec() - ), - exit = fadeOut(MaterialTheme.motionScheme.fastSpatialSpec()) + shrinkHorizontally( - MaterialTheme.motionScheme.fastSpatialSpec() - ) + Box( + Modifier + .padding(end = 15.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.secondary) + .padding(5.dp) ) { - Box( - Modifier - .padding(end = 15.dp) - .clip(CircleShape) - .background(MaterialTheme.colorScheme.secondary) - .padding(5.dp) - ) { - Icon( - painter = painterResource(R.drawable.ic_check), - tint = contentColorFor(MaterialTheme.colorScheme.secondary), - contentDescription = stringResource(R.string.tip_selected) - ) - } + Icon( + painter = painterResource(R.drawable.ic_check), + tint = contentColorFor(MaterialTheme.colorScheme.secondary), + contentDescription = stringResource(R.string.tip_selected) + ) } + } - Column( - verticalArrangement = Arrangement.spacedBy( - space = 5.dp, - alignment = Alignment.CenterVertically - ), - modifier = Modifier - .weight(1f) - .fillMaxSize() + Column( + verticalArrangement = Arrangement.spacedBy( + space = 5.dp, + alignment = Alignment.CenterVertically + ), + modifier = Modifier + .weight(1f) + .fillMaxSize() + ) { + Text( + text = content, + style = MaterialTheme.typography.titleLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, + modifier = Modifier.basicMarquee() // TODO: 后续评估性能影响 + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(5.dp) ) { - Text( - text = content, - style = MaterialTheme.typography.titleLarge, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, - modifier = Modifier.basicMarquee() // TODO: 后续评估性能影响 - ) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(5.dp) - ) { - Badge(containerColor = MaterialTheme.colorScheme.primary) { - Text( - text = category.ifEmpty { stringResource(R.string.tip_default_category) }, - style = MaterialTheme.typography.labelMedium, - textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, - maxLines = 1 - ) - } - + Badge(containerColor = MaterialTheme.colorScheme.primary) { Text( - text = stringResource(priority.nameRes), - style = MaterialTheme.typography.labelMedium.copy(priority.containerColor()), + text = category.ifEmpty { stringResource(R.string.tip_default_category) }, + style = MaterialTheme.typography.labelMedium, textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, + maxLines = 1 ) } - } - AnimatedVisibility(!selected && !completed) { - IconButton( - onClick = { - VibrationUtils.performHapticFeedback(view) - onChecked() - } - ) { - Icon( - painter = painterResource(R.drawable.ic_check), - tint = MaterialTheme.colorScheme.primary, - contentDescription = stringResource(R.string.tip_mark_completed) - ) + Text( + text = stringResource(priority.nameRes), + style = MaterialTheme.typography.labelMedium.copy(priority.containerColor()), + textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, + ) + } + } + + AnimatedVisibility(!selected && !completed) { + IconButton( + onClick = { + VibrationUtils.performHapticFeedback(view) + onChecked() } + ) { + Icon( + painter = painterResource(R.drawable.ic_check), + tint = MaterialTheme.colorScheme.primary, + contentDescription = stringResource(R.string.tip_mark_completed) + ) } - /*Box( contentAlignment = Alignment.Center, modifier = Modifier @@ -179,7 +186,7 @@ fun TodoCard( ) }*/ } - + } } /* diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/theme/Shape.kt b/app/src/main/kotlin/cn/super12138/todo/ui/theme/Shape.kt new file mode 100644 index 0000000..a55cc50 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/theme/Shape.kt @@ -0,0 +1,175 @@ +package cn.super12138.todo.ui.theme + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.foundation.shape.CornerBasedShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ButtonShapes +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +internal val ButtonShapes.hasRoundedCornerShapes: Boolean + get() = shape is RoundedCornerShape && pressedShape is RoundedCornerShape + +/** + * 来自 Compose 内部类 + * + * 根据交互状态返回对应的形状 + * + * * 如果 *原形状* 和 *按下形状* **均为圆角形状**则在形状变换时使用动画过渡 + * + * @param shapes 按钮形状(需包含原形状和按下形状) + * @param pressed 是否处于按下状态 + * @param animationSpec 动画曲线 + */ +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun shapeByInteraction( + shapes: ButtonShapes, + pressed: Boolean, + animationSpec: FiniteAnimationSpec, +): Shape { + val shape = + if (pressed) { + shapes.pressedShape + } else { + shapes.shape + } + + ButtonDefaults.shapes() + if (shapes.hasRoundedCornerShapes) + return key(shapes) { rememberAnimatedShape(shape as RoundedCornerShape, animationSpec) } + + return shape +} + +@Stable +internal class AnimatedShapeState( + val shape: RoundedCornerShape, + val spec: FiniteAnimationSpec, +) { + var size: Size = Size.Zero + var density: Density = Density(0f, 0f) + + private var topStart: Animatable? = null + + private var topEnd: Animatable? = null + + private var bottomStart: Animatable? = null + + private var bottomEnd: Animatable? = null + + fun topStart(size: Size = this.size, density: Density = this.density): Float { + return (topStart ?: Animatable(shape.topStart.toPx(size, density)).also { topStart = it }) + .value + } + + fun topEnd(size: Size = this.size, density: Density = this.density): Float { + return (topEnd ?: Animatable(shape.topEnd.toPx(size, density)).also { topEnd = it }).value + } + + fun bottomStart(size: Size = this.size, density: Density = this.density): Float { + return (bottomStart + ?: Animatable(shape.bottomStart.toPx(size, density)).also { bottomStart = it }) + .value + } + + fun bottomEnd(size: Size = this.size, density: Density = this.density): Float { + return (bottomEnd + ?: Animatable(shape.bottomEnd.toPx(size, density)).also { bottomEnd = it }) + .value + } + + suspend fun animateToShape(shape: CornerBasedShape) = coroutineScope { + launch { topStart?.animateTo(shape.topStart.toPx(size, density), spec) } + launch { topEnd?.animateTo(shape.topEnd.toPx(size, density), spec) } + launch { bottomStart?.animateTo(shape.bottomStart.toPx(size, density), spec) } + launch { bottomEnd?.animateTo(shape.bottomEnd.toPx(size, density), spec) } + } +} + +@Composable +private fun rememberAnimatedShape(state: AnimatedShapeState): Shape { + val density = LocalDensity.current + state.density = density + + return remember(density, state) { + object : ShapeWithHorizontalCenterOptically { + var clampedRange by mutableStateOf(0f..1f) + + override fun offset(): Float { + val topStart = state.topStart().coerceIn(clampedRange) + val topEnd = state.topEnd().coerceIn(clampedRange) + val bottomStart = state.bottomStart().coerceIn(clampedRange) + val bottomEnd = state.bottomEnd().coerceIn(clampedRange) + val avgStart = (topStart + bottomStart) / 2 + val avgEnd = (topEnd + bottomEnd) / 2 + return CenterOpticallyCoefficient * (avgStart - avgEnd) + } + + override fun createOutline( + size: Size, + layoutDirection: LayoutDirection, + density: Density, + ): Outline { + state.size = size + + clampedRange = 0f..size.height / 2 + return RoundedCornerShape( + topStart = state.topStart().coerceIn(clampedRange), + topEnd = state.topEnd().coerceIn(clampedRange), + bottomStart = state.bottomStart().coerceIn(clampedRange), + bottomEnd = state.bottomEnd().coerceIn(clampedRange), + ) + .createOutline(size, layoutDirection, density) + } + } + } +} + +@Composable +internal fun rememberAnimatedShape( + currentShape: RoundedCornerShape, + animationSpec: FiniteAnimationSpec, +): Shape { + val state = + remember(animationSpec) { AnimatedShapeState(shape = currentShape, spec = animationSpec) } + + val channel = remember { Channel(Channel.CONFLATED) } + + SideEffect { channel.trySend(currentShape) } + LaunchedEffect(state, channel) { + for (target in channel) { + val newTarget = channel.tryReceive().getOrNull() ?: target + launch { state.animateToShape(newTarget) } + } + } + + return rememberAnimatedShape(state) +} + +internal interface ShapeWithHorizontalCenterOptically : Shape { + fun offset(): Float +} + +internal const val CenterOpticallyCoefficient = 0.11f