- Add KDoc to all public Composable functions with parameter descriptions - Add inline comments for non-obvious algorithms (42-cell grid, pager mapping) - Add @Suppress reason comments for monthNumber deprecation - Document collapseProgress range and physical meaning - Add named constant comments for START_PAGE - Add Preview name to App() Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
120 lines
4.1 KiB
Kotlin
120 lines
4.1 KiB
Kotlin
package plus.rua.project.ui
|
||
|
||
import androidx.compose.foundation.layout.Column
|
||
import androidx.compose.foundation.layout.Row
|
||
import androidx.compose.foundation.layout.fillMaxWidth
|
||
import androidx.compose.foundation.layout.height
|
||
import androidx.compose.foundation.layout.padding
|
||
import androidx.compose.runtime.Composable
|
||
import androidx.compose.runtime.getValue
|
||
import androidx.compose.runtime.mutableIntStateOf
|
||
import androidx.compose.runtime.remember
|
||
import androidx.compose.runtime.setValue
|
||
import androidx.compose.ui.Modifier
|
||
import androidx.compose.ui.layout.onSizeChanged
|
||
import androidx.compose.ui.platform.LocalDensity
|
||
import androidx.compose.ui.unit.dp
|
||
import kotlinx.datetime.DatePeriod
|
||
import kotlinx.datetime.LocalDate
|
||
import kotlinx.datetime.minus
|
||
import kotlinx.datetime.plus
|
||
|
||
/**
|
||
* 月度日历网格页,6×7 布局,支持折叠动画。
|
||
*
|
||
* 折叠时选中行保持原高,上方行向上收缩、下方行向下收缩,模拟"挤压"效果。
|
||
*
|
||
* @param year 年份
|
||
* @param month 月份(1-12)
|
||
* @param selectedDate 当前选中日期
|
||
* @param today 今天的日期,用于高亮标记
|
||
* @param onDateClick 日期点击回调
|
||
* @param collapseProgress 折叠进度,0f=展开(6行),1f=折叠(仅选中行可见)
|
||
* @param modifier 外部布局修饰符
|
||
*/
|
||
@Composable
|
||
fun CalendarMonthPage(
|
||
year: Int,
|
||
month: Int,
|
||
selectedDate: LocalDate,
|
||
today: LocalDate,
|
||
onDateClick: (LocalDate) -> Unit,
|
||
collapseProgress: Float,
|
||
modifier: Modifier = Modifier
|
||
) {
|
||
val days = remember(year, month) {
|
||
generateMonthDays(year, month)
|
||
}
|
||
val density = LocalDensity.current
|
||
|
||
val weeks = days.chunked(7)
|
||
val selectedWeekIndex = remember(weeks, selectedDate) {
|
||
weeks.indexOfFirst { week -> week.any { it.date == selectedDate } }
|
||
}
|
||
|
||
var rowHeightPx by remember { mutableIntStateOf(0) }
|
||
|
||
Column(modifier = modifier) {
|
||
weeks.forEachIndexed { weekIndex, week ->
|
||
val isAboveSelected = weekIndex < selectedWeekIndex
|
||
val isBelowSelected = weekIndex > selectedWeekIndex
|
||
|
||
val rowScale = when {
|
||
isAboveSelected || isBelowSelected -> 1f - collapseProgress
|
||
else -> 1f
|
||
}
|
||
|
||
val rowHeightDp = if (rowHeightPx > 0 && rowScale > 0.01f) {
|
||
with(density) { (rowHeightPx * rowScale).toDp() }
|
||
} else {
|
||
0.dp
|
||
}
|
||
|
||
if (rowHeightDp > 0.dp) {
|
||
Row(
|
||
modifier = Modifier
|
||
.fillMaxWidth()
|
||
.height(rowHeightDp)
|
||
.onSizeChanged { size ->
|
||
if (weekIndex == 0 && size.height > 0) {
|
||
rowHeightPx = size.height
|
||
}
|
||
}
|
||
.padding(vertical = 2.dp)
|
||
) {
|
||
week.forEach { dayData ->
|
||
DayCell(
|
||
date = dayData.date,
|
||
isCurrentMonth = dayData.isCurrentMonth,
|
||
isSelected = dayData.date == selectedDate,
|
||
isToday = dayData.date == today,
|
||
onClick = { onDateClick(dayData.date) },
|
||
modifier = Modifier.weight(1f)
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private data class DayData(
|
||
val date: LocalDate,
|
||
val isCurrentMonth: Boolean
|
||
)
|
||
|
||
@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口
|
||
private fun generateMonthDays(year: Int, month: Int): List<DayData> {
|
||
val firstOfMonth = LocalDate(year, month, 1)
|
||
val offset = firstOfMonth.dayOfWeek.ordinal
|
||
val startDate = firstOfMonth.minus(DatePeriod(days = offset))
|
||
|
||
// 6行×7列=42格,覆盖跨月首尾周,保证网格完整
|
||
return (0 until 42).map { i ->
|
||
val date = startDate.plus(DatePeriod(days = i))
|
||
DayData(
|
||
date = date,
|
||
isCurrentMonth = date.monthNumber == month && date.year == year
|
||
)
|
||
}
|
||
} |