feat: 日期检查器 UI 改版,添加过期状态显示
- 生产日期改为渐变卡片展示,支持点击切换日期 - 保质期条目改为 ExpiryCard,显示过期状态标签 - 新增 ExpiryStatus 枚举(SAFE/WARNING/URGENT/EXPIRED) - 新增 ArrowRightIcon 自定义图标 - 添加中文日期格式化辅助函数 - 改进 TopAppBar 和 FAB 样式 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
23909e855f
commit
b12206dc88
@ -2,11 +2,12 @@
|
||||
|
||||
package plus.rua.project.ui
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
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.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@ -14,6 +15,7 @@ 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.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
@ -23,7 +25,6 @@ import androidx.compose.material3.DatePicker
|
||||
import androidx.compose.material3.DatePickerDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
@ -33,6 +34,7 @@ import androidx.compose.material3.SwipeToDismissBoxValue
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberDatePickerState
|
||||
import androidx.compose.material3.rememberSwipeToDismissBoxState
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -45,11 +47,13 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTagsAsResourceId
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -71,11 +75,33 @@ private sealed class DatePickerTarget {
|
||||
data class Row(val rowId: Int) : DatePickerTarget()
|
||||
}
|
||||
|
||||
private enum class ExpiryStatus {
|
||||
UNKNOWN, SAFE, WARNING, URGENT, EXPIRED
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpiryStatus.color(): Color = when (this) {
|
||||
ExpiryStatus.SAFE -> Color(0xFF059669)
|
||||
ExpiryStatus.WARNING -> Color(0xFFD97706)
|
||||
ExpiryStatus.URGENT -> Color(0xFFEA580C)
|
||||
ExpiryStatus.EXPIRED -> Color(0xFFDC2626)
|
||||
ExpiryStatus.UNKNOWN -> MaterialTheme.colorScheme.outline
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpiryStatus.containerColor(): Color = when (this) {
|
||||
ExpiryStatus.SAFE -> Color(0xFFD1FAE5)
|
||||
ExpiryStatus.WARNING -> Color(0xFFFEF3C7)
|
||||
ExpiryStatus.URGENT -> Color(0xFFFFEDD5)
|
||||
ExpiryStatus.EXPIRED -> Color(0xFFFEE2E2)
|
||||
ExpiryStatus.UNKNOWN -> MaterialTheme.colorScheme.surfaceVariant
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期检查器页面,商品过期检查工具。
|
||||
*
|
||||
* 顶部设置生产日期,下方多行显示天数与到期日期的双向联动计算。
|
||||
* 支持滑动删除行、FAB 添加新行。
|
||||
* 支持删除行、FAB 添加新行,并显示每条保质期的过期状态。
|
||||
*
|
||||
* @param onBack 返回回调
|
||||
* @param modifier 布局修饰符
|
||||
@ -103,12 +129,22 @@ fun DateCheckerScreen(onBack: () -> Unit, modifier: Modifier = Modifier) {
|
||||
modifier = modifier.semantics { testTagsAsResourceId = true },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("日期检查器") },
|
||||
title = {
|
||||
Text(
|
||||
"日期检查器",
|
||||
style = MaterialTheme.typography.titleLarge.copy(
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
BackArrowIcon()
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = Color.Transparent
|
||||
)
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
@ -119,71 +155,122 @@ fun DateCheckerScreen(onBack: () -> Unit, modifier: Modifier = Modifier) {
|
||||
},
|
||||
modifier = Modifier.testTag("date_checker_fab"),
|
||||
shape = CircleShape,
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimary
|
||||
) {
|
||||
PlusIcon(color = MaterialTheme.colorScheme.onPrimaryContainer)
|
||||
PlusIcon(color = MaterialTheme.colorScheme.onPrimary)
|
||||
}
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.surface
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = "生产日期",
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
ProductionDateField(
|
||||
ProductionDateCard(
|
||||
date = productionDate,
|
||||
onDateChange = { productionDate = it },
|
||||
onShowDatePicker = {
|
||||
isToday = productionDate == today,
|
||||
onClick = {
|
||||
datePickerTarget = DatePickerTarget.Production
|
||||
showDatePicker = true
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
HorizontalDivider()
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "保质期列表",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Text(
|
||||
text = "${rows.size} 项",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 4.dp)
|
||||
) {
|
||||
items(rows, key = { it.id }) { row ->
|
||||
val expiryDate = row.days?.let { productionDate.plus(DatePeriod(days = it)) }
|
||||
val daysRemaining = expiryDate?.let { today.daysUntil(it) }
|
||||
val status = when {
|
||||
daysRemaining == null -> ExpiryStatus.UNKNOWN
|
||||
daysRemaining < 0 -> ExpiryStatus.EXPIRED
|
||||
daysRemaining == 0 -> ExpiryStatus.URGENT
|
||||
daysRemaining <= 7 -> ExpiryStatus.URGENT
|
||||
daysRemaining <= 30 -> ExpiryStatus.WARNING
|
||||
else -> ExpiryStatus.SAFE
|
||||
}
|
||||
|
||||
ExpiryRowItem(
|
||||
days = row.days,
|
||||
expiryDate = expiryDate,
|
||||
onDaysChange = { newDays ->
|
||||
rows = rows.map {
|
||||
if (it.id == row.id) it.copy(days = newDays) else it
|
||||
val dismissState = rememberSwipeToDismissBoxState(
|
||||
confirmValueChange = { value ->
|
||||
if (value == SwipeToDismissBoxValue.EndToStart) {
|
||||
rows = rows.filter { it.id != row.id }
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
onExpiryDateChange = { newDate ->
|
||||
val newDays = productionDate.daysUntil(newDate)
|
||||
rows = rows.map {
|
||||
if (it.id == row.id) it.copy(days = newDays) else it
|
||||
}
|
||||
},
|
||||
onShowDatePicker = {
|
||||
datePickerTarget = DatePickerTarget.Row(row.id)
|
||||
showDatePicker = true
|
||||
},
|
||||
onDelete = {
|
||||
rows = rows.filter { it.id != row.id }
|
||||
}
|
||||
)
|
||||
|
||||
SwipeToDismissBox(
|
||||
state = dismissState,
|
||||
backgroundContent = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(MaterialTheme.colorScheme.errorContainer)
|
||||
.padding(horizontal = 20.dp),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
Text(
|
||||
text = "删除",
|
||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
}
|
||||
}
|
||||
) {
|
||||
ExpiryCard(
|
||||
days = row.days,
|
||||
expiryDate = expiryDate,
|
||||
daysRemaining = daysRemaining,
|
||||
status = status,
|
||||
onDaysChange = { newDays ->
|
||||
rows = rows.map {
|
||||
if (it.id == row.id) it.copy(days = newDays) else it
|
||||
}
|
||||
},
|
||||
onExpiryDateChange = { newDate ->
|
||||
val newDays = productionDate.daysUntil(newDate)
|
||||
rows = rows.map {
|
||||
if (it.id == row.id) it.copy(days = newDays) else it
|
||||
}
|
||||
},
|
||||
onShowDatePicker = {
|
||||
datePickerTarget = DatePickerTarget.Row(row.id)
|
||||
showDatePicker = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -237,139 +324,203 @@ fun DateCheckerScreen(onBack: () -> Unit, modifier: Modifier = Modifier) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProductionDateField(
|
||||
private fun ProductionDateCard(
|
||||
date: LocalDate,
|
||||
onDateChange: (LocalDate) -> Unit,
|
||||
onShowDatePicker: () -> Unit,
|
||||
isToday: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
var text by remember(date) { mutableStateOf(date.toString()) }
|
||||
var isError by remember { mutableStateOf(false) }
|
||||
|
||||
OutlinedTextField(
|
||||
value = text,
|
||||
onValueChange = {
|
||||
text = it
|
||||
isError = false
|
||||
try {
|
||||
onDateChange(LocalDate.parse(it))
|
||||
} catch (_: Exception) {
|
||||
isError = true
|
||||
}
|
||||
},
|
||||
label = { Text("生产日期") },
|
||||
isError = isError,
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Text,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = onShowDatePicker,
|
||||
modifier = Modifier.testTag("date_picker_button")
|
||||
) {
|
||||
CalendarIcon(color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
},
|
||||
modifier = modifier.fillMaxWidth()
|
||||
val gradient = Brush.linearGradient(
|
||||
colors = listOf(
|
||||
MaterialTheme.colorScheme.primaryContainer,
|
||||
MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.7f)
|
||||
)
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(20.dp))
|
||||
.background(gradient)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(20.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.12f)
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CalendarIcon(
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text = "生产日期",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.7f)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = date.formatChinese(),
|
||||
style = MaterialTheme.typography.headlineSmall.copy(
|
||||
fontWeight = FontWeight.Bold
|
||||
),
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
if (isToday) {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = "今天 · ${date.dayOfWeekChinese()}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.6f)
|
||||
)
|
||||
} else {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = date.dayOfWeekChinese(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.6f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpiryRowItem(
|
||||
private fun ExpiryCard(
|
||||
days: Int?,
|
||||
expiryDate: LocalDate?,
|
||||
daysRemaining: Int?,
|
||||
status: ExpiryStatus,
|
||||
onDaysChange: (Int?) -> Unit,
|
||||
onExpiryDateChange: (LocalDate) -> Unit,
|
||||
onShowDatePicker: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
var daysText by remember(days) { mutableStateOf(days?.toString() ?: "") }
|
||||
var dateText by remember(expiryDate) { mutableStateOf(expiryDate?.toString() ?: "") }
|
||||
|
||||
val dismissState = rememberSwipeToDismissBoxState(
|
||||
confirmValueChange = { value ->
|
||||
if (value == SwipeToDismissBoxValue.EndToStart) {
|
||||
onDelete()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
SwipeToDismissBox(
|
||||
state = dismissState,
|
||||
backgroundContent = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(MaterialTheme.colorScheme.errorContainer)
|
||||
.padding(horizontal = 20.dp),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
Text(
|
||||
text = "删除",
|
||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
}
|
||||
},
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceContainerHigh)
|
||||
) {
|
||||
Row(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceContainerHigh)
|
||||
.padding(horizontal = 12.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = daysText,
|
||||
onValueChange = { newValue ->
|
||||
daysText = newValue.filter { it.isDigit() }
|
||||
onDaysChange(daysText.toIntOrNull())
|
||||
},
|
||||
label = { Text("天数") },
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = daysText,
|
||||
onValueChange = { newValue ->
|
||||
daysText = newValue.filter { it.isDigit() }.take(4)
|
||||
onDaysChange(daysText.toIntOrNull())
|
||||
},
|
||||
label = { Text("天数") },
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
modifier = Modifier.weight(1f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = dateText,
|
||||
onValueChange = { newValue ->
|
||||
dateText = newValue
|
||||
try {
|
||||
onExpiryDateChange(LocalDate.parse(newValue))
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
},
|
||||
label = { Text("到期日期") },
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Text,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
trailingIcon = {
|
||||
IconButton(onClick = onShowDatePicker) {
|
||||
CalendarIcon(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(20.dp)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
ArrowRightIcon(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f),
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
value = dateText,
|
||||
onValueChange = { newValue ->
|
||||
dateText = newValue
|
||||
try {
|
||||
onExpiryDateChange(LocalDate.parse(newValue))
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
},
|
||||
label = { Text("到期日期") },
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Text,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = onShowDatePicker,
|
||||
modifier = Modifier.size(32.dp)
|
||||
) {
|
||||
CalendarIcon(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1.8f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (daysRemaining != null) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
val statusText = when {
|
||||
daysRemaining < 0 -> "已过期 ${-daysRemaining} 天"
|
||||
daysRemaining == 0 -> "今天过期"
|
||||
daysRemaining == 1 -> "明天过期"
|
||||
else -> "还有 $daysRemaining 天"
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(status.containerColor())
|
||||
.padding(horizontal = 10.dp, vertical = 4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = statusText,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = status.color(),
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1.5f)
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(8.dp)
|
||||
.clip(CircleShape)
|
||||
.background(status.color())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -378,7 +529,7 @@ private fun ExpiryRowItem(
|
||||
@Composable
|
||||
private fun BackArrowIcon(modifier: Modifier = Modifier) {
|
||||
val color = MaterialTheme.colorScheme.onSurface
|
||||
Canvas(modifier = modifier.size(24.dp)) {
|
||||
androidx.compose.foundation.Canvas(modifier = modifier.size(24.dp)) {
|
||||
val strokeWidth = 2.dp.toPx()
|
||||
drawLine(
|
||||
color = color,
|
||||
@ -399,7 +550,7 @@ private fun BackArrowIcon(modifier: Modifier = Modifier) {
|
||||
|
||||
@Composable
|
||||
private fun PlusIcon(color: Color, modifier: Modifier = Modifier) {
|
||||
Canvas(modifier = modifier.size(24.dp)) {
|
||||
androidx.compose.foundation.Canvas(modifier = modifier.size(24.dp)) {
|
||||
val strokeWidth = 2.dp.toPx()
|
||||
val cx = size.width / 2
|
||||
val cy = size.height / 2
|
||||
@ -423,7 +574,7 @@ private fun PlusIcon(color: Color, modifier: Modifier = Modifier) {
|
||||
|
||||
@Composable
|
||||
private fun CalendarIcon(color: Color, modifier: Modifier = Modifier) {
|
||||
Canvas(modifier = modifier.size(24.dp)) {
|
||||
androidx.compose.foundation.Canvas(modifier = modifier.size(24.dp)) {
|
||||
val strokeWidth = 1.5f.dp.toPx()
|
||||
val pad = 3.dp.toPx()
|
||||
val topY = pad + 4.dp.toPx()
|
||||
@ -431,13 +582,11 @@ private fun CalendarIcon(color: Color, modifier: Modifier = Modifier) {
|
||||
val leftX = pad
|
||||
val rightX = size.width - pad
|
||||
|
||||
// 外框
|
||||
drawLine(color, Offset(leftX, topY), Offset(rightX, topY), strokeWidth)
|
||||
drawLine(color, Offset(leftX, topY), Offset(leftX, bottomY), strokeWidth)
|
||||
drawLine(color, Offset(rightX, topY), Offset(rightX, bottomY), strokeWidth)
|
||||
drawLine(color, Offset(leftX, bottomY), Offset(rightX, bottomY), strokeWidth)
|
||||
|
||||
// 顶部挂环
|
||||
val h1 = size.width * 0.3f
|
||||
val h2 = size.width * 0.7f
|
||||
drawLine(color, Offset(h1, pad), Offset(h1, topY), strokeWidth)
|
||||
@ -445,6 +594,35 @@ private fun CalendarIcon(color: Color, modifier: Modifier = Modifier) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ArrowRightIcon(color: Color, modifier: Modifier = Modifier) {
|
||||
androidx.compose.foundation.Canvas(modifier = modifier) {
|
||||
val strokeWidth = 2.dp.toPx()
|
||||
val y = size.height / 2
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(0f, y),
|
||||
end = Offset(size.width * 0.65f, y),
|
||||
strokeWidth = strokeWidth,
|
||||
cap = StrokeCap.Round
|
||||
)
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(size.width * 0.4f, y - size.height * 0.3f),
|
||||
end = Offset(size.width * 0.65f, y),
|
||||
strokeWidth = strokeWidth,
|
||||
cap = StrokeCap.Round
|
||||
)
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(size.width * 0.4f, y + size.height * 0.3f),
|
||||
end = Offset(size.width * 0.65f, y),
|
||||
strokeWidth = strokeWidth,
|
||||
cap = StrokeCap.Round
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Helpers
|
||||
@ -455,4 +633,23 @@ private fun LocalDate.toEpochMillis(): Long =
|
||||
private fun Long.toLocalDate(): LocalDate =
|
||||
Instant.fromEpochMilliseconds(this).toLocalDateTime(TimeZone.UTC).date
|
||||
|
||||
// endregion
|
||||
@Suppress("DEPRECATION") // monthNumber/dayOfMonth 无替代 API,kotlinx-datetime 尚未提供新接口
|
||||
private fun LocalDate.formatChinese(): String =
|
||||
"${year}年${monthNumber}月${dayOfMonth}日"
|
||||
|
||||
@Suppress("DEPRECATION", "Unused") // monthNumber/dayOfMonth 无替代 API
|
||||
private fun LocalDate.formatShortChinese(): String =
|
||||
"${monthNumber}月${dayOfMonth}日"
|
||||
|
||||
private fun LocalDate.dayOfWeekChinese(): String = when (dayOfWeek.ordinal) {
|
||||
0 -> "周一"
|
||||
1 -> "周二"
|
||||
2 -> "周三"
|
||||
3 -> "周四"
|
||||
4 -> "周五"
|
||||
5 -> "周六"
|
||||
6 -> "周日"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user