From 08d7706b1545eb19a78496435105ef1d4a6d15cc Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Fri, 17 Jan 2025 14:39:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E4=BC=98=E5=85=88=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo/logic/database/TodoEntity.kt | 1 + .../todo/ui/components/ChipGroup.kt | 2 +- .../ui/components/FloatingActionButton.kt | 8 +- .../todo/ui/components/Scaffolds.kt | 6 +- .../todo/ui/navigation/TodoNavigation.kt | 3 +- .../todo/ui/pages/editor/TodoEditorPage.kt | 147 +++++++++++++----- .../super12138/todo/ui/pages/main/MainPage.kt | 2 + .../todo/ui/pages/main/ManagerFragment.kt | 3 +- .../todo/ui/pages/main/components/TodoCard.kt | 82 +++++++++- app/src/main/res/values-zh-rCN/strings.xml | 6 + app/src/main/res/values/strings.xml | 6 + 11 files changed, 213 insertions(+), 53 deletions(-) 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 6dc8b2d..90957b1 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 @@ -9,5 +9,6 @@ data class TodoEntity( @ColumnInfo(name = "content") val content: String, @ColumnInfo(name = "subject") val subject: Int, @ColumnInfo(name = "completed") val isCompleted: Boolean = false, + @ColumnInfo(name = "priority") val priority: Float, @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") val id: Int = 0, ) 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 e4ff340..b6f25ad 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 @@ -56,7 +56,7 @@ fun FilterChipGroup( label = { Text(item) }, - modifier = Modifier.padding(horizontal = 5.dp) + modifier = Modifier.padding(end = 10.dp) ) } } 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 b245f96..1102f2a 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 @@ -8,9 +8,11 @@ import androidx.compose.foundation.layout.width import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.FloatingActionButtonElevation +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.unit.dp /** @@ -28,14 +30,18 @@ import androidx.compose.ui.unit.dp fun AnimatedExtendedFloatingActionButton( icon: @Composable () -> Unit, text: @Composable () -> Unit, - elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), expanded: Boolean, + containerColor: Color = FloatingActionButtonDefaults.containerColor, + contentColor: Color = contentColorFor(containerColor), + elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), onClick: () -> Unit, modifier: Modifier = Modifier ) { FloatingActionButton( onClick = onClick, elevation = elevation, + containerColor = containerColor, + contentColor = contentColor, modifier = modifier ) { Row( 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 06741f6..7f9ef93 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,6 +4,8 @@ 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.exclude +import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.safeContent import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack @@ -41,7 +43,7 @@ fun LargeTopAppBarScaffold( snackbarHost: @Composable () -> Unit = {}, floatingActionButton: @Composable () -> Unit = {}, floatingActionButtonPosition: FabPosition = FabPosition.End, - contentWindowInsets: WindowInsets = WindowInsets.safeContent, + contentWindowInsets: WindowInsets = WindowInsets.safeContent.exclude(WindowInsets.ime), modifier: Modifier = Modifier, content: @Composable (PaddingValues) -> Unit ) { @@ -62,6 +64,8 @@ fun LargeTopAppBarScaffold( } }, scrollBehavior = scrollBehavior, + floatingActionButton = floatingActionButton, + floatingActionButtonPosition = floatingActionButtonPosition, contentWindowInsets = contentWindowInsets ) { innerPadding -> Box { content(innerPadding) } 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 afff978..083e0cb 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 @@ -62,6 +62,7 @@ fun TodoNavigation( toDo = viewModel.selectedEditTodo, onSave = { viewModel.addTodo(it) + viewModel.setEditTodoItem(null) navController.navigateUp() }, onDelete = { @@ -72,8 +73,8 @@ fun TodoNavigation( navController.navigateUp() }, onNavigateUp = { - viewModel.setEditTodoItem(null) navController.navigateUp() + viewModel.setEditTodoItem(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 6adb795..ff7cc4b 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 @@ -1,6 +1,8 @@ package cn.super12138.todo.ui.pages.editor +import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -9,17 +11,26 @@ 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.layout.sizeIn +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.ButtonColors +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.Save import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon +import androidx.compose.material3.Label import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.PlainTooltip +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -28,13 +39,17 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import cn.super12138.todo.R import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.model.Subjects import cn.super12138.todo.ui.TodoDefaults +import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton import cn.super12138.todo.ui.components.FilterChipGroup import cn.super12138.todo.ui.components.LargeTopAppBarScaffold +import cn.super12138.todo.ui.pages.main.components.getPriorityString @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -45,11 +60,65 @@ fun TodoEditorPage( onNavigateUp: () -> Unit, modifier: Modifier = Modifier ) { + BackHandler { + onNavigateUp() + } val context = LocalContext.current val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + + var text by rememberSaveable { mutableStateOf(toDo?.content ?: "") } + var isError by rememberSaveable { mutableStateOf(false) } + var selectedItemIndex by rememberSaveable { mutableIntStateOf(toDo?.subject ?: 0) } + var sliderPosition by rememberSaveable { mutableFloatStateOf(toDo?.priority ?: 0f) } + LargeTopAppBarScaffold( title = stringResource(if (toDo != null) R.string.title_edit_task else R.string.action_add_task), scrollBehavior = scrollBehavior, + floatingActionButton = { + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + if (toDo !== null) { + AnimatedExtendedFloatingActionButton( + icon = { + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = stringResource(R.string.action_delete) + ) + }, + text = { Text(stringResource(R.string.action_delete)) }, + expanded = true, + containerColor = MaterialTheme.colorScheme.errorContainer, + onClick = onDelete + ) + } + AnimatedExtendedFloatingActionButton( + icon = { + Icon( + imageVector = Icons.Outlined.Save, + contentDescription = stringResource(R.string.action_save) + ) + }, + text = { Text(stringResource(R.string.action_save)) }, + expanded = true, + onClick = { + if (text.trim().isEmpty()) { + isError = true + return@AnimatedExtendedFloatingActionButton + } + + isError = false + onSave( + TodoEntity( + content = text, + subject = selectedItemIndex, + isCompleted = toDo?.isCompleted ?: false, + priority = sliderPosition, + id = toDo?.id ?: 0 + ) + ) + } + ) + } + }, onBack = onNavigateUp, modifier = modifier ) { innerPadding -> @@ -60,8 +129,6 @@ fun TodoEditorPage( .fillMaxSize() .verticalScroll(rememberScrollState()) ) { - var text by rememberSaveable { mutableStateOf(toDo?.content ?: "") } - var isError by rememberSaveable { mutableStateOf(false) } TextField( value = text, onValueChange = { text = it }, @@ -82,11 +149,13 @@ fun TodoEditorPage( it.getDisplayName(context) } } - var selectedItemIndex by rememberSaveable { mutableIntStateOf(toDo?.subject ?: 0) } Text( text = stringResource(R.string.label_subject), style = MaterialTheme.typography.titleMedium ) + + Spacer(Modifier.size(5.dp)) + FilterChipGroup( items = subjects, defaultSelectedItemIndex = toDo?.subject ?: 0, @@ -96,47 +165,45 @@ fun TodoEditorPage( modifier = Modifier.fillMaxWidth() ) - Spacer(Modifier.size(20.dp)) + Spacer(Modifier.size(10.dp)) - // 操作按钮 - Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { - if (toDo !== null) { - FilledTonalButton( - onClick = { - onDelete() + Text( + text = stringResource(R.string.label_priority), + style = MaterialTheme.typography.titleMedium + ) + + Spacer(Modifier.size(5.dp)) + + val interactionSource = remember { MutableInteractionSource() } + + Slider( + modifier = Modifier.semantics { + contentDescription = context.getString(R.string.label_priority) + }, + value = sliderPosition, + onValueChange = { sliderPosition = it }, + valueRange = -10f..10f, + steps = 3, + interactionSource = interactionSource, + thumb = { + Label( + label = { + PlainTooltip( + modifier = Modifier + .sizeIn(45.dp, 25.dp) + .wrapContentWidth() + ) { + Text(stringResource(getPriorityString(sliderPosition))) + } }, - colors = ButtonColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.onErrorContainer, - disabledContainerColor = MaterialTheme.colorScheme.onSurface, - disabledContentColor = MaterialTheme.colorScheme.onSurfaceVariant - ) + interactionSource = interactionSource ) { - Text(stringResource(R.string.action_delete)) + SliderDefaults.Thumb(interactionSource) } } - FilledTonalButton( - onClick = { - // 文本为空 - if (text.trim().isEmpty()) { - isError = true - return@FilledTonalButton - } + ) - isError = false - onSave( - TodoEntity( - content = text, - subject = selectedItemIndex, - isCompleted = toDo?.isCompleted ?: false, - id = toDo?.id ?: 0 - ) - ) - } - ) { - Text(stringResource(R.string.action_save)) - } - } + Spacer(Modifier.size(40.dp)) } } } \ 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 ef234a3..f33517a 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 @@ -125,6 +125,7 @@ fun MainPage( content = content, subject = subject, isCompleted = true, + priority = priority, id = id ) ) @@ -166,6 +167,7 @@ fun MainPage( content = content, subject = subject, isCompleted = true, + priority = priority, id = id ) ) 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 1f4ff8e..1869831 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 @@ -69,10 +69,11 @@ fun ManagerFragment( content = item.content, subject = Subjects.fromId(item.subject).getDisplayName(context), completed = item.isCompleted, + priority = item.priority, + selected = selectedTodoIds.contains(item.id), onCardClick = { onItemClick(item) }, onCardLongClick = { onItemLongClick(item) }, onChecked = { onItemChecked(item) }, - selected = selectedTodoIds.contains(item.id), modifier = Modifier .padding(vertical = 5.dp) .animateItem() // TODO: 设置动画时间 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 74cb07e..960a922 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 @@ -26,8 +26,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import cn.super12138.todo.R @@ -37,10 +39,11 @@ fun TodoCard( content: String, subject: String, completed: Boolean, + priority: Float, + selected: Boolean, onCardClick: () -> Unit = {}, onCardLongClick: () -> Unit = {}, onChecked: () -> Unit = {}, - selected: Boolean, modifier: Modifier = Modifier ) { ElevatedCard( @@ -85,14 +88,27 @@ fun TodoCard( maxLines = 1, overflow = TextOverflow.Ellipsis, textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, - modifier = Modifier.basicMarquee() // TODO: 后续评估性能影响 - ) - Text( - text = subject, - style = MaterialTheme.typography.labelMedium, - textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, - maxLines = 1 + modifier = Modifier + .fillMaxWidth() + .basicMarquee() // TODO: 后续评估性能影响 ) + Row( + horizontalArrangement = Arrangement.spacedBy(10.dp), + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = subject, + style = MaterialTheme.typography.labelMedium, + textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, + maxLines = 1 + ) + + + PriorityText( + priority = priority, + isCompleted = completed + ) + } } AnimatedVisibility(!selected && !completed) { @@ -123,4 +139,54 @@ fun TodoCard( }*/ } } +} + +@Composable +fun PriorityText( + priority: Float, + isCompleted: Boolean, + modifier: Modifier = Modifier +) { + Text( + text = stringResource(getPriorityString(priority)), + style = MaterialTheme.typography.labelMedium.copy( + fontWeight = FontWeight.Bold, + color = when (priority) { + -10f -> MaterialTheme.colorScheme.outline + -5f -> MaterialTheme.colorScheme.onSurfaceVariant + 0f -> MaterialTheme.colorScheme.secondary + 5f -> MaterialTheme.colorScheme.tertiary + 10f -> MaterialTheme.colorScheme.onErrorContainer + else -> MaterialTheme.colorScheme.secondary + } + ), + textDecoration = if (isCompleted) TextDecoration.LineThrough else TextDecoration.None, + maxLines = 1 + ) +} + +fun getPriorityString(priority: Float): Int { + return when (priority) { + -10f -> R.string.priority_not_urgent + -5f -> R.string.priority_not_important + 0f -> R.string.priority_default + 5f -> R.string.priority_important + 10f -> R.string.priority_urgent + else -> R.string.priority_default + } +} + +@Preview(locale = "zh-rCN", showBackground = true) +@Composable +private fun TodoCardPreview() { + TodoCard( + content = "背《岳阳楼记》《出师表》《琵琶行》", + subject = "语文", + completed = false, + priority = 5f, + selected = false, + onCardClick = {}, + onCardLongClick = {}, + onChecked = {} + ) } \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a47fdc9..2772a14 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -40,4 +40,10 @@ 开放源代码许可 查看应用使用的开源库及其许可 学科 + 不紧急 + 不重要 + 默认 + 重要 + 紧急 + 优先级 \ 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 b909faf..8e059ca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -41,4 +41,10 @@ Open Source Licences Check the open source libraries used by the application and their licences. Subject + Not Urgent + Not Important + Default + Important + Urgent + Priority \ No newline at end of file