81aeb8e894
fix(date-recorder): split handwriting strokes on finger lift
...
endStroke() was a no-op, so addStrokePoint() kept appending every new
pointer-down to the previous stroke (its only segmentation cue was
points.isEmpty()). A second drag after lift got joined into the first,
and toPath() drew them as a single polyline.
Add isFinished flag to HandStroke and implement real endStroke() that
marks the current stroke finished. withAddedPoint() now starts a new
segment when the previous stroke is null, empty, or finished.
2026-07-17 16:01:44 +08:00
8963b6162b
feat(date-recorder): photo journal tool with camera, editor, and album grid
...
新增"日期记录器"工具:相册式记录管理,支持拍照→编辑→记录信息的完整流程。
数据层:
- Room 2.8.4 首个数据库(KSP 2.3.10),DateRecord 实体含标题/备注/拍摄日期/关联日期
- DateRecordConverters 处理 kotlinx-datetime ↔ ISO 字符串
- DateRecorderRepository 封装 DAO + filesDir 照片文件管理
UI 层:
- CameraScreen: CameraX 1.5.3 应用内预览,运行时权限请求,前后摄切换
- PhotoEditorScreen: 纯 Compose 自实现旋转/裁剪/手写三 Tab 编辑器
- DateRecorderScreen: LazyVerticalGrid 相册网格 + 多选 + 6 种排序 + 批量删除
- RecordEditScreen / RecordDetailScreen: 记录信息编辑与详情查看
基础设施:
- CAMERA 权限 + FileProvider(filesDir 存储,免存储权限)
- 5 个 Activity 延续项目 Activity+Intent 滑动转场模式
- 2 个单测:排序逻辑 6 种组合 + 降采样计算
2026-07-17 13:21:45 +08:00
f8df108bf3
refactor(shift): replace phaseBreaks with atomic RephaseFlip
...
数据模型重构:把"翻转+重排"从两个独立字段(override+phaseBreak)
改为单一原子结构 RephaseFlip(date, flippedTo, rephaseFrom)。
- 删除 PhaseBreak / phaseBreaks / cycleOffset(UI 未使用 offset!=0)
- kindAt:overrides → rephaseFlip 当天 → 活跃锚点(从 rephaseFrom 取)
- Storage:KEY_BREAKS → KEY_REPHASE,编码 翻转日:值:重排起点
- ShiftCalendarGrid:toggleFlipAndRephase 产出原子记录,撤销按 date 精确匹配
- toggleOverride 加保护:rephaseFlip 翻转日被点时整体移除,防幽灵重排
解决问题:撤销误删独立断点、单独点翻转留幽灵、连续长按断点链不级联。
2026-07-09 16:33:29 +08:00
d8014b5154
test(shift): add safety-net tests for long-press rephase scenarios
...
锁定重构前行为:单次长按重排、连续两次长按、撤销。
作为 RephaseFlip 重构的回归基线。
2026-07-09 16:25:23 +08:00
e08243d0fa
test(shift): cover refreshShiftPattern reload after storage change
2026-07-06 18:37:47 +08:00
ef55ffcf64
feat(shift): inject ShiftPatternStorage into CalendarViewModel
2026-07-06 18:34:12 +08:00
1f1db17384
fix(shift): persist name field, guard empty cycle, test corrupt-data path
2026-07-06 18:30:40 +08:00
e4834a52c9
feat(shift): add ShiftPatternStorage for persistence
2026-07-06 18:24:28 +08:00
eca6c61058
test(shift): cover non-zero cycleOffset and multiple phase breaks; add kindAt KDoc
2026-07-06 18:20:48 +08:00
778d5dc8be
feat(shift): support overrides and phase breaks in ShiftPattern
2026-07-06 18:14:24 +08:00
25885d84b3
feat: compute rose day flag in LunarCache
2026-06-16 17:59:00 +08:00
6830c1e510
feat: add easter egg toast message logic with tests
2026-06-16 15:50:46 +08:00
f33e68d1db
feat: compute birthday flag in LunarCache
2026-06-15 16:22:47 +08:00
58a97d1725
refactor: WebP 文件列表改由构建期 BuildConfig 注入,消除 (1..152) 硬编码
...
问题
- AnimatedWebp.kt 原写死 WEBP_FILES = (1..152).map{...}, 与磁盘文件耦合却无校验
- 加 153.webp → 永远不会被随机到; 删某个 → random() 偶发命中不存在的资源
- 这是静默失败, 运行期无报错, 难排查
改动
- core/build.gradle.kts defaultConfig: 构建期扫描 assets/animations/ 生成
buildConfigField("String[]", "WEBP_FILES", "new String[]{...}")
含 require(webpFiles.isNotEmpty()) 防空目录构建
- AnimatedWebp.kt: WEBP_FILES 从 private 硬编码改为 internal val = BuildConfig.WEBP_FILES.toList()
(internal 让同模块测试可访问)
- 新增 AnimatedWebpFilesTest: 2 个守卫测试
1. webpFilesMatchDirectoryContents: WEBP_FILES 必须与 assets/animations/ 一一对应
2. webpFilesUseZeroPaddedThreeDigitNames: 锁定 NNN.webp 命名约定
TDD 流程
- 先写测试 → 编译失败(WEBP_FILES 是 private) → 红灯成立
- 改实现 → 测试 PASS → 绿灯
- 突变验证: 临时改 WEBP_FILES = (1..150) 漏掉 151/152
→ webpFilesMatchDirectoryContents 立即 FAIL, 守卫有效
- 恢复后全量 146 个测试 0 失败
设计说明
- 当前实现里 WEBP_FILES 与目录都来自同一次构建期扫描, 二者天然一致
- 测试的核心价值是锁定「两者一致」不变量, 防止有人回退成硬编码
(突变验证已证明: 回退后测试立即失败)
- BuildConfig 生成结果验证: String[] 含 152 个元素 001~152.webp
验证
- ./gradlew :core:testDebugUnitTest → 146 tests, 0 failures, 0 errors
- ./gradlew :app:assembleDebug → BUILD SUCCESSFUL
- BuildConfig.java: public static final String[] WEBP_FILES = new String[]{"001.webp",...,"152.webp"}
2026-06-15 14:02:21 +08:00
55d50c3e1d
feat: 添加 clampExpiryDays 纯函数钳制保质期天数为非负
2026-06-15 11:24:38 +08:00
0560e81fb2
feat: 日期检查器添加数据持久化和恢复默认按钮
...
- 新增 DateCheckerStorage,SharedPreferences 持久化生产日期和保质期列表
- 添加 DateCheckerStorageTest 单元测试(含 InMemorySharedPreferences stub)
- 页面退出后保留数据,重新打开自动恢复
- 左下角新增恢复默认 FAB,点击弹出确认弹窗后重置全部数据
- 双 FAB 样式统一(FloatingActionButton + CircleShape + primary 配色)
2026-06-08 11:08:48 +08:00
829f89eb7c
refactor: 删除 P0 冗余代码(自定义combine/死StateFlow/网格重复算法/runBlocking)
...
- 删除自定义 6 参数 combine + Quintuple,改用标准库嵌套 combine + Triple
- 删除 yearViewProgress 死 StateFlow 及关联测试
- generateMonthDays/generateMiniMonthDays 复用 getMonthGridInfo
- WeekPager 移除 runBlocking,交由 DayCell 自行异步计算
- 修复 AnimLog 在 JVM 单元测试中因 android.util.Log 不可用而崩溃
2026-06-01 18:03:27 +08:00
e249700ee5
refactor: 清理不必要的 @Suppress 注解并替换弃用 API
...
移除 23 处不必要或可通过改写避免的 @Suppress。
- 删除 12 处完全不必要(误加/重构遗留)
- 替换 9 处 monthNumber/dayOfMonth → month.number/day
- 替换 4 处弃用构造函数 LocalDate(year,month,1) → LocalDate(year,Month(month),1)
- 保留 6 处必要 @Suppress(NOTHING_TO_INLINE、Android API 兼容等)
2026-06-01 17:55:18 +08:00
0cdac663c9
refactor: 移除 fling 速度阈值,改用 spring 动画驱动折叠过渡
...
- ViewModel onDragEnd/onExpandDragEnd 移除 velocity 参数
- BottomCard 移除 VelocityTracker,简化回调签名
- CalendarMonthView 添加 animateFloatAsState spring 动画
- 更新单元测试:移除 fling 测试,调整阈值边界
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:36:58 +08:00
fb7e19ddc9
test: Wave 4 — 补充 fling 速度阈值交互测试
...
- test: onDragEnd 高速上滑强制折叠(无视 progress 阈值)
- test: onDragEnd 高速下滑强制展开(无视 progress 阈值)
- test: onExpandDragEnd 高速下滑强制展开
- test: onExpandDragEnd 高速上滑保持折叠
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:18:34 +08:00
6f4d62b78f
refactor: Wave 3 — ViewModel 解耦 Compose 运行时,迁移到 StateFlow
...
- refactor: CalendarViewModel 从 mutableStateOf/Animatable 迁移到 StateFlow
- refactor: 继承 Android ViewModel,使用 viewModelScope 管理生命周期
- refactor: 拖拽/动画方法改为同步修改 StateFlow(移除协程 launch)
- refactor: onDragEnd/onExpandDragEnd 实现 fling 速度阈值判断
- refactor: BottomCard 解耦为纯参数驱动
- refactor: CalendarMonthView 使用 viewModel() + collectAsState()
- test: 适配 StateFlow API,139 测试全部通过
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:16:20 +08:00
bf28008d17
refactor: Wave 1 — 构建清理、测试修复、文档修正、a11y 改进
...
- build: 清理版本目录死依赖(appcompat、core、material3)
- build: 精简 ProGuard keep 规则,仅保留热点路径和第三方库
- refactor: 重命名 PredictiveBackHandler → AppPredictiveBackHandler
- test: formatLunarDate 精确断言 + 闰月测试
- test: 统一测试命名风格(下划线),Unconfined → StandardTestDispatcher
- docs: 修正文档为纯 Android + Jetpack Compose 描述
- feat(a11y): 添加可访问性语义标注(WeekdayHeader heading、BottomCard 拖拽描述)
- feat: 为 DayCell、MonthHeader、WeekdayHeader、BottomCard 添加 @Preview
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:51:12 +08:00
2028897ce3
docs: 同步 AGENTS.md 文档,反映纯 Android 项目结构
...
- 更新所有层级 AGENTS.md 中的模块描述(:app / :core)
- 移除 KMP/iOS 相关引用,统一为纯 Android + Jetpack Compose
- 同步构建命令和测试路径
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 16:46:17 +08:00
f5cc4ef2e5
refactor: 完成纯 Android 迁移,清理旧 KMP 模块并添加 app/core 新模块
...
- 删除遗留的 androidApp/、shared/ 等 KMP 结构文件
- 将新的 app/、core/ 纯 Android 模块纳入版本控制
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:55:08 +08:00