feat: 法定假日改为背景色显示,支持连续边缘圆角

- 移除右上角 Text 角标,改用整格淡色背景("休"淡红、"班"淡蓝)
- 连续假日自动边缘圆角:开头左边、结尾右边、中间无、单个四边
- 排班标记固定右上角,不再随 showLegalHoliday 切换位置

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xfy 2026-05-25 10:06:38 +08:00
parent b1185da27a
commit e97909ec34
3 changed files with 73 additions and 47 deletions

View File

@ -9,7 +9,9 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
@ -24,6 +26,7 @@ import kotlinx.datetime.LocalDate
import kotlinx.datetime.minus
import kotlinx.datetime.number
import kotlinx.datetime.plus
import plus.rua.project.LunarCache
import plus.rua.project.ShiftKind
@ -42,7 +45,7 @@ import plus.rua.project.ShiftKind
* @param rowHeightPx 从外层传入的锁定行高像素折叠过程中不变
* @param effectiveWeeks 当前有效行数含翻页插值用于计算总高度
* @param shiftKindAt 日期 个人轮班类型的查询闭包
* @param showLegalHoliday 是否显示法定调休角标详见 [DayCell] 的同名参数
* @param showLegalHoliday 是否显示法定调休背景色详见 [DayCell] 的同名参数
* @param onRowHeightMeasured 首次行高测量回调外层据此锁定行高
* @param modifier 外部布局修饰符
*/
@ -67,6 +70,34 @@ fun CalendarMonthPage(
val density = LocalDensity.current
val interactionSource = remember { MutableInteractionSource() }
val holidayBadges by produceState(
initialValue = emptyMap<LocalDate, String?>(),
key1 = days
) {
val map = mutableMapOf<LocalDate, String?>()
for (dayData in days) {
val info = LunarCache.default.getOrCompute(dayData.date)
map[dayData.date] = info.holidayBadge
}
value = map
}
val holidayEdges = remember(holidayBadges, days) {
val map = mutableMapOf<LocalDate, HolidayEdgeInfo>()
for (dayData in days) {
val date = dayData.date
val badge = holidayBadges[date]
if (badge == null) continue
val prevBadge = holidayBadges[date.minus(DatePeriod(days = 1))]
val nextBadge = holidayBadges[date.plus(DatePeriod(days = 1))]
map[date] = HolidayEdgeInfo(
isStart = prevBadge != badge,
isEnd = nextBadge != badge
)
}
map
}
val weeks = remember(days) { days.chunked(7) }
val anchorIndex = remember(weeks, selectedDate) {
weeks.indexOfFirst { week -> week.any { it.date == selectedDate } }
@ -96,6 +127,7 @@ fun CalendarMonthPage(
today = today,
shiftKindAt = shiftKindAt,
showLegalHoliday = showLegalHoliday,
holidayEdges = holidayEdges,
onDateClick = onDateClick,
onRowHeightMeasured = onRowHeightMeasured,
interactionSource = interactionSource
@ -117,6 +149,7 @@ private fun WeekRow(
today: LocalDate,
shiftKindAt: (LocalDate) -> ShiftKind?,
showLegalHoliday: Boolean,
holidayEdges: Map<LocalDate, HolidayEdgeInfo>,
onDateClick: (LocalDate) -> Unit,
onRowHeightMeasured: ((Int) -> Unit)?,
interactionSource: MutableInteractionSource,
@ -197,6 +230,7 @@ private fun WeekRow(
isToday = dayData.date == today,
shiftKind = shiftKindAt(dayData.date),
showLegalHoliday = showLegalHoliday,
holidayEdgeInfo = holidayEdges[dayData.date],
onClick = { onDateClick(dayData.date) },
modifier = Modifier.weight(1f),
interactionSource = interactionSource
@ -212,6 +246,17 @@ private data class DayData(
val isCurrentMonth: Boolean
)
/**
* 法定假日在连续序列中的边缘状态用于决定背景圆角
*
* @param isStart 是否为同类型连续假日的开始前一天不是同类型
* @param isEnd 是否为同类型连续假日的结束后一天不是同类型
*/
data class HolidayEdgeInfo(
val isStart: Boolean,
val isEnd: Boolean
)
@Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口
private fun generateMonthDays(year: Int, month: Int): List<DayData> {
val firstOfMonth = LocalDate(year, month, 1)

View File

@ -32,7 +32,7 @@ import kotlin.math.abs
* @param rowHeightPx 锁定行高像素
* @param effectiveWeeks 当前有效行数含翻页插值
* @param shiftKindAt 日期 个人轮班类型的查询闭包
* @param showLegalHoliday 是否显示法定调休角标详见 [DayCell] 的同名参数
* @param showLegalHoliday 是否显示法定调休背景色详见 [DayCell] 的同名参数
* @param onRowHeightMeasured 首次行高测量回调
* @param pagerState 外层共享的 PagerState用于保持翻页状态
* @param modifier 外部布局修饰符

View File

@ -12,8 +12,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -52,9 +52,10 @@ enum class DayCellState {
* @param isSelected 是否为选中日期
* @param isToday 是否为今天
* @param shiftKind 个人轮班类型;null 表示不显示与法定调休完全独立
* @param showLegalHoliday 是否显示法定调休角标
* false(默认):排班放右上角,左上角空白,不显示法定调休
* true:排班放左上角,法定调休放右上角(旧版布局)
* @param showLegalHoliday 是否显示法定调休背景色
* false(默认):排班放右上角,不显示法定调休背景
* true:排班仍在右上角,法定假日以淡色背景显示(""淡红,""淡蓝)
* @param holidayEdgeInfo 假日在连续序列中的边缘状态,决定背景圆角null 表示无假日
* @param onClick 点击回调
* @param modifier 外部布局修饰符
*/
@ -66,6 +67,7 @@ fun DayCell(
isToday: Boolean,
shiftKind: ShiftKind?,
showLegalHoliday: Boolean,
holidayEdgeInfo: HolidayEdgeInfo? = null,
onClick: () -> Unit,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
@ -158,16 +160,30 @@ fun DayCell(
label = "lunarColor"
)
val holidayBadgeColor = when (holidayBadge) {
"" -> MaterialTheme.colorScheme.error
"" -> MaterialTheme.colorScheme.primary
val holidayBgColor = when (holidayBadge) {
"" -> MaterialTheme.colorScheme.error.copy(alpha = 0.10f)
"" -> MaterialTheme.colorScheme.primary.copy(alpha = 0.08f)
else -> Color.Transparent
}
val holidayBadgeAlpha = if (isCurrentMonth) 1f else 0.38f
Box(
modifier = modifier.aspectRatio(1f)
) {
// 法定假日背景(最底层,与选中/今天状态叠加)
if (showLegalHoliday && holidayBadge != null) {
val holidayShape = when {
holidayEdgeInfo?.isStart == true && holidayEdgeInfo.isEnd -> RoundedCornerShape(8.dp)
holidayEdgeInfo?.isStart == true -> RoundedCornerShape(topStart = 8.dp, bottomStart = 8.dp)
holidayEdgeInfo?.isEnd == true -> RoundedCornerShape(topEnd = 8.dp, bottomEnd = 8.dp)
else -> RoundedCornerShape(0.dp)
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 0.5.dp)
.background(holidayBgColor, holidayShape)
)
}
Box(
modifier = Modifier
.fillMaxSize()
@ -231,51 +247,16 @@ fun DayCell(
}
val shiftLabel = if (shiftKind == ShiftKind.WORK) "" else ""
val shiftAlpha = if (isCurrentMonth) 1f else 0.38f
if (showLegalHoliday) {
Box(
modifier = Modifier
.align(Alignment.TopStart)
.zIndex(1f)
.padding(top = 1.dp, start = 2.dp)
.background(shiftAccentColor.copy(alpha = 0.12f), CircleShape)
.size(16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = shiftLabel,
color = shiftAccentColor.copy(alpha = shiftAlpha),
fontSize = 9.sp,
fontWeight = FontWeight.Bold,
lineHeight = 9.sp
)
}
} else {
Text(
text = shiftLabel,
color = shiftAccentColor.copy(alpha = shiftAlpha),
fontSize = 9.sp,
fontWeight = FontWeight.Bold,
lineHeight = 9.sp,
modifier = Modifier
.align(Alignment.TopEnd)
.zIndex(1f)
.padding(top = 1.dp, end = 2.dp)
)
}
}
if (showLegalHoliday && holidayBadge != null) {
Text(
text = holidayBadge,
color = holidayBadgeColor.copy(alpha = holidayBadgeAlpha),
text = shiftLabel,
color = shiftAccentColor.copy(alpha = shiftAlpha),
fontSize = 9.sp,
fontWeight = FontWeight.Bold,
lineHeight = 9.sp,
modifier = Modifier
.align(Alignment.TopEnd)
.zIndex(1f)
.background(MaterialTheme.colorScheme.background)
.padding(top = 1.dp, end = 2.dp)
.padding(horizontal = 2.dp)
)
}
}