diff --git a/README.md b/README.md index 35149a4..d0e9644 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,115 @@ - iOS 入口为 `MainViewController.kt`,Xcode 工程位于 `iosApp/` 线条小狗表情包来自 https://www.douban.com/group/topic/264788645/?_i=9181692phrDzjR,9241256phrDzjR + +## 快速开始 + +```bash +# 编译 Android debug APK +./gradlew :app:assembleDebug + +# 安装到设备 +./gradlew :app:installDebug + +# 编译 release APK(含 Baseline Profiles) +./gradlew :app:assembleRelease +``` + +## Baseline Profiles 维护指南 + +本项目已集成 [Baseline Profiles](https://developer.android.com/topic/performance/baselineprofiles),用于消除冷启动时的 JIT 编译开销。Release APK 构建时会自动将 `core/src/main/baseline-prof.txt` 打包进 `assets/dexopt/baseline.prof`。 + +### 何时需要更新 `baseline-prof.txt` + +每次发版前,检查以下清单。任一条件命中,就需要更新 profile: + +| 检查项 | 是否需要更新 | 说明 | +|--------|-------------|------| +| 新增/修改了首帧渲染的 composable | 必须 | `MainActivity` → `CalendarMonthView` 启动路径上的任何 composable | +| 修改了 `DayCell.kt` 的方法签名 | 必须 | DayCell 是启动最热点(35 次调用) | +| 修改了 `CalendarMonthPage.kt` 的方法签名 | 必须 | 月度网格页面在首帧渲染 | +| 修改了 `CalendarMonthView.kt` 的方法签名 | 必须 | 根 composable | +| 修改了 `LunarCache.kt` 的计算逻辑 | 建议 | 缓存 miss 时会走 compute() 路径 | +| 新增/删除了 `tyme4kt` 的调用 | 建议 | 农历计算是 CPU 密集型 | +| 仅修改 UI 颜色、文字、布局 | 不需要 | 不涉及方法签名变化 | +| 新增设置页、关于页等非首屏页面 | 不需要 | 不在冷启动路径 | + +### 如何更新 + +1. **定位新增/变更的热点方法** + + 通过 logcat 抓取启动时的 JIT 编译日志: + ```bash + adb shell setprop dalvik.vm.extra-opts -verbose:compiler + adb logcat -s "JIT" | grep "plus/rua/project" + ``` + + 或抓取带 ART 详细日志的启动 trace: + ```bash + adb shell am start -W -n plus.rua.project/.MainActivity + adb logcat -d | grep -E "JIT|dex2oat|BaselineProfile" + ``` + +2. **编辑 `core/src/main/baseline-prof.txt`** + + 新增规则格式: + ``` + HSPL<类全路径;-><方法名>(<参数类型签名>)<返回类型签名> + ``` + + 例如新增一个 composable `NewWidget`: + ``` + HSPLplus/rua/project/ui/NewWidgetKt;->NewWidget(Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V + ``` + + 常用签名速查: + + | Kotlin 类型 | Profile 签名 | + |------------|-------------| + | `Int` | `I` | + | `Float` | `F` | + | `Boolean` | `Z` | + | `String` | `Ljava/lang/String;` | + | `LocalDate` | `Lkotlinx/datetime/LocalDate;` | + | `Modifier` | `Landroidx/compose/ui/Modifier;` | + | `Composer` | `Landroidx/compose/runtime/Composer;` | + | `Function0` | `Lkotlin/jvm/functions/Function0;` | + | `Function1` | `Lkotlin/jvm/functions/Function1;` | + | `ShiftKind?` | `Lplus/rua/project/ShiftKind;` | + | `Unit` / 无返回值 | `V` | + +3. **验证方法名不被混淆** + + 如果新增的方法在 `core/proguard-rules.pro` 中没有保留规则,添加: + ```proguard + -keepclassmembers class plus.rua.project.ui.NewWidgetKt { + public static void NewWidget(...); + } + ``` + +4. **编译验证** + ```bash + ./gradlew :core:compileDebugKotlin + ./gradlew :app:assembleRelease + ``` + +5. **确认 profile 打包成功** + ```bash + unzip -l app/build/outputs/apk/release/app-release-unsigned.apk | grep baseline + # 应看到 assets/dexopt/baseline.prof + ``` + +### 自动化替代方案(推荐后期迁移) + +手动维护容易遗漏,长期建议迁移到 **Macrobenchmark 自动生成**: + +1. 创建 `:macrobenchmark` 模块 +2. 编写启动基准测试(自动遍历冷启动路径) +3. `./gradlew :macrobenchmark:connectedBenchmarkAndroidTest` +4. 自动输出 `baseline-prof.txt`,直接替换即可 + +参考:[Android Baseline Profiles 官方文档](https://developer.android.com/topic/performance/baselineprofiles/create-profile) + +## 性能监控 + +项目内置了 `LunarCache`(农历/节气/节假日缓存)和性能追踪(`ComposeTrace.kt`)。查看 `DEVELOPMENT.md` 了解如何使用 Perfetto/Systrace 进行深度性能分析。 diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 3d0fa43..81b99fc 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -11,6 +11,7 @@ android { defaultConfig { minSdk = libs.versions.android.minSdk.get().toInt() + consumerProguardFiles("proguard-rules.pro") } buildFeatures { @@ -55,6 +56,7 @@ dependencies { implementation(libs.tyme4kt) implementation(libs.sketch.compose) implementation(libs.sketch.animated.gif) + implementation(libs.androidx.profileinstaller) testImplementation("org.jetbrains.kotlin:kotlin-test-junit:${libs.versions.kotlin.get()}") testImplementation(libs.kotlinx.coroutines.test) diff --git a/core/proguard-rules.pro b/core/proguard-rules.pro new file mode 100644 index 0000000..e5a4d9e --- /dev/null +++ b/core/proguard-rules.pro @@ -0,0 +1,17 @@ +# Baseline Profiles 保留规则:确保方法名不被 R8 混淆,使 profile 规则匹配正确 +-keepattributes SourceFile,LineNumberTable + +# 保留 YaYa 业务类方法名(profile 中引用的类) +-keepclassmembers class plus.rua.project.ui.DayCellKt { + public static void DayCell(...); +} +-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); +} +-keepclassmembers class plus.rua.project.DayCellInfo { + public java.lang.String getAnnotationText(); + public boolean getIsAnnotationHighlight(); + public java.lang.String getHolidayBadge(); + public java.lang.String getLunarMonthName(); +} diff --git a/core/src/main/baseline-prof.txt b/core/src/main/baseline-prof.txt new file mode 100644 index 0000000..96526fd --- /dev/null +++ b/core/src/main/baseline-prof.txt @@ -0,0 +1,142 @@ +# YaYa 日历应用 Baseline Profile +# 覆盖冷启动热点路径,消除 JIT 编译导致的启动掉帧 + +# ========== 业务层热点方法 ========== + +# DayCell — 启动时最热的 composable(35 次调用) +HSPLplus/rua/project/ui/DayCellKt;->DayCell(Lkotlinx/datetime/LocalDate;ZZZLplus/rua/project/ShiftKind;ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;)V + +# LunarCache — 日期计算缓存 +HSPLplus/rua/project/LunarCache;->getOrCompute(Lkotlinx/datetime/LocalDate;)Lplus/rua/project/DayCellInfo; +HSPLplus/rua/project/LunarCache;->compute(Lkotlinx/datetime/LocalDate;)Lplus/rua/project/DayCellInfo; + +# DayCellInfo 数据类 +HSPLplus/rua/project/DayCellInfo;->(Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V +HSPLplus/rua/project/DayCellInfo;->getAnnotationText()Ljava/lang/String; +HSPLplus/rua/project/DayCellInfo;->getIsAnnotationHighlight()Z +HSPLplus/rua/project/DayCellInfo;->getHolidayBadge()Ljava/lang/String; + +# CalendarMonthPage — 月度网格页面 +HSPLplus/rua/project/ui/CalendarMonthPageKt;->CalendarMonthPage(ILkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Lkotlin/jvm/functions/Function1;FIFLkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;)V +HSPLplus/rua/project/ui/CalendarMonthPageKt;->generateMonthDays(II)Ljava/util/List; + +# CalendarMonthView — 根 composable +HSPLplus/rua/project/ui/CalendarMonthViewKt;->CalendarMonthView(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;)V +HSPLplus/rua/project/ui/CalendarMonthViewKt;->access$uiPerfLog(Ljava/lang/String;Ljava/lang/String;)V + +# CalendarPager — 分页器 +HSPLplus/rua/project/ui/CalendarPagerKt;->CalendarPager(Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;FIFLkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function1;Landroidx/compose/foundation/pager/PagerState;Landroidx/compose/ui/Modifier;)V + +# WeekPager — 周视图分页器 +HSPLplus/rua/project/ui/WeekPagerKt;->WeekPager(Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ZLandroidx/compose/ui/Modifier;)V + +# BottomCard — 底部卡片 +HSPLplus/rua/project/ui/BottomCardKt;->BottomCard(Lplus/rua/project/CalendarViewModel;Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;FLandroidx/compose/ui/Modifier;)V +HSPLplus/rua/project/ui/BottomCardKt;->access$formatLunarDate(Lkotlinx/datetime/LocalDate;)Ljava/lang/String; + +# CalendarViewModel — 状态管理 +HSPLplus/rua/project/CalendarViewModel;->(Lkotlinx/coroutines/CoroutineScope;Lkotlin/time/Clock;)V +HSPLplus/rua/project/CalendarViewModel;->getSelectedDate()Lkotlinx/datetime/LocalDate; +HSPLplus/rua/project/CalendarViewModel;->getMonthDays(II)Ljava/util/List; + +# CalendarUtils — 工具函数 +HSPLplus/rua/project/ui/CalendarUtilsKt;->calculateWeeksCount(II)I +HSPLplus/rua/project/ui/CalendarUtilsKt;->calculateWeeksCountForPage(ILkotlinx/datetime/LocalDate;)I +HSPLplus/rua/project/ui/CalendarUtilsKt;->pageToYearMonth(IILkotlinx/datetime/LocalDate;)Lkotlin/Pair; +HSPLplus/rua/project/ui/CalendarUtilsKt;->yearMonthToPage(IIII)I + +# ========== Compose 框架关键路径 ========== + +# Composer — Compose 运行时核心 +HSPLandroidx/compose/runtime/ComposerImpl;->startRestartGroup(I)Landroidx/compose/runtime/Composer; +HSPLandroidx/compose/runtime/ComposerImpl;->endRestartGroup()Landroidx/compose/runtime/ScopeUpdateScope; +HSPLandroidx/compose/runtime/ComposerImpl;->startReplaceableGroup(I)V +HSPLandroidx/compose/runtime/ComposerImpl;->endReplaceableGroup()V +HSPLandroidx/compose/runtime/ComposerImpl;->changed(Ljava/lang/Object;)Z +HSPLandroidx/compose/runtime/ComposerImpl;->rememberedValue()Ljava/lang/Object; +HSPLandroidx/compose/runtime/ComposerImpl;->updateRememberedValue(Ljava/lang/Object;)V + +# Recomposer +HSPLandroidx/compose/runtime/Recomposer;->composeInitial$runtime_release(Landroidx/compose/runtime/ControlledComposition;Lkotlin/jvm/functions/Function2;)V +HSPLandroidx/compose/runtime/Recomposer;->runRecomposeAndApplyChanges(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + +# AndroidComposeView — 渲染 +HSPLandroidx/compose/ui/platform/AndroidComposeView;->draw(Landroid/graphics/Canvas;)V +HSPLandroidx/compose/ui/platform/AndroidComposeView;->onMeasure(II)V +HSPLandroidx/compose/ui/platform/AndroidComposeView;->onLayout(ZIIII)V +HSPLandroidx/compose/ui/platform/AndroidComposeView;->dispatchDraw(Landroid/graphics/Canvas;)V + +# remember / derivedStateOf +HSPLandroidx/compose/runtime/EffectsKt;->remember(Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; +HSPLandroidx/compose/runtime/EffectsKt;->remember([Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; +HSPLandroidx/compose/runtime/SnapshotStateKt;->derivedStateOf(Lkotlin/jvm/functions/Function0;)Landroidx/compose/runtime/State; + +# SideEffect / LaunchedEffect +HSPLandroidx/compose/runtime/EffectsKt;->SideEffect(Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V +HSPLandroidx/compose/runtime/EffectsKt;->LaunchedEffect(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V + +# ========== Compose UI 基础组件 ========== + +# Box / Column / Row +HSPLandroidx/compose/foundation/layout/BoxKt;->Box(Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;ZLkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V +HSPLandroidx/compose/foundation/layout/ColumnKt;->Column(Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/layout/Arrangement$Vertical;Landroidx/compose/ui/Alignment$Horizontal;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V +HSPLandroidx/compose/foundation/layout/RowKt;->Row(Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/layout/Arrangement$Horizontal;Landroidx/compose/ui/Alignment$Vertical;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V + +# Text +HSPLandroidx/compose/material3/TextKt;->Text(Ljava/lang/String;Landroidx/compose/ui/Modifier;JJLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontSynthesis;Landroidx/compose/ui/text/style/TextAlign;Landroidx/compose/ui/text/style/TextDirection;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/text/style/TextGeometricTransform;Landroidx/compose/ui/text/style/TextIndent;Landroidx/compose/ui/text/PlatformTextStyle;Landroidx/compose/ui/text/style/LineHeightStyle;Landroidx/compose/ui/text/style/LineBreak;Landroidx/compose/ui/text/style/Hyphens;Landroidx/compose/ui/text/IntLocale;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/runtime/Composer;IIII)V + +# Surface / MaterialTheme +HSPLandroidx/compose/material3/SurfaceKt;->Surface(Landroidx/compose/ui/Modifier;Landroidx/compose/ui/graphics/Shape;JJFFLandroidx/compose/foundation/BorderStroke;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V +HSPLandroidx/compose/material3/MaterialTheme;->()V + +# Modifier +HSPLandroidx/compose/ui/Modifier$Companion;->()V +HSPLandroidx/compose/ui/Modifier$Companion;->getInstance$annotations()V +HSPLandroidx/compose/ui/Modifier;->then(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier; + +# ========== 动画相关 ========== + +# updateTransition / animateFloat / animateColor +HSPLandroidx/compose/animation/core/TransitionKt;->updateTransition(Ljava/lang/Object;Ljava/lang/String;Landroidx/compose/runtime/Composer;II)Landroidx/compose/animation/core/Transition; +HSPLandroidx/compose/animation/core/Transition$TransitionAnimationState;->(Landroidx/compose/animation/core/Transition;Ljava/lang/Object;Landroidx/compose/animation/core/TwoWayConverter;Landroidx/compose/animation/core/AnimationVector;)V +HSPLandroidx/compose/animation/core/Transition$TransitionAnimationState;->updateTargetValues$animation_core_release(Ljava/lang/Object;Landroidx/compose/animation/core/AnimationSpec;)V + +# animateFloatAsState +HSPLandroidx/compose/animation/core/AnimateAsStateKt;->animateFloatAsState(FLandroidx/compose/animation/core/AnimationSpec;FLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State; + +# ========== Pager 相关 ========== + +# HorizontalPager +HSPLandroidx/compose/foundation/pager/PagerKt;->HorizontalPager(Landroidx/compose/foundation/pager/PagerState;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/pager/PagerFlingBehavior;ZIILandroidx/compose/foundation/gestures/snapping/SnapPosition;Landroidx/compose/ui/input/nestedscroll/NestedScrollConnection;ZLkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V +HSPLandroidx/compose/foundation/pager/PagerState;->(IFLandroidx/compose/foundation/lazy/LazyListState;)V +HSPLandroidx/compose/foundation/pager/PagerState;->getCurrentPage()I +HSPLandroidx/compose/foundation/pager/PagerState;->getCurrentPageOffsetFraction()F + +# ========== Sketch 图片加载 ========== + +# AsyncImage +HSPLio/github/panpf/sketch4/compose/AsyncImageKt;->AsyncImage(Ljava/lang/String;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;FLandroidx/compose/ui/graphics/ColorFilter;Landroidx/compose/ui/graphics/FilterQuality;ZLkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V + +# ========== tyme4kt 农历计算 ========== + +# SolarDay — 创建和查询 +HSPLcom/tyme/solar/SolarDay;->fromYmd(III)Lcom/tyme/solar/SolarDay; +HSPLcom/tyme/solar/SolarDay;->getLunarDay()Lcom/tyme/lunar/LunarDay; +HSPLcom/tyme/solar/SolarDay;->getTermDay()Lcom/tyme/solar/SolarTermDay; +HSPLcom/tyme/solar/SolarDay;->getFestival()Lcom/tyme/solar/SolarFestival; +HSPLcom/tyme/solar/SolarDay;->getLegalHoliday()Lcom/tyme/solar/LegalHoliday; + +# LunarDay +HSPLcom/tyme/lunar/LunarDay;->getFestival()Lcom/tyme/lunar/LunarFestival; +HSPLcom/tyme/lunar/LunarDay;->getName()Ljava/lang/String; +HSPLcom/tyme/lunar/LunarDay;->getLunarMonth()Lcom/tyme/lunar/LunarMonth; + +# LunarMonth +HSPLcom/tyme/lunar/LunarMonth;->getName()Ljava/lang/String; + +# SolarTermDay +HSPLcom/tyme/solar/SolarTermDay;->getDayIndex()I +HSPLcom/tyme/solar/SolarTermDay;->getSolarTerm()Lcom/tyme/solar/SolarTerm; + +# SolarTerm +HSPLcom/tyme/solar/SolarTerm;->getName()Ljava/lang/String; diff --git a/core/src/main/kotlin/plus/rua/project/LunarCache.kt b/core/src/main/kotlin/plus/rua/project/LunarCache.kt index 914e73c..8e33d9a 100644 --- a/core/src/main/kotlin/plus/rua/project/LunarCache.kt +++ b/core/src/main/kotlin/plus/rua/project/LunarCache.kt @@ -43,6 +43,19 @@ object LunarCache { trimIfNeeded() } + /** + * 获取完整农历日期字符串,如"农历四月初三"。 + * + * 复用缓存中的 lunarMonthName 和 annotationText,避免重复创建 SolarDay。 + */ + @Synchronized + @Suppress("DEPRECATION") // monthNumber 无替代 API + fun formatLunarDate(date: LocalDate): String { + val info = getOrCompute(date) + val dayText = info.annotationText.removeSuffix("月") + return "农历${info.lunarMonthName}${dayText}" + } + private fun trimIfNeeded() { if (cache.size > MAX_SIZE) { val toRemove = (cache.size * 0.2).toInt().coerceAtLeast(1) @@ -61,34 +74,35 @@ object LunarCache { val solarDay = SolarDay.fromYmd(date.year, date.monthNumber, date.day) val holidayBadge = solarDay.getLegalHoliday()?.let { if (it.isWork()) "班" else "休" } val lunarDay = solarDay.getLunarDay() + val lunarMonth = lunarDay.getLunarMonth() + val lunarMonthName = lunarMonth.getName() // 农历传统节日(仅当天) val lunarFestival = lunarDay.getFestival() if (lunarFestival != null) { - return DayCellInfo(lunarFestival.getName(), true, holidayBadge) + return DayCellInfo(lunarFestival.getName(), true, holidayBadge, lunarMonthName) } // 节气(当天才显示) val termDay = solarDay.getTermDay() if (termDay.getDayIndex() == 0) { - return DayCellInfo(termDay.getSolarTerm().getName(), true, holidayBadge) + return DayCellInfo(termDay.getSolarTerm().getName(), true, holidayBadge, lunarMonthName) } // 公历节日(仅当天) val solarFestival = solarDay.getFestival() if (solarFestival != null) { - return DayCellInfo(solarFestival.getName(), true, holidayBadge) + return DayCellInfo(solarFestival.getName(), true, holidayBadge, lunarMonthName) } // 默认:农历日期 val name = lunarDay.getName() val text = if (name == "初一") { - val lunarMonth = lunarDay.getLunarMonth() - "${lunarMonth.getName()}月" + "${lunarMonthName}月" } else { name } - return DayCellInfo(text, false, holidayBadge) + return DayCellInfo(text, false, holidayBadge, lunarMonthName) } } @@ -102,5 +116,6 @@ object LunarCache { data class DayCellInfo( val annotationText: String, val isAnnotationHighlight: Boolean, - val holidayBadge: String? + val holidayBadge: String?, + val lunarMonthName: String? = null ) diff --git a/core/src/main/kotlin/plus/rua/project/ui/BottomCard.kt b/core/src/main/kotlin/plus/rua/project/ui/BottomCard.kt index 1c41994..8ff72c7 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/BottomCard.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/BottomCard.kt @@ -17,6 +17,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -26,6 +27,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.datetime.LocalDate import plus.rua.project.CalendarViewModel +import plus.rua.project.LunarCache import plus.rua.project.ShiftKind /** @@ -55,7 +57,7 @@ fun BottomCard( @Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口 val solarDesc = "${selectedDate.monthNumber}月${selectedDate.day}日" - val lunarDesc = formatLunarDate(selectedDate) + val lunarDesc = remember(selectedDate) { LunarCache.formatLunarDate(selectedDate) } val shiftMessage = when (viewModel.shiftKindAt(selectedDate)) { ShiftKind.WORK -> "小小上班,轻松拿下!" ShiftKind.OFF -> "耶耶耶,美美休息!" diff --git a/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthView.kt b/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthView.kt index e15d8ec..7a22073 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthView.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthView.kt @@ -552,16 +552,23 @@ private fun BottomCardArea( label = "bottomCardSlide" ) - BottomCard( - viewModel = viewModel, - selectedDate = viewModel.selectedDate, - today = today, - dragRangePx = dragRangePx, - modifier = modifier.graphicsLayer { - translationY = slideProgress * 200.dp.toPx() - alpha = 1f - slideProgress - } - ) + // P0-J: 延迟一帧显示 BottomCard,避免 AnimatedGif 和 lunar 计算阻塞首帧 + var frameCount by remember { mutableIntStateOf(0) } + androidx.compose.runtime.SideEffect { frameCount++ } + val shouldShow = frameCount >= 2 + + if (shouldShow) { + BottomCard( + viewModel = viewModel, + selectedDate = viewModel.selectedDate, + today = today, + dragRangePx = dragRangePx, + modifier = modifier.graphicsLayer { + translationY = slideProgress * 200.dp.toPx() + alpha = 1f - slideProgress + } + ) + } } @Composable diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bfa9231..9cd30b5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ material3 = "1.10.0-alpha05" kotlinx-datetime = "0.8.0" tyme4kt = "1.4.5" sketch = "4.4.0" +profileinstaller = "1.4.0" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -35,6 +36,7 @@ kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-t tyme4kt = { module = "cn.6tail:tyme4kt", version.ref = "tyme4kt" } sketch-compose = { module = "io.github.panpf.sketch4:sketch-compose", version.ref = "sketch" } sketch-animated-gif = { module = "io.github.panpf.sketch4:sketch-animated-gif", version.ref = "sketch" } +androidx-profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version.ref = "profileinstaller" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" }