feat: 进一步实现临近任务卡片

This commit is contained in:
Super12138 2026-02-08 12:51:35 +08:00
parent 2f3e84b8f9
commit b585565459
5 changed files with 132 additions and 40 deletions

View file

@ -38,25 +38,7 @@ object TodoDefaults {
val settingsItemPadding = 4.dp val settingsItemPadding = 4.dp
/** val overviewCardHeight = 120.dp
* 待办进度条粗度
*/
val trackThickness = 7.0.dp
/**
* 待办进度条波长
*/
val waveLength = 35.dp
/**
* 待办进度条波速
*/
val waveSpeed = 3.dp
/**
* 待办进度条波幅
*/
const val waveAmplitude = 0.6f
val ScreenContainerShape: Shape val ScreenContainerShape: Shape
@Composable @Composable

View file

@ -59,7 +59,7 @@ fun OverviewPage(
Column { Column {
LazyVerticalStaggeredGrid( LazyVerticalStaggeredGrid(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
columns = StaggeredGridCells.Adaptive(150.dp), columns = StaggeredGridCells.Adaptive(160.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp), horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalItemSpacing = 10.dp verticalItemSpacing = 10.dp
) { ) {

View file

@ -7,9 +7,13 @@ import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.ButtonShapes import androidx.compose.material3.ButtonShapes
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
@ -23,6 +27,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorProducer
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -44,14 +49,15 @@ fun RoundedCornerCardLarge(
val pressed by interactionSource.collectIsPressedAsState() val pressed by interactionSource.collectIsPressedAsState()
val animatedShape = shapeByInteraction(shapes, pressed, TodoDefaults.shapesDefaultAnimationSpec) val animatedShape = shapeByInteraction(shapes, pressed, TodoDefaults.shapesDefaultAnimationSpec)
val cardColors = CardDefaults.cardColors(containerColor = containerColor)
Card( Card(
modifier = modifier.wrapContentHeight(), modifier = modifier.height(TodoDefaults.overviewCardHeight),
colors = CardDefaults.cardColors(containerColor = containerColor), colors = cardColors,
shape = animatedShape shape = animatedShape
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxSize()
.clickable( .clickable(
enabled = true, enabled = true,
onClick = onClick, onClick = onClick,
@ -63,18 +69,29 @@ fun RoundedCornerCardLarge(
) { ) {
Icon( Icon(
painter = painterResource(iconRes), painter = painterResource(iconRes),
contentDescription = null contentDescription = null,
modifier = Modifier.size(30.dp)
) )
Column(horizontalAlignment = Alignment.Start) { Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxHeight()
.padding(vertical = 8.dp)
) {
Text( Text(
text = title, text = title,
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
Text(
BasicText(
text = count.toString(), text = count.toString(),
style = MaterialTheme.typography.headlineLarge.copy( style = MaterialTheme.typography.headlineLarge.copy(fontWeight = FontWeight.Bold),
fontWeight = FontWeight.ExtraBold color = ColorProducer { cardColors.contentColor },
) autoSize = TextAutoSize.StepBased(
MaterialTheme.typography.headlineSmall.fontSize,
MaterialTheme.typography.displayMedium.fontSize
),
) )
} }
} }

View file

@ -2,11 +2,14 @@ package cn.super12138.todo.ui.pages.overview.components
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Badge
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -16,12 +19,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.logic.database.TodoEntity 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.TodoDefaults
import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.utils.containerColor
import cn.super12138.todo.utils.toLocalDateString import cn.super12138.todo.utils.toRelativeTimeString
@Composable @Composable
fun UpcomingTaskCard( fun UpcomingTaskCard(
@ -30,7 +36,7 @@ fun UpcomingTaskCard(
containerColor: Color = TodoDefaults.ContainerColor containerColor: Color = TodoDefaults.ContainerColor
) { ) {
Card( Card(
modifier = modifier.height(300.dp), modifier = modifier.height(TodoDefaults.overviewCardHeight * 2),
colors = CardDefaults.cardColors(containerColor = containerColor), colors = CardDefaults.cardColors(containerColor = containerColor),
shape = TodoDefaults.defaultShape shape = TodoDefaults.defaultShape
) { ) {
@ -39,11 +45,11 @@ fun UpcomingTaskCard(
.fillMaxWidth() .fillMaxWidth()
.padding(TodoDefaults.screenHorizontalPadding), .padding(TodoDefaults.screenHorizontalPadding),
horizontalAlignment = Alignment.Start, horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Text( Text(
text = stringResource(R.string.title_upcoming_task), text = stringResource(R.string.title_upcoming_task),
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleLarge
) )
LazyColumn( LazyColumn(
@ -53,13 +59,72 @@ fun UpcomingTaskCard(
items = nextWeekTodo, items = nextWeekTodo,
key = { it.id } key = { it.id }
) { ) {
SettingsItem( UpcomingTaskItem(
title = it.content, content = it.content,
description = it.dueDate.toLocalDateString(), category = it.category,
enableClick = false priority = Priority.fromFloat(it.priority),
dueDate = it.dueDate
) )
} }
} }
} }
} }
}
@Composable
fun UpcomingTaskItem(
content: String,
category: String,
priority: Priority,
dueDate: Long?,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(vertical = TodoDefaults.settingsItemVerticalPadding / 4),
horizontalAlignment = Alignment.Start
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = content,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.weight(1f)
)
Text(
text = dueDate.toRelativeTimeString(),
style = MaterialTheme.typography.labelMedium.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontWeight = FontWeight.Bold
),
modifier = Modifier.padding(start = TodoDefaults.screenVerticalPadding)
)
}
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,
maxLines = 1
)
}
Text(
text = stringResource(priority.nameRes),
style = MaterialTheme.typography.labelMedium.copy(priority.containerColor()),
)
}
}
} }

View file

@ -13,7 +13,10 @@ import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import cn.super12138.todo.logic.model.Priority import cn.super12138.todo.logic.model.Priority
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date import java.util.Date
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.milliseconds
@Composable @Composable
@Stable @Stable
@ -83,4 +86,29 @@ fun Long?.toLocalDateString(): String {
val date = Date(this) val date = Date(this)
val format = SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()) val format = SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault())
return format.format(date) return format.format(date)
}
// 未来相对时间如“3天后”的实现最小层级为天
fun Long?.toRelativeTimeString(): String {
if (this == null) return ""
val today = Calendar.getInstance().apply {
// 将时间设置为当天的开始00:00:00.000
// 兼容API24
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
val diff = (this - today).milliseconds
return diff.toComponents { days, _, _, _, _ ->
when {
days.days == 1.days -> "明天"
days.days > 1.days && days.days < 7.days -> "${days}天后"
days.days in 7.days..<30.days -> "${days / 7}周后"
days.days in 30.days..<365.days -> "${days / 30}个月后"
days.days >= 365.days -> "${days / 365}年后"
else -> "今天"
}
}
} }