xfy 3612efb665 Replace deprecated dayOfMonth with day and fix naming conventions
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 10:55:04 +08:00

155 lines
5.2 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package plus.rua.project
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.spring
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.datetime.DatePeriod
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.daysUntil
import kotlinx.datetime.minus
import kotlinx.datetime.number
import kotlinx.datetime.plus
import kotlinx.datetime.todayIn
import kotlin.time.Clock
data class CalendarDay(
val date: LocalDate,
val isCurrentMonth: Boolean,
val isToday: Boolean,
val isSelected: Boolean
)
/**
* 日历状态管理,持有选中日期、折叠状态和 ISO 周号计算逻辑。
*
* @param coroutineScope 协程作用域,用于驱动折叠动画
*/
class CalendarViewModel(private val coroutineScope: CoroutineScope) {
private val today: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault())
var selectedDate by mutableStateOf(today)
private set
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
val currentMonth: Int get() = selectedDate.month.number
fun selectDate(date: LocalDate) {
selectedDate = date
}
fun onDrag(delta: Float) {
coroutineScope.launch {
val new = (_collapseAnimatable.value + delta).coerceIn(0f, 1f)
_collapseAnimatable.snapTo(new)
}
}
// 拖拽超过 50% 时自动折叠到周视图,否则回弹到月视图
fun onDragEnd() {
coroutineScope.launch {
val current = _collapseAnimatable.value
if (current > 0.5f) {
_collapseAnimatable.animateTo(
targetValue = 1f,
animationSpec = spring(dampingRatio = 0.8f, stiffness = 400f)
)
isCollapsed = true
} else {
_collapseAnimatable.animateTo(
targetValue = 0f,
animationSpec = spring(dampingRatio = 0.8f, stiffness = 400f)
)
}
}
}
// 折叠状态下下拉恢复delta 为负值(向下拖)推动 progress 向 0
fun onExpandDrag(delta: Float) {
coroutineScope.launch {
val new = (_collapseAnimatable.value + delta).coerceIn(0f, 1f)
_collapseAnimatable.snapTo(new)
}
}
// 下拉超过 50% 时自动展开到月视图,否则回弹到周视图
fun onExpandDragEnd() {
coroutineScope.launch {
val current = _collapseAnimatable.value
if (current < 0.5f) {
_collapseAnimatable.animateTo(
targetValue = 0f,
animationSpec = spring(dampingRatio = 0.8f, stiffness = 400f)
)
isCollapsed = false
} else {
_collapseAnimatable.animateTo(
targetValue = 1f,
animationSpec = spring(dampingRatio = 0.8f, stiffness = 400f)
)
}
}
}
/**
* 计算给定日期的 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
val week1Monday = jan4.minus(DatePeriod(days = jan4DayOfWeek))
val diff = week1Monday.daysUntil(date)
val weekNumber = diff / 7 + 1
return if (weekNumber < 1) {
getIsoWeekNumber(LocalDate(date.year - 1, 12, 28))
} else if (weekNumber > getIsoWeeksInYear(date.year)) {
1
} else {
weekNumber
}
}
private fun getIsoWeeksInYear(year: Int): Int {
val dec28 = LocalDate(year, 12, 28)
val jan4 = LocalDate(year, 1, 4)
val jan4DayOfWeek = jan4.dayOfWeek.ordinal
val week1Monday = jan4.minus(DatePeriod(days = jan4DayOfWeek))
val diff = week1Monday.daysUntil(dec28)
return diff / 7 + 1
}
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))
val nextMonth = if (month == 12) LocalDate(year + 1, 1, 1) else LocalDate(year, month + 1, 1)
val daysInMonth = nextMonth.minus(DatePeriod(days = 1)).day
val rows = ((dayOfWeekOffset + daysInMonth - 1) / 7) + 1
val totalDays = rows * 7
return (0 until totalDays).map { i ->
val date = startDate.plus(DatePeriod(days = i))
CalendarDay(
date = date,
isCurrentMonth = date.month.number == month && date.year == year,
isToday = date == today,
isSelected = date == selectedDate
)
}
}
}