36 Commits

Author SHA1 Message Date
xfy
7bf16dfe5a feat(ui): 重新设计开放源代码许可页面,集成 M3 Expressive 设计与弹性动画 2026-07-24 13:30:55 +08:00
xfy
4f6dc2f36a refactor(ui): 补全开放源代码许可依赖项并优化页面组件 2026-07-24 13:28:48 +08:00
xfy
bff6e37908 fix(recorder): preserve record ID when editing photo of an existing record 2026-07-24 11:16:44 +08:00
xfy
f9e54f36fe refactor(photo-editor): 重构照片编辑旋转体验,干掉外框卡片实现全视口自适应无缝旋转
- 干掉 Card 容器:彻底移除了带 1.dp 灰色 Border、20.dp 圆角黑底及 4.dp 阴影的外层 Card 盒,解除照片被固定容器包裹旋转的视觉限制。
- 视口自适应包围盒算法:在 RotationGeometry 中新增 calculateLayoutSize 纯函数,按任意旋转角度动态计算照片 AABB 包围盒适配合理布局大小,保证 ContentScale.Fit 内部 0 留白 Padding 且 0 黑边。
- 重构 EditableImage:照片直接在全屏视口中心做平滑自适应旋转,手写 Canvas 与裁剪框精准挂载在稳态照片视觉 Bounds 上。
- 补充 RotationGeometryTest 针对 calculateLayoutSize 视口包围盒自适应的单元测试。
2026-07-24 11:12:19 +08:00
xfy
c39cecf343 fix(photo-editor): 修复旋转照片过程中的错位黑边
- 根因:isSwapped 原先直接基于目标旋转角度 state.rotationDegrees 判定,导致在旋转动画起始阶段(如 0° 向 90° 动画时)提前将布局 measure 尺寸转置,Fit 适配产生了内缩黑边 Padding。
- 修复:基于当前动画渲染角度 displayRotation.value 动态计算最近的 90° 基准角 (baseRotation),使布局尺寸转置与旋转动画平滑同步,彻底消除错位黑边。
- 测试:在 RotationGeometryTest 中补充 baseRotation、offsetDegrees、isSwapped 动态角度过渡断言。
2026-07-24 11:09:33 +08:00
xfy
b7607eebd9 fix(photo-editor): 修复照片旋转二次叠加与黑边问题 2026-07-23 15:02:11 +08:00
xfy
57da898ed7 feat(ui): 重新设计记录编辑页面 (RecordEditScreen)
- 重构 RecordEditScreen 布局:新增 Hero 照片卡片,分组卡片化基本信息(标题与笔记)与日期设定(拍摄日期与关联日历)
- 引入图形化 OutlinedTextField(带图标、提示与标题快速清除)与交互式 DatePickerTile(带星期显示与清除关联)
- 优化顶部 TopAppBar 操作:添加顶部右上角“保存”快捷入口,配合底部固定“保存记录”主按钮
- 保持现有所有 testTag 兼容性
2026-07-23 14:54:01 +08:00
xfy
9ad9e39164 feat(ui): 重新设计日期记录器详情页并优化 Flow 实时监听
- 重构 RecordDetailScreen:新增 Hero 大图卡片(带全屏放大与涂鸦编辑快捷入口)、优雅标题 header、双列元信息卡片(带星期与日历关联状态)及格式化笔记卡片
- 优化底部操作栏:提供直观的编辑信息、涂鸦编辑与删除按扭
- ViewModel 变更:将 DateRecordDetailViewModel 从 single-snapshot first() 改为持续 collect,支持编辑返回后实时刷新数据
- 新增 DateRecordDetailViewModelTest 单元测试
2026-07-23 14:51:29 +08:00
xfy
a2a631c141 feat(ui): 支持长按单个记录直接进入多选模式并选中 2026-07-23 14:26:19 +08:00
xfy
b6146a29ae feat(photo-editor): 旋转图片时加平滑动画,裁剪填满零黑边
- 旋转角用 Animatable + tween(300ms) 平滑过渡,容器宽高比用
  animateFloatAsState 在目标稳态比例间过渡,消除硬切
