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:
parent
0b9a516ed3
commit
d7552e469f
@ -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)
|
||||
|
||||
@ -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<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(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<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(
|
||||
|
||||
@ -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
|
||||
}
|
||||
)
|
||||
|
||||
@ -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<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)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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()) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user