Files
ring-health-app/docs/verification-m1.md
T

155 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# M1 静态页面验收记录
## 环境
- 记录日期:2026-09-10
- 构建版本:0.1.0 (versionCode 1)
- 构建机:Windows 11,JDK 17.0.12,Android SDK `D:\Android\Sdk`(仅 `platform-tools` /
`platforms;android-36` / `build-tools;36.1.0`)
- **测试设备:无** —— 本机没有安装任何模拟器镜像,也没有连接真机
补充证据(说明「无设备」不是随口一说,而是逐项查过的):
```
$ ls D:\Android\Sdk
build-tools cmdline-tools licenses platform-tools platforms
# 没有 emulator/,也没有 system-images/
$ adb devices
List of devices attached
# 空列表,没有连接真机
```
`C:\Users\10314\.android\avd\` 下**残留着两个 AVD 定义**(`Medium_Phone_API_36.1`、
`Pixel_9_Pro`),但它们是**不可用的空壳**:二者都指向已删除的旧 SDK
`C:\Users\10314\AppData\Local\Android\Sdk`(其 `emulator\emulator.exe` 与
`system-images\android-36.1\google_apis_playstore\x86_64\` 均已不存在),且属于另一个项目
(`D:\work\xiangmu\mediq-app`)。**不要因为看到 AVD 目录就以为本机可以起模拟器。**
另注:当前 shell 的 `ANDROID_HOME` 仍指向那个已删除的旧路径,构建实际靠
`local.properties` 的 `sdk.dir=D:\Android\Sdk` 生效。
## 自动化验证(已执行)
- 单元测试:`./gradlew :app:testDebugUnitTest` —— **21 项全绿**
(`FormattersTest` 7 + `MockDataProviderTest` 5 + `HomeViewModelTest` 1 + `WorkoutViewModelTest` 1
+ `DeviceViewModelTest` 2 + `MetricDetailViewModelTest` 3 + `RoutesTest` 2)
- 构建:`./gradlew clean assembleDebug` —— `BUILD SUCCESSFUL`,
产物 `app/build/outputs/apk/debug/app-debug.apk`(18,907,329 字节)
- **未执行**:各页面的 `@Preview` 在 Android Studio 中渲染。本机没有 IDE GUI,agent 无法打开
Preview 面板,**这一项与人工验收一样是未执行的** —— 不要把它写进「已执行」段落
(旧稿把它列在上面这一节里,那等于声称验证过一件没做过的事)。
### 终审修复轮(`5fe0d4a`)各项改动的可验证性
终审后又有一轮修复,共 6 处改动。**不要把这 6 处一律当成「已验证」** —— 它们能验到什么程度
差别很大,逐项列明:
| 改动 | 实际验到了什么 |
|---|---|
| 详情页按指标取色(原先一律绿) | `when` 穷尽七个 `MetricType`,代码可确认;**配色是否好看/是否与首页一致,未经渲染验证** |
| 详情页空数据态(原先渲染全 0 柱状图 + 三行 `0.0`) | **无测试覆盖**,仅代码推理。且血压卡改为不可点后,该路径在当前 Mock 下实际不可达(`metricHistory` 恒返回 7 或 30 个点)—— 属防御性代码 |
| 血压卡不可点击(原先显示「未开启」却能点进一张有数据的图表) | `onClick` 改为可空并传 `null`,代码可确认;**点击涟漪是否真的消失,未经渲染验证** |
| 压力卡 `unit` 接线(4 条字符串此前无调用点) | 调用点已存在,代码可确认;**文字是否在卡片里溢出,未经渲染验证** |
| `RingHealthApp` NavHost 补 `consumeWindowInsets(innerPadding)` | **推理得出,未经设备验证** —— 见下方人工验收第 1 项 |
| README 注明 `installDebug` 需设备 | 文档,无需验证 |
结论不变:**这几处里凡带「视觉」二字的,本机都没验过。**
## 人工验收(未执行 —— 无设备)
下表**全部未打勾**。这份清单已原样保留,交给有设备的人执行;不要在没有实测的情况下填入结果。
| 项 | 结果 | 备注 |
|---|---|---|
| 应用启动无崩溃 | 未执行 | 无设备 |
| 四 Tab 切换 | 未执行 | 无设备 |
| 重按已高亮的父 Tab 的行为 | 未执行 | 需实测:推测会停在子页而非回到 Tab 根 |
| 每个页面的 `@Preview` 可渲染 | 未执行 | 需在 Android Studio 中逐个打开确认 |
| 次要文字灰度一致 | 未执行 | 不得出现 `TextSecondary` 与 `onSurfaceVariant` 混用 |
| 首页:设备状态条 / 步数大环 8,426 / 六个指标卡(血压“未开启”) | 未执行 | 无设备 |
| 指标详情页:周/月切换(7 根 / 30 根柱子) | 未执行 | 无设备 |
| 睡眠详情:分期卡片只在睡眠指标出现,入睡/起床读起来像「一晚」 | 未执行 | 无设备 |
| 运动页 / 运动历史页 | 未执行 | 无设备 |
| 设备页 / 设备设置开关(返回后状态保持) | 未执行 | 无设备 |
| 我的页 | 未执行 | 无设备 |
| 旋转屏幕、深色系统设置下不崩溃 | 未执行 | 无设备 |
### 需在有设备时重点确认的项(原清单细目,逐条保留)
- **【第 1 优先】内边距是否正确(终审 fix 5,本表最可能出问题的一项)**:
`RingHealthApp` 的 NavHost 现为 `Modifier.padding(innerPadding).consumeWindowInsets(innerPadding)`。
原因是详情页/设备设置页**自带** `Scaffold` + `TopAppBar`,而外层的 `Modifier.padding`
**不消费** window insets,于是状态栏高度被上下叠加两次。补 `consumeWindowInsets` 是标准修法,
但**这一改动从未在设备上看过一眼**。请优先确认两件事:
① 详情页、设备设置页的返回箭头与标题**没有被状态栏遮住**;
② 顶部也**没有多出一条无意义的空白**。
若仍有多余留白,问题在 insets 的消费方式而不在这行代码本身
- 应用启动无崩溃,底部四个 Tab 显示为 健康 / 运动 / 设备 / 我的
- 四个 Tab 可自由切换,切换后状态保留、无闪烁
- **重按已高亮的父 Tab 的行为**:实现为 `popUpTo(start){saveState}` + `restoreState`,
推测会**恢复到该 Tab 保存的子页栈**(停在子页而非回到 Tab 根)。请实测并记录实际行为;
若实测是「停在子页」而产品希望「回到根」,那是一个独立的小改动,记下来即可
- 首页:步数大环显示 8,426,目标 10,000,环按比例填充
- 首页:点击任一指标卡进入详情页,标题为该指标中文名
- 详情页:返回后回到首页
- 运动页:显示“开始运动”卡片 + 5 条运动记录;「查看全部」进入历史页后底部导航栏仍可见
- 设备页:戒指卡片显示电量 78%、固件版本 1.0.5
- 睡眠卡片的入睡/起床时间**读起来像「一晚」**(如 入睡 22:41 / 起床 06:41),而不是跨好几天。
阶段一按「最后一个数据点往前 8 小时」演示(R29);若显示成跨天,说明
`DEMO_SLEEP_WINDOW_MILLIS` 那段被改坏了
## 已知限制(阶段一有意为之)
- 数据全部为 Mock,不连蓝牙、不连后端
- 血压恒为“未开启”占位(`HealthSummary.bloodPressure = null`)
- 设备设置页的开关只改内存状态,重启即重置(对应 M2 的协议 0502/0504)
- 睡眠分期条与入睡/起床时间是演示值(固定比例 + 最后一个数据点往前 8 小时),无真实分期数据
- 详情页统计摘要一律保留一位小数(步数因此显示为「平均 7512.3」这类精度过剩的形式)
- 仅浅色模式
- 血压卡**不可点击**(`value == null` 时 `onClick` 传 `null`)—— 阶段一没有血压数据,
点进去只会看到一张编出来的图表,不如不点
- 详情页的**周/月切换常驻**,空数据时只把切换条下方的图表与统计换成一行空态文案。
切换条属于页头而非数据区:数据显示为空时,切换区间正是用户的第一反应,它也是判断
「哪个区间是空的」的唯一线索
### 与设计文档 §6 的偏差(有意收窄,非遗漏)
- **设备页少了四项**:查找戒指(050B)、智能提醒/闹钟占位、固件升级占位(OTA 17xx)、佩戴检测。
阶段一没有真实协议可对接,摆一排点不动的入口不如不摆(ledger R27)。**这些能力仍属产品范围,
将于后续里程碑回归**;设计文档 §6 的设备页一行已用删除线标注。
- **详情页由「四个专属设计」收敛为一个模板**:`detail/step`、`detail/heart_rate`、`detail/sleep`、
`detail/blood_oxygen` 四条路由合并为 `detail/{metric}`,七个指标共用同一模板(柱状趋势图 +
周/月切换 + 平均/最高/最低)。**仅睡眠保留了专属区块**(`SleepStagesCard`)。
未实现的是:步数的大环、心率的折线/静息-运动区间、血氧的正常范围说明文案。
收敛本身是有意的(避免七套几乎相同的页面代码),详见设计文档 §6 下的注记。
### M2 接入时的已知接缝(交接用,本阶段不修)
`RingDeviceRepository` 目前**不足以承载 M2 的设备控制**:
- `refreshDeviceState()` **零调用者** —— 接口定义了,UI 从不触发。M2 需要接上「下拉刷新」或
进入设备页时刷新,否则真机断连后界面不会更新。
- `setFunctionEnabled` 是**非 `suspend`** 的 —— Mock 只改内存态所以无所谓,但 M2 写向 BLE
必然是挂起操作,接口签名届时需要改,连带 `DeviceViewModel` 的调用点。
- 预计 M2 的改动面:`domain/repository/Repositories.kt`、`data/mock/*`、`di/AppContainer.kt`、
`AndroidManifest.xml`(BLE 权限)、`app/build.gradle.kts` + `gradle/libs.versions.toml`
(厂商 Java 依赖,**很可能需要 Jetifier**)、`feature/device/DeviceScreen.kt`。
完整清单见实施计划的「M2 交接提示」一节。
## 覆盖缺口声明
**M1 的全部运行时行为目前只有编译期与单元测试保证,没有任何一次真机/模拟器运行。**
`assembleDebug` 成功只说明代码能编译、能打包,**不说明界面正确、不说明不会崩溃**。
这是本机没有设备导致的已知缺口(ledger Ruling R21),不是「已验证通过」。
首次拿到设备时,请从上面那张人工验收表开始逐项补齐。
具体而言,**没有任何一行 M1 的 Compose 界面代码被真正渲染过**:导航图能否起来、
四个 Tab 是否显示正确文案、`MetricBarChart` 的柱子数量、`RingProgress` 的填充比例、
睡眠分期卡片是否只在睡眠指标下出现、次要文字灰度是否统一 —— 这些全部未经运行时检验。
单元测试覆盖的是 `Formatters` 的纯函数、`MockDataProvider` 的数据形状,以及七个 ViewModel
的状态流;**它们不经过 Compose 渲染管线,也不能发现布局、主题或导航层面的错误。**
终审修复轮新改的几处**尤其如此**:按指标取色、血压卡不可点、空数据态、压力卡的单位接线、
以及 `consumeWindowInsets` —— 这五处**全部是界面行为**,而 21 项单测**一项都没有、也不可能**
碰到它们。它们的依据是代码阅读与 Compose 的既有语义,不是运行结果。