- 黑边根因:旋转矩形填不满自身 AABB 的四角(45° 时黑色占 51%)。
  用户选「裁剪填满」方案:图片显示 rotatedBitmap(目标态),
  graphicsLayer 旋转 + coverScale 放大使四角覆盖容器 → 零黑边
- 提取旋转几何为纯函数 RotationGeometry(无 Android 依赖):
  stableAspect + coverScale,便于单测
- 回归测试 RotationGeometryTest 锁住「任意角度旋转图片四角
  覆盖容器四角」断言(maxViolation <= 1e-4),防止黑边回归
- 稳态(offset=0) scale=1 零裁剪;动画中(±45°) scale≈1.65 裁边缘

保存逻辑未变:PhotoProcessor.render 仍用 rotationDegrees 烘焙。
2026-07-22 18:31:48 +08:00
xfy
d47a38bc19 feat(date-recorder): 美化记录器界面,卡片图上叠加文字、排序菜单精简
- 网格卡片:照片铺满整卡,底部渐变蒙层叠加标题与拍摄日期
- 网格改为自适应约 3 列,间距 8dp→4dp,卡片圆角 12dp→8dp
- 排序菜单 6 项精简为 3 个字段项,点击已选字段切换升降序
- 新增 RecordSortOrder.nextAfter 纯函数及单元测试
2026-07-20 18:28:39 +08:00
xfy
0d83fef3a9 feat: 新建记录标题预填拍摄日期并支持联动 2026-07-20 17:23:33 +08:00
xfy
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
xfy
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
xfy
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
xfy
d8014b5154 test(shift): add safety-net tests for long-press rephase scenarios
锁定重构前行为:单次长按重排、连续两次长按、撤销。
作为 RephaseFlip 重构的回归基线。
2026-07-09 16:25:23 +08:00
xfy
e08243d0fa test(shift): cover refreshShiftPattern reload after storage change 2026-07-06 18:37:47 +08:00
xfy
ef55ffcf64 feat(shift): inject ShiftPatternStorage into CalendarViewModel 2026-07-06 18:34:12 +08:00
xfy
1f1db17384 fix(shift): persist name field, guard empty cycle, test corrupt-data path 2026-07-06 18:30:40 +08:00
xfy
e4834a52c9 feat(shift): add ShiftPatternStorage for persistence 2026-07-06 18:24:28 +08:00
xfy
eca6c61058 test(shift): cover non-zero cycleOffset and multiple phase breaks; add kindAt KDoc 2026-07-06 18:20:48 +08:00
xfy
778d5dc8be feat(shift): support overrides and phase breaks in ShiftPattern 2026-07-06 18:14:24 +08:00
xfy
25885d84b3 feat: compute rose day flag in LunarCache 2026-06-16 17:59:00 +08:00
xfy
6830c1e510 feat: add easter egg toast message logic with tests 2026-06-16 15:50:46 +08:00
xfy
f33e68d1db feat: compute birthday flag in LunarCache 2026-06-15 16:22:47 +08:00
xfy
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
xfy
55d50c3e1d feat: 添加 clampExpiryDays 纯函数钳制保质期天数为非负 2026-06-15 11:24:38 +08:00
xfy
0560e81fb2 feat: 日期检查器添加数据持久化和恢复默认按钮
- 新增 DateCheckerStorage,SharedPreferences 持久化生产日期和保质期列表
- 添加 DateCheckerStorageTest 单元测试(含 InMemorySharedPreferences stub)
- 页面退出后保留数据,重新打开自动恢复
- 左下角新增恢复默认 FAB,点击弹出确认弹窗后重置全部数据
- 双 FAB 样式统一(FloatingActionButton + CircleShape + primary 配色)
2026-06-08 11:08:48 +08:00
xfy
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
xfy
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
xfy
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
xfy
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
xfy
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
xfy
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
xfy
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
xfy
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