package com.taskttl.presentation.statistics import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer 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.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Assignment import androidx.compose.material.icons.automirrored.filled.TrendingUp import androidx.compose.material.icons.filled.CalendarToday import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Schedule import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProgressIndicatorDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue 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.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import com.taskttl.core.routes.Routes import com.taskttl.core.ui.Chip import com.taskttl.data.local.model.Category import com.taskttl.data.state.CountdownIntent import com.taskttl.data.state.TaskIntent import com.taskttl.data.viewmodel.CountdownViewModel import com.taskttl.data.viewmodel.TaskViewModel import com.taskttl.ui.components.AppHeader import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview import org.koin.compose.viewmodel.koinViewModel import taskttl.composeapp.generated.resources.Res import taskttl.composeapp.generated.resources.category_countdown import taskttl.composeapp.generated.resources.category_statistics import taskttl.composeapp.generated.resources.category_task import taskttl.composeapp.generated.resources.completed import taskttl.composeapp.generated.resources.completion_rate import taskttl.composeapp.generated.resources.overview import taskttl.composeapp.generated.resources.setting_category_management import taskttl.composeapp.generated.resources.title_statistics import taskttl.composeapp.generated.resources.total_tasks @Composable @Preview fun StatisticsScreen( navController: NavHostController, taskViewModel: TaskViewModel = koinViewModel(), countdownViewModel: CountdownViewModel = koinViewModel(), ) { val taskState by taskViewModel.state.collectAsState() val countdownState by countdownViewModel.state.collectAsState() LaunchedEffect(Unit) { taskViewModel.processIntent(TaskIntent.LoadTasks) countdownViewModel.handleIntent(CountdownIntent.LoadCountdowns) } Column( modifier = Modifier .fillMaxSize() .background(Color.White) ) { AppHeader( title = Res.string.title_statistics, trailingIcon = Icons.Default.CalendarToday ) LazyColumn( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) .padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { // 总览统计 item { Text( text = stringResource(Res.string.overview), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold ) } item { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { StatisticCard( titleRes = Res.string.total_tasks, value = taskState.tasks.size.toString(), icon = Icons.AutoMirrored.Filled.Assignment, color = MaterialTheme.colorScheme.primary, modifier = Modifier.weight(1f) ) StatisticCard( titleRes = Res.string.completed, value = taskState.tasks.count { it.isCompleted }.toString(), icon = Icons.Default.CheckCircle, color = Color(0xFF4CAF50), modifier = Modifier.weight(1f) ) } } item { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { StatisticCard( titleRes = Res.string.category_countdown, value = countdownState.countdowns.size.toString(), icon = Icons.Default.Schedule, color = Color(0xFFFF9800), modifier = Modifier.weight(1f) ) val completionRate = if (taskState.tasks.isNotEmpty()) { (taskState.tasks.count { it.isCompleted } .toFloat() / taskState.tasks.size * 100).toInt() } else 0 StatisticCard( titleRes = Res.string.completion_rate, value = "$completionRate%", icon = Icons.AutoMirrored.Filled.TrendingUp, color = Color(0xFF9C27B0), modifier = Modifier.weight(1f) ) } } // 分类统计 item { Spacer(modifier = Modifier.height(8.dp)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( text = stringResource(Res.string.category_statistics), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold ) TextButton(onClick = { navController.navigate(Routes.Main.Settings.CategoryManagement) }) { Text(stringResource(Res.string.setting_category_management)) } } } item { taskState.categories.let { taskState.categories.forEach { category -> val categoryTasks = taskState.tasks.filter { it.category == category } val completedTasks = categoryTasks.count { it.isCompleted } CategoryStatisticItem( category = category, totalCount = categoryTasks.size, completedCount = completedTasks, typeRes = Res.string.category_task ) } } countdownState.categories.let { countdownState.categories.forEach { category -> val categoryCountdowns = countdownState.countdowns.filter { it.category == category } val activeCountdowns = categoryCountdowns.count { it.isActive } CategoryStatisticItem( category = category, totalCount = categoryCountdowns.size, completedCount = activeCountdowns, typeRes = Res.string.category_countdown ) } } } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable private fun StatisticCard( titleRes: StringResource, value: String, icon: ImageVector, color: Color, modifier: Modifier = Modifier, ) { Card( modifier = modifier, colors = CardDefaults.cardColors(containerColor = color.copy(alpha = 0.1f)) ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Icon( imageVector = icon, contentDescription = stringResource(titleRes), tint = color, modifier = Modifier.size(32.dp) ) Spacer(modifier = Modifier.height(8.dp)) Text( text = value, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, color = color ) Text( text = stringResource(titleRes), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } } @OptIn(ExperimentalMaterial3Api::class) @Composable private fun CategoryStatisticItem( category: Category, totalCount: Int, completedCount: Int, typeRes: StringResource, ) { if (totalCount == 0) return Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = Color.White), ) { Row( modifier = Modifier .fillMaxWidth() .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { // 分类颜色指示器 Box( modifier = Modifier .size(40.dp) .clip(CircleShape) .background(category.color.backgroundColor), contentAlignment = Alignment.Center ) { Icon( imageVector = category.icon.icon, contentDescription = stringResource(category.icon.displayNameRes), tint = category.color.iconColor, modifier = Modifier.size(24.dp) ) } Spacer(modifier = Modifier.width(16.dp)) // 分类信息 Column( modifier = Modifier.weight(1f) ) { Row() { Text( text = category.name, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Medium, color = category.color.textColor ) Spacer(modifier = Modifier.width(6.dp)) Chip( textRes = category.type.displayNameRes, ) } Text( text = "${stringResource(typeRes)}: $completedCount/$totalCount", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } // 进度条 Column( horizontalAlignment = Alignment.End ) { val progress = if (totalCount > 0) completedCount.toFloat() / totalCount else 0f Text( text = "${(progress * 100).toInt()}%", style = MaterialTheme.typography.labelMedium, fontWeight = FontWeight.Medium, color = category.color.textColor ) Spacer(modifier = Modifier.height(4.dp)) LinearProgressIndicator( progress = { progress }, modifier = Modifier .width(80.dp) .height(6.dp) .clip(RoundedCornerShape(3.dp)), color = category.color.textColor, trackColor = ProgressIndicatorDefaults.linearTrackColor, strokeCap = ProgressIndicatorDefaults.LinearStrokeCap, ) } } } Spacer(modifier = Modifier.height(10.dp)) }