|
@@ -0,0 +1,503 @@
|
|
|
|
|
+package com.tixing.app.ui
|
|
|
|
|
+
|
|
|
|
|
+import android.app.TimePickerDialog
|
|
|
|
|
+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.PaddingValues
|
|
|
|
|
+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.navigationBarsPadding
|
|
|
|
|
+import androidx.compose.foundation.layout.padding
|
|
|
|
|
+import androidx.compose.foundation.layout.size
|
|
|
|
|
+import androidx.compose.foundation.layout.statusBarsPadding
|
|
|
|
|
+import androidx.compose.foundation.lazy.LazyColumn
|
|
|
|
|
+import androidx.compose.foundation.lazy.items
|
|
|
|
|
+import androidx.compose.foundation.rememberScrollState
|
|
|
|
|
+import androidx.compose.foundation.shape.RoundedCornerShape
|
|
|
|
|
+import androidx.compose.foundation.verticalScroll
|
|
|
|
|
+import androidx.compose.material.icons.Icons
|
|
|
|
|
+import androidx.compose.material.icons.filled.Add
|
|
|
|
|
+import androidx.compose.material.icons.filled.CheckCircle
|
|
|
|
|
+import androidx.compose.material.icons.filled.DateRange
|
|
|
|
|
+import androidx.compose.material.icons.filled.Delete
|
|
|
|
|
+import androidx.compose.material.icons.filled.ListAlt
|
|
|
|
|
+import androidx.compose.material.icons.filled.QueryStats
|
|
|
|
|
+import androidx.compose.material3.Button
|
|
|
|
|
+import androidx.compose.material3.Card
|
|
|
|
|
+import androidx.compose.material3.CardDefaults
|
|
|
|
|
+import androidx.compose.material3.Checkbox
|
|
|
|
|
+import androidx.compose.material3.FilterChip
|
|
|
|
|
+import androidx.compose.material3.FloatingActionButton
|
|
|
|
|
+import androidx.compose.material3.HorizontalDivider
|
|
|
|
|
+import androidx.compose.material3.Icon
|
|
|
|
|
+import androidx.compose.material3.IconButton
|
|
|
|
|
+import androidx.compose.material3.MaterialTheme
|
|
|
|
|
+import androidx.compose.material3.NavigationBar
|
|
|
|
|
+import androidx.compose.material3.NavigationBarItem
|
|
|
|
|
+import androidx.compose.material3.OutlinedTextField
|
|
|
|
|
+import androidx.compose.material3.Scaffold
|
|
|
|
|
+import androidx.compose.material3.Surface
|
|
|
|
|
+import androidx.compose.material3.Text
|
|
|
|
|
+import androidx.compose.material3.TextButton
|
|
|
|
|
+import androidx.compose.runtime.Composable
|
|
|
|
|
+import androidx.compose.runtime.getValue
|
|
|
|
|
+import androidx.compose.runtime.mutableIntStateOf
|
|
|
|
|
+import androidx.compose.runtime.mutableStateOf
|
|
|
|
|
+import androidx.compose.runtime.remember
|
|
|
|
|
+import androidx.compose.runtime.setValue
|
|
|
|
|
+import androidx.compose.ui.Alignment
|
|
|
|
|
+import androidx.compose.ui.Modifier
|
|
|
|
|
+import androidx.compose.ui.graphics.Brush
|
|
|
|
|
+import androidx.compose.ui.graphics.Color
|
|
|
|
|
+import androidx.compose.ui.platform.LocalContext
|
|
|
|
|
+import androidx.compose.ui.text.font.FontWeight
|
|
|
|
|
+import androidx.compose.ui.unit.dp
|
|
|
|
|
+import androidx.compose.ui.window.Dialog
|
|
|
|
|
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
|
|
|
+import com.tixing.app.data.GoalProgress
|
|
|
|
|
+import com.tixing.app.data.HistoryRow
|
|
|
|
|
+import com.tixing.app.util.DateUtils
|
|
|
|
|
+
|
|
|
|
|
+private enum class HomeTab(val label: String, val icon: androidx.compose.ui.graphics.vector.ImageVector) {
|
|
|
|
|
+ TODAY("今日", Icons.Default.CheckCircle),
|
|
|
|
|
+ GOALS("目标", Icons.Default.ListAlt),
|
|
|
|
|
+ HISTORY("历史", Icons.Default.DateRange),
|
|
|
|
|
+ STATS("统计", Icons.Default.QueryStats)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+fun AppScreen(vm: AppViewModel) {
|
|
|
|
|
+ val goals by vm.goalProgress.collectAsStateWithLifecycle()
|
|
|
|
|
+ val history by vm.history.collectAsStateWithLifecycle()
|
|
|
|
|
+ var tab by remember { mutableStateOf(HomeTab.TODAY) }
|
|
|
|
|
+ var showAddDialog by remember { mutableStateOf(false) }
|
|
|
|
|
+
|
|
|
|
|
+ Scaffold(
|
|
|
|
|
+ modifier = Modifier.fillMaxSize(),
|
|
|
|
|
+ topBar = {
|
|
|
|
|
+ Column(
|
|
|
|
|
+ modifier = Modifier
|
|
|
|
|
+ .fillMaxWidth()
|
|
|
|
|
+ .statusBarsPadding()
|
|
|
|
|
+ .padding(horizontal = 20.dp, vertical = 12.dp)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = "目标打卡",
|
|
|
|
|
+ style = MaterialTheme.typography.headlineMedium,
|
|
|
|
|
+ fontWeight = FontWeight.Bold
|
|
|
|
|
+ )
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = "长期坚持,结果会说话",
|
|
|
|
|
+ style = MaterialTheme.typography.bodyMedium,
|
|
|
|
|
+ color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ floatingActionButton = {
|
|
|
|
|
+ if (tab == HomeTab.GOALS) {
|
|
|
|
|
+ FloatingActionButton(onClick = { showAddDialog = true }) {
|
|
|
|
|
+ Icon(Icons.Default.Add, contentDescription = "添加目标")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ bottomBar = {
|
|
|
|
|
+ NavigationBar(modifier = Modifier.navigationBarsPadding()) {
|
|
|
|
|
+ HomeTab.entries.forEach { item ->
|
|
|
|
|
+ NavigationBarItem(
|
|
|
|
|
+ selected = item == tab,
|
|
|
|
|
+ onClick = { tab = item },
|
|
|
|
|
+ icon = { Icon(item.icon, contentDescription = item.label) },
|
|
|
|
|
+ label = { Text(item.label) }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ) { innerPadding ->
|
|
|
|
|
+ Box(
|
|
|
|
|
+ modifier = Modifier
|
|
|
|
|
+ .fillMaxSize()
|
|
|
|
|
+ .background(
|
|
|
|
|
+ Brush.verticalGradient(
|
|
|
|
|
+ colors = listOf(
|
|
|
|
|
+ MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.65f),
|
|
|
|
|
+ MaterialTheme.colorScheme.background
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ .padding(innerPadding)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ when (tab) {
|
|
|
|
|
+ HomeTab.TODAY -> TodayScreen(goals = goals, onToggle = vm::toggleToday)
|
|
|
|
|
+ HomeTab.GOALS -> GoalsScreen(goals = goals, onDelete = vm::deleteGoal)
|
|
|
|
|
+ HomeTab.HISTORY -> HistoryScreen(history = history)
|
|
|
|
|
+ HomeTab.STATS -> StatsScreen(goals = goals)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (showAddDialog) {
|
|
|
|
|
+ AddGoalDialog(
|
|
|
|
|
+ onDismiss = { showAddDialog = false },
|
|
|
|
|
+ onConfirm = { title, category, hour, minute ->
|
|
|
|
|
+ vm.addGoal(title, category, hour, minute)
|
|
|
|
|
+ showAddDialog = false
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun TodayScreen(
|
|
|
|
|
+ goals: List<GoalProgress>,
|
|
|
|
|
+ onToggle: (Int, Boolean) -> Unit
|
|
|
|
|
+) {
|
|
|
|
|
+ if (goals.isEmpty()) {
|
|
|
|
|
+ EmptyTip("还没有目标", "去“目标”页添加你的第一个打卡计划")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ LazyColumn(
|
|
|
|
|
+ contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp),
|
|
|
|
|
+ verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ item {
|
|
|
|
|
+ Card(
|
|
|
|
|
+ colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
|
|
|
|
+ shape = RoundedCornerShape(20.dp)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Row(
|
|
|
|
|
+ modifier = Modifier
|
|
|
|
|
+ .fillMaxWidth()
|
|
|
|
|
+ .padding(16.dp),
|
|
|
|
|
+ verticalAlignment = Alignment.CenterVertically,
|
|
|
|
|
+ horizontalArrangement = Arrangement.SpaceBetween
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = "今日完成率",
|
|
|
|
|
+ style = MaterialTheme.typography.titleMedium,
|
|
|
|
|
+ fontWeight = FontWeight.SemiBold
|
|
|
|
|
+ )
|
|
|
|
|
+ val percent = if (goals.isNotEmpty()) {
|
|
|
|
|
+ ((goals.count { it.completedToday } / goals.size.toFloat()) * 100).toInt()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ 0
|
|
|
|
|
+ }
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = "$percent%",
|
|
|
|
|
+ style = MaterialTheme.typography.titleLarge,
|
|
|
|
|
+ color = MaterialTheme.colorScheme.primary,
|
|
|
|
|
+ fontWeight = FontWeight.Bold
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ items(goals, key = { it.goal.id }) { item ->
|
|
|
|
|
+ Card(
|
|
|
|
|
+ shape = RoundedCornerShape(18.dp),
|
|
|
|
|
+ colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Row(
|
|
|
|
|
+ modifier = Modifier
|
|
|
|
|
+ .fillMaxWidth()
|
|
|
|
|
+ .padding(horizontal = 14.dp, vertical = 12.dp),
|
|
|
|
|
+ verticalAlignment = Alignment.CenterVertically,
|
|
|
|
|
+ horizontalArrangement = Arrangement.SpaceBetween
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Column(modifier = Modifier.weight(1f)) {
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = item.goal.title,
|
|
|
|
|
+ style = MaterialTheme.typography.titleMedium,
|
|
|
|
|
+ fontWeight = FontWeight.SemiBold
|
|
|
|
|
+ )
|
|
|
|
|
+ Spacer(modifier = Modifier.height(4.dp))
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = "${item.goal.category} · 提醒 ${DateUtils.toMinutesLabel(item.goal.reminderHour, item.goal.reminderMinute)}",
|
|
|
|
|
+ style = MaterialTheme.typography.bodySmall,
|
|
|
|
|
+ color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
|
|
|
+ )
|
|
|
|
|
+ Spacer(modifier = Modifier.height(6.dp))
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = "当前连续 ${item.currentStreak} 天",
|
|
|
|
|
+ style = MaterialTheme.typography.bodyMedium,
|
|
|
|
|
+ color = MaterialTheme.colorScheme.primary
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ Checkbox(
|
|
|
|
|
+ checked = item.completedToday,
|
|
|
|
|
+ onCheckedChange = { checked -> onToggle(item.goal.id, checked) }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun GoalsScreen(
|
|
|
|
|
+ goals: List<GoalProgress>,
|
|
|
|
|
+ onDelete: (GoalProgress) -> Unit
|
|
|
|
|
+) {
|
|
|
|
|
+ if (goals.isEmpty()) {
|
|
|
|
|
+ EmptyTip("没有进行中的目标", "点击右下角 + 创建新目标")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ LazyColumn(
|
|
|
|
|
+ contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp),
|
|
|
|
|
+ verticalArrangement = Arrangement.spacedBy(10.dp)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ items(goals, key = { it.goal.id }) { item ->
|
|
|
|
|
+ Card(shape = RoundedCornerShape(18.dp)) {
|
|
|
|
|
+ Column(modifier = Modifier.padding(14.dp)) {
|
|
|
|
|
+ Row(
|
|
|
|
|
+ modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
+ horizontalArrangement = Arrangement.SpaceBetween,
|
|
|
|
|
+ verticalAlignment = Alignment.CenterVertically
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Column {
|
|
|
|
|
+ Text(item.goal.title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
|
|
|
|
+ Text(
|
|
|
|
|
+ "${item.goal.category} · 每天 ${DateUtils.toMinutesLabel(item.goal.reminderHour, item.goal.reminderMinute)}",
|
|
|
|
|
+ style = MaterialTheme.typography.bodySmall,
|
|
|
|
|
+ color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ IconButton(onClick = { onDelete(item) }) {
|
|
|
|
|
+ Icon(Icons.Default.Delete, contentDescription = "删除")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ HorizontalDivider(modifier = Modifier.padding(vertical = 10.dp))
|
|
|
|
|
+ Row(
|
|
|
|
|
+ modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
+ horizontalArrangement = Arrangement.SpaceBetween
|
|
|
|
|
+ ) {
|
|
|
|
|
+ MiniStat("总打卡", "${item.totalCompletedDays}天")
|
|
|
|
|
+ MiniStat("当前连签", "${item.currentStreak}天")
|
|
|
|
|
+ MiniStat("最佳连签", "${item.bestStreak}天")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun HistoryScreen(history: List<HistoryRow>) {
|
|
|
|
|
+ if (history.isEmpty()) {
|
|
|
|
|
+ EmptyTip("暂无历史记录", "完成一次打卡后,这里会出现你的轨迹")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ val grouped = history.groupBy { it.dateKey }
|
|
|
|
|
+
|
|
|
|
|
+ LazyColumn(
|
|
|
|
|
+ contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp),
|
|
|
|
|
+ verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ grouped.forEach { (date, rows) ->
|
|
|
|
|
+ item(key = date) {
|
|
|
|
|
+ Card(shape = RoundedCornerShape(18.dp)) {
|
|
|
|
|
+ Column(modifier = Modifier.padding(14.dp)) {
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = DateUtils.toDisplayDate(date),
|
|
|
|
|
+ style = MaterialTheme.typography.titleMedium,
|
|
|
|
|
+ fontWeight = FontWeight.Bold
|
|
|
|
|
+ )
|
|
|
|
|
+ Spacer(modifier = Modifier.height(8.dp))
|
|
|
|
|
+ rows.forEachIndexed { index, row ->
|
|
|
|
|
+ Row(
|
|
|
|
|
+ modifier = Modifier
|
|
|
|
|
+ .fillMaxWidth()
|
|
|
|
|
+ .padding(vertical = 6.dp),
|
|
|
|
|
+ horizontalArrangement = Arrangement.SpaceBetween,
|
|
|
|
|
+ verticalAlignment = Alignment.CenterVertically
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = "${row.goalTitle}(${row.category})",
|
|
|
|
|
+ style = MaterialTheme.typography.bodyMedium
|
|
|
|
|
+ )
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = if (row.isCompleted) "已完成" else "未完成",
|
|
|
|
|
+ color = if (row.isCompleted) Color(0xFF227D5F) else MaterialTheme.colorScheme.error,
|
|
|
|
|
+ style = MaterialTheme.typography.bodyMedium,
|
|
|
|
|
+ fontWeight = FontWeight.SemiBold
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ if (index != rows.lastIndex) HorizontalDivider()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun StatsScreen(goals: List<GoalProgress>) {
|
|
|
|
|
+ if (goals.isEmpty()) {
|
|
|
|
|
+ EmptyTip("没有可统计的数据", "添加目标并坚持打卡后,这里会显示趋势")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ val totalGoals = goals.size
|
|
|
|
|
+ val totalDone = goals.sumOf { it.totalCompletedDays }
|
|
|
|
|
+ val avgRate = goals.map { it.completionRate30Days }.average().toInt()
|
|
|
|
|
+
|
|
|
|
|
+ Column(
|
|
|
|
|
+ modifier = Modifier
|
|
|
|
|
+ .fillMaxSize()
|
|
|
|
|
+ .verticalScroll(rememberScrollState())
|
|
|
|
|
+ .padding(16.dp),
|
|
|
|
|
+ verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Card(shape = RoundedCornerShape(20.dp)) {
|
|
|
|
|
+ Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
|
|
|
+ Text("全局表现", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
|
|
|
|
+ Text("进行中目标:$totalGoals")
|
|
|
|
|
+ Text("累计打卡次数:$totalDone")
|
|
|
|
|
+ Text("近30天平均完成率:$avgRate%")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ goals.forEach { item ->
|
|
|
|
|
+ Card(shape = RoundedCornerShape(18.dp)) {
|
|
|
|
|
+ Column(modifier = Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
|
|
|
|
+ Text(item.goal.title, fontWeight = FontWeight.SemiBold, style = MaterialTheme.typography.titleMedium)
|
|
|
|
|
+ Text(
|
|
|
|
|
+ "当前 ${item.currentStreak} 天,最佳 ${item.bestStreak} 天",
|
|
|
|
|
+ style = MaterialTheme.typography.bodyMedium,
|
|
|
|
|
+ color = MaterialTheme.colorScheme.primary
|
|
|
|
|
+ )
|
|
|
|
|
+ Text("近30天完成率 ${item.completionRate30Days}%", style = MaterialTheme.typography.bodyMedium)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun MiniStat(label: String, value: String) {
|
|
|
|
|
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
|
|
|
|
+ Text(label, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
|
|
|
|
+ Text(value, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun EmptyTip(title: String, desc: String) {
|
|
|
|
|
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
|
|
|
|
+ Surface(
|
|
|
|
|
+ shape = RoundedCornerShape(20.dp),
|
|
|
|
|
+ tonalElevation = 3.dp,
|
|
|
|
|
+ modifier = Modifier.padding(24.dp)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Column(modifier = Modifier.padding(horizontal = 22.dp, vertical = 20.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
|
|
|
|
+ Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
|
|
|
|
+ Spacer(modifier = Modifier.height(6.dp))
|
|
|
|
|
+ Text(desc, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun AddGoalDialog(
|
|
|
|
|
+ onDismiss: () -> Unit,
|
|
|
|
|
+ onConfirm: (String, String, Int, Int) -> Unit
|
|
|
|
|
+) {
|
|
|
|
|
+ var title by remember { mutableStateOf("") }
|
|
|
|
|
+ var selectedCategory by remember { mutableStateOf("健身") }
|
|
|
|
|
+ var hour by remember { mutableIntStateOf(21) }
|
|
|
|
|
+ var minute by remember { mutableIntStateOf(0) }
|
|
|
|
|
+ val context = LocalContext.current
|
|
|
|
|
+
|
|
|
|
|
+ val categories = listOf("健身", "戒酒", "学习英语", "阅读", "早睡", "自定义")
|
|
|
|
|
+
|
|
|
|
|
+ Dialog(onDismissRequest = onDismiss) {
|
|
|
|
|
+ Surface(shape = RoundedCornerShape(24.dp), modifier = Modifier.fillMaxWidth()) {
|
|
|
|
|
+ Column(
|
|
|
|
|
+ modifier = Modifier.padding(18.dp),
|
|
|
|
|
+ verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text("新建目标", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
|
|
|
|
+ OutlinedTextField(
|
|
|
|
|
+ value = title,
|
|
|
|
|
+ onValueChange = { title = it },
|
|
|
|
|
+ label = { Text("目标名称") },
|
|
|
|
|
+ placeholder = { Text("例如:晚饭后跑步30分钟") },
|
|
|
|
|
+ singleLine = true,
|
|
|
|
|
+ modifier = Modifier.fillMaxWidth()
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ Text("目标类别", style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold)
|
|
|
|
|
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
|
|
|
+ categories.take(3).forEach { category ->
|
|
|
|
|
+ FilterChip(
|
|
|
|
|
+ selected = selectedCategory == category,
|
|
|
|
|
+ onClick = { selectedCategory = category },
|
|
|
|
|
+ label = { Text(category) }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
|
|
|
+ categories.drop(3).forEach { category ->
|
|
|
|
|
+ FilterChip(
|
|
|
|
|
+ selected = selectedCategory == category,
|
|
|
|
|
+ onClick = { selectedCategory = category },
|
|
|
|
|
+ label = { Text(category) }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Card(
|
|
|
|
|
+ shape = RoundedCornerShape(16.dp),
|
|
|
|
|
+ colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Row(
|
|
|
|
|
+ modifier = Modifier
|
|
|
|
|
+ .fillMaxWidth()
|
|
|
|
|
+ .padding(horizontal = 12.dp, vertical = 10.dp),
|
|
|
|
|
+ horizontalArrangement = Arrangement.SpaceBetween,
|
|
|
|
|
+ verticalAlignment = Alignment.CenterVertically
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text("提醒时间:${DateUtils.toMinutesLabel(hour, minute)}")
|
|
|
|
|
+ TextButton(onClick = {
|
|
|
|
|
+ TimePickerDialog(
|
|
|
|
|
+ context,
|
|
|
|
|
+ { _, selectedHour, selectedMinute ->
|
|
|
|
|
+ hour = selectedHour
|
|
|
|
|
+ minute = selectedMinute
|
|
|
|
|
+ },
|
|
|
|
|
+ hour,
|
|
|
|
|
+ minute,
|
|
|
|
|
+ true
|
|
|
|
|
+ ).show()
|
|
|
|
|
+ }) {
|
|
|
|
|
+ Text("修改")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Row(
|
|
|
|
|
+ modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
+ horizontalArrangement = Arrangement.End
|
|
|
|
|
+ ) {
|
|
|
|
|
+ TextButton(onClick = onDismiss) { Text("取消") }
|
|
|
|
|
+ Spacer(modifier = Modifier.size(8.dp))
|
|
|
|
|
+ Button(
|
|
|
|
|
+ onClick = { onConfirm(title.trim(), selectedCategory, hour, minute) },
|
|
|
|
|
+ enabled = title.trim().isNotBlank()
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text("保存")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|