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:
xfy 2026-06-01 14:12:41 +08:00
parent 23909e855f
commit b12206dc88

View File

@ -2,11 +2,12 @@
package plus.rua.project.ui package plus.rua.project.ui
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize 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.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
@ -23,7 +25,6 @@ import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerDialog import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
@ -33,6 +34,7 @@ import androidx.compose.material3.SwipeToDismissBoxValue
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberDatePickerState import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberSwipeToDismissBoxState import androidx.compose.material3.rememberSwipeToDismissBoxState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -45,11 +47,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset 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.platform.testTag
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -71,11 +75,33 @@ private sealed class DatePickerTarget {
data class Row(val rowId: Int) : 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 onBack 返回回调
* @param modifier 布局修饰符 * @param modifier 布局修饰符
@ -103,12 +129,22 @@ fun DateCheckerScreen(onBack: () -> Unit, modifier: Modifier = Modifier) {
modifier = modifier.semantics { testTagsAsResourceId = true }, modifier = modifier.semantics { testTagsAsResourceId = true },
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { Text("日期检查器") }, title = {
Text(
"日期检查器",
style = MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight.SemiBold
)
)
},
navigationIcon = { navigationIcon = {
IconButton(onClick = onBack) { IconButton(onClick = onBack) {
BackArrowIcon() BackArrowIcon()
} }
} },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent
)
) )
}, },
floatingActionButton = { floatingActionButton = {
@ -119,71 +155,122 @@ fun DateCheckerScreen(onBack: () -> Unit, modifier: Modifier = Modifier) {
}, },
modifier = Modifier.testTag("date_checker_fab"), modifier = Modifier.testTag("date_checker_fab"),
shape = CircleShape, shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primaryContainer, containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer contentColor = MaterialTheme.colorScheme.onPrimary
) { ) {
PlusIcon(color = MaterialTheme.colorScheme.onPrimaryContainer) PlusIcon(color = MaterialTheme.colorScheme.onPrimary)
} }
}, },
containerColor = MaterialTheme.colorScheme.surface
) { innerPadding -> ) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(innerPadding) .padding(innerPadding)
.padding(horizontal = 16.dp)
) { ) {
Spacer(modifier = Modifier.height(16.dp)) ProductionDateCard(
Text(
text = "生产日期",
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(8.dp))
ProductionDateField(
date = productionDate, date = productionDate,
onDateChange = { productionDate = it }, isToday = productionDate == today,
onShowDatePicker = { onClick = {
datePickerTarget = DatePickerTarget.Production datePickerTarget = DatePickerTarget.Production
showDatePicker = true showDatePicker = true
} },
modifier = Modifier.padding(horizontal = 16.dp)
) )
Spacer(modifier = Modifier.height(24.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( LazyColumn(
modifier = Modifier.fillMaxWidth(), 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 -> items(rows, key = { it.id }) { row ->
val expiryDate = row.days?.let { productionDate.plus(DatePeriod(days = it)) } 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( val dismissState = rememberSwipeToDismissBoxState(
days = row.days, confirmValueChange = { value ->
expiryDate = expiryDate, if (value == SwipeToDismissBoxValue.EndToStart) {
onDaysChange = { newDays -> rows = rows.filter { it.id != row.id }
rows = rows.map { true
if (it.id == row.id) it.copy(days = newDays) else it } 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 @Composable
private fun ProductionDateField( private fun ProductionDateCard(
date: LocalDate, date: LocalDate,
onDateChange: (LocalDate) -> Unit, isToday: Boolean,
onShowDatePicker: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
var text by remember(date) { mutableStateOf(date.toString()) } val gradient = Brush.linearGradient(
var isError by remember { mutableStateOf(false) } colors = listOf(
MaterialTheme.colorScheme.primaryContainer,
OutlinedTextField( MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.7f)
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()
) )
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 @Composable
private fun ExpiryRowItem( private fun ExpiryCard(
days: Int?, days: Int?,
expiryDate: LocalDate?, expiryDate: LocalDate?,
daysRemaining: Int?,
status: ExpiryStatus,
onDaysChange: (Int?) -> Unit, onDaysChange: (Int?) -> Unit,
onExpiryDateChange: (LocalDate) -> Unit, onExpiryDateChange: (LocalDate) -> Unit,
onShowDatePicker: () -> Unit, onShowDatePicker: () -> Unit,
onDelete: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
var daysText by remember(days) { mutableStateOf(days?.toString() ?: "") } var daysText by remember(days) { mutableStateOf(days?.toString() ?: "") }
var dateText by remember(expiryDate) { mutableStateOf(expiryDate?.toString() ?: "") } var dateText by remember(expiryDate) { mutableStateOf(expiryDate?.toString() ?: "") }
val dismissState = rememberSwipeToDismissBoxState( Box(
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
)
}
},
modifier = modifier modifier = modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.surfaceContainerHigh)
) { ) {
Row( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clip(RoundedCornerShape(12.dp)) .padding(16.dp)
.background(MaterialTheme.colorScheme.surfaceContainerHigh)
.padding(horizontal = 12.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
OutlinedTextField( Row(
value = daysText, modifier = Modifier.fillMaxWidth(),
onValueChange = { newValue -> verticalAlignment = Alignment.CenterVertically
daysText = newValue.filter { it.isDigit() } ) {
onDaysChange(daysText.toIntOrNull()) OutlinedTextField(
}, value = daysText,
label = { Text("天数") }, onValueChange = { newValue ->
singleLine = true, daysText = newValue.filter { it.isDigit() }.take(4)
keyboardOptions = KeyboardOptions( onDaysChange(daysText.toIntOrNull())
keyboardType = KeyboardType.Number, },
imeAction = ImeAction.Done label = { Text("天数") },
), singleLine = true,
modifier = Modifier.weight(1f) keyboardOptions = KeyboardOptions(
) keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(12.dp)
)
OutlinedTextField( Spacer(modifier = Modifier.width(8.dp))
value = dateText,
onValueChange = { newValue -> ArrowRightIcon(
dateText = newValue color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f),
try { modifier = Modifier.size(20.dp)
onExpiryDateChange(LocalDate.parse(newValue)) )
} catch (_: Exception) {
} Spacer(modifier = Modifier.width(8.dp))
},
label = { Text("到期日期") }, OutlinedTextField(
singleLine = true, value = dateText,
keyboardOptions = KeyboardOptions( onValueChange = { newValue ->
keyboardType = KeyboardType.Text, dateText = newValue
imeAction = ImeAction.Done try {
), onExpiryDateChange(LocalDate.parse(newValue))
trailingIcon = { } catch (_: Exception) {
IconButton(onClick = onShowDatePicker) { }
CalendarIcon( },
color = MaterialTheme.colorScheme.onSurfaceVariant, label = { Text("到期日期") },
modifier = Modifier.size(20.dp) 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 @Composable
private fun BackArrowIcon(modifier: Modifier = Modifier) { private fun BackArrowIcon(modifier: Modifier = Modifier) {
val color = MaterialTheme.colorScheme.onSurface 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() val strokeWidth = 2.dp.toPx()
drawLine( drawLine(
color = color, color = color,
@ -399,7 +550,7 @@ private fun BackArrowIcon(modifier: Modifier = Modifier) {
@Composable @Composable
private fun PlusIcon(color: Color, modifier: Modifier = Modifier) { 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 strokeWidth = 2.dp.toPx()
val cx = size.width / 2 val cx = size.width / 2
val cy = size.height / 2 val cy = size.height / 2
@ -423,7 +574,7 @@ private fun PlusIcon(color: Color, modifier: Modifier = Modifier) {
@Composable @Composable
private fun CalendarIcon(color: Color, modifier: Modifier = Modifier) { 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 strokeWidth = 1.5f.dp.toPx()
val pad = 3.dp.toPx() val pad = 3.dp.toPx()
val topY = pad + 4.dp.toPx() val topY = pad + 4.dp.toPx()
@ -431,13 +582,11 @@ private fun CalendarIcon(color: Color, modifier: Modifier = Modifier) {
val leftX = pad val leftX = pad
val rightX = size.width - pad val rightX = size.width - pad
// 外框
drawLine(color, Offset(leftX, topY), Offset(rightX, topY), strokeWidth) drawLine(color, Offset(leftX, topY), Offset(rightX, topY), strokeWidth)
drawLine(color, Offset(leftX, topY), Offset(leftX, bottomY), strokeWidth) drawLine(color, Offset(leftX, topY), Offset(leftX, bottomY), strokeWidth)
drawLine(color, Offset(rightX, topY), Offset(rightX, bottomY), strokeWidth) drawLine(color, Offset(rightX, topY), Offset(rightX, bottomY), strokeWidth)
drawLine(color, Offset(leftX, bottomY), Offset(rightX, bottomY), strokeWidth) drawLine(color, Offset(leftX, bottomY), Offset(rightX, bottomY), strokeWidth)
// 顶部挂环
val h1 = size.width * 0.3f val h1 = size.width * 0.3f
val h2 = size.width * 0.7f val h2 = size.width * 0.7f
drawLine(color, Offset(h1, pad), Offset(h1, topY), strokeWidth) 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 // endregion
// region Helpers // region Helpers
@ -455,4 +633,23 @@ private fun LocalDate.toEpochMillis(): Long =
private fun Long.toLocalDate(): LocalDate = private fun Long.toLocalDate(): LocalDate =
Instant.fromEpochMilliseconds(this).toLocalDateTime(TimeZone.UTC).date Instant.fromEpochMilliseconds(this).toLocalDateTime(TimeZone.UTC).date
@Suppress("DEPRECATION") // monthNumber/dayOfMonth 无替代 APIkotlinx-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 // endregion