This commit is contained in:
2025-10-08 18:08:15 +08:00
parent dc71bb19a9
commit 989e5be041
109 changed files with 10815 additions and 170 deletions

View File

@@ -0,0 +1,258 @@
package com.taskttl.presentation.countdown
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.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CalendarToday
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.taskttl.core.ui.Chip
import com.taskttl.core.utils.DateUtils
import com.taskttl.data.state.CountdownEffect
import com.taskttl.data.viewmodel.CountdownViewModel
import com.taskttl.ui.components.AppHeader
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel
import taskttl.composeapp.generated.resources.Res
import taskttl.composeapp.generated.resources.countdown_not_found
import taskttl.composeapp.generated.resources.created_at
import taskttl.composeapp.generated.resources.detail_information
import taskttl.composeapp.generated.resources.event_description
import taskttl.composeapp.generated.resources.label_days
import taskttl.composeapp.generated.resources.reminder
import taskttl.composeapp.generated.resources.title_countdown_info
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CountdownDetailScreen(
countdownId: String,
onNavigateBack: () -> Unit,
onNavigateToEdit: () -> Unit,
viewModel: CountdownViewModel = koinViewModel()
) {
val state by viewModel.state.collectAsState()
val countdown = state.countdowns.find { it.id == countdownId }
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
when (effect) {
is CountdownEffect.NavigateBack -> {
onNavigateBack()
}
else -> {}
}
}
}
if (countdown == null) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(stringResource(Res.string.countdown_not_found))
}
return
}
// 剩余天数
val daysRemaining = DateUtils.daysRemaining(countdown.targetDate)
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
) {
Column(
modifier = Modifier.fillMaxSize()
) {
AppHeader(
title = Res.string.title_countdown_info,
showBack = true,
onBackClick = { onNavigateBack.invoke() },
trailingIcon = Icons.Default.Edit,
onTrailingClick = { onNavigateToEdit.invoke() }
)
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.padding(16.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(20.dp))
.background(
brush = androidx.compose.ui.graphics.Brush.linearGradient(
colors = listOf(
countdown.category.color.backgroundColor,
Color.Transparent
)
)
)
.padding(20.dp)
) {
Column(modifier = Modifier.align(Alignment.CenterStart)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = countdown.title,
fontSize = 18.sp,
fontWeight = FontWeight.ExtraBold,
color = Color(0xFF111111)
)
Spacer(modifier = Modifier.width(6.dp))
Chip(text = countdown.category.name)
}
Spacer(modifier = Modifier.height(6.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Default.CalendarToday,
contentDescription = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = countdown.targetDate.toString(),
fontSize = 14.sp,
color = Color(0xFF444444)
)
}
}
Column(
modifier = Modifier.align(Alignment.TopEnd),
horizontalAlignment = Alignment.End
) {
Text(
text = daysRemaining.toString(),
fontSize = 44.sp,
fontWeight = FontWeight.ExtraBold,
color = Color(0xFF111111)
)
Text(
text = stringResource(Res.string.label_days),
fontSize = 12.sp,
fontWeight = FontWeight.SemiBold,
color = Color(0xFF666666)
)
}
}
countdown.description.let {
Spacer(modifier = Modifier.height(16.dp))
Column(modifier = Modifier.fillMaxWidth()) {
Text(
text = stringResource(Res.string.event_description),
fontWeight = FontWeight.SemiBold,
fontSize = 14.sp,
color = Color(0xFF333333)
)
Spacer(modifier = Modifier.height(10.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(Color.White)
.padding(12.dp)
) {
Column {
Text(
text = countdown.description,
color = countdown.category.color.textColor,
lineHeight = 20.sp
)
}
}
}
}
Spacer(modifier = Modifier.height(12.dp))
Column(modifier = Modifier.fillMaxWidth()) {
Text(
text = stringResource(Res.string.detail_information),
fontWeight = FontWeight.SemiBold,
fontSize = 14.sp,
color = Color(0xFF333333)
)
Spacer(modifier = Modifier.height(10.dp))
Column {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
InfoItem(
iconTint = Color(0xFF667EEA),
text = "${stringResource(Res.string.reminder)}${
stringResource(countdown.notificationEnabled.displayNameRes)
}",
modifier = Modifier.weight(1f)
)
}
Spacer(modifier = Modifier.height(10.dp))
Row(modifier = Modifier.fillMaxWidth()) {
InfoItem(
iconTint = Color(0xFF999999),
text = "${stringResource(Res.string.created_at)}${countdown.createdAt}",
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
}
}
}
@Composable
private fun InfoItem(iconTint: Color, text: String, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.clip(RoundedCornerShape(10.dp))
.background(Color.White)
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(18.dp)
.clip(RoundedCornerShape(6.dp))
.background(iconTint)
) {}
Spacer(modifier = Modifier.width(10.dp))
Text(text = text, fontSize = 13.sp, color = Color(0xFF555555))
}
}

