feat: 支持设置任务优先级

This commit is contained in:
Super12138 2025-01-17 14:39:10 +08:00
parent e962a9f361
commit 08d7706b15
11 changed files with 213 additions and 53 deletions

View file

@ -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,
)

View file

@ -56,7 +56,7 @@ fun FilterChipGroup(
label = {
Text(item)
},
modifier = Modifier.padding(horizontal = 5.dp)
modifier = Modifier.padding(end = 10.dp)
)
}
}

View file

@ -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(

View file

@ -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) }

View file

@ -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)
}
)
}

View file

@ -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))
}
}
}

View file

@ -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
)
)

View file

@ -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: 设置动画时间

View file

@ -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 = {}
)
}

View file

@ -40,4 +40,10 @@
<string name="pref_licence">开放源代码许可</string>
<string name="pref_licence_desc">查看应用使用的开源库及其许可</string>
<string name="label_subject">学科</string>
<string name="priority_not_urgent">不紧急</string>
<string name="priority_not_important">不重要</string>
<string name="priority_default">默认</string>
<string name="priority_important">重要</string>
<string name="priority_urgent">紧急</string>
<string name="label_priority">优先级</string>
</resources>

View file

@ -41,4 +41,10 @@
<string name="pref_licence">Open Source Licences</string>
<string name="pref_licence_desc">Check the open source libraries used by the application and their licences.</string>
<string name="label_subject">Subject</string>
<string name="priority_not_urgent">Not Urgent</string>
<string name="priority_not_important">Not Important</string>
<string name="priority_default">Default</string>
<string name="priority_important">Important</string>
<string name="priority_urgent">Urgent</string>
<string name="label_priority">Priority</string>
</resources>