From 9c233e5750ef64db173a7baa2eb0fc59bf7b1ee0 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 12:54:40 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=A1=A5=E5=85=85=20M1=20=E9=AA=8C?= =?UTF-8?q?=E6=94=B6=E8=AE=B0=E5=BD=95=E4=B8=8E=E9=A1=B5=E9=9D=A2=E6=B8=85?= =?UTF-8?q?=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 14 收尾(几乎不含生产代码改动): - 清扫三条孤儿字符串:`placeholder_workout_history` / `placeholder_device_settings` / `placeholder_metric_detail`。grep 全 `app/src` 确认三者均无调用点后删除; 顺带逐条核对了 `strings.xml` 其余字符串,除 manifest 引用的 `app_name` 外全部有调用点, 未发现别的漏接线(不是顺手一起删,是先查清才决定)。 - 按 ledger R31,给 `MockRingDeviceRepository.setFunctionEnabled` 补中文 KDoc, 写明「必须返回新列表」及其原因(`DeviceViewModel.stateIn(initialValue=…)` 在构造时 冻结 List 引用,原地改写会让 `DeviceViewModelTest` 第一条用例退化成自我确认)。 验证:`./gradlew :app:testDebugUnitTest` 21 项全绿(FormattersTest 7 + MockDataProviderTest 5 + HomeViewModelTest 1 + WorkoutViewModelTest 1 + DeviceViewModelTest 2 + MetricDetailViewModelTest 3 + RoutesTest 2);`./gradlew clean assembleDebug` BUILD SUCCESSFUL, 产物 app-debug.apk(18,907,329 字节)。 新增 `docs/verification-m1.md` 记录 M1 验收。**人工验收全表标注「未执行 —— 无设备」**: 本机无模拟器镜像、无连接真机(`adb devices` 为空;`~/.android/avd` 下两个 AVD 只是指向 已删除旧 SDK 的空壳,不可启动),IDE Preview 亦无法打开。记录中含覆盖缺口声明: M1 全部运行时行为目前只有编译期与单元测试保证,`assembleDebug` 成功不代表界面正确或不会崩溃。 README 补 M1 页面清单表,路由与 `core/navigation/Routes.kt` 逐条核对一致; 并核对 `profile_check_update_desc` 的手抄版本号 `0.1.0` 与 build.gradle.kts 的 `versionName` 一致(计划 3682 行交办项)。 Co-Authored-By: Claude Code --- README.md | 15 +++ .../data/mock/MockRingDeviceRepository.kt | 8 ++ app/src/main/res/values/strings.xml | 3 - docs/verification-m1.md | 97 +++++++++++++++++++ 4 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 docs/verification-m1.md diff --git a/README.md b/README.md index 458b9f3..971739b 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,21 @@ - 设计文档:见 `ring-health-docs` 仓库 `superpowers/specs/` - 当前阶段:M1 静态页面(数据来自 Mock,未接入蓝牙 SDK) +## 页面清单(M1) + +| 页面 | 路由 | +|---|---| +| 健康首页 | `home` | +| 运动 | `workout` | +| 运动历史 | `workout/history` | +| 设备 | `device` | +| 设备设置 | `device/settings` | +| 我的 | `profile` | +| 指标详情(步数/心率/睡眠/血氧/压力/体温/血压) | `detail/{metric}` | + +> 页面路由的唯一定义处是 `core/navigation/Routes.kt`,上表与其保持一致。 +> M1 的验收状态见 `docs/verification-m1.md`。 + ## 构建 ```bash diff --git a/app/src/main/java/com/placeholder/ringhealth/data/mock/MockRingDeviceRepository.kt b/app/src/main/java/com/placeholder/ringhealth/data/mock/MockRingDeviceRepository.kt index c4f1b3e..669a9b8 100644 --- a/app/src/main/java/com/placeholder/ringhealth/data/mock/MockRingDeviceRepository.kt +++ b/app/src/main/java/com/placeholder/ringhealth/data/mock/MockRingDeviceRepository.kt @@ -33,6 +33,14 @@ class MockRingDeviceRepository( _deviceState.value = provider.deviceState() } + /** + * 切换某个功能开关。 + * + * **必须返回新列表**(`map` / `+` 等),不要原地改写同一个 `MutableList`: + * `DeviceViewModel` 的 `stateIn(initialValue = …)` 在**构造时**就把当时的 List 引用冻结了, + * 而 `DeviceViewModelTest` 第一条用例的鉴别力正依赖于此 —— 若这里改成原地改写, + * `initialValue` 会被连带改动,那条断言就退化成自我确认,测试会静默失去意义。 + */ override fun setFunctionEnabled(id: String, enabled: Boolean) { _functionSettings.update { settings -> settings.map { if (it.id == id) it.copy(enabled = enabled) else it } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1992dda..5520167 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,9 +11,6 @@ 运动 设备 我的 - 设备设置 - 运动历史 - 指标详情 %1$s · 已连接 未连接设备 diff --git a/docs/verification-m1.md b/docs/verification-m1.md new file mode 100644 index 0000000..c94dfce --- /dev/null +++ b/docs/verification-m1.md @@ -0,0 +1,97 @@ +# 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 面板,**这一项与人工验收一样是未执行的** —— 不要把它写进「已执行」段落 + (旧稿把它列在上面这一节里,那等于声称验证过一件没做过的事)。 + +## 人工验收(未执行 —— 无设备) + +下表**全部未打勾**。这份清单已原样保留,交给有设备的人执行;不要在没有实测的情况下填入结果。 + +| 项 | 结果 | 备注 | +|---|---|---| +| 应用启动无崩溃 | 未执行 | 无设备 | +| 四 Tab 切换 | 未执行 | 无设备 | +| 重按已高亮的父 Tab 的行为 | 未执行 | 需实测:推测会停在子页而非回到 Tab 根 | +| 每个页面的 `@Preview` 可渲染 | 未执行 | 需在 Android Studio 中逐个打开确认 | +| 次要文字灰度一致 | 未执行 | 不得出现 `TextSecondary` 与 `onSurfaceVariant` 混用 | +| 首页:设备状态条 / 步数大环 8,426 / 六个指标卡(血压“未开启”) | 未执行 | 无设备 | +| 指标详情页:周/月切换(7 根 / 30 根柱子) | 未执行 | 无设备 | +| 睡眠详情:分期卡片只在睡眠指标出现,入睡/起床读起来像「一晚」 | 未执行 | 无设备 | +| 运动页 / 运动历史页 | 未执行 | 无设备 | +| 设备页 / 设备设置开关(返回后状态保持) | 未执行 | 无设备 | +| 我的页 | 未执行 | 无设备 | +| 旋转屏幕、深色系统设置下不崩溃 | 未执行 | 无设备 | + +### 需在有设备时重点确认的项(原清单细目,逐条保留) + +- 应用启动无崩溃,底部四个 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」这类精度过剩的形式) +- 仅浅色模式 + +## 覆盖缺口声明 + +**M1 的全部运行时行为目前只有编译期与单元测试保证,没有任何一次真机/模拟器运行。** +`assembleDebug` 成功只说明代码能编译、能打包,**不说明界面正确、不说明不会崩溃**。 +这是本机没有设备导致的已知缺口(ledger Ruling R21),不是「已验证通过」。 +首次拿到设备时,请从上面那张人工验收表开始逐项补齐。 + +具体而言,**没有任何一行 M1 的 Compose 界面代码被真正渲染过**:导航图能否起来、 +四个 Tab 是否显示正确文案、`MetricBarChart` 的柱子数量、`RingProgress` 的填充比例、 +睡眠分期卡片是否只在睡眠指标下出现、次要文字灰度是否统一 —— 这些全部未经运行时检验。 +单元测试覆盖的是 `Formatters` 的纯函数、`MockDataProvider` 的数据形状,以及七个 ViewModel +的状态流;**它们不经过 Compose 渲染管线,也不能发现布局、主题或导航层面的错误。**