diff --git a/shared/src/commonMain/kotlin/plus/rua/project/App.kt b/shared/src/commonMain/kotlin/plus/rua/project/App.kt index 266cd7b..c5693f4 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/App.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/App.kt @@ -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) diff --git a/shared/src/commonMain/kotlin/plus/rua/project/CalendarViewModel.kt b/shared/src/commonMain/kotlin/plus/rua/project/CalendarViewModel.kt index c7a3610..d2eeb7c 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/CalendarViewModel.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/CalendarViewModel.kt @@ -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 无替代 API,kotlinx-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 无替代 API,kotlinx-datetime 尚未提供新接口 fun getMonthDays(year: Int, month: Int): List { 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( diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/BottomCard.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/BottomCard.kt index e351256..005c748 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/BottomCard.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/BottomCard.kt @@ -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, diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthPage.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthPage.kt index 34e63a9..a895d51 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthPage.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthPage.kt @@ -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=展开(6行),1f=折叠(仅选中行可见) + * @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 无替代 API,kotlinx-datetime 尚未提供新接口 private fun generateMonthDays(year: Int, month: Int): List { 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( diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt index 73e0212..6576007 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt @@ -25,6 +25,13 @@ import kotlinx.datetime.todayIn import kotlin.time.Clock import plus.rua.project.CalendarViewModel +/** + * 日历主界面,包含月/周视图切换和折叠动画。 + * + * 折叠时日历从月视图(6行)收缩为周视图(1行),BottomCard 同步上移填充空间。 + * + * @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 无替代 API,kotlinx-datetime 尚未提供新接口 currentMonth = weekMonday.monthNumber } ) diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarPager.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarPager.kt index 584e3cf..241b538 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarPager.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarPager.kt @@ -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 无替代 API,kotlinx-datetime 尚未提供新接口 private fun LocalDate.toYearMonth(): Pair = Pair(year, monthNumber) +// 页码→年月:偏移量 + 初始月份的绝对月数,再拆分回年月 private fun pageToYearMonth(page: Int, initial: Pair): Pair { 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, initial: Pair): Int { val targetTotal = yearMonth.first * 12 + (yearMonth.second - 1) val initialTotal = initial.first * 12 + (initial.second - 1) diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/DayCell.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/DayCell.kt index 257fe10..52ce41f 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/DayCell.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/DayCell.kt @@ -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, diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/MonthHeader.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/MonthHeader.kt index 49dda07..9efe528 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/MonthHeader.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/MonthHeader.kt @@ -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, diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/WeekPager.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/WeekPager.kt index 2f00bc7..022f673 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/WeekPager.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/WeekPager.kt @@ -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, diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/WeekdayHeader.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/WeekdayHeader.kt index 7bbd3f5..6ccc35c 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/WeekdayHeader.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/WeekdayHeader.kt @@ -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()) {