Compare commits
3 Commits
ffafee550e
...
ca00839c9b
| Author | SHA1 | Date | |
|---|---|---|---|
| ca00839c9b | |||
| 023ae44364 | |||
| 7d8e0fca61 |
174
AGENTS.md
174
AGENTS.md
@ -1,85 +1,135 @@
|
|||||||
# YaYa
|
# Repository Guidelines
|
||||||
|
|
||||||
纯 Android + Jetpack Compose 日历应用。功能:农历/节气/节日、个人班次排期(WORK/OFF 循环)、月/周/年三视图。
|
Guide for AI assistants working in **YaYa (鸭鸭日历)** — a pure-Android Jetpack Compose lunar/shift calendar app. Remote: `github.com/DefectingCat/yayacal` (+ `git.rua.plus` mirror). UI text is Chinese; date logic is `kotlinx-datetime` only (`java.util.Calendar` is banned).
|
||||||
|
|
||||||
## 模块结构
|
> Note: `CLAUDE.md` is a symlink to this file. `COMMENTS.md` is referenced from several docs but does not exist yet — treat the KDoc rules in [Code Conventions](#code-conventions--common-patterns) as authoritative until it is created.
|
||||||
|
|
||||||
| 模块 | 类型 | 职责 |
|
## Project Overview
|
||||||
|------|------|------|
|
|
||||||
| `:core` | `com.android.library` | 所有 Compose UI、ViewModel、业务逻辑 |
|
|
||||||
| `:app` | `com.android.application` | 薄壳:MainActivity + Manifest + 主题 |
|
|
||||||
| `:macrobenchmark` | `com.android.test` | Baseline Profile / Startup Profile 生成 |
|
|
||||||
|
|
||||||
**铁律**:`:app` 不添加业务逻辑,所有代码写在 `:core`。
|
YaYa is a single-app Android calendar focused on: Chinese lunar calendar, solar terms (节气) and traditional festivals (via **tyme4kt**), a personal WORK/OFF shift cycle, month/week/year views with infinite paging, and a photo-journal ("date recorder") feature with an in-app photo editor. Latest release `1.3.0` (see `CHANGELOG.md`); base version lives in `gradle.properties` (`app.version.base`).
|
||||||
|
|
||||||
## 常用命令
|
## Architecture & Data Flow
|
||||||
|
|
||||||
```bash
|
**Three Gradle modules** (`settings.gradle.kts`, typesafe accessors on):
|
||||||
./gradlew :app:assembleDebug # 构建 debug APK
|
|
||||||
./gradlew :app:installDebug # 安装到设备
|
| Module | Type | Responsibility |
|
||||||
./gradlew :core:testDebugUnitTest # 运行全部单元测试
|
|--------|------|----------------|
|
||||||
./gradlew :core:testDebugUnitTest --tests "plus.rua.project.ui.CalendarUtilsTest" # 单类
|
| `:core` | `com.android.library` | **All** Compose UI, ViewModels, business logic, Room data layer |
|
||||||
./gradlew spotlessApply # 格式化(ktlint)
|
| `:app` | `com.android.application` | Thin shell: Activities + Manifest + theme. **Iron rule: no business logic here.** |
|
||||||
|
| `:macrobenchmark` | `com.android.test` | Baseline Profile / Startup Profile generation |
|
||||||
|
|
||||||
|
Dependency chain: `:app` → `:core`; `:macrobenchmark` → `:app`.
|
||||||
|
|
||||||
|
**Navigation is Activity + Intent, NOT Compose Navigation.** Each screen has a 1:1 Activity in `:app` that just does `setContent { YaYaTheme { SomeScreen() } }`. `core/.../ui/DateRecorderNav.kt` centralizes the Intent-extra contract (`EXTRA_TEMP_PHOTO_PATH`, `EXTRA_FINAL_PHOTO_PATH`, `EXTRA_RECORD_ID`). Slide/fade transitions come from `app/.../BaseActivity.kt`.
|
||||||
|
|
||||||
|
**State pattern (all ViewModels):** private `MutableStateFlow` backing fields exposed as read-only `StateFlow` via `asStateFlow()`; many aggregate into a single `uiState: StateFlow<UiState>` through `combine(...).stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), initial)`. The aggregation is an explicit design goal to minimize Compose recomposition. Tests read `stateFlow.value` directly (no Turbine).
|
||||||
|
|
||||||
|
**Home-screen data flow:** `CalendarViewModel` owns `selectedDate`, `isCollapsed`, `collapseProgress` (0=month↔1=week), `isYearView`, `shiftPattern`. It pulls lunar data from `LunarCache` (LRU `LinkedHashMap` + coroutine `Mutex`) which builds `DayCellInfo` per cell from tyme4kt `SolarDay`. `CalendarMonthView` lifts the shared `PagerState` and renders `CalendarPager` → `CalendarMonthPage` → `DayCell`.
|
||||||
|
|
||||||
|
**Persistence:** Room (`DateRecordDatabase` v1, `exportSchema = true` → `core/schemas/`). Repository stores relative photo paths under `filesDir/Pictures/date_recorder/` and deletes photos on record delete. Non-DB prefs (shift pattern, date checker) use SharedPreferences with **custom string encoding** (no JSON dependency) in `*Storage.kt`.
|
||||||
|
|
||||||
|
## Key Directories
|
||||||
|
|
||||||
|
```
|
||||||
|
app/src/main/
|
||||||
|
kotlin/plus/rua/project/ # Activities (shell only): MainActivity, BaseActivity, *Activity
|
||||||
|
AndroidManifest.xml # Activity registry; SplashActivity present but disabled
|
||||||
|
res/anim/, res/values/ # slide transitions, themes (Theme.Material.* NoActionBar)
|
||||||
|
core/src/main/
|
||||||
|
kotlin/plus/rua/project/ # ViewModels, business logic, data, storage, tracing
|
||||||
|
kotlin/plus/rua/project/ui/ # All Composable screens + calendar grid + nav contract
|
||||||
|
kotlin/plus/rua/project/ui/theme/ # YaYaTheme
|
||||||
|
assets/animations/*.webp # scanned at config time → BuildConfig.WEBP_FILES
|
||||||
|
baseline-prof.txt / baselineProfiles/startup-prof.txt # generated profiles
|
||||||
|
core/src/test/kotlin/plus/rua/project[/ui]/ # the only unit tests
|
||||||
|
macrobenchmark/src/main/java/.../baseline/ # StartupBenchmark, BaselineProfileGenerator
|
||||||
|
scripts/ # profile.sh, analyze-trace.sh, resize_duck_icon.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Baseline Profile / Startup Profile 和性能追踪需连接设备:
|
**Namespace quirk:** `:core`'s `android.namespace` is `plus.rua.project.shared`, so core's `R` and `BuildConfig` live there — but the code package is `plus.rua.project`.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./gradlew :macrobenchmark:updateBaselineProfile # 生成并复制两份 Profile 到 :core
|
./gradlew :app:assembleDebug # build debug APK
|
||||||
./scripts/profile.sh # 默认 8 秒,输出到 logs/
|
./gradlew :app:installDebug # install to device/emulator
|
||||||
|
./gradlew :core:testDebugUnitTest # all unit tests (JVM, no emulator)
|
||||||
|
./gradlew :core:testDebugUnitTest --tests "plus.rua.project.ui.CalendarUtilsTest" # one class
|
||||||
|
./gradlew spotlessApply # format (ktlint) — run before committing
|
||||||
```
|
```
|
||||||
|
|
||||||
## 构建配置
|
Profiling & profiles (device required):
|
||||||
|
```bash
|
||||||
|
./gradlew :macrobenchmark:updateBaselineProfile # generates + copies both profiles into :core
|
||||||
|
./gradlew :macrobenchmark:connectedBenchmarkAndroidTest # benchmarks only (no copy)
|
||||||
|
./scripts/profile.sh # Perfetto trace, default 8s → logs/
|
||||||
|
./scripts/profile.sh --scenario month_browse --trace 15 # specific scenario, trace build
|
||||||
|
./scripts/profile.sh --list-scenarios # list named scenarios
|
||||||
|
./scripts/analyze-trace.sh [logs/trace_*.perfetto-trace] # post-hoc SQL analysis
|
||||||
|
```
|
||||||
|
|
||||||
- **AGP** 9.2.1 · **Kotlin** 2.3.21 · **JVM target** 17
|
## Workflow
|
||||||
- **compileSdk/targetSdk** 37 · **minSdk** 24
|
|
||||||
- **版本目录**:`gradle/libs.versions.toml`
|
|
||||||
- **构建类型**:`debug`(默认)、`release`(R8 混淆 + 资源压缩)、`trace`(release + trace 标记保留)、`benchmark`(macrobenchmark 专用)
|
|
||||||
- **R8**:`android.enableR8.fullMode=true`(`gradle.properties`)
|
|
||||||
- **缓存**:configuration cache + build cache 已启用
|
|
||||||
- **版本号**:动态 `baseVersion_gitHash_buildDate`(例:`1.0.0_a1b2c_010626`)
|
|
||||||
- **格式化**:Spotless + ktlint,覆盖 `src/**/*.kt` 和 `*.gradle.kts`(根 `build.gradle.kts`)
|
|
||||||
|
|
||||||
## 包名
|
- **每完成一个功能点,提交一次。** 不要攒着最后一起提交。一个"功能点"是一个可独立说明的小块改动(一个 bug 修复、一个 Composable、一组测试、一次重构),而不是整次会话的全部产出。
|
||||||
|
- **提交粒度与信息由 AI 自主决定**:自行判断该功能点属于 `feat` / `fix` / `refactor` / `docs` / `test` / `chore`,写清摘要;改动小则合并提交,改动跨多步则在每步落盘。
|
||||||
|
- 只暂存本次功能点相关的文件,不要 `git add .` 把无关改动(如会话前遗留的脏文件)一并带入。提交前看一眼 `git status`。
|
||||||
|
- 当前在 `main` 分支:按仓库惯例可以直接在 `main` 上提交常规改动;如改动涉及发布或需评审,先开分支。
|
||||||
|
- **每写完一个功能后跑 `./gradlew :app:installDebug`** 做一次真机/模拟器冒烟安装,验证能编过、能装上。**允许失败**:无连接设备、无模拟器、签名/环境问题等导致失败属正常,不必阻塞后续工作——记下失败原因即可继续。这条是为了在能装上时尽早暴露问题,不是硬性 gate。
|
||||||
|
|
||||||
- `:core` 逻辑层:`plus.rua.project`
|
## Code Conventions & Common Patterns
|
||||||
- `:core` UI 层:`plus.rua.project.ui`
|
|
||||||
- `:core` `android.namespace` 实际为 `plus.rua.project.shared`(build.gradle.kts),但代码包名用 `plus.rua.project`
|
|
||||||
- `:app`:`plus.rua.project`
|
|
||||||
|
|
||||||
## 代码规范
|
- **KDoc required** on every public `@Composable`: document parameters and *when callbacks fire*. (This is the missing `COMMENTS.md` contract.)
|
||||||
|
- **`Modifier` always last** in Composable signatures.
|
||||||
|
- **Callbacks use `on` prefix** (`onDateClick`, `onDragEnd`).
|
||||||
|
- **Clickable list items:** use `Card(onClick = …)` with `CardDefaults.cardElevation(defaultElevation = 0.dp)` — **not** bare `Modifier.clickable()`.
|
||||||
|
- **`@Suppress("DEPRECATION")`** must carry an inline comment explaining why (currently used for `monthNumber`).
|
||||||
|
- **Dates:** `kotlinx-datetime` everywhere. `java.util.Calendar` is forbidden.
|
||||||
|
- **Testability seams:** `Clock` is constructor-injectable (tests pass a `FixedClock`); SharedPreferences are faked with in-memory impls written per-test-file (duplicated deliberately to avoid same-package private-class name clashes).
|
||||||
|
- **Pagers:** `HorizontalPager` with `pageCount = { Int.MAX_VALUE }` centered at `START_PAGE = Int.MAX_VALUE/2`; page↔month via `pageToYearMonth`/`yearMonthToPage` in `ui/CalendarUtils.kt`; sync via `snapshotFlow { pagerState.settledPage }.drop(1)`.
|
||||||
|
- **Trace markers:** `ComposeTrace.kt` wraps `android.os.Trace`, gated by `BuildConfig.ENABLE_TRACE` (on in `debug`/`trace`, off in `release`). Key markers: `MonthView:Compose`, `YearView:Compose`, `CalendarPager:Page:<year>-<month>`, `WeekPager:Page`, `VM:collapseProgress:*`, `YearGridView:*`. Full catalog in `DEVELOPMENT.md`.
|
||||||
|
- **Formatting:** Spotless + ktlint cover `src/**/*.kt` and root `*.gradle.kts` (not `.toml`/`.md`/scripts). `.editorconfig` permits PascalCase `@Composable` function names.
|
||||||
|
|
||||||
- 公共 `@Composable` 函数必须有 KDoc(参数说明 + 回调触发时机),见 `COMMENTS.md`
|
## Important Files
|
||||||
- `Modifier` 参数始终放签名最后
|
|
||||||
- 回调参数用 `on` 前缀(`onDateClick`)
|
|
||||||
- 可点击列表项用 `Card(onClick = ...)` + `CardDefaults.cardElevation(defaultElevation = 0.dp)`,不要用 `Modifier.clickable()` 裸包
|
|
||||||
- `@Suppress("DEPRECATION")` 必须附带行内注释说明原因(当前主要用于 `monthNumber`)
|
|
||||||
- UI 文本使用中文
|
|
||||||
- **禁止** `java.util.Calendar`,日期逻辑全部用 `kotlinx-datetime`
|
|
||||||
|
|
||||||
## 关键依赖
|
Central / load-bearing source (paths relative to repo root):
|
||||||
|
|
||||||
- **Compose BOM** `2026.05.01` + **Material 3**
|
- `core/src/main/kotlin/plus/rua/project/CalendarViewModel.kt` — home-screen state: collapse math, shift resolution, ISO week, grid generation. The heart of the app.
|
||||||
- **`kotlinx-datetime`** 0.8.0 — 所有日期逻辑
|
- `core/src/main/kotlin/plus/rua/project/ui/CalendarMonthView.kt` — home Composable: month↔year transition, FAB menu, pager wiring (largest UI file).
|
||||||
- **`tyme4kt`** 1.4.5 — 农历、节气、传统节日
|
- `core/src/main/kotlin/plus/rua/project/ui/CalendarUtils.kt` — all page↔date math + layout constants (`START_PAGE`, `COLLAPSE_THRESHOLD`).
|
||||||
- **`sketch`** 4.4.0 — GIF 显示(`sketch-compose` + `sketch-animated-webp`)
|
- `core/src/main/kotlin/plus/rua/project/LunarCache.kt` — tyme4kt integration; produces `DayCellInfo` (the per-cell render contract).
|
||||||
|
- `core/src/main/kotlin/plus/rua/project/ShiftPattern.kt` — shift resolution (`kindAt`: overrides → rephaseFlips → active-anchor cycle). Most rigorously tested file.
|
||||||
|
- `core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt`, `ui/WeekPager.kt` — the two infinite pagers.
|
||||||
|
- `core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt`, `ui/DayCell.kt` — per-page grid + cell rendering with collapse animation.
|
||||||
|
- `core/src/main/kotlin/plus/rua/project/ui/YearGridView.kt` — year overview grid.
|
||||||
|
- `core/src/main/kotlin/plus/rua/project/PhotoProcessor.kt` + `PhotoEditorState.kt` + `PhotoEditorViewModel.kt` — photo editor pipeline (load/rotate/crop/render with `HandStroke` overlay).
|
||||||
|
- `core/src/main/kotlin/plus/rua/project/{DateRecord,DateRecordDao,DateRecordDatabase,DateRecordConverters,DateRecorderRepository}.kt` — Room data layer for the photo journal.
|
||||||
|
- `core/src/main/kotlin/plus/rua/project/ComposeTrace.kt` — perf tracing shim.
|
||||||
|
- `core/src/main/kotlin/plus/rua/project/ui/DateRecorderNav.kt` — Intent extra keys (cross-Activity contract).
|
||||||
|
- `app/src/main/kotlin/plus/rua/project/{MainActivity,BaseActivity}.kt` — entry point + transition base.
|
||||||
|
- `gradle/libs.versions.toml` — all dependency versions.
|
||||||
|
- `app/build.gradle.kts` — build types, dynamic versioning (`baseVersion_gitHash_buildDate`).
|
||||||
|
|
||||||
## 性能追踪标记
|
Other docs: `DEVELOPMENT.md` (perf/profile workflow + trace marker catalog), `README.md` (user intro), `CHANGELOG.md` (Keep-a-Changelog; `[Unreleased]` + `[1.3.0] - 2026-07-09`). Module rules: `app/AGENTS.md`, `core/AGENTS.md`, `macrobenchmark/AGENTS.md`.
|
||||||
|
|
||||||
`ComposeTrace.kt` 提供自定义 Perfetto/Systrace 标记,关键标记名:
|
## Runtime/Tooling Preferences
|
||||||
- `MonthView:Compose` / `YearView:Compose`
|
|
||||||
- `CalendarPager:Page:*` / `WeekPager:Page`
|
|
||||||
- `VM:collapseProgress:*`
|
|
||||||
- `YearGridView:*`
|
|
||||||
|
|
||||||
详见 `DEVELOPMENT.md`。
|
- **JDK 17** (`VERSION_17` in all modules). Gradle wrapper **9.5.1**.
|
||||||
|
- **AGP 9.2.1 · Kotlin 2.3.21 · KSP 2.3.10 · Compose BOM 2026.06.01.** compileSdk/targetSdk **37**, minSdk **24**.
|
||||||
|
- Key libs: **kotlinx-datetime 0.8.0 · tyme4kt 1.5.0 · sketch 4.4.0 · zoomimage 1.6.0 · Room 2.8.4 · lifecycle 2.11.0 · activity-compose 1.13.0 · CameraX 1.5.3 · Media3 1.6.1.**
|
||||||
|
- **Gradle caches on:** configuration cache + build cache + parallel (`gradle.properties`). R8 full mode enabled.
|
||||||
|
- **Build types:** `debug` (default, trace on) · `release` (R8 + resource shrink, trace off, debug-signed) · `trace` (release + trace markers) · `benchmark` (release base, **no** minify — so generated profile names aren't obfuscated).
|
||||||
|
- **Profiling needs a device/emulator** with GPU acceleration (software renderer can't produce `gfxinfo` framestats). Real benchmarks need a physical device on a release target.
|
||||||
|
- No CI/CD exists — all builds, tests, profiling, and releases run locally.
|
||||||
|
|
||||||
## 参考文档
|
## Testing & QA
|
||||||
|
|
||||||
| 文件 | 内容 |
|
- **All automated tests are pure-JVM unit tests** in `core/src/test/kotlin/plus/rua/project[/ui]/` — **no `androidTest` source set, no Compose UI tests, no Robolectric/Turbine/Mockk.** Run on JVM 17, no emulator.
|
||||||
|------|------|
|
- **Frameworks:** `kotlin-test-junit` (+ JUnit 4 in a few classes) and `kotlinx-coroutines-test` (`runTest`). `androidx.room:room-testing` is declared but unused.
|
||||||
| `CLAUDE.md` | 完整架构说明、组件树、动画机制、Pager 映射逻辑 |
|
- **Covered well:** date math (`CalendarUtilsTest`), the shift engine (`ShiftPatternTest` — cycle/override/`RephaseFlip`), `CalendarViewModel` observable state + grid generation, storage round-trips, record sorting, lunar birthday/rose-day flags, `PhotoProcessor.calculateInSampleSize`, `HandStroke` segmentation.
|
||||||
| `DEVELOPMENT.md` | 性能追踪、Baseline Profile / Startup Profile、模拟器启动参数 |
|
- **Coverage gaps to be aware of:** no Compose UI/instrumented tests (UI exercised only via the macrobenchmark journey); Room DAO/Database/migration logic untested; `Flow` emission sequences untested (only synchronous `.value` snapshots); `DateRecordDetailViewModel`/`PhotoEditorViewModel` and the edit-record path of `RecordEditViewModel` have no dedicated tests.
|
||||||
| `COMMENTS.md` | KDoc 规范、注释原则、反模式清单 |
|
- **Test conventions:** classes `*Test.kt`; methods `method_condition_result`; in-memory SharedPreferences fakes written per file; fixtures inline (no shared helpers). Comments/KDoc are in Chinese and state scope explicitly.
|
||||||
| `app/AGENTS.md` | `:app` 模块细则 |
|
- **Macrobenchmark** (`macrobenchmark/src/main/.../baseline/`) provides the only on-device coverage — `StartupBenchmark` (cold start) and `BaselineProfileGenerator` (drives a 12-step user journey to emit `baseline-prof.txt` + `startup-prof.txt`).
|
||||||
| `core/AGENTS.md` | `:core` 模块细则 |
|
|
||||||
| `macrobenchmark/AGENTS.md` | Baseline Profile / Startup Profile 模块细则 |
|
## Release Process
|
||||||
|
|
||||||
|
Driven by the `yayacal-release` skill (`.agents/skills/yayacal-release/SKILL.md`). Flow: bump `app.version.base` in `gradle.properties` + `versionCode` in `app/build.gradle.kts` → update `CHANGELOG.md` from `git log <last-tag>..HEAD` → commit exactly `release: vx.y.z` → lightweight tag `vx.y.z` → push `main --tags` → `assembleRelease` → `gh release create` with extracted notes. Release only from clean `main`; keep debug signing (no release keystore). Note: the skill's doc links to `github.com/xfy/yayacal` — the actual remote is `DefectingCat/yayacal`.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user