refactor: 清理无效 ProGuard 规则、调试日志,trace 解耦 VM

P0 正确性与工程卫生修复:

- 删除 core/proguard-rules.pro 中全部无效的 -keepclassmembers 规则
  (LunarCache.getOrCompute 实为实例 suspend 方法,generateMonthDays 实为
   private,签名均不匹配,属 placebo);同时移除 core/build.gradle.kts
  release block 内重复的 consumerProguardFiles 声明
- 将 7 处 composeTrace 调用从 CalendarViewModel 移至 Compose 层
  (MenuItem.onClick / onMonthClick / BottomCard 回调),VM 不再依赖
  android.os.Trace,可在纯 JVM 环境测试无需兜底
- 删除 CalendarViewModel 与 4 个 UI 文件中约 30 处 logd 调用及其辅助
  变量/ SideEffect/ DisposableEffect (AnimLog.kt 工具函数保留)
- 删除 CalendarViewModel.getIsoWeekNumber 不可达的 weekNumber < 1 递归分支
- 修正 MainActivity.kt setContent 块缩进错位
- 同步 README: sketch 渲染 GIF→动画 WebP,补提 :macrobenchmark 模块;
  删除 AGENTS.md 顶部过时的「README 与实际不符」注释

验证: spotlessApply UP-TO-DATE, testDebugUnitTest 全过,
       assembleDebug + assembleRelease (R8) 均成功
This commit is contained in:
xfy 2026-06-15 16:24:38 +08:00
parent ef785a3ca7
commit 564e4e3960
10 changed files with 48 additions and 193 deletions

View File

@ -2,8 +2,6 @@
纯 Android + Jetpack Compose 日历应用。功能:农历/节气/节日、个人班次排期WORK/OFF 循环)、月/周/年三视图。
> **README 与实际不符**README 提及 "Kotlin Multiplatform / iOS",实际为纯 Android 项目。模块名为 `:core``:app`,无 `:shared``:androidApp`
## 模块结构
| 模块 | 类型 | 职责 |

View File

@ -19,8 +19,8 @@
- Kotlin 2.3 · Jetpack Compose · Material 3
- `kotlinx-datetime` 处理所有日期逻辑
- `tyme4kt` 提供农历、节气与传统节日
- `sketch` 渲染 GIF 动画
- 模块:`:core`UI + 逻辑) · `:app`(薄壳)
- `sketch` 渲染动画 WebP
- 模块:`:core`UI + 逻辑) · `:app`(薄壳) · `:macrobenchmark`Baseline Profile 生成)
## 构建

View File

