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

View File

@ -32,7 +32,7 @@ import kotlin.math.abs
* @param rowHeightPx 锁定行高像素 * @param rowHeightPx 锁定行高像素
* @param effectiveWeeks 当前有效行数含翻页插值 * @param effectiveWeeks 当前有效行数含翻页插值
* @param shiftKindAt 日期 个人轮班类型的查询闭包 * @param shiftKindAt 日期 个人轮班类型的查询闭包
* @param showLegalHoliday 是否显示法定调休角标详见 [DayCell] 的同名参数 * @param showLegalHoliday 是否显示法定调休背景色详见 [DayCell] 的同名参数
* @param onRowHeightMeasured 首次行高测量回调 * @param onRowHeightMeasured 首次行高测量回调
* @param pagerState 外层共享的 PagerState用于保持翻页状态 * @param pagerState 外层共享的 PagerState用于保持翻页状态
* @param modifier 外部布局修饰符 * @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.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -52,9 +52,10 @@ enum class DayCellState {
* @param isSelected 是否为选中日期 * @param isSelected 是否为选中日期
* @param isToday 是否为今天 * @param isToday 是否为今天
* @param shiftKind 个人轮班类型;null 表示不显示与法定调休完全独立 * @param shiftKind 个人轮班类型;null 表示不显示与法定调休完全独立
* @param showLegalHoliday 是否显示法定调休角标 * @param showLegalHoliday 是否显示法定调休背景色
* false(默认):排班放右上角,左上角空白,不显示法定调休 * false(默认):排班放右上角,不显示法定调休背景
* true:排班放左上角,法定调休放右上角(旧版布局) * true:排班仍在右上角,法定假日以淡色背景显示(""淡红,""淡蓝)
* @param holidayEdgeInfo 假日在连续序列中的边缘状态,决定背景圆角null 表示无假日
* @param onClick 点击回调 * @param onClick 点击回调
* @param modifier 外部布局修饰符 * @param modifier 外部布局修饰符
*/ */
@ -66,6 +67,7 @@ fun DayCell(
isToday: Boolean, isToday: Boolean,
shiftKind: ShiftKind?, shiftKind: ShiftKind?,
showLegalHoliday: Boolean, showLegalHoliday: Boolean,
holidayEdgeInfo: HolidayEdgeInfo? = null,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
@ -158,16 +160,30 @@ fun DayCell(
label = "lunarColor" label = "lunarColor"
) )
val holidayBadgeColor = when (holidayBadge) { val holidayBgColor = when (holidayBadge) {
"" -> MaterialTheme.colorScheme.error "" -> MaterialTheme.colorScheme.error.copy(alpha = 0.10f)
"" -> MaterialTheme.colorScheme.primary "" -> MaterialTheme.colorScheme.primary.copy(alpha = 0.08f)
else -> Color.Transparent else -> Color.Transparent
} }
val holidayBadgeAlpha = if (isCurrentMonth) 1f else 0.38f
Box( Box(
modifier = modifier.aspectRatio(1f) 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( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@ -231,25 +247,6 @@ fun DayCell(
} }
val shiftLabel = if (shiftKind == ShiftKind.WORK) "" else "" val shiftLabel = if (shiftKind == ShiftKind.WORK) "" else ""
val shiftAlpha = if (isCurrentMonth) 1f else 0.38f 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(
text = shiftLabel, text = shiftLabel,
color = shiftAccentColor.copy(alpha = shiftAlpha), color = shiftAccentColor.copy(alpha = shiftAlpha),
@ -263,20 +260,4 @@ fun DayCell(
) )
} }
} }
if (showLegalHoliday && holidayBadge != null) {
Text(
text = holidayBadge,
color = holidayBadgeColor.copy(alpha = holidayBadgeAlpha),
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)
)
}
}
} }