Add KDoc and inline comments per COMMENTS.md conventions

- 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>
This commit is contained in:
xfy 2026-05-14 15:22:49 +08:00
parent 0b9a516ed3
commit d7552e469f
10 changed files with 123 additions and 21 deletions

View File

@ -6,8 +6,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import plus.rua.project.ui.CalendarMonthView
/**
* 应用入口 Composable包裹 CalendarMonthView 并提供 MaterialTheme
*/
@Composable
@Preview
@Preview(name = "Calendar App")
fun App() {
MaterialTheme {
CalendarMonthView(modifier = Modifier)

View File

@ -23,6 +23,11 @@ data class CalendarDay(
val isSelected: Boolean
)
/**
* 日历状态管理持有选中日期折叠状态和 ISO 周号计算逻辑
*
* @param coroutineScope 协程作用域用于驱动折叠动画
*/
class CalendarViewModel(private val coroutineScope: CoroutineScope) {
private val today: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault())
@ -32,11 +37,12 @@ class CalendarViewModel(private val coroutineScope: CoroutineScope) {
var isCollapsed by mutableStateOf(false)
private set
// collapseProgress: 0f=展开(月视图), 1f=折叠(周视图)
private val _collapseAnimatable = Animatable(0f)
val collapseProgress: Float get() = _collapseAnimatable.value
val currentYear: Int get() = selectedDate.year
@Suppress("DEPRECATION")
@Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口
val currentMonth: Int get() = selectedDate.monthNumber
fun selectDate(date: LocalDate) {
@ -50,6 +56,7 @@ class CalendarViewModel(private val coroutineScope: CoroutineScope) {
}
}
// 拖拽超过 50% 时自动折叠到周视图,否则回弹到月视图
fun onDragEnd() {
coroutineScope.launch {
if (_collapseAnimatable.value > 0.5f) {
@ -67,6 +74,12 @@ class CalendarViewModel(private val coroutineScope: CoroutineScope) {
}
}
/**
* 计算给定日期的 ISO 8601 周号
*
* @param date 目标日期
* @return ISO 周号1-53
*/
fun getIsoWeekNumber(date: LocalDate): Int {
val jan4 = LocalDate(date.year, 1, 4)
val jan4DayOfWeek = jan4.dayOfWeek.ordinal
@ -91,12 +104,13 @@ class CalendarViewModel(private val coroutineScope: CoroutineScope) {
return diff / 7 + 1
}
@Suppress("DEPRECATION")
@Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口
fun getMonthDays(year: Int, month: Int): List<CalendarDay> {
val firstOfMonth = LocalDate(year, month, 1)
val dayOfWeekOffset = firstOfMonth.dayOfWeek.ordinal
val startDate = firstOfMonth.minus(DatePeriod(days = dayOfWeekOffset))
// 6行×7列=42格覆盖跨月首尾周保证网格完整
return (0 until 42).map { i ->
val date = startDate.plus(DatePeriod(days = i))
CalendarDay(

View File

@ -20,6 +20,12 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import plus.rua.project.CalendarViewModel
/**
* 底部卡片折叠状态下支持垂直拖拽触发折叠动画
*
* @param viewModel 日历 ViewModel用于读取折叠状态和驱动拖拽
* @param modifier 外部布局修饰符
*/
@Composable
fun BottomCard(
viewModel: CalendarViewModel,

View File

@ -3,18 +3,35 @@ 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.offset
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.draw.alpha
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=展开61f=折叠仅选中行可见
* @param modifier 外部布局修饰符
*/
@Composable
fun CalendarMonthPage(
year: Int,
@ -28,37 +45,42 @@ fun CalendarMonthPage(
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 progress = collapseProgress
val isAboveSelected = weekIndex < selectedWeekIndex
val isBelowSelected = weekIndex > selectedWeekIndex
val offsetY = when {
isAboveSelected -> -progress * 200f
isBelowSelected -> progress * 200f
else -> 0f
}
val alpha = when {
isAboveSelected || isBelowSelected -> 1f - progress
val rowScale = when {
isAboveSelected || isBelowSelected -> 1f - collapseProgress
else -> 1f
}
if (alpha > 0.01f) {
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)
.offset(y = offsetY.dp)
.alpha(alpha)
) {
week.forEach { dayData ->
DayCell(
@ -81,12 +103,13 @@ private data class DayData(
val isCurrentMonth: Boolean
)
@Suppress("DEPRECATION")
@Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-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(

View File

@ -25,6 +25,13 @@ import kotlinx.datetime.todayIn
import kotlin.time.Clock
import plus.rua.project.CalendarViewModel
/**
* 日历主界面包含月/周视图切换和折叠动画
*
* 折叠时日历从月视图6收缩为周视图1BottomCard 同步上移填充空间
*
* @param modifier 外部布局修饰符
*/
@Composable
fun CalendarMonthView(
modifier: Modifier = Modifier
@ -40,6 +47,8 @@ fun CalendarMonthView(
var screenHeightPx by remember { mutableIntStateOf(0) }
var expandedCalendarHeightPx by remember { mutableIntStateOf(0) }
// collapseProgress: 0f=月视图(6行), 1f=周视图(1行)
// 折叠偏移量 = 进度 × 展开高度的5/6保留1行可见
val collapseOffsetPx = if (viewModel.isCollapsed) {
0
} else {
@ -62,6 +71,7 @@ fun CalendarMonthView(
) {
Column(modifier = Modifier.padding(horizontal = 16.dp).onSizeChanged { size ->
calendarHeightPx = size.height
// 仅在首次展开时记录完整日历高度,折叠后不再覆盖
if (!viewModel.isCollapsed && viewModel.collapseProgress < 0.01f) {
expandedCalendarHeightPx = size.height
}
@ -79,7 +89,7 @@ fun CalendarMonthView(
onDateClick = { date -> viewModel.selectDate(date) },
onWeekChanged = { weekMonday ->
currentYear = weekMonday.year
@Suppress("DEPRECATION")
@Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口
currentMonth = weekMonday.monthNumber
}
)

View File

@ -12,8 +12,19 @@ import androidx.compose.ui.Modifier
import kotlinx.coroutines.launch
import kotlinx.datetime.LocalDate
/** 无限分页中心页,用于 HorizontalPager 的起始位置 */
private const val START_PAGE = Int.MAX_VALUE / 2
/**
* 月度日历分页器HorizontalPager 实现无限左右滑动切换月份
*
* @param selectedDate 当前选中日期
* @param today 今天的日期
* @param onDateClick 日期点击回调
* @param onMonthChanged 月份切换回调滑动到新月份时触发
* @param collapseProgress 折叠进度0f=展开1f=折叠
* @param modifier 外部布局修饰符
*/
@Composable
fun CalendarPager(
selectedDate: LocalDate,
@ -68,15 +79,17 @@ fun CalendarPager(
}
}
@Suppress("DEPRECATION")
@Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口
private fun LocalDate.toYearMonth(): Pair<Int, Int> = Pair(year, monthNumber)
// 页码→年月:偏移量 + 初始月份的绝对月数,再拆分回年月
private fun pageToYearMonth(page: Int, initial: Pair<Int, Int>): Pair<Int, Int> {
val offset = page - START_PAGE
val totalMonths = initial.first * 12 + (initial.second - 1) + offset
return Pair(totalMonths / 12, totalMonths % 12 + 1)
}
// 年月→页码:目标与初始的绝对月数差 + 起始页
private fun yearMonthToPage(yearMonth: Pair<Int, Int>, initial: Pair<Int, Int>): Int {
val targetTotal = yearMonth.first * 12 + (yearMonth.second - 1)
val initialTotal = initial.first * 12 + (initial.second - 1)

View File

@ -14,6 +14,16 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import kotlinx.datetime.LocalDate
/**
* 单个日期单元格显示日期数字并支持选中/今天/非当月状态
*
* @param date 日期
* @param isCurrentMonth 是否属于当前显示月份
* @param isSelected 是否为选中日期
* @param isToday 是否为今天
* @param onClick 点击回调
* @param modifier 外部布局修饰符
*/
@Composable
fun DayCell(
date: LocalDate,

View File

@ -11,6 +11,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
/**
* 月份标题栏显示"年月"文字和 ISO 周号
*
* @param year 年份
* @param month 月份1-12
* @param weekNumber 当前 ISO 周号
* @param modifier 外部布局修饰符
*/
@Composable
fun MonthHeader(
year: Int,

View File

@ -19,8 +19,18 @@ import kotlinx.datetime.LocalDate
import kotlinx.datetime.minus
import kotlinx.datetime.plus
/** 无限分页中心页,用于 HorizontalPager 的起始位置 */
private const val START_PAGE = Int.MAX_VALUE / 2
/**
* 周视图分页器折叠状态下显示选中日期所在周支持左右滑动切换周
*
* @param selectedDate 当前选中日期
* @param today 今天的日期
* @param onDateClick 日期点击回调
* @param onWeekChanged 周切换回调滑动到新周时触发参数为该周周一日期
* @param modifier 外部布局修饰符
*/
@Composable
fun WeekPager(
selectedDate: LocalDate,

View File

@ -10,6 +10,11 @@ import androidx.compose.ui.text.style.TextAlign
private val WEEKDAY_LABELS = listOf("", "", "", "", "", "", "")
/**
* 星期标题行固定显示"一二三四五六日"
*
* @param modifier 外部布局修饰符
*/
@Composable
fun WeekdayHeader(modifier: Modifier = Modifier) {
Row(modifier = modifier.fillMaxWidth()) {