Add KDoc for public APIs, suppress monthNumber deprecation, and refine comments
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
270346e6ef
commit
6351caf776
@ -19,6 +19,14 @@ import kotlin.time.Clock
|
||||
import plus.rua.project.ui.COLLAPSE_THRESHOLD
|
||||
import plus.rua.project.ui.FLING_VELOCITY_THRESHOLD_DP
|
||||
|
||||
/**
|
||||
* 日历日期数据,用于网格单元格渲染。
|
||||
*
|
||||
* @param date 日期
|
||||
* @param isCurrentMonth 是否属于当前显示月份
|
||||
* @param isToday 是否为今天
|
||||
* @param isSelected 是否为选中日期
|
||||
*/
|
||||
data class CalendarDay(
|
||||
val date: LocalDate,
|
||||
val isCurrentMonth: Boolean,
|
||||
@ -30,6 +38,7 @@ data class CalendarDay(
|
||||
* 日历状态管理,持有选中日期、折叠状态和 ISO 周号计算逻辑。
|
||||
*
|
||||
* @param coroutineScope 协程作用域,用于驱动折叠动画
|
||||
* @param clock 时钟源,默认系统时钟;测试时可注入固定时钟
|
||||
*/
|
||||
class CalendarViewModel(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
@ -47,13 +56,25 @@ class CalendarViewModel(
|
||||
private val _collapseAnimatable = Animatable(0f)
|
||||
val collapseProgress: Float get() = _collapseAnimatable.value
|
||||
|
||||
val currentYear: Int get() = selectedDate.year
|
||||
@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口
|
||||
val currentMonth: Int get() = selectedDate.month.number
|
||||
|
||||
val currentYear: Int get() = selectedDate.year
|
||||
|
||||
/**
|
||||
* 选中指定日期。
|
||||
*
|
||||
* @param date 目标日期
|
||||
*/
|
||||
fun selectDate(date: LocalDate) {
|
||||
selectedDate = date
|
||||
}
|
||||
|
||||
/**
|
||||
* 展开状态下拖拽折叠,delta 正值推动 progress 向 1(折叠方向)。
|
||||
*
|
||||
* @param delta 拖拽增量,已归一化到 [0,1] 区间
|
||||
*/
|
||||
fun onDrag(delta: Float) {
|
||||
coroutineScope.launch {
|
||||
val new = (_collapseAnimatable.value + delta).coerceIn(0f, 1f)
|
||||
@ -61,8 +82,13 @@ class CalendarViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽超过阈值时自动折叠到周视图,否则回弹到月视图
|
||||
// velocityDpPerSec: 松手时的 fling 速度 (dp/s),正值=上滑(折叠方向),负值=下滑(展开方向)
|
||||
/**
|
||||
* 展开状态拖拽结束,根据进度和速度决定折叠或回弹。
|
||||
*
|
||||
* 拖拽超过阈值时自动折叠到周视图,否则回弹到月视图。
|
||||
*
|
||||
* @param velocityDpPerSec 松手时的 fling 速度 (dp/s),正值=上滑(折叠方向),负值=下滑(展开方向)
|
||||
*/
|
||||
fun onDragEnd(velocityDpPerSec: Float = 0f) {
|
||||
coroutineScope.launch {
|
||||
val progress = _collapseAnimatable.value
|
||||
@ -86,7 +112,11 @@ class CalendarViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
// 折叠状态下下拉恢复:delta 为负值(向下拖)推动 progress 向 0
|
||||
/**
|
||||
* 折叠状态下下拉恢复,delta 为负值(向下拖)推动 progress 向 0。
|
||||
*
|
||||
* @param delta 拖拽增量,已归一化到 [0,1] 区间
|
||||
*/
|
||||
fun onExpandDrag(delta: Float) {
|
||||
coroutineScope.launch {
|
||||
val new = (_collapseAnimatable.value + delta).coerceIn(0f, 1f)
|
||||
@ -94,8 +124,13 @@ class CalendarViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉超过阈值时自动展开到月视图,否则回弹到周视图
|
||||
// velocityDpPerSec: 同上,正值=上滑,负值=下滑
|
||||
/**
|
||||
* 折叠状态拖拽结束,根据进度和速度决定展开或回弹。
|
||||
*
|
||||
* 下拉超过阈值时自动展开到月视图,否则回弹到周视图。
|
||||
*
|
||||
* @param velocityDpPerSec 松手时的 fling 速度 (dp/s),正值=上滑,负值=下滑
|
||||
*/
|
||||
fun onExpandDragEnd(velocityDpPerSec: Float = 0f) {
|
||||
coroutineScope.launch {
|
||||
val progress = _collapseAnimatable.value
|
||||
@ -149,6 +184,16 @@ class CalendarViewModel(
|
||||
return diff / 7 + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算给定年月的日历网格数据,包含跨月填充至完整行。
|
||||
*
|
||||
* 网格行数按实际需要计算(4/5/6行),每行7格,首行从该月1号所在周的周一开始。
|
||||
*
|
||||
* @param year 年份
|
||||
* @param month 月份(1-12)
|
||||
* @return 日历网格列表,每项包含日期、是否当月、是否今天、是否选中
|
||||
*/
|
||||
@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口
|
||||
fun getMonthDays(year: Int, month: Int): List<CalendarDay> {
|
||||
val firstOfMonth = LocalDate(year, month, 1)
|
||||
val dayOfWeekOffset = firstOfMonth.dayOfWeek.ordinal
|
||||
|
||||
@ -26,7 +26,16 @@ import kotlinx.datetime.plus
|
||||
* 折叠时非选中行高度按 (1-p) 缩放,选中行保持原始高度,
|
||||
* 所有行通过手动 y-offset 定位,形成向选中行收缩的视觉效果。
|
||||
*
|
||||
* @param year 年份
|
||||
* @param month 月份(1-12)
|
||||
* @param selectedDate 当前选中日期
|
||||
* @param today 今天的日期,用于高亮标记
|
||||
* @param onDateClick 日期点击回调
|
||||
* @param collapseProgress 折叠进度,0f=展开,1f=折叠
|
||||
* @param rowHeightPx 从外层传入的锁定行高(像素),折叠过程中不变
|
||||
* @param effectiveWeeks 当前有效行数(含翻页插值),用于计算总高度
|
||||
* @param onRowHeightMeasured 首次行高测量回调,外层据此锁定行高
|
||||
* @param modifier 外部布局修饰符
|
||||
*/
|
||||
@Composable
|
||||
fun CalendarMonthPage(
|
||||
@ -143,6 +152,7 @@ private data class DayData(
|
||||
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
|
||||
|
||||
@ -47,6 +47,7 @@ fun CalendarMonthView(
|
||||
val viewModel = remember { CalendarViewModel(coroutineScope) }
|
||||
val today = remember { Clock.System.todayIn(TimeZone.currentSystemDefault()) }
|
||||
val currentYear by remember { derivedStateOf { viewModel.selectedDate.year } }
|
||||
@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口
|
||||
val currentMonth by remember { derivedStateOf { viewModel.selectedDate.month.number } }
|
||||
val density = LocalDensity.current
|
||||
|
||||
@ -64,6 +65,7 @@ fun CalendarMonthView(
|
||||
val cardGapPx = with(density) { lerp(CARD_GAP_EXPANDED_DP.toFloat(), CARD_GAP_COLLAPSED_DP.toFloat(), collapseProgress).dp.toPx() }.toInt()
|
||||
|
||||
// 翻页时在相邻月份行数之间插值,使 BottomCard 高度平滑过渡
|
||||
// abs(fraction) > 阈值时启用插值,避免静止时的浮点抖动
|
||||
val interpolatedWeeks by remember {
|
||||
derivedStateOf {
|
||||
val fraction = pagerState.currentPageOffsetFraction
|
||||
@ -80,7 +82,7 @@ fun CalendarMonthView(
|
||||
}
|
||||
|
||||
// 预估行高:DayCell aspectRatio=1,宽度 = (screenWidth - horizontalPadding) / 7
|
||||
// 加上 Row 的 vertical padding (4dp × 2)
|
||||
// 加上 Row 的 vertical padding (6dp × 2)
|
||||
// 用于 rowHeightPx 尚未测量时的 fallback,避免首次布局高度为 0
|
||||
val estimatedRowHeightPx = if (screenWidthPx > 0) {
|
||||
val cellWidth = (screenWidthPx - with(density) { (HORIZONTAL_PADDING_DP * 2).dp.toPx() }) / 7
|
||||
@ -95,8 +97,8 @@ fun CalendarMonthView(
|
||||
// 折叠时网格高度公式(与 CalendarMonthPage 一致):
|
||||
// collapseProgress=0 展开时 gridH = rowH × weeks;collapseProgress=1 折叠时 gridH = rowH × 1
|
||||
// 中间态:gridH = rowH × (1 + (weeks-1) × (1-collapseProgress))
|
||||
// 必须直接计算而非 derivedStateOf:effectiveRowHeightPx 依赖 rowHeightPx state,
|
||||
// derivedStateOf 无法追踪非 State 局部变量变化,导致 rowHeightPx 从 0 变为测量值时 gridHeightPx 不更新
|
||||
// 直接计算而非 derivedStateOf:effectiveRowHeightPx 依赖 rowHeightPx state,
|
||||
// derivedStateOf 无法追踪非 State 局部变量,rowHeightPx 从 0 变为测量值时 gridHeightPx 不会更新
|
||||
val gridHeightPx = if (effectiveRowHeightPx > 0) {
|
||||
val rowH = effectiveRowHeightPx.toFloat()
|
||||
if (collapseProgress > OFFSET_FRACTION_THRESHOLD) {
|
||||
@ -110,7 +112,7 @@ fun CalendarMonthView(
|
||||
val calendarAreaHeightPx = headerHeightPx + gridHeightPx + rowPaddingPx + cardGapPx
|
||||
val cardHeightPx = if (screenHeightPx > 0 && calendarAreaHeightPx > 0) screenHeightPx - calendarAreaHeightPx else 0
|
||||
|
||||
// 行高已知时约束 pager 高度,防止内容溢出;否则让 pager 自由扩展以触发首次行高测量
|
||||
// 行高已知时约束 pager 高度防止内容溢出;否则让 pager 自由扩展以触发首次行高测量
|
||||
val pagerModifier = if (rowHeightPx > 0 && gridHeightPx > 0) {
|
||||
Modifier
|
||||
.height(with(density) { gridHeightPx.toDp() })
|
||||
@ -164,6 +166,7 @@ fun CalendarMonthView(
|
||||
onDateClick = { date -> viewModel.selectDate(date) },
|
||||
onMonthChanged = { year, month ->
|
||||
// 优先选中当月内的今天,否则选中该月1号
|
||||
@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口
|
||||
val date = if (year == today.year && today.month.number == month) today
|
||||
else LocalDate(year, month, 1)
|
||||
viewModel.selectDate(date)
|
||||
|
||||
@ -17,12 +17,18 @@ import kotlinx.datetime.number
|
||||
/**
|
||||
* 月度日历分页器,HorizontalPager 实现无限左右滑动切换月份。
|
||||
*
|
||||
* 使用 Int.MAX_VALUE 页数,中心页为起始月份。点击跨月日期时自动滚动到对应页。
|
||||
* 跳过初始 snapshotFlow 发射以保留"今天"选中状态。
|
||||
*
|
||||
* @param selectedDate 当前选中日期
|
||||
* @param today 今天的日期
|
||||
* @param onDateClick 日期点击回调
|
||||
* @param onMonthChanged 月份切换回调,滑动到新月份时触发
|
||||
* @param onMonthChanged 月份切换回调,滑动到新月份稳定后触发
|
||||
* @param collapseProgress 折叠进度,0f=展开,1f=折叠
|
||||
* @param rowHeightPx 从外层传入的锁定行高(像素),折叠过程中不变
|
||||
* @param rowHeightPx 锁定行高(像素)
|
||||
* @param effectiveWeeks 当前有效行数(含翻页插值)
|
||||
* @param onRowHeightMeasured 首次行高测量回调
|
||||
* @param pagerState 外层共享的 PagerState,用于保持翻页状态
|
||||
* @param modifier 外部布局修饰符
|
||||
*/
|
||||
@Composable
|
||||
@ -39,10 +45,11 @@ fun CalendarPager(
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val initialYear = remember { today.year }
|
||||
@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口
|
||||
val initialMonth = remember { today.month.number }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
// Sync settled page to onMonthChanged (skip initial emission to preserve "today" selection)
|
||||
// 跳过初始发射,保留首次渲染时的"今天"选中状态
|
||||
LaunchedEffect(pagerState) {
|
||||
snapshotFlow { pagerState.settledPage }.drop(1).collect { page ->
|
||||
val yearMonth = pageToYearMonth(page, initialYear, initialMonth)
|
||||
@ -63,9 +70,10 @@ fun CalendarPager(
|
||||
selectedDate = selectedDate,
|
||||
today = today,
|
||||
onDateClick = { date ->
|
||||
onDateClick(date)
|
||||
// If clicking a date in a different month, scroll to that page
|
||||
val clickedYear = date.year
|
||||
onDateClick(date)
|
||||
// 点击跨月日期时,滚动到该月对应的页
|
||||
val clickedYear = date.year
|
||||
@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口
|
||||
val clickedMonth = date.month.number
|
||||
if (clickedYear != year || clickedMonth != month) {
|
||||
val targetPage = yearMonthToPage(clickedYear, clickedMonth, initialYear, initialMonth)
|
||||
|
||||
@ -38,6 +38,10 @@ fun lerp(start: Float, end: Float, fraction: Float): Float = start + (end - star
|
||||
|
||||
/**
|
||||
* 计算月份在日历网格中需要的行数(4/5/6)。
|
||||
*
|
||||
* @param year 年份
|
||||
* @param month 月份(1-12)
|
||||
* @return 网格行数
|
||||
*/
|
||||
fun calculateWeeksCount(year: Int, month: Int): Int {
|
||||
val firstOfMonth = LocalDate(year, month, 1)
|
||||
@ -49,9 +53,14 @@ fun calculateWeeksCount(year: Int, month: Int): Int {
|
||||
|
||||
/**
|
||||
* 根据 pager 页码计算该页月份的行数。
|
||||
*
|
||||
* @param page 分页器页码
|
||||
* @param today 今天的日期,用于确定起始月份
|
||||
* @return 网格行数
|
||||
*/
|
||||
fun calculateWeeksCountForPage(page: Int, today: LocalDate): Int {
|
||||
val initialYear = today.year
|
||||
@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口
|
||||
val initialMonth = today.month.number
|
||||
val offset = page - START_PAGE
|
||||
val totalMonths = initialYear * 12 + (initialMonth - 1) + offset
|
||||
@ -62,6 +71,14 @@ fun calculateWeeksCountForPage(page: Int, today: LocalDate): Int {
|
||||
|
||||
/**
|
||||
* 页码转年月。
|
||||
*
|
||||
* 中心页 (Int.MAX_VALUE/2) 对应起始月份,向左递减、向右递增,
|
||||
* 自动处理跨年(12月→1月)。
|
||||
*
|
||||
* @param page 分页器页码
|
||||
* @param initialYear 起始年份(中心页对应的年份)
|
||||
* @param initialMonth 起始月份(中心页对应的月份,1-12)
|
||||
* @return Pair(year, month)
|
||||
*/
|
||||
fun pageToYearMonth(page: Int, initialYear: Int, initialMonth: Int): Pair<Int, Int> {
|
||||
val offset = page - START_PAGE
|
||||
@ -71,6 +88,14 @@ fun pageToYearMonth(page: Int, initialYear: Int, initialMonth: Int): Pair<Int, I
|
||||
|
||||
/**
|
||||
* 年月转页码。
|
||||
*
|
||||
* [pageToYearMonth] 的逆运算,用于点击跨月日期时定位目标页。
|
||||
*
|
||||
* @param year 目标年份
|
||||
* @param month 目标月份(1-12)
|
||||
* @param initialYear 起始年份
|
||||
* @param initialMonth 起始月份
|
||||
* @return 分页器页码
|
||||
*/
|
||||
fun yearMonthToPage(year: Int, month: Int, initialYear: Int, initialMonth: Int): Int {
|
||||
val targetTotal = year * 12 + (month - 1)
|
||||
@ -80,6 +105,11 @@ fun yearMonthToPage(year: Int, month: Int, initialYear: Int, initialMonth: Int):
|
||||
|
||||
/**
|
||||
* 获取日期所在周的周一。
|
||||
*
|
||||
* ISO 8601 周从周一开始。周一返回自身,其他日期回退到该周周一。
|
||||
*
|
||||
* @receiver 目标日期
|
||||
* @return 该日期所在周的周一
|
||||
*/
|
||||
fun LocalDate.toWeekMonday(): LocalDate {
|
||||
val dayOfWeekOrdinal = dayOfWeek.ordinal
|
||||
@ -88,6 +118,12 @@ fun LocalDate.toWeekMonday(): LocalDate {
|
||||
|
||||
/**
|
||||
* 根据 pager 页码计算该页对应的周周一日期。
|
||||
*
|
||||
* 中心页对应参考周一,向左/右每页偏移一周。用于 WeekPager 的单周视图渲染。
|
||||
*
|
||||
* @param page 分页器页码
|
||||
* @param initial 参考周一日期(中心页对应的周一)
|
||||
* @return 该页周一的 LocalDate
|
||||
*/
|
||||
fun pageToWeekMonday(page: Int, initial: LocalDate): LocalDate {
|
||||
val offset = page - START_PAGE
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user