From e5f935625efad1a1af4adec95a4d92a0eeaaf431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B4=94=E9=B2=81=E6=AD=A3?= <13780741225@163.com> Date: Thu, 10 Sep 2026 14:14:18 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=A1=A5=E5=BD=95=E6=A8=A1=E6=8B=9F?= =?UTF-8?q?=E5=99=A8=E5=AE=9E=E6=B5=8B=E7=BB=93=E6=9E=9C=EF=BC=8C=E5=85=B3?= =?UTF-8?q?=E9=97=AD=20M1=20=E8=BF=90=E8=A1=8C=E6=97=B6=E9=AA=8C=E6=94=B6?= =?UTF-8?q?=E7=BC=BA=E5=8F=A3=EF=BC=88=E5=90=AB=20consumeWindowInsets=20?= =?UTF-8?q?=E5=83=8F=E7=B4=A0=E7=BA=A7=E9=AA=8C=E8=AF=81=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/verification-m1.md | 214 ++++++++++++++++++++-------------------- 1 file changed, 107 insertions(+), 107 deletions(-) diff --git a/docs/verification-m1.md b/docs/verification-m1.md index a3a948b..053f4fa 100644 --- a/docs/verification-m1.md +++ b/docs/verification-m1.md @@ -4,124 +4,105 @@ - 记录日期: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`) -- **测试设备:无** —— 本机没有安装任何模拟器镜像,也没有连接真机 +- 构建机:Windows 11,JDK 17.0.12,Android SDK `D:\Android\Sdk` +- **验收设备:Android 模拟器**(非真机) + - AVD `ringtest`,外形 `pixel_7`,镜像 `system-images;android-36;google_apis;x86_64` + - 1080×2400 @ 420dpi;系统栏 inset:状态栏 136 px、导航栏 63 px -补充证据(说明「无设备」不是随口一说,而是逐项查过的): - -``` -$ 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` 生效。 +> **本次与上一次记录的差别:** 上一版这份文件里,人工验收**整张表都是「未执行 —— 无设备」**。 +> 2026-09-10 当天下载并装好了模拟器(见文末「模拟器是怎么装上的」),因此**下面这张表已经真的跑过了**。 +> 但仍有一处必须说清楚:**跑的是模拟器,不是真机**。模拟器能证明界面渲染正确、导航不崩, +> 但证明不了真机上的性能、厂商 ROM 差异、以及 M2 之后才涉及的蓝牙权限行为。 ## 自动化验证(已执行) - 单元测试:`./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 面板,**这一项与人工验收一样是未执行的** —— 不要把它写进「已执行」段落 - (旧稿把它列在上面这一节里,那等于声称验证过一件没做过的事)。 +- 构建:`./gradlew :app:assembleDebug` —— `BUILD SUCCESSFUL`, + 产物 `app/build/outputs/apk/debug/app-debug.apk`(18,907,725 字节) +- 安装并启动:`adb install -r` + `am start` —— 成功,进程存活,`logcat -b crash` 全程为空 +- **未执行**:各页面的 `@Preview` 在 Android Studio 中渲染。本机没有 IDE GUI 自动化手段, + 这一项仍是未执行的 —— 不过它原本的作用(没有设备时唯一的界面检查手段)已被下面的模拟器实测取代 -### 终审修复轮(`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 根柱子) | 未执行 | 无设备 | -| 睡眠详情:分期卡片只在睡眠指标出现,入睡/起床读起来像「一晚」 | 未执行 | 无设备 | -| 运动页 / 运动历史页 | 未执行 | 无设备 | -| 设备页 / 设备设置开关(返回后状态保持) | 未执行 | 无设备 | -| 我的页 | 未执行 | 无设备 | -| 旋转屏幕、深色系统设置下不崩溃 | 未执行 | 无设备 | +| 应用启动无崩溃 | ✅ | 进程 pid 存活,`mCurrentFocus` 为 `MainActivity`,crash buffer 空 | +| 四个 Tab 显示为 健康 / 运动 / 设备 / 我的 | ✅ | 四个 Tab 文案与图标正确,选中态为淡紫底 + 绿色文字 | +| 四个 Tab 可自由切换 | ✅ | 逐个点过,无闪烁、无崩溃 | +| 首页:设备状态条 | ✅ | 「RingHealth 智能戒指 · 已连接 / 电量 78%」 | +| 首页:步数大环 8,426 / 目标 10,000 | ✅ | 环按 84% 填充,另有「6.21 公里 / 327 千卡」 | +| 首页:六个指标卡,血压显示「未开启」 | ✅ | 心率 72 次/分、睡眠 7小时32分、血氧 98%、压力 34 正常、体温 36.5 °C、血压 未开启 | +| 首页:点指标卡进详情,标题为该指标中文名 | ✅ | 点「睡眠」→ 标题「睡眠」;点「心率」→ 标题「心率」 | +| 详情页:近 7 天 7 根柱 / 近 30 天 30 根柱 | ✅ | 切换后柱子数量与统计值都变了(心率:78.0/95.1/58.0 → 77.0/96.0/59.8) | +| 睡眠详情额外显示分期卡片,其他指标没有 | ✅ | 睡眠有「睡眠分期」卡(深睡/浅睡/清醒 + 入睡/起床),心率页没有该卡 | +| 睡眠入睡/起床时间读起来像「一晚」 | ✅ | 入睡 22:08 / 起床 06:08,正好相隔 8 小时(`DEMO_SLEEP_WINDOW_MILLIS` 生效) | +| 详情页:返回后回到首页 | ✅ | `keyevent 4` 后回到健康首页 | +| 运动页:开始运动 + 5 条记录 | ✅ | 5 条:户外跑步/健走/骑行/自由训练/户外跑步 | +| 运动页「查看全部」进历史页,底部导航栏仍可见 | ✅ | 历史页 5 条记录,底部四 Tab 正常显示 | +| 设备页:戒指卡片 电量 78% / 固件 1.0.5 | ✅ | 另显示「已连接」与蓝牙图标 | +| 设备页「更多设置」开关可切换,返回后状态保持 | ✅ | 打开「压力监测」→ 返回设备页 → 再进入,开关仍为开 | +| 我的页 | ✅ | 头像卡与分组入口正常 | +| 全程无崩溃 | ✅ | `logcat -b crash` 自始至终为空 | -### 需在有设备时重点确认的项(原清单细目,逐条保留) +### 补充实测:终审修复轮的六项改动 -- **【第 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` 那段被改坏了 +这六项原先只有代码阅读作为依据,现在逐项在模拟器上确认: + +| 改动 | 实测结果 | +|---|---| +| 详情页按指标取色 | ✅ 睡眠=蓝、心率=红,与首页各卡的 tint 一致 | +| 血压卡不可点击 | ✅ 点血压卡后与点击前**逐像素一致**,未发生跳转 | +| 压力卡 `unit` 接线 | ✅ 卡片显示「34 **正常**」,即 `stressLevelLabelRes` 真的接上了 | +| 周/月 chips 常驻 | ✅ 两个范围的详情页都始终显示 chips | +| `consumeWindowInsets` 顶栏内边距 | ✅ **量了像素**:状态栏 136 px,顶栏白底 136→303 px,即**正好 168 px = 64 dp**(Material 3 `TopAppBar` 标准高度),标题文字中心在顶栏内 86 px ≈ 32 dp,**正中**。若 insets 被叠加,顶栏会变成 88 dp 且标题偏下 —— 实测不是 | +| README 注明 `installDebug` 需设备 | ✅ 文档项 | + +> `consumeWindowInsets` 这一项此前被反复标注「推理得出、未经设备验证」,**现在验证过了**。 +> 顺带确认:状态栏上方那条灰带就是状态栏本身(136 px),不是多余留白。 + +## 本轮新发现的观察(非缺陷,记录备查) + +1. **设备设置页与运动历史页没有 `TopAppBar`,也就没有返回箭头** —— 标题(「功能设置」)是普通 + 标题文字。返回只能靠系统返回手势,或点底部「设备」Tab。功能上可用,但与详情页(有 `←`) + 不一致。**是否补一个顶栏,留给 M2 一起定。** +2. **运动记录的日期随运行时间变化** —— 两次截图里同一批记录显示 06:08 与 06:10。 + 因为 Mock 的时间戳是相对「此刻」生成的。这**削弱了设计文档 §8「每次运行截图一致」的说法**: + 数据形状是确定性的,但日期不是。若将来要做截图对比测试,需要连时间源一起固定。 +3. **趋势图把最后一根柱子画成深色**(其余为其 30% 透明度)—— 这是**有意的**, + `MetricBarChart(highlightLast = true)` 即「高亮今天」,KDoc 里写明了。 + 记录在此只是为了说明:实测看到的「只有最后一根是深色」不是渲染错误。 ## 已知限制(阶段一有意为之) - 数据全部为 Mock,不连蓝牙、不连后端 -- 血压恒为“未开启”占位(`HealthSummary.bloodPressure = null`) -- 设备设置页的开关只改内存状态,重启即重置(对应 M2 的协议 0502/0504) +- 血压恒为「未开启」占位(`HealthSummary.bloodPressure = null`) +- 设备设置页的开关只改内存状态,**重启即重置**(对应 M2 的协议 0502/0504); + 本次只验证了跨页面保持,没有验证跨进程重启 - 睡眠分期条与入睡/起床时间是演示值(固定比例 + 最后一个数据点往前 8 小时),无真实分期数据 - 详情页统计摘要一律保留一位小数(步数因此显示为「平均 7512.3」这类精度过剩的形式) - 仅浅色模式 -- 血压卡**不可点击**(`value == null` 时 `onClick` 传 `null`)—— 阶段一没有血压数据, - 点进去只会看到一张编出来的图表,不如不点 -- 详情页的**周/月切换常驻**,空数据时只把切换条下方的图表与统计换成一行空态文案。 - 切换条属于页头而非数据区:数据显示为空时,切换区间正是用户的第一反应,它也是判断 - 「哪个区间是空的」的唯一线索 +- 血压卡**不可点击**(`value == null` 时 `onClick` 传 `null`) +- 详情页的**周/月切换常驻**,空数据时只把切换条下方的图表与统计换成一行空态文案 + +### 仍然没有验证到的 + +- **详情页的空数据态**:当前 Mock 恒返回 7 或 30 个点,该分支不可达,**本次没有、也无法触达**。 + 它仍然是只有代码推理保证的防御性代码。 +- **旋转屏幕 / 深色系统设置下不崩溃**:本次未测。 +- **`@Preview` 渲染**:未测(见上)。 +- **真机**:未测。模拟器不能替代真机验收。 ### 与设计文档 §6 的偏差(有意收窄,非遗漏) - **设备页少了四项**:查找戒指(050B)、智能提醒/闹钟占位、固件升级占位(OTA 17xx)、佩戴检测。 阶段一没有真实协议可对接,摆一排点不动的入口不如不摆(ledger R27)。**这些能力仍属产品范围, - 将于后续里程碑回归**;设计文档 §6 的设备页一行已用删除线标注。 -- **详情页由「四个专属设计」收敛为一个模板**:`detail/step`、`detail/heart_rate`、`detail/sleep`、 - `detail/blood_oxygen` 四条路由合并为 `detail/{metric}`,七个指标共用同一模板(柱状趋势图 + - 周/月切换 + 平均/最高/最低)。**仅睡眠保留了专属区块**(`SleepStagesCard`)。 - 未实现的是:步数的大环、心率的折线/静息-运动区间、血氧的正常范围说明文案。 - 收敛本身是有意的(避免七套几乎相同的页面代码),详见设计文档 §6 下的注记。 + 将于后续里程碑回归**;设计文档 §6 的设备页一行已用删除线标注。**本次实测确认设备页确实没有这四项。** +- **详情页由「四个专属设计」收敛为一个模板**:四条路由合并为 `detail/{metric}`,七个指标共用 + 同一模板。**仅睡眠保留了专属区块**。未实现的是:步数的大环、心率的折线/静息-运动区间、 + 血氧的正常范围说明文案。详见设计文档 §6 下的注记。 ### M2 接入时的已知接缝(交接用,本阶段不修) @@ -136,19 +117,38 @@ List of devices attached (厂商 Java 依赖,**很可能需要 Jetifier**)、`feature/device/DeviceScreen.kt`。 完整清单见实施计划的「M2 交接提示」一节。 -## 覆盖缺口声明 +## 覆盖缺口声明(已大幅收窄,但仍非零) -**M1 的全部运行时行为目前只有编译期与单元测试保证,没有任何一次真机/模拟器运行。** -`assembleDebug` 成功只说明代码能编译、能打包,**不说明界面正确、不说明不会崩溃**。 -这是本机没有设备导致的已知缺口(ledger Ruling R21),不是「已验证通过」。 -首次拿到设备时,请从上面那张人工验收表开始逐项补齐。 +M1 的运行时行为**已在 Android 模拟器上完整走查过一遍**(上表逐项打勾),不再是「只有编译期保证」。 +但仍有三个缺口,**不要把它们读成「已验证」**: -具体而言,**没有任何一行 M1 的 Compose 界面代码被真正渲染过**:导航图能否起来、 -四个 Tab 是否显示正确文案、`MetricBarChart` 的柱子数量、`RingProgress` 的填充比例、 -睡眠分期卡片是否只在睡眠指标下出现、次要文字灰度是否统一 —— 这些全部未经运行时检验。 -单元测试覆盖的是 `Formatters` 的纯函数、`MockDataProvider` 的数据形状,以及七个 ViewModel -的状态流;**它们不经过 Compose 渲染管线,也不能发现布局、主题或导航层面的错误。** +1. **跑的是模拟器不是真机** —— 真机上的厂商 ROM、性能、字体缩放、刘海/挖孔适配均未检验。 +2. **空数据态与旋转/深色模式未触达**(见上)。 +3. **`@Preview` 未渲染** —— 不过它原本的替代作用已被模拟器实测覆盖。 -终审修复轮新改的几处**尤其如此**:按指标取色、血压卡不可点、空数据态、压力卡的单位接线、 -以及 `consumeWindowInsets` —— 这五处**全部是界面行为**,而 21 项单测**一项都没有、也不可能** -碰到它们。它们的依据是代码阅读与 Compose 的既有语义,不是运行结果。 +首次拿到真机时,请至少重跑上表中的「启动无崩溃」「四 Tab 切换」「详情页顶栏内边距」三项。 + +## 附:模拟器是怎么装上的(含一个踩过的坑) + +本机原先 `D:\Android\Sdk` 下没有 `emulator/` 也没有 `system-images/`,本次新装: + +```bash +# 1) 装包(注意坑:见下) +/d/Android/Sdk/cmdline-tools/latest/bin/sdkmanager.bat \ + --sdk_root=D:/Android/Sdk "emulator" "system-images;android-36;google_apis;x86_64" + +# 2) 建 AVD(avdmanager 不认 --sdk_root,只认环境变量) +export ANDROID_SDK_ROOT='D:\Android\Sdk' ANDROID_AVD_HOME='D:\Android\avd' +echo no | /d/Android/Sdk/cmdline-tools/latest/bin/avdmanager.bat create avd \ + -n ringtest -k "system-images;android-36;google_apis;x86_64" -d pixel_7 + +# 3) 启动 +/d/Android/Sdk/emulator/emulator.exe -avd ringtest +``` + +**坑:`--sdk_root` 一定要写正斜杠。** 写成 `D:\Android\Sdk` 时,Git Bash 会把反斜杠吃掉, +变成 Windows 的**盘符相对路径** `D:AndroidSdk` —— 这不是绝对路径,而是「D 盘当前目录下的 +AndroidSdk」。结果 sdkmanager 把 5.3 GB 的包装进了 `cmdline-tools\latest\bin\AndroidSdk\`。 +同盘移动是瞬时的,已搬回 `D:\Android\Sdk\`。**在 Git Bash 里给 Windows 工具传路径,一律用正斜杠。** + +另外 `ANDROID_AVD_HOME` 已设为用户级变量 `D:\Android\avd`(AVD 镜像有几个 G,C 盘只剩 29 G)。