@ -35,7 +35,6 @@ android {
}
release {
isMinifyEnabled = false
consumerProguardFiles("proguard-rules.pro")
buildConfigField("boolean", "ENABLE_TRACE", "false")
}
create("trace") {

View File

@ -1,34 +1,6 @@
# Baseline Profiles 保留规则:确保方法名不被 R8 混淆,使 profile 规则匹配正确
-keepattributes SourceFile,LineNumberTable
# ========== 启动热点路径保留 ==========
# DayCell 启动最热点
-keepclassmembers class plus.rua.project.ui.DayCellKt {
public static void DayCell(...);
}
# LunarCache 日期计算缓存
-keepclassmembers class plus.rua.project.LunarCache {
public static plus.rua.project.DayCellInfo getOrCompute(kotlinx.datetime.LocalDate);
public static java.lang.String formatLunarDate(kotlinx.datetime.LocalDate);
public static void precompute(...);
}
# DayCellInfo 数据类
-keepclassmembers class plus.rua.project.DayCellInfo {
public java.lang.String getAnnotationText();
public boolean getIsAnnotationHighlight();
public java.lang.String getHolidayBadge();
public java.lang.String getLunarMonthName();
}
# CalendarMonthPage
-keepclassmembers class plus.rua.project.ui.CalendarMonthPageKt {
public static void CalendarMonthPage(...);
public static java.util.List generateMonthDays(...);
}
# ========== 第三方库保留 ==========
-keep class kotlinx.datetime.** { *; }
-keep class cn.tyme.** { *; }

View File

@ -21,11 +21,8 @@ import kotlinx.datetime.plus
import kotlinx.datetime.todayIn
import plus.rua.project.ui.COLLAPSE_THRESHOLD
import plus.rua.project.ui.getMonthGridInfo
import plus.rua.project.util.logd
import kotlin.time.Clock
private const val TAG_VM = "CalendarExpand"
/**
* 日历日期数据用于网格单元格渲染
*
@ -170,23 +167,11 @@ class CalendarViewModel(
* 当前视图被直接移除动画只作用在目标视图的 scale/alpha
*/
fun toggleYearView() {
val t0 = System.nanoTime()
if (_isYearView.value) {
logd(TAG_VM, "[toggleYearView] ===== START Year→Month t=$t0 =====")
composeTraceBeginSection("YearView→MonthView")
_isYearView.value = false
logd(TAG_VM, "[toggleYearView] isYearView=false dt=${(System.nanoTime() - t0) / 1_000_000}ms")
composeTraceEndSection()
logd(TAG_VM, "[toggleYearView] ===== END Year→Month total=${(System.nanoTime() - t0) / 1_000_000}ms =====")
} else {
logd(TAG_VM, "[toggleYearView] ===== START Month→Year t=$t0 =====")
composeTraceBeginSection("MonthView→YearView")
_yearViewYear.value = _selectedDate.value.year
logd(TAG_VM, "[toggleYearView] yearViewYear=${_yearViewYear.value} dt=${(System.nanoTime() - t0) / 1_000_000}ms")
_isYearView.value = true
logd(TAG_VM, "[toggleYearView] isYearView=true dt=${(System.nanoTime() - t0) / 1_000_000}ms")
composeTraceEndSection()
logd(TAG_VM, "[toggleYearView] ===== END Month→Year total=${(System.nanoTime() - t0) / 1_000_000}ms =====")
}
}
@ -201,18 +186,10 @@ class CalendarViewModel(
* 从年视图选择月份后返回月视图
*/
fun selectMonthFromYearView(month: Int) {
val t0 = System.nanoTime()
logd(TAG_VM, "[selectMonthFromYearView] ===== START month=$month t=$t0 =====")
composeTraceBeginSection("YearView:SelectMonth")
val date = if (_yearViewYear.value == today.year && today.month.number == month) today
else LocalDate(_yearViewYear.value, Month(month), 1)
logd(TAG_VM, "[selectMonthFromYearView] targetDate=$date dt=${(System.nanoTime() - t0) / 1_000_000}ms")
_selectedDate.value = date
logd(TAG_VM, "[selectMonthFromYearView] selectedDate set dt=${(System.nanoTime() - t0) / 1_000_000}ms")
_isYearView.value = false
logd(TAG_VM, "[selectMonthFromYearView] isYearView=false dt=${(System.nanoTime() - t0) / 1_000_000}ms")
composeTraceEndSection()
logd(TAG_VM, "[selectMonthFromYearView] ===== END total=${(System.nanoTime() - t0) / 1_000_000}ms =====")
}
fun incrementYear() {
@ -233,9 +210,7 @@ class CalendarViewModel(
* @param delta 拖拽增量已归一化到 [0,1] 区间
*/
fun onDrag(delta: Float) {
composeTraceBeginSection("VM:collapseProgress:onDrag")
_collapseProgress.value = (_collapseProgress.value + delta).coerceIn(0f, 1f)
composeTraceEndSection()
}
/**
@ -244,7 +219,6 @@ class CalendarViewModel(
* 拖拽超过阈值时自动折叠到周视图否则回弹到月视图
*/
fun onDragEnd() {
composeTraceBeginSection("VM:collapseProgress:onDragEnd")
val progress = _collapseProgress.value
if (progress > COLLAPSE_THRESHOLD) {
_isCollapsed.value = true
@ -253,7 +227,6 @@ class CalendarViewModel(
_isCollapsed.value = false
_collapseProgress.value = 0f
}
composeTraceEndSection()
}
/**
@ -262,11 +235,7 @@ class CalendarViewModel(
* @param delta 拖拽增量已归一化到 [0,1] 区间
*/
fun onExpandDrag(delta: Float) {
composeTraceBeginSection("VM:collapseProgress:onExpandDrag")
val old = _collapseProgress.value
_collapseProgress.value = (_collapseProgress.value + delta).coerceIn(0f, 1f)
logd(TAG_VM, "onExpandDrag: delta=$delta old=$old new=${_collapseProgress.value}")
composeTraceEndSection()
}
/**
@ -275,19 +244,14 @@ class CalendarViewModel(
* 下拉超过阈值时自动展开到月视图否则回弹到周视图
*/
fun onExpandDragEnd() {
composeTraceBeginSection("VM:collapseProgress:onExpandDragEnd")
val progress = _collapseProgress.value
val result = if (progress < (1 - COLLAPSE_THRESHOLD)) {
if (progress < (1 - COLLAPSE_THRESHOLD)) {
_isCollapsed.value = false
_collapseProgress.value = 0f
"EXPANDED"
} else {
_isCollapsed.value = true
_collapseProgress.value = 1f
"COLLAPSED (bounce back)"
}
logd(TAG_VM, "onExpandDragEnd: progress=$progress threshold=${1 - COLLAPSE_THRESHOLD} result=$result")
composeTraceEndSection()
}
/**
@ -302,9 +266,7 @@ class CalendarViewModel(
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)) {
return if (weekNumber > getIsoWeeksInYear(date.year)) {
1
} else {
weekNumber
@ -325,14 +287,16 @@ class CalendarViewModel(
*
* 网格行数按实际需要计算4/5/6每行7格首行从该月1号所在周的周一开始
*
* 注意此方法当前无 UI 调用方UI 层使用 [CalendarMonthPage] 内的 generateMonthDays
* 保留此方法供测试覆盖和未来复用 isToday/isSelected 字段语义
*
* @param year 年份
* @param month 月份1-12
* @return 日历网格列表每项包含日期是否当月是否今天是否选中
*/
fun getMonthDays(year: Int, month: Int): List<CalendarDay> {
composeTraceBeginSection("getMonthDays:$year-$month")
val info = getMonthGridInfo(year, month)
val result = (0 until info.totalDays).map { i ->
return (0 until info.totalDays).map { i ->
val date = info.startDate.plus(DatePeriod(days = i))
CalendarDay(
date = date,
@ -341,7 +305,5 @@ class CalendarViewModel(
isSelected = date == selectedDate.value
)
}
composeTraceEndSection()
return result
}
}

View File

@ -23,7 +23,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import plus.rua.project.composeTraceBeginSection
import plus.rua.project.composeTraceEndSection
import plus.rua.project.util.logd
import kotlinx.datetime.DatePeriod
import kotlinx.datetime.LocalDate
import kotlinx.datetime.Month
@ -34,8 +33,6 @@ import plus.rua.project.DayCellInfo
import plus.rua.project.LunarCache
import plus.rua.project.ShiftKind
private const val TAG_CMP = "CalendarExpandAnim"
/**
* 月度日历网格页面支持两阶段折叠动画
@ -110,16 +107,6 @@ fun CalendarMonthPage(
weeks.indexOfFirst { week -> week.any { it.date == selectedDate } }
}
// 全局动画参数日志(每次重组)
val pageFrameNs = System.nanoTime()
val totalCells = weeks.size * 7
logd(TAG_CMP) {
"Page[$year-$month]: anchorIndex=$anchorIndex weeksSize=${weeks.size} totalCells=$totalCells " +
"phase1End=${if (anchorIndex > 0 && weeks.size > 1) anchorIndex.toFloat() / (weeks.size - 1) else 0f} " +
"effectiveWeeks=$effectiveWeeks rowHeightPx=$rowHeightPx " +
"collapseProgress=$collapseProgress lunarMapSize=${lunarDataMap.size} frameNs=$pageFrameNs"
}
val totalHeightDp = if (rowHeightPx > 0) {
val h = rowHeightPx.toFloat()
val totalPx = h * (1 + (effectiveWeeks - 1) * (1f - collapseProgress))
@ -216,17 +203,6 @@ private fun WeekRow(
else -> 1f
}
val frameTimeNs = System.nanoTime()
logd(TAG_CMP) {
"WeekRow[$weekIndex]: " +
"isAnchor=$isAnchor isAbove=$isAbove isBelow=$isBelow " +
"phase1=$phase1 phase2=$phase2 phase1End=$phase1End " +
"belowRowsHeight=$belowRowsHeight rowHeightPx=$rowHeightPx " +
"yOffsetPx=$yOffsetPx rowAlpha=$rowAlpha " +
"collapseProgress=$collapseProgress " +
"frameNs=$frameTimeNs"
}
if (rowAlpha > 0.01f) {
Row(
modifier = Modifier

View File

@ -49,7 +49,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
@ -85,7 +84,6 @@ import plus.rua.project.composeTraceEndSection
import kotlin.math.abs
import kotlin.time.Clock
import androidx.lifecycle.viewmodel.compose.viewModel
import plus.rua.project.util.logd
/**
* 日历主界面包含月/周视图切换折叠动画和年视图转场
@ -120,14 +118,6 @@ fun CalendarMonthView(
animationSpec = spring(stiffness = Spring.StiffnessMedium),
label = "collapseProgress"
)
var lastLoggedCollapse by remember { mutableStateOf(-1f) }
SideEffect {
if (kotlin.math.abs(lastLoggedCollapse - collapseProgress) > 0.001f) {
lastLoggedCollapse = collapseProgress
logd("AnimLog", "[Collapse] target=$collapseProgress animated=$animatedCollapseProgress isCollapsed=$isCollapsed")
}
logd("AnimLog", "[MonthView] isYearView=$isYearView isCollapsed=$isCollapsed collapseProgress=$collapseProgress animated=$animatedCollapseProgress selectedDate=$selectedDate yearViewYear=$yearViewYear")
}
val density = LocalDensity.current
val coroutineScope = rememberCoroutineScope()
@ -182,13 +172,6 @@ fun CalendarMonthView(
screenWidthPx = size.width
}
) {
var lastLoggedTargetState by remember { mutableStateOf(false) }
SideEffect {
if (lastLoggedTargetState != isYearView) {
lastLoggedTargetState = isYearView
logd("AnimLog", "[AnimatedContent] ★ targetState CHANGE isYearView=$isYearView t=${System.nanoTime()}")
}
}
AnimatedContent(
targetState = isYearView,
label = "month_year_transition",
@ -206,13 +189,6 @@ fun CalendarMonthView(
modifier = Modifier.fillMaxSize()
) { yearViewActive ->
if (!yearViewActive) {
androidx.compose.runtime.DisposableEffect(Unit) {
val t = System.nanoTime()
logd("AnimLog", "[MonthView] ★★★ ENTER composable t=$t")
onDispose {
logd("AnimLog", "[MonthView] ★★★ LEAVE composable alive=${(System.nanoTime() - t) / 1_000_000}ms")
}
}
composeTraceBeginSection("MonthView:Compose")
composeTraceBeginSection("CalendarPagerArea")
val layoutReady = rowHeightPx > 0
@ -285,13 +261,6 @@ fun CalendarMonthView(
composeTraceEndSection()
composeTraceEndSection()
} else {
androidx.compose.runtime.DisposableEffect(Unit) {
val t = System.nanoTime()
logd("AnimLog", "[YearView] ★★★ ENTER composable t=$t")
onDispose {
logd("AnimLog", "[YearView] ★★★ LEAVE composable alive=${(System.nanoTime() - t) / 1_000_000}ms")
}
}
composeTraceBeginSection("YearView:Compose")
Column(
modifier = Modifier
@ -310,13 +279,6 @@ fun CalendarMonthView(
}
}
)
var lastLoggedYearPage by remember { mutableIntStateOf(-1) }
SideEffect {
if (lastLoggedYearPage != yearPagerState.currentPage) {
lastLoggedYearPage = yearPagerState.currentPage
logd("AnimLog", "[YearPager] page=${yearPagerState.currentPage} settledPage=${yearPagerState.settledPage} offset=${yearPagerState.currentPageOffsetFraction}")
}
}
HorizontalPager(
state = yearPagerState,
beyondViewportPageCount = 0,
@ -329,26 +291,21 @@ fun CalendarMonthView(
val pageYear = remember(page, yearViewYear, yearPagerState.settledPage) {
yearViewYear + (page - yearPagerState.settledPage)
}
val isCurrentPage = page == yearPagerState.currentPage
if (isCurrentPage) {
logd("AnimLog") { "[YearPager] Compose page=$page year=$pageYear" }
}
YearGridView(
year = pageYear,
selectedMonth = if (pageYear == currentYear) currentMonth else 0,
today = today,
onMonthClick = { month ->
val clickT = System.nanoTime()
logd("AnimLog") { "[YearGridView] MonthClick month=$month year=$pageYear t=$clickT" }
composeTraceBeginSection("YearView:SelectMonth")
viewModel.selectMonthFromYearView(month)
val targetPage = yearMonthToPage(
yearViewYear, month,
today.year, today.month.number
)
if (targetPage != pagerState.currentPage) {
logd("AnimLog") { "[YearPager] scrollToPage target=$targetPage" }
coroutineScope.launch { pagerState.scrollToPage(targetPage) }
}
composeTraceEndSection()
},
modifier = Modifier
)
@ -426,7 +383,11 @@ fun CalendarMonthView(
selected = !isYearView,
onClick = {
isMenuExpanded = false
if (isYearView) viewModel.toggleYearView()
if (isYearView) {
composeTraceBeginSection("YearView→MonthView")
viewModel.toggleYearView()
composeTraceEndSection()
}
}
)
MenuItem(
@ -434,7 +395,11 @@ fun CalendarMonthView(
selected = isYearView,
onClick = {
isMenuExpanded = false
if (!isYearView) viewModel.toggleYearView()
if (!isYearView) {
composeTraceBeginSection("MonthView→YearView")
viewModel.toggleYearView()
composeTraceEndSection()
}
}
)
HorizontalDivider(
@ -492,13 +457,12 @@ private fun CalendarPagerArea(
pagerState: PagerState,
modifier: Modifier = Modifier
) {
val t0 = System.nanoTime()
val density = LocalDensity.current
val interpolatedWeeks by remember {
derivedStateOf {
val fraction = pagerState.currentPageOffsetFraction
val result = if (abs(fraction) > OFFSET_FRACTION_THRESHOLD) {
if (abs(fraction) > OFFSET_FRACTION_THRESHOLD) {
val cp = pagerState.currentPage
val baseWeeks = calculateWeeksCountForPage(cp, today)
val targetPage = cp + if (fraction > 0) 1 else -1
@ -507,8 +471,6 @@ private fun CalendarPagerArea(
} else {
calculateWeeksCountForPage(pagerState.currentPage, today).toFloat()
}
logd("AnimLog", "[PagerArea] interpolatedWeeks=$result fraction=$fraction page=${pagerState.currentPage}")
result
}
}
@ -532,8 +494,6 @@ private fun CalendarPagerArea(
}
} else 0
logd("AnimLog", "[PagerArea] gridHeightPx=$gridHeightPx effectiveRowHeightPx=$effectiveRowHeightPx effectiveWeeks=$effectiveWeeks collapseProgress=$collapseProgress screenW=$screenWidthPx rowH=$rowHeightPx dt=${(System.nanoTime() - t0) / 1_000_000}ms")
val pagerModifier = if (rowHeightPx > 0 && gridHeightPx > 0) {
Modifier
.height(with(density) { gridHeightPx.toDp() })
@ -597,10 +557,26 @@ private fun BottomCardArea(
selectedDate = uiState.selectedDate,
today = today,
shiftKind = shiftKind,
onDrag = { delta -> viewModel.onDrag(delta) },
onDragEnd = { viewModel.onDragEnd() },
onExpandDrag = { delta -> viewModel.onExpandDrag(delta) },
onExpandDragEnd = { viewModel.onExpandDragEnd() },
onDrag = { delta ->
composeTraceBeginSection("VM:collapseProgress:onDrag")
viewModel.onDrag(delta)
composeTraceEndSection()
},
onDragEnd = {
composeTraceBeginSection("VM:collapseProgress:onDragEnd")
viewModel.onDragEnd()
composeTraceEndSection()
},
onExpandDrag = { delta ->
composeTraceBeginSection("VM:collapseProgress:onExpandDrag")
viewModel.onExpandDrag(delta)
composeTraceEndSection()
},
onExpandDragEnd = {
composeTraceBeginSection("VM:collapseProgress:onExpandDragEnd")
viewModel.onExpandDragEnd()
composeTraceEndSection()
},
dragRangePx = dragRangePx,
modifier = modifier
.offset(y = with(density) { (slideProgress * 300).dp })

View File

@ -5,19 +5,15 @@ 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.SideEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import plus.rua.project.composeTraceBeginSection
import plus.rua.project.composeTraceEndSection
import plus.rua.project.util.logd
import androidx.compose.ui.draw.alpha
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch
@ -80,14 +76,6 @@ fun CalendarPager(
derivedStateOf { pagerState.currentPage }
}
var lastLoggedPage by remember { mutableIntStateOf(-1) }
SideEffect {
if (lastLoggedPage != pagerState.currentPage) {
lastLoggedPage = pagerState.currentPage
logd("AnimLog", "[CalendarPager] page=${pagerState.currentPage} settledPage=${pagerState.settledPage} offsetFraction=${pagerState.currentPageOffsetFraction}")
}
}
HorizontalPager(
state = pagerState,
beyondViewportPageCount = 0,
@ -102,9 +90,6 @@ fun CalendarPager(
pageOffset
}
val (year, month) = pageToYearMonth(page, initialYear, initialMonth)
if (isCurrentPage) {
logd("AnimLog", "[CalendarPager] Compose page=$page ($year-$month) alpha=$alpha pageOffset=$pageOffset")
}
composeTraceBeginSection("CalendarPager:Page:$year-$month")
CalendarMonthPage(
year = year,
@ -112,7 +97,6 @@ fun CalendarPager(
selectedDate = selectedDate,
today = today,
onDateClick = { date ->
val clickT = System.nanoTime()
onDateClick(date)
// 点击跨月日期时,滚动到该月对应的页
val clickedYear = date.year
@ -121,7 +105,6 @@ fun CalendarPager(
val targetPage =
yearMonthToPage(clickedYear, clickedMonth, initialYear, initialMonth)
if (targetPage != pagerState.currentPage) {
logd("AnimLog", "[CalendarPager] Cross-month click date=$date targetPage=$targetPage t=$clickT")
coroutineScope.launch {
pagerState.animateScrollToPage(targetPage)
}

View File

@ -23,11 +23,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import plus.rua.project.util.logd
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -82,14 +79,6 @@ fun YearGridView(
onMonthClick: (Int) -> Unit,
modifier: Modifier = Modifier
) {
val enterT = System.nanoTime()
logd("AnimLog", "[YearGridView] ★★★ ENTER year=$year selectedMonth=$selectedMonth t=$enterT")
androidx.compose.runtime.DisposableEffect(year) {
logd("AnimLog", "[YearGridView] DisposableEffect attached year=$year")
onDispose {
logd("AnimLog", "[YearGridView] ★★★ LEAVE year=$year alive=${(System.nanoTime() - enterT) / 1_000_000}ms")
}
}
composeTraceBeginSection("YearGridView:$year")
// P0-F: 主题色在 YearGridView 级别一次性读取并缓存