feat: 增加今日任务进度板块

This commit is contained in:
Super12138 2026-02-09 17:03:33 +08:00
parent 1facdc28b7
commit 2959966552
7 changed files with 182 additions and 21 deletions

View file

@ -7,10 +7,12 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.contentColorFor
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.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@ -26,8 +28,10 @@ enum class EmptyTipType {
@Composable
fun EmptyTip(
modifier: Modifier = Modifier,
type: EmptyTipType,
size: Dp = 48.dp,
type: EmptyTipType
containerColor: Color = MaterialTheme.colorScheme.secondaryContainer,
contentColor: Color = contentColorFor(containerColor)
) {
Box(
contentAlignment = Alignment.Center,
@ -35,7 +39,7 @@ fun EmptyTip(
.padding(TodoDefaults.screenHorizontalPadding)
.size(size)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.secondaryContainer)
.background(containerColor)
) {
Icon(
painter = painterResource(
@ -46,7 +50,7 @@ fun EmptyTip(
}
),
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
tint = contentColor,
modifier = Modifier.size(size / 2)
)
}

View file

@ -17,8 +17,9 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cn.super12138.todo.R
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.pages.overview.components.ListCard
import cn.super12138.todo.ui.pages.overview.components.ProgressCard
import cn.super12138.todo.ui.pages.overview.components.RoundedCornerCardLarge
import cn.super12138.todo.ui.pages.overview.components.UpcomingTaskCard
import cn.super12138.todo.ui.viewmodels.MainViewModel
import cn.super12138.todo.utils.SystemUtils
@ -31,13 +32,26 @@ fun OverviewPage(
val toDos by viewModel.sortedTodos.collectAsState(initial = emptyList())
val totalTasks by remember { derivedStateOf { toDos.size } }
val completedTasks by remember { derivedStateOf { toDos.count { it.isCompleted } } }
val nextWeekTodo by remember {
val todayMillis = SystemUtils.getToday()
val dayMillis = 24L * 60 * 60 * 1000
val todayEnd = todayMillis + dayMillis
val todayTodo by remember {
derivedStateOf {
val today = SystemUtils.getToday()
val weekFromToday = today + 7L * 24 * 60 * 60 * 1000
toDos.filter { todo ->
val due = todo.dueDate ?: return@filter false
due in today..<weekFromToday
due in todayMillis until todayEnd
}
}
}
val nextWeekTodo by remember {
derivedStateOf {
val weekFromToday = todayMillis + 7 * dayMillis
toDos.filter { todo ->
val due = todo.dueDate ?: return@filter false
due in todayMillis until weekFromToday
}
}
}
@ -74,12 +88,21 @@ fun OverviewPage(
iconRes = R.drawable.ic_pending,
title = stringResource(R.string.title_pending_task),
count = totalTasks - completedTasks,
containerColor = MaterialTheme.colorScheme.errorContainer // tertiaryContainer
containerColor = MaterialTheme.colorScheme.errorContainer
)
}
item {
UpcomingTaskCard(
nextWeekTodo = nextWeekTodo
ProgressCard(
title = stringResource(R.string.title_today_task),
total = todayTodo.size,
completed = todayTodo.count { it.isCompleted }
)
}
item {
ListCard(
title = stringResource(R.string.title_upcoming_task),
list = nextWeekTodo
)
}
}

View file

@ -40,10 +40,12 @@ import cn.super12138.todo.utils.containerColor
import cn.super12138.todo.utils.toRelativeTimeString
@Composable
fun UpcomingTaskCard(
fun ListCard(
modifier: Modifier = Modifier,
nextWeekTodo: List<TodoEntity>,
containerColor: Color = TodoDefaults.Colors.Container
title: String,
list: List<TodoEntity>,
containerColor: Color = TodoDefaults.Colors.Container,
emptyTipContainerColor: Color = MaterialTheme.colorScheme.secondaryContainer
) {
Card(
modifier = modifier.height(TodoDefaults.overviewCardHeight * 2),
@ -58,12 +60,12 @@ fun UpcomingTaskCard(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = stringResource(R.string.title_upcoming_task),
text = title,
style = MaterialTheme.typography.titleLarge
)
val transitionSpec = fadeScale()
AnimatedContent(
targetState = nextWeekTodo.isEmpty(),
targetState = list.isEmpty(),
transitionSpec = { transitionSpec }
) {
if (it) {
@ -75,7 +77,10 @@ fun UpcomingTaskCard(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
EmptyTip(type = EmptyTipType.List)
EmptyTip(
type = EmptyTipType.List,
containerColor = emptyTipContainerColor
)
Text(
text = stringResource(R.string.tip_no_task_brief),
@ -87,7 +92,7 @@ fun UpcomingTaskCard(
} else {
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
items(
items = nextWeekTodo,
items = list,
key = { task -> task.id }
) { task ->
UpcomingTaskItem(

View file

@ -0,0 +1,130 @@
package cn.super12138.todo.ui.pages.overview.components
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
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.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity
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.ui.TodoDefaults
import cn.super12138.todo.ui.components.EmptyTip
import cn.super12138.todo.ui.components.EmptyTipType
import cn.super12138.todo.ui.theme.fadeScale
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ProgressCard(
modifier: Modifier = Modifier,
title: String,
total: Int,
completed: Int,
containerColor: Color = TodoDefaults.Colors.Container,
emptyTipContainerColor: Color = MaterialTheme.colorScheme.secondaryContainer
) {
val progress = if (total == 0) 0f else completed / total.toFloat()
Card(
modifier = modifier.height(TodoDefaults.overviewCardHeight * 2),
colors = CardDefaults.cardColors(containerColor = containerColor),
shape = TodoDefaults.defaultShape
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(TodoDefaults.screenHorizontalPadding),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge
)
val transitionSpec = fadeScale()
AnimatedContent(
targetState = total == 0,
transitionSpec = { transitionSpec }
) {
if (it) {
Column(
modifier = Modifier
.fillMaxSize()
.weight(1f) // 占满剩余空间
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
EmptyTip(
type = EmptyTipType.List,
containerColor = emptyTipContainerColor
)
Text(
text = stringResource(R.string.tip_no_task_brief),
style = MaterialTheme.typography.labelLarge,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
} else {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
val animatedProgress by animateFloatAsState(
targetValue = progress,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
)
val thickStrokeWidth = with(LocalDensity.current) { 5.dp.toPx() }
val thickStroke = remember(thickStrokeWidth) {
Stroke(width = thickStrokeWidth, cap = StrokeCap.Round)
}
CircularWavyProgressIndicator(
progress = { animatedProgress },
waveSpeed = 3.dp,
wavelength = 20.dp,
stroke = thickStroke,
trackStroke = thickStroke,
modifier = Modifier
.padding(TodoDefaults.screenHorizontalPadding)
.size(90.dp)
)
Text(
text = "$completed / $total",
style = MaterialTheme.typography.labelLarge,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
}
}

View file

@ -53,7 +53,6 @@ object SystemUtils {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
}
fun ComponentActivity.configureEdgeToEdge() {

View file

@ -134,5 +134,5 @@
<string name="sorting_due_date">截止日期</string>
<string name="tip_search_task_not_found">暂未搜索到内容</string>
<string name="tip_no_task_brief">暂无任务</string>
<string name="title_today_task">今日任务</string>
<string name="title_today_task">今日任务进度</string>
</resources>

View file

@ -135,5 +135,5 @@
<string name="sorting_due_date">Due date</string>
<string name="tip_search_task_not_found">No tasks found</string>
<string name="tip_no_task_brief">No tasks</string>
<string name="title_today_task">Today</string>
<string name="title_today_task">Today\'s Task Progress</string>
</resources>