diff --git a/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt b/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt index 2aa63f9..4bb276f 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt @@ -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(), + key1 = days + ) { + val map = mutableMapOf() + 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() + 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, 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 无替代 API,kotlinx-datetime 尚未提供新接口 private fun generateMonthDays(year: Int, month: Int): List { val firstOfMonth = LocalDate(year, month, 1) diff --git a/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt b/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt index 7c71a4f..7031a26 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt @@ -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 外部布局修饰符 diff --git a/core/src/main/kotlin/plus/rua/project/ui/DayCell.kt b/core/src/main/kotlin/plus/rua/project/ui/DayCell.kt index 2750318..83e2c25 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/DayCell.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/DayCell.kt @@ -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) ) } }