View File

@@ -0,0 +1,274 @@
package com.taskttl.presentation.countdown
import androidx.compose.foundation.clickable
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.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.taskttl.data.local.model.Category
import com.taskttl.data.local.model.Countdown
import com.taskttl.data.local.model.ReminderFrequency
import com.taskttl.data.state.CountdownEffect
import com.taskttl.data.state.CountdownIntent
import com.taskttl.data.viewmodel.CountdownViewModel
import com.taskttl.ui.components.AppHeader
import com.taskttl.ui.components.CategoryCard
import com.taskttl.ui.components.CompactDatePickerDialog
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel
import taskttl.composeapp.generated.resources.Res
import taskttl.composeapp.generated.resources.desc_select_date
import taskttl.composeapp.generated.resources.label_countdown_description
import taskttl.composeapp.generated.resources.label_countdown_title
import taskttl.composeapp.generated.resources.label_notification_setting
import taskttl.composeapp.generated.resources.label_select_category
import taskttl.composeapp.generated.resources.label_target_date
import taskttl.composeapp.generated.resources.title_add_countdown
import taskttl.composeapp.generated.resources.title_edit_countdown
import kotlin.time.Clock
import kotlin.time.ExperimentalTime
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@OptIn(ExperimentalMaterial3Api::class, ExperimentalTime::class, ExperimentalUuidApi::class)
@Composable
fun CountdownEditScreen(
countdownId: String? = null,
onNavigateBack: () -> Unit,
viewModel: CountdownViewModel = koinViewModel()
) {
LaunchedEffect(countdownId) {
countdownId?.let { viewModel.handleIntent(CountdownIntent.GetCountdownById(it)) }
}
val state by viewModel.state.collectAsState()
val editingCountdown = state.editingCountdown
var showDatePicker by remember { mutableStateOf(false) }
var title by remember { mutableStateOf("") }
var description by remember { mutableStateOf("") }
var selectedCategory by remember { mutableStateOf<Category?>(null) }
var targetDate by remember { mutableStateOf<LocalDateTime?>(null) }
var notificationEnabled by remember { mutableStateOf(ReminderFrequency.OFF) }
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
when (effect) {
is CountdownEffect.NavigateBack -> {
onNavigateBack.invoke()
}
else -> {}
}
}
}
LaunchedEffect(editingCountdown) {
editingCountdown?.let {
title = it.title
description = it.description
selectedCategory = it.category
targetDate = it.targetDate
notificationEnabled = it.notificationEnabled
}
}
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier.fillMaxSize()
) {
AppHeader(
title = if (countdownId == null) Res.string.title_add_countdown else Res.string.title_edit_countdown,
showBack = true,
onBackClick = { onNavigateBack.invoke() },
trailingIcon = if (countdownId == null) Icons.Default.Add else Icons.Default.Edit,
onTrailingClick = {
if (title.isNotBlank() && targetDate != null && selectedCategory != null) {
val now = Clock.System.now()
.toLocalDateTime(TimeZone.currentSystemDefault())
val countdown = Countdown(
id = editingCountdown?.id ?: Uuid.random().toString(),
title = title.trim(),
description = description.trim(),
category = selectedCategory!!,
targetDate = targetDate!!,
createdAt = now,
updatedAt = now,
notificationEnabled = notificationEnabled
)
if (countdownId == null) {
viewModel.handleIntent(CountdownIntent.AddCountdown(countdown))
} else {
editingCountdown?.let { countdown.isActive = editingCountdown.isActive }
viewModel.handleIntent(CountdownIntent.UpdateCountdown(countdown))
}
}
}
)
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
) {
// 倒数日标题
OutlinedTextField(
value = title,
onValueChange = { title = it },
label = { Text(stringResource(Res.string.label_countdown_title)) },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
Spacer(modifier = Modifier.height(16.dp))
// 倒数日描述
OutlinedTextField(
value = description,
onValueChange = { description = it },
label = { Text(stringResource(Res.string.label_countdown_description)) },
modifier = Modifier.fillMaxWidth(),
minLines = 3,
maxLines = 5
)
Spacer(modifier = Modifier.height(24.dp))
// 分类选择
Text(
text = stringResource(Res.string.label_select_category),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(12.dp))
LazyVerticalGrid(
columns = GridCells.Fixed(4),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.height(80.dp)
) {
items(state.categories) { category ->
CategoryCard(
category = category,
isSelected = selectedCategory == category,
onClick = { selectedCategory = category }
)
}
}
Spacer(modifier = Modifier.height(24.dp))
// 目标日期
OutlinedTextField(
value = targetDate?.toString() ?: "",
onValueChange = { },
label = { Text(stringResource(Res.string.label_target_date)) },
modifier = Modifier.fillMaxWidth().clickable { showDatePicker = true },
readOnly = true,
trailingIcon = {
IconButton(onClick = { showDatePicker = true }) {
Icon(
Icons.Default.DateRange,
contentDescription = stringResource(Res.string.desc_select_date)
)
}
}
)
CompactDatePickerDialog(
show = showDatePicker,
initialSelected = targetDate,
onConfirm = { selected -> targetDate = selected },
onDismiss = { showDatePicker = false }
)
Spacer(modifier = Modifier.height(24.dp))
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)
) {
Text(
text = stringResource(Res.string.label_notification_setting),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
ReminderFrequency.entries.forEach { frequency ->
FilterChip(
selected = notificationEnabled == frequency,
onClick = { notificationEnabled = frequency },
label = {
Text(
text = stringResource(frequency.displayNameRes),
textAlign = TextAlign.Center,
fontSize = 10.sp,
modifier = Modifier.fillMaxWidth()
)
},
modifier = Modifier.weight(1f),
colors = FilterChipDefaults.filterChipColors(
selectedContainerColor = MaterialTheme.colorScheme.primary.copy(
alpha = 0.2f
),
selectedLabelColor = MaterialTheme.colorScheme.primary
),
)
Spacer(modifier = Modifier.width(8.dp))
}
}
}
}
}
}
}

