diff --git a/core/src/main/kotlin/plus/rua/project/CalendarViewModel.kt b/core/src/main/kotlin/plus/rua/project/CalendarViewModel.kt index 5256691..e4ec3b7 100644 --- a/core/src/main/kotlin/plus/rua/project/CalendarViewModel.kt +++ b/core/src/main/kotlin/plus/rua/project/CalendarViewModel.kt @@ -147,9 +147,12 @@ class CalendarViewModel( /** 聚合 UI 状态,减少 Compose 层分散订阅导致的重组。 */ val uiState: StateFlow = combine( - combine(_selectedDate, _isCollapsed, _isYearView) { s, c, y -> Triple(s, c, y) }, - combine(_yearViewYear, _collapseProgress, _showLegalHoliday) { y, p, h -> Triple(y, p, h) } - ) { (selectedDate, isCollapsed, isYearView), (yearViewYear, collapseProgress, showLegalHoliday) -> + _selectedDate, + _isCollapsed, + _isYearView, + _yearViewYear, + combine(_collapseProgress, _showLegalHoliday) { cp, h -> cp to h } + ) { selectedDate, isCollapsed, isYearView, yearViewYear, (collapseProgress, showLegalHoliday) -> CalendarUiState( selectedDate = selectedDate, isCollapsed = isCollapsed, diff --git a/core/src/main/kotlin/plus/rua/project/LunarCache.kt b/core/src/main/kotlin/plus/rua/project/LunarCache.kt index 9390786..8f76af5 100644 --- a/core/src/main/kotlin/plus/rua/project/LunarCache.kt +++ b/core/src/main/kotlin/plus/rua/project/LunarCache.kt @@ -31,6 +31,31 @@ class LunarCache( trimIfNeeded() computed } + /** + * 批量获取指定日期的信息,一次性加锁并返回 Map。 + * + * @param dates 日期列表 + * @return 日期 → DayCellInfo 的映射 + */ + suspend fun getOrComputeBatch(dates: List): Map = mutex.withLock { + val result = HashMap(dates.size) + var modified = false + for (date in dates) { + val cached = cache[date] + if (cached != null) { + result[date] = cached + } else { + val computed = compute(date) + cache[date] = computed + result[date] = computed + modified = true + } + } + if (modified) { + trimIfNeeded() + } + result + } /** * 批量预计算并填充缓存。 diff --git a/core/src/main/kotlin/plus/rua/project/PhotoProcessor.kt b/core/src/main/kotlin/plus/rua/project/PhotoProcessor.kt index 1a7f00c..7e0a03d 100644 --- a/core/src/main/kotlin/plus/rua/project/PhotoProcessor.kt +++ b/core/src/main/kotlin/plus/rua/project/PhotoProcessor.kt @@ -147,15 +147,29 @@ object PhotoProcessor { var result = rotate(source, rotationDegrees) // 2. 裁剪 if (cropLeft != null && cropRight != null) { - result = crop(result, cropLeft, cropTop, cropRight, cropBottom) + val cropped = crop(result, cropLeft, cropTop, cropRight, cropBottom) + if (result != source && result != cropped) { + result.recycle() + } + result = cropped } // 3. 合成手写笔触 if (strokes.isNotEmpty()) { - result = drawStrokes(result, strokes, displayWidth, displayHeight) + val withStrokes = drawStrokes(result, strokes, displayWidth, displayHeight) + if (result != source && result != withStrokes) { + result.recycle() + } + result = withStrokes } // 4. 落盘(JPEG 90% 质量) - FileOutputStream(destFile).use { out -> - result.compress(Bitmap.CompressFormat.JPEG, 90, out) + try { + FileOutputStream(destFile).use { out -> + result.compress(Bitmap.CompressFormat.JPEG, 90, out) + } + } finally { + if (result != source && !result.isRecycled) { + result.recycle() + } } return destFile.absolutePath } 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 6c0568a..e3b202f 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt @@ -80,11 +80,7 @@ fun CalendarMonthPage( key1 = year, key2 = month ) { - val map = mutableMapOf() - for (dayData in days) { - map[dayData.date] = LunarCache.default.getOrCompute(dayData.date) - } - value = map + value = LunarCache.default.getOrComputeBatch(days.map { it.date }) } val holidayEdges = remember(lunarDataMap, year, month) { 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 9c04b07..a4249e8 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import plus.rua.project.composeTraceBeginSection import plus.rua.project.composeTraceEndSection -import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.graphicsLayer import kotlinx.coroutines.flow.drop import kotlinx.coroutines.launch import kotlinx.datetime.LocalDate @@ -69,12 +69,6 @@ fun CalendarPager( } } - val currentPageOffsetFraction by remember { - derivedStateOf { pagerState.currentPageOffsetFraction } - } - val currentPage by remember { - derivedStateOf { pagerState.currentPage } - } HorizontalPager( state = pagerState, @@ -82,13 +76,6 @@ fun CalendarPager( flingBehavior = PagerDefaults.flingBehavior(state = pagerState), modifier = modifier.testTag("calendar_pager") ) { page -> - val pageOffset = abs(currentPageOffsetFraction) - val isCurrentPage = page == currentPage - val alpha = if (isCurrentPage) { - 1f - pageOffset - } else { - pageOffset - } val (year, month) = pageToYearMonth(page, initialYear, initialMonth) composeTraceBeginSection("CalendarPager:Page:$year-$month") CalendarMonthPage( @@ -117,7 +104,11 @@ fun CalendarPager( shiftKindAt = shiftKindAt, showLegalHoliday = showLegalHoliday, onRowHeightMeasured = onRowHeightMeasured, - modifier = Modifier.alpha(alpha) + modifier = Modifier.graphicsLayer { + val pageOffset = abs(pagerState.currentPageOffsetFraction) + val isCurrentPage = page == pagerState.currentPage + alpha = if (isCurrentPage) 1f - pageOffset else pageOffset + } ) composeTraceEndSection() } diff --git a/core/src/main/kotlin/plus/rua/project/ui/CalendarUtils.kt b/core/src/main/kotlin/plus/rua/project/ui/CalendarUtils.kt index e616160..f2eee06 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/CalendarUtils.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/CalendarUtils.kt @@ -79,7 +79,13 @@ fun getMonthGridInfo(year: Int, month: Int): MonthGridInfo { * @return 网格行数 */ fun calculateWeeksCount(year: Int, month: Int): Int { - return getMonthGridInfo(year, month).rows + val offset = LocalDate(year, Month(month), 1).dayOfWeek.ordinal + val daysInMonth = when (month) { + 2 -> if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) 29 else 28 + 4, 6, 9, 11 -> 30 + else -> 31 + } + return ((offset + daysInMonth - 1) / 7) + 1 } /** diff --git a/core/src/main/kotlin/plus/rua/project/ui/WeekPager.kt b/core/src/main/kotlin/plus/rua/project/ui/WeekPager.kt index bcf5b3e..7aa4c42 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/WeekPager.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/WeekPager.kt @@ -15,7 +15,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp import kotlinx.coroutines.flow.drop import kotlinx.datetime.DatePeriod @@ -79,17 +79,14 @@ fun WeekPager( modifier = modifier ) { page -> composeTraceBeginSection("WeekPager:Page") - val pageOffset = abs(pagerState.currentPageOffsetFraction) - val isCurrentPage = page == pagerState.currentPage - val alpha = if (isCurrentPage) { - 1f - pageOffset - } else { - pageOffset - } val weekMonday = pageToWeekMonday(page, initialWeekMonday) Row( modifier = Modifier - .alpha(alpha) + .graphicsLayer { + val pageOffset = abs(pagerState.currentPageOffsetFraction) + val isCurrentPage = page == pagerState.currentPage + alpha = if (isCurrentPage) 1f - pageOffset else pageOffset + } .fillMaxWidth() .background(MaterialTheme.colorScheme.surface) .padding(vertical = ROW_PADDING_DP.dp) diff --git a/core/src/main/kotlin/plus/rua/project/ui/YearGridView.kt b/core/src/main/kotlin/plus/rua/project/ui/YearGridView.kt index 252d7ac..7eb8345 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/YearGridView.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/YearGridView.kt @@ -106,30 +106,32 @@ fun YearGridView( val dayTextStyle = remember { TextStyle(fontSize = 8.sp, lineHeight = 12.sp) } // 延迟文本测量到下一帧,避免首帧阻塞 - val dayLayouts by produceState, androidx.compose.ui.text.TextLayoutResult>?>(null, textMeasurer, dayTextStyle, colors) { + val dayLayouts by produceState?>(null, textMeasurer, dayTextStyle, colors) { val days = 1..31 - val colorList = listOf(colors.day, colors.todayText, colors.otherMonth) - value = days.flatMap { d -> - colorList.map { c -> - (d to c) to textMeasurer.measure(d.toString(), dayTextStyle.copy(color = c)) + val colorTypes = listOf(0 to colors.day, 1 to colors.todayText, 2 to colors.otherMonth) + val map = HashMap(32 * 3) + days.forEach { d -> + colorTypes.forEach { (type, c) -> + map[d * 3 + type] = textMeasurer.measure(d.toString(), dayTextStyle.copy(color = c)) } - }.toMap() + } + value = map } - val titleLayouts by produceState, androidx.compose.ui.text.TextLayoutResult>?>(null, textMeasurer, colors) { - value = (1..12).flatMap { month -> + val titleLayouts by produceState?>(null, textMeasurer, colors) { + val map = HashMap(13 * 2) + (1..12).forEach { month -> val text = "${month}月" - listOf( - (month to true) to textMeasurer.measure( - text, - TextStyle(fontSize = 10.sp, color = colors.titleSelected, fontWeight = FontWeight.Bold) - ), - (month to false) to textMeasurer.measure( - text, - TextStyle(fontSize = 10.sp, color = colors.titleNormal) - ) + map[month * 2 + 1] = textMeasurer.measure( + text, + TextStyle(fontSize = 10.sp, color = colors.titleSelected, fontWeight = FontWeight.Bold) ) - }.toMap() + map[month * 2] = textMeasurer.measure( + text, + TextStyle(fontSize = 10.sp, color = colors.titleNormal) + ) + } + value = map } val weekdayLayouts by produceState?>(null, textMeasurer, colors) { @@ -193,8 +195,8 @@ private fun MiniMonth( today: LocalDate, days: List, colors: MiniMonthColors, - dayLayouts: Map, androidx.compose.ui.text.TextLayoutResult>?, - titleLayouts: Map, androidx.compose.ui.text.TextLayoutResult>?, + dayLayouts: Map?, + titleLayouts: Map?, weekdayLayouts: Map?, onClick: () -> Unit, modifier: Modifier = Modifier @@ -226,7 +228,7 @@ private fun MiniMonth( val cellWidth = size.width / 7f // 1. 绘制标题 - val titleLayout = tl[month to isSelected]!! + val titleLayout = tl[month * 2 + (if (isSelected) 1 else 0)]!! drawText( textLayoutResult = titleLayout, topLeft = Offset( @@ -259,9 +261,14 @@ private fun MiniMonth( val isToday = dayData.date == today && dayData.isCurrentMonth val dayNum = if (dayData.isCurrentMonth) dayData.date.day else 0 - val textColor: Color = when { - !dayData.isCurrentMonth -> colors.otherMonth - isToday -> colors.todayText + val colorType = when { + !dayData.isCurrentMonth -> 2 + isToday -> 1 + else -> 0 + } + val textColor: Color = when (colorType) { + 2 -> colors.otherMonth + 1 -> colors.todayText else -> colors.day } @@ -275,7 +282,7 @@ private fun MiniMonth( } if (dayNum > 0) { - dl[dayNum to textColor]?.let { layoutResult -> + dl[dayNum * 3 + colorType]?.let { layoutResult -> drawText( textLayoutResult = layoutResult, topLeft = Offset(