docs: 更正 M2 交接面清单,回填 spec 与交付的实际偏差(ledger R35)

This commit is contained in:
2026-09-10 13:10:28 +08:00
parent a18523f032
commit c3eb0b5b76
2 changed files with 51 additions and 4 deletions
@@ -4951,5 +4951,27 @@ Expected: 三行都是 `## main...origin/main`(不带 `[ahead N]`),且每
**M2 交接提示**(本计划不实施):
- 厂商通讯类原样拷贝到 `com.sr.smartring` 包(保持不改);
- 新增 `data/sdk/SdkHealthDataRepository` 与 `SdkRingDeviceRepository` 实现同名的两个接口;
- 在 `DefaultAppContainer` 中把 Mock 换成 SDK 实现——**这是 M2 唯一需要改动的既有文件**;
- 在 `DefaultAppContainer` 中把 Mock 换成 SDK 实现;
- BLE 权限(`BLUETOOTH_SCAN` / `BLUETOOTH_CONNECT` / 位置)在 M2 加入清单与运行时请求。
> ⚠️ **更正(ledger Ruling R35,全分支终审发现)—— 本节原先写的是
> 「在 `DefaultAppContainer` 中把 Mock 换成 SDK 实现,这是 M2 唯一需要改动的既有文件」。
> 那句话是错的,且已被写进 `verification-m1.md` 的交接叙述里,**必须按下面这份真实清单来规划 M2**,
> 否则会在做到一半时才发现「接口要改」,而那正是「接口不变则 UI 零改动」失效的地方。**
**M2 的真实改动面(经终审逐文件核验):**
| 文件 | 为什么必须改 |
|---|---|
| `domain/repository/Repositories.kt` | `RingDeviceRepository` **只有** `deviceState` / `functionSettings` / `refreshDeviceState()` / `setFunctionEnabled(id, enabled)`。**没有扫描、没有连接、没有绑定、没有地址、没有权限/可用性查询** —— spec §10 的 M2 验收是「真机扫描到戒指、连接成功、读到电量/固件」,当前接口一个都表达不了 |
| 同上(`setFunctionEnabled`) | 签名是 `fun setFunctionEnabled(id: String, enabled: Boolean)` —— **非 `suspend`、返回 `Unit`**。写开关到戒指(协议 0502/0504)是一次**可能失败、可能超时**的 BLE 往返。M2 要么改签名(级联到 `MockRingDeviceRepository` / `DeviceViewModel` / `DeviceSettingsScreen` / `DeviceViewModelTest`),要么在 UI 上留着这个谎 |
| `AppContainer.kt` | `DefaultAppContainer` 是**无参构造**;SDK 适配器需要 `Context` / `Application` 才能拿到 `BluetoothAdapter`,所以 `RingHealthApplication.kt` 也要改 |
| `AndroidManifest.xml` | BLE 权限(本节最后一条自己就要求「加入清单」,与「唯一一个文件」自相矛盾) |
| `app/build.gradle.kts` + `libs.versions.toml` + 可能 `gradle.properties` | 厂商 Java 源码要接 EventBus / Support-Library,大概率还要 `android.enableJetifier=true`(spec:206 已把这条标为已知风险) |
| `feature/device/DeviceScreen.kt` | **当前没有任何扫描 / 连接 / 绑定 UI**,而 spec §10 的 M2 验收要求它 |
**结论:读取路径(健康数据)的接缝是真的做对了 —— 换实现即可;但设备控制与发现这条路径压根还没开缝。**
`refreshDeviceState()` 目前**一个调用点都没有**,所以「读到电量/固件」在 UI 上也没有触发入口。
**M1 阶段不做补救性改动**(对着尚不存在的 SDK 改接口属于投机设计)。
本条的价值在于:**下一个里程碑照着这份清单立项,而不是照着那句错话。**
@@ -67,8 +67,16 @@ d:\cherry
| 异步 | Coroutines + Flow | — |
| 图表 | **Compose Canvas 手绘**(今日大环、柱状/折线占位) | 阶段一不引第三方图表库(YAGNI) |
| DI | 手动组合根(`Application` 中构造仓库) | 阶段一无需 Hilt,后续可平滑替换 |
| 构建 | **Gradle 8.14 + AGP 8.11.x** + JDK 17 + `libs.versions.toml` | 本机已缓存 Gradle 8.14,免联网下载 |
| SDK | `minSdk 26` / `targetSdk 35` / `compileSdk 35`,build-tools 35.0.0 | 对齐厂商 minSdk(26);compileSdk 35 与本机已装 platform/build-tools 一致 |
| 构建 | **Gradle 8.14 + AGP 8.13.1** + JDK 17 + `libs.versions.toml` | 本机已缓存 Gradle 8.14,免联网下载。~~AGP 8.11.x~~ 已按 R5/R6 改为 **8.13.1**(见下方注记) |
| SDK | `minSdk 26` / `targetSdk 36` / `compileSdk 36`,build-tools **36.1.0** | 对齐厂商 minSdk(26);~~35~~ 已按 R5 改为 **36**,与本机已装 platform/build-tools 一致 |
> **注记(2026-09-10,M1 实施后回填):** 上表两行原写 AGP 8.11.x / compileSdk 35 / build-tools 35.0.0,
> 与最终交付的 `app/build.gradle.kts` 不符 —— **代码是对的,是本文档当时没跟着更新。**
> 实际工具链:AGP **8.13.1**、compileSdk/targetSdk **36**、buildToolsVersion **36.1.0**、minSdk 26、
> Kotlin 2.2.20。下调理由见 Ruling R5(工具链以本机已装版本为准)与 R6(越界依赖下调至工具链可接受的最新版本),
> 完整证据链写在 `gradle/libs.versions.toml` 顶部的注释里。
> 另外 `compose-bom` 用的是 **2026.06.01**(compose 1.11.4),不是 2026.09.00 —— 那组版本要
> compileSdk 37 + AGP 9.1.0,与本工具链互斥。
**阶段一依赖清单(刻意精简)**:`androidx.core:core-ktx`、`androidx.activity:activity-compose`、
`androidx.lifecycle:lifecycle-runtime-compose` / `lifecycle-viewmodel-compose`、
@@ -142,7 +150,7 @@ UI 统一走"未开启占位态",不显示 0。
|---|---|---|
| **① 健康·首页** | 顶部问候 + 设备连接状态条;今日大环(步数/卡路里/距离);指标卡片网格:**心率 / 睡眠 / 血氧 / 压力 / 体温**(血压显示"未开启/需校准"占位) | `home` |
| **② 运动** | "开始运动"入口(跑步/骑行/自由训练占位)+ 运动历史记录列表(静态) | `workout` |
| **③ 设备** | 戒指卡片(名称/电量/连接态/固件)+ 设置列表:健康自动监测开关(心率/血氧/血压/压力/睡眠,对应协议 0502 开关位)、查找戒指(050B)、智能提醒/闹钟占位、**固件升级占位**(OTA 17xx)、佩戴检测 | `device` |
| **③ 设备** | 戒指卡片(名称/电量/连接态/固件)+ 设置列表:健康自动监测开关(心率/血氧/血压/压力/睡眠,对应协议 0502 开关位)~~、查找戒指(050B)、智能提醒/闹钟占位、**固件升级占位**(OTA 17xx)、佩戴检测~~ | `device` |
| **④ 我的** | 头像/昵称(登录占位)、App 设置(单位/通知)、通知权限引导、关于/检查更新、帮助与反馈 | `profile` |
**详情子页**(全部静态 + Canvas 图表占位):
@@ -156,6 +164,14 @@ UI 统一走"未开启占位态",不显示 0。
| `device/settings` | 戒指功能开关详情(静态开关,M2 后写向设备) |
| `workout/history` | 运动历史列表 |
> **M1 实际交付与上表的差异(2026-09-10 回填,全分支终审发现):**
> 上面 `detail/*` 的**四条路由实际合并成了一条**:`detail/{metric}`(`Routes.DETAIL_PATTERN`,
> 参数为 `MetricType.name`),七个指标共用同一个模板 —— 柱状趋势图 + 周/月切换 + 平均/最高/最低。
> **仅睡眠保留了专属区块**(`SleepStagesCard`:深睡/浅睡/清醒分段条 + 入睡/起床时间)。
> **未实现的是:** 步数的大环、心率的折线/静息-运动区间、血氧的正常范围说明文案。
> 收敛本身是有意的(避免七套几乎相同的页面代码),但当时没留下裁定记录 —— 这不妥,
> 此处补记,供后续决定是否回归为专属设计。**这是 M1 已知的、有意的范围收窄,不是漏做。**
**明确不做**(小米有但我们戒指不支持):跑步课程、商城、社区、多设备接入、体重秤/体脂秤。
## 7. 设计系统与视觉(仿小米运动健康)
@@ -167,6 +183,15 @@ UI 统一走"未开启占位态",不显示 0。
- 深色模式:阶段一**仅保证浅色**可用,深色留到 M5。
- 所有中文字符串进 `strings.xml`(默认中文,附英文),支持后续多语言;**禁止硬编码文案**。
> **M1 实施状态注记(2026-09-10,全分支终审后回填)—— 两条与上文不符的实际交付,均为有意为之:**
>
> 1. **设备页删减了四项:** 查找戒指(050B)、智能提醒/闹钟占位、固件升级占位(OTA 17xx)、佩戴检测,
> M1 **未实现**(见 Ruling R27,并已同步删除对应的 8 条字符串资源)。理由是阶段一没有真实协议可对接,
> 摆一排点不动的入口不如不摆。**这些能力仍属于产品范围,将随后续里程碑回归**;本文档上表已用删除线标注。
> 2. **详情页由「四个专属设计」收敛为一个模板** —— 详见**上方「详情子页」表下的注记**。
> 3. **多语言**:`strings.xml` 目前**只有中文**,没有 `values-en/`。本文档
> 「附英文」与 §198 把多语言排在 **M5** 两处自相矛盾;以 M5 为准,M1 不做。
## 8. Mock 数据策略
- `data/mock/MockDataProvider.kt` 提供**确定性**演示数据(固定随机种子),保证每次运行截图一致、便于展示。