View File

@@ -0,0 +1,543 @@
package com.taskttl.presentation.countdown
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
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.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.CalendarToday
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.FilterList
import androidx.compose.material.icons.filled.MoreHoriz
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Switch
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.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.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
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.sp
import androidx.navigation.NavHostController
import com.taskttl.core.routes.Routes
import com.taskttl.core.utils.DateUtils
import com.taskttl.data.local.model.Countdown
import com.taskttl.data.state.CountdownEffect
import com.taskttl.data.state.CountdownIntent
import com.taskttl.data.viewmodel.CountdownViewModel
import com.taskttl.ui.components.AppHeader
import com.taskttl.ui.components.CategoryFilter
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.delete
import taskttl.composeapp.generated.resources.desc_add_countdown
import taskttl.composeapp.generated.resources.label_countdown_list
import taskttl.composeapp.generated.resources.label_days
import taskttl.composeapp.generated.resources.label_edit
import taskttl.composeapp.generated.resources.text_add_countdown_tip
import taskttl.composeapp.generated.resources.text_no_countdowns
import taskttl.composeapp.generated.resources.title_countdown
import taskttl.composeapp.generated.resources.title_edit_countdown
@Composable
@Preview
fun CountdownScreen(
navController: NavHostController,
viewModel: CountdownViewModel = koinViewModel()
) {
val state by viewModel.state.collectAsState()
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
when (effect) {
is CountdownEffect.NavigateBack -> {
navController.popBackStack()
}
is CountdownEffect.NavigateToCountdownDetail -> {
// onNavigateToCountdownDetail(effect.countdownId)
}
else -> {}
}
}
}
state.error?.let { error ->
LaunchedEffect(error) {
viewModel.handleIntent(CountdownIntent.ClearError)
}
}
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
) {
Column(modifier = Modifier.fillMaxSize()) {
AppHeader(
title = Res.string.title_countdown,
trailingIcon = Icons.Default.FilterList
)
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.padding(16.dp)
) {
// 分类筛选
CategoryFilter(
categories = state.categories,
selectedCategory = state.selectedCategory,
onCategorySelected = {
viewModel.handleIntent(CountdownIntent.FilterByCategory(it))
},
modifier = Modifier.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "${stringResource(Res.string.label_countdown_list)} (${state.filteredCountdowns.size})",
style = MaterialTheme.typography.titleMedium
)
Spacer(modifier = Modifier.height(8.dp))
when {
state.isLoading -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
state.filteredCountdowns.isEmpty() -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = stringResource(Res.string.text_no_countdowns),
style = MaterialTheme.typography.bodyLarge
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(Res.string.text_add_countdown_tip),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
else -> {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(state.filteredCountdowns) { countdown ->
CountdownCard(
countdown = countdown,
onCardClick = {
navController.navigate(
Routes.Main.Countdown.CountdownDetail(countdown.id)
)
},
onEdit = {
navController.navigate(
Routes.Main.Countdown.EditCountdown(countdown.id)
)
},
onDelete = {
viewModel.handleIntent(
CountdownIntent.DeleteCountdown(countdown.id)
)
}
)
}
}
}
}
}
}
// 悬浮按钮
FloatingActionButton(
onClick = { navController.navigate(Routes.Main.Countdown.AddCountdown) },
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(20.dp),
containerColor = Color(0xFF667EEA)
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = stringResource(Res.string.desc_add_countdown)
)
}
}
}
@Composable
fun CountdownCard(
countdown: Countdown,
onEdit: () -> Unit = {},
onDelete: () -> Unit = {},
onCardClick: () -> Unit = {}
) {
val countdownTime = DateUtils.calculateCountdownTime(countdown.targetDate)
countdown.category
Box(
modifier = Modifier
.fillMaxWidth()
.shadow(4.dp, RoundedCornerShape(12.dp))
.background(Color.White, RoundedCornerShape(12.dp))
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) { onCardClick() }
) {
Column {
Box(
modifier = Modifier
.fillMaxWidth()
.height(4.dp)
.background(countdown.category.color.backgroundColor)
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 6.dp, end = 6.dp, top = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = countdown.title,
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold,
color = Color(0xFF1A1A1A)
)
Spacer(modifier = Modifier.height(4.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Default.CalendarToday,
contentDescription = null,
tint = Color(0xFF666666),
modifier = Modifier.size(14.dp),
)
Spacer(modifier = Modifier.width(6.dp))
Text(
text = countdown.targetDate.toString(),
fontSize = 14.sp,
color = Color(0xFF666666)
)
}
}
Column(
horizontalAlignment = Alignment.End,
modifier = Modifier.widthIn(min = 80.dp)
) {
Text(
text = countdownTime.days.toString(),
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = countdown.category.color.textColor
)
Text(
text = stringResource(Res.string.label_days),
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF999999)
)
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 6.dp, end = 6.dp, bottom = 8.dp)
) {
if (countdown.description.isNotBlank()) {
Spacer(modifier = Modifier.height(6.dp))
Text(
text = countdown.description,
fontSize = 13.sp,
color = Color(0xFF999999),
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 6.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(
modifier = Modifier.background(
countdown.category.color.backgroundColor,
RoundedCornerShape(20.dp)
).padding(horizontal = 12.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = countdown.category.icon.icon,
contentDescription = null,
tint = countdown.category.color.iconColor,
modifier = Modifier.size(14.dp)
)
Spacer(modifier = Modifier.width(6.dp))
Text(
text = countdown.category.name,
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
color = countdown.category.color.textColor
)
}
var showReminderDialog by remember { mutableStateOf(false) }
var showMoreMenu by remember { mutableStateOf(false) }
Row(verticalAlignment = Alignment.CenterVertically) {
// IconBut({ showReminderDialog = true }, Icons.Default.Notifications)
// Spacer(modifier = Modifier.width(6.dp))
Box {
IconBut({ showMoreMenu = true }, Icons.Default.MoreHoriz)
DropdownMenu(
expanded = showMoreMenu,
onDismissRequest = { showMoreMenu = false }
) {
DropdownMenuItem(
text = { Text(stringResource(Res.string.label_edit)) },
onClick = {
onEdit.invoke();
showMoreMenu = false
}
)
// DropdownMenuItem(
// text = { Text("分享倒数日") },
// onClick = {
// // onShare();
// showMoreMenu = false
// }
// )
DropdownMenuItem(
text = {
Text(
text = stringResource(Res.string.delete),
color = Color.Red
)
},
onClick = {
onDelete.invoke();
showMoreMenu = false
}
)
}
}
}
if (showReminderDialog) {
ReminderDialog(
onDismiss = { showReminderDialog = false },
onSave = { /* TODO: 保存提醒逻辑 */ }
)
}
}
}
}
}
@Composable
fun IconBut(onClick: () -> Unit = {}, icon: ImageVector) {
Box(
modifier = Modifier
.size(32.dp)
.clip(CircleShape)
.background(Color(0xFFF5F5F5))
.clickable { onClick() },
contentAlignment = Alignment.Center
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = Color(0xFF666666),
modifier = Modifier.size(20.dp)
)
}
}
@Composable
fun ReminderDialog(
onDismiss: () -> Unit,
onSave: (String) -> Unit
) {
var isEnabled by remember { mutableStateOf(true) }
var timeBefore by remember { mutableStateOf("1天前") }
var expanded by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = { onDismiss() },
title = { Text("提醒设置") },
text = {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text("启用提醒")
Spacer(modifier = Modifier.weight(1f))
Switch(checked = isEnabled, onCheckedChange = { isEnabled = it })
}
if (isEnabled) {
Text("提前提醒时间")
Box {
OutlinedButton(
onClick = { expanded = true },
modifier = Modifier.fillMaxWidth()
) {
Text(timeBefore)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
listOf("当天", "1天前", "3天前", "1周前").forEach {
DropdownMenuItem(
text = { Text(it) },
onClick = {
timeBefore = it
expanded = false
}
)
}
}
}
}
}
},
confirmButton = {
TextButton(onClick = {
onSave(timeBefore)
onDismiss()
}) {
Text("保存")
}
},
dismissButton = {
TextButton(onClick = { onDismiss() }) {
Text("取消")
}
}
)
}
@Composable
fun MoreActionsDialog(
onDismiss: () -> Unit,
onEdit: () -> Unit,
onShare: () -> Unit,
onDelete: () -> Unit
) {
AlertDialog(
onDismissRequest = { onDismiss() },
title = { Text("更多操作") },
text = {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
ActionItem("编辑倒数日", Icons.Default.Edit) {
onEdit(); onDismiss()
}
ActionItem("分享倒数日", Icons.Default.Share) {
onShare(); onDismiss()
}
ActionItem("删除倒数日", Icons.Default.Delete, danger = true) {
onDelete(); onDismiss()
}
}
},
confirmButton = {
TextButton(onClick = { onDismiss() }) {
Text("关闭")
}
}
)
}
@Composable
private fun ActionItem(
text: String,
icon: ImageVector,
danger: Boolean = false,
onClick: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.clickable { onClick() }
.background(if (danger) Color(0xFFFFE6E6) else Color(0xFFF5F5F5))
.padding(horizontal = 12.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = if (danger) Color(0xFFE53E3E) else Color(0xFF555555)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = text,
color = if (danger) Color(0xFFE53E3E) else Color(0xFF333333),
style = MaterialTheme.typography.bodyMedium
)
}
}