diff --git a/core/src/main/kotlin/plus/rua/project/CalendarViewModel.kt b/core/src/main/kotlin/plus/rua/project/CalendarViewModel.kt index 12b12b4..e134f2b 100644 --- a/core/src/main/kotlin/plus/rua/project/CalendarViewModel.kt +++ b/core/src/main/kotlin/plus/rua/project/CalendarViewModel.kt @@ -3,6 +3,7 @@ package plus.rua.project import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -51,6 +52,37 @@ data class CalendarUiState( val showLegalHoliday: Boolean ) +/** + * 将六个 [Flow] 合并为一个 [Flow],使用 [transform] 处理最新值。 + * + * kotlinx-coroutines 1.11 仅内置到 5 参数的 [combine] 重载, + * 此扩展用于扁平化 6 个 StateFlow 的合并,避免多层嵌套产生的中间流。 + */ +private inline fun combine( + flow: Flow, + flow2: Flow, + flow3: Flow, + flow4: Flow, + flow5: Flow, + flow6: Flow, + crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R +): Flow = combine( + combine(flow, flow2, flow3, flow4, flow5) { t1, t2, t3, t4, t5 -> + Quintuple(t1, t2, t3, t4, t5) + }, + flow6 +) { quintuple, t6 -> + transform(quintuple.first, quintuple.second, quintuple.third, quintuple.fourth, quintuple.fifth, t6) +} + +private data class Quintuple( + val first: T1, + val second: T2, + val third: T3, + val fourth: T4, + val fifth: T5 +) + /** * 日历状态管理,持有选中日期、折叠状态和 ISO 周号计算逻辑。 * @@ -137,17 +169,20 @@ class CalendarViewModel( /** 聚合 UI 状态,减少 Compose 层分散订阅导致的重组。 */ val uiState: StateFlow = combine( - combine(_selectedDate, _isCollapsed, ::Pair), - combine(_isYearView, _yearViewYear, ::Pair), - combine(_collapseProgress, _showLegalHoliday, ::Pair) - ) { dateCollapsed, yearViewYearPair, progressHoliday -> + _selectedDate, + _isCollapsed, + _isYearView, + _yearViewYear, + _collapseProgress, + _showLegalHoliday + ) { selectedDate, isCollapsed, isYearView, yearViewYear, collapseProgress, showLegalHoliday -> CalendarUiState( - selectedDate = dateCollapsed.first, - isCollapsed = dateCollapsed.second, - isYearView = yearViewYearPair.first, - yearViewYear = yearViewYearPair.second, - collapseProgress = progressHoliday.first, - showLegalHoliday = progressHoliday.second + selectedDate = selectedDate, + isCollapsed = isCollapsed, + isYearView = isYearView, + yearViewYear = yearViewYear, + collapseProgress = collapseProgress, + showLegalHoliday = showLegalHoliday ) }.stateIn( viewModelScope, @@ -245,7 +280,7 @@ class CalendarViewModel( } /** - * 折叠状态下下拉恢复,delta 为负值(向下拖)推动 progress 向 0。 + * 折叠状态下拉恢复,delta 为负值(向下拖)推动 progress 向 0。 * * @param delta 拖拽增量,已归一化到 [0,1] 区间 */ diff --git a/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt b/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt index 4bb276f..d7748e2 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt @@ -72,7 +72,8 @@ fun CalendarMonthPage( val holidayBadges by produceState( initialValue = emptyMap(), - key1 = days + key1 = year, + key2 = month ) { val map = mutableMapOf() for (dayData in days) { @@ -82,7 +83,7 @@ fun CalendarMonthPage( value = map } - val holidayEdges = remember(holidayBadges, days) { + val holidayEdges = remember(holidayBadges, year, month) { val map = mutableMapOf() for (dayData in days) { val date = dayData.date @@ -99,7 +100,7 @@ fun CalendarMonthPage( } val weeks = remember(days) { days.chunked(7) } - val anchorIndex = remember(weeks, selectedDate) { + val anchorIndex = remember(year, month, selectedDate) { weeks.indexOfFirst { week -> week.any { it.date == selectedDate } } } val totalHeightDp = if (rowHeightPx > 0) { diff --git a/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt b/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt index 7031a26..73b8a1b 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt @@ -5,6 +5,8 @@ import androidx.compose.foundation.pager.PagerDefaults import androidx.compose.foundation.pager.PagerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshotFlow @@ -66,14 +68,21 @@ fun CalendarPager( } } + val currentPageOffsetFraction by remember { + derivedStateOf { pagerState.currentPageOffsetFraction } + } + val currentPage by remember { + derivedStateOf { pagerState.currentPage } + } + HorizontalPager( state = pagerState, - beyondViewportPageCount = 0, + beyondViewportPageCount = 1, flingBehavior = PagerDefaults.flingBehavior(state = pagerState), modifier = modifier ) { page -> - val pageOffset = abs(pagerState.currentPageOffsetFraction) - val isCurrentPage = page == pagerState.currentPage + val pageOffset = abs(currentPageOffsetFraction) + val isCurrentPage = page == currentPage val alpha = if (isCurrentPage) { 1f - pageOffset } else { diff --git a/core/src/main/kotlin/plus/rua/project/ui/DayCell.kt b/core/src/main/kotlin/plus/rua/project/ui/DayCell.kt index e243c1d..bc52234 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/DayCell.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/DayCell.kt @@ -1,9 +1,10 @@ package plus.rua.project.ui -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.animateColor import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -91,52 +92,61 @@ fun DayCell( else -> DayCellState.NORMAL } - val revealProgress by animateFloatAsState( - targetValue = when (currentState) { + val transition = updateTransition(targetState = currentState, label = "DayCell") + + val revealProgress by transition.animateFloat( + transitionSpec = { tween(150, easing = FastOutSlowInEasing) }, + label = "revealProgress" + ) { state -> + when (state) { DayCellState.SELECTED, DayCellState.SELECTED_TODAY -> 1f else -> 0f - }, - animationSpec = tween(150, easing = FastOutSlowInEasing), - label = "revealProgress" - ) + } + } - val contentColor by animateColorAsState( - targetValue = when (currentState) { + val contentColor by transition.animateColor( + transitionSpec = { tween(150, easing = FastOutSlowInEasing) }, + label = "contentColor" + ) { state -> + when (state) { DayCellState.SELECTED_TODAY -> MaterialTheme.colorScheme.onPrimaryContainer DayCellState.SELECTED -> MaterialTheme.colorScheme.primary DayCellState.TODAY -> MaterialTheme.colorScheme.primary DayCellState.OTHER_MONTH -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) DayCellState.NORMAL -> MaterialTheme.colorScheme.onSurface - }, - animationSpec = tween(150, easing = FastOutSlowInEasing), - label = "contentColor" - ) + } + } // 选中今天:实心填充 primaryContainer;其他状态不填充。 - val selectedFillColor by animateColorAsState( - targetValue = when (currentState) { + val selectedFillColor by transition.animateColor( + transitionSpec = { tween(150, easing = FastOutSlowInEasing) }, + label = "selectedFillColor" + ) { state -> + when (state) { DayCellState.SELECTED_TODAY -> MaterialTheme.colorScheme.primaryContainer else -> Color.Transparent - }, - animationSpec = tween(150, easing = FastOutSlowInEasing), - label = "selectedFillColor" - ) + } + } // 选中非今天:绘制描边圆,避免遮挡右上角角标。 - val selectedOutlineAlpha by animateFloatAsState( - targetValue = when (currentState) { + val selectedOutlineAlpha by transition.animateFloat( + transitionSpec = { tween(150, easing = FastOutSlowInEasing) }, + label = "selectedOutlineAlpha" + ) { state -> + when (state) { DayCellState.SELECTED -> 1f else -> 0f - }, - animationSpec = tween(150, easing = FastOutSlowInEasing), - label = "selectedOutlineAlpha" - ) + } + } val selectedOutlineColor = MaterialTheme.colorScheme.primary - val lunarColor by animateColorAsState( - targetValue = if (isAnnotationHighlight) { - when (currentState) { + val lunarColor by transition.animateColor( + transitionSpec = { tween(150, easing = FastOutSlowInEasing) }, + label = "lunarColor" + ) { state -> + if (isAnnotationHighlight) { + when (state) { DayCellState.SELECTED_TODAY -> MaterialTheme.colorScheme.onPrimaryContainer.copy( alpha = 0.85f ) @@ -146,7 +156,7 @@ fun DayCell( DayCellState.NORMAL -> MaterialTheme.colorScheme.error.copy(alpha = 0.7f) } } else { - when (currentState) { + when (state) { DayCellState.SELECTED_TODAY -> MaterialTheme.colorScheme.onPrimaryContainer.copy( alpha = 0.7f ) @@ -155,10 +165,8 @@ fun DayCell( DayCellState.OTHER_MONTH -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.26f) DayCellState.NORMAL -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) } - }, - animationSpec = tween(150, easing = FastOutSlowInEasing), - label = "lunarColor" - ) + } + } val holidayBgColor = when (holidayBadge) { "休" -> MaterialTheme.colorScheme.error.copy(alpha = 0.10f)