# 智能戒指健康 App 设计文档(阶段一) - 日期:2026-09-10 - 状态:待评审 - 产品代号(占位):`ring-health` - 相关仓库:`ring-health-app`(Android App)、`ring-health-api`(后端 API) --- ## 1. 背景与目标 我们要为自有的智能戒指/手环做一款手机 App,用于后续与设备联调、并最终面向用户。 交互参照**小米运动健康**(Mi Fitness),但**按我们戒指的真实能力裁剪**。 **阶段一目标(本文档范围)**:用原生 Android + Jetpack Compose 搭出完整可运行的 **静态页面骨架**,页面齐全、导航顺畅、真机可跑;所有数据来自本地 Mock。 **不在阶段一范围**:真实蓝牙连接、协议解析、本地数据库、账号登录、云端同步。 这些在 M2 及以后的里程碑中,通过预留的接口边界接入。 ## 2. 现状:厂商 SDK 分析(已探明) 手上的 `SmartRingF01_SDK` **不是 `.aar` 库,而是厂商「鑫域隆 SmartRingF01/F02」的完整 Demo App**。 | 项 | 现状 | |---|---| | 工程名 / 包名 | `SmartRingDCLT_SDK` / `com.sr.smartring` | | 可复用核心 | `BluetoothLeClass`(BLE 扫描/连接/GATT 收发,**仅依赖 Android 框架,零第三方依赖**) | | 协议收发 | 写在厂商 `MainActivity` 的静态方法:`sendBleData` / `recvBleData` / `recvBleWriteData`,帧格式 `DC 0001 LT`(下行)、`DC 0002 …`(上报) | | 辅助类 | `HEROUtils`(OTA 下载 + CRC32)、`Utils`、`BluetoothApplication`、`LeDeviceListAdapter` | | 技术栈 | Java + Android **Support Library(非 AndroidX)** + EventBus 3 + Gradle 6.7.1 / AGP 4.2.2(很旧) | | 协议文档 | `鑫域隆智能戒指协议.txt`(GBK 编码,含全部指令码) | **戒指能力(来自协议文档,决定我们能做哪些页面)**: 计步、心率、血氧、睡眠、血压、体温、压力、运动检测、查找戒指、电量、佩戴/充电状态、OTA 升级、自动监测开关配置、时间同步。 **复用策略**:SDK 内容**不修改**。M2 接 BLE 时,把 `BluetoothLeClass` 等纯通讯类**原样拷贝**进 App 的 `vendor/` 包(保持 `com.sr.smartring` 包名与代码原样),我们只**新写一层适配器**去调用它。 UI 永远不直接依赖厂商代码。 ## 3. 仓库与目录结构 遵循团队 `docs/` + `repos/` 惯例;仓库名 kebab-case,移动端 `-app`、后端 `-api`。 ``` d:\cherry ├── SmartRingF01_SDK/ # 厂商原始工程(只读参考,不纳入任何 git 仓库) ├── docs/ # 项目文档根(独立 git 仓库) │ └── superpowers/specs/ # 设计/技术文档 └── repos/ ├── ring-health-app/ # Android App(Kotlin + Compose) ← 阶段一主战场 └── ring-health-api/ # 后端 API ← 阶段一仅脚手架 + 接口契约 ``` - `ring-health-app`:阶段一全部实现在此。 - `ring-health-api`:阶段一**只放空骨架(README + gitignore)与 `docs/api-contract.md` 契约草稿**, 不写业务接口。后端技术栈(Spring Boot / .NET)待 M4 真正做云同步时再定。 ## 4. App 技术选型 | 项 | 选择 | 理由 | |---|---|---| | 语言 | Kotlin | Compose 唯一舒适选择 | | UI | Jetpack Compose + Material 3 | 写卡片/图表类仿小米页面效率最高 | | 导航 | Navigation-Compose,单 Activity | 多 Tab + 深层子页标准做法 | | 架构 | MVVM + `StateFlow`(`UiState`) | 官方推荐,易测试 | | 异步 | 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 一致 | **阶段一依赖清单(刻意精简)**:`androidx.core:core-ktx`、`androidx.activity:activity-compose`、 `androidx.lifecycle:lifecycle-runtime-compose` / `lifecycle-viewmodel-compose`、 `androidx.navigation:navigation-compose`、`androidx.compose:compose-bom`(ui / material3 / material-icons-extended / ui-tooling-preview)、`kotlinx-coroutines`。 **不引** Hilt、Room、Retrofit、第三方图表库。 ## 5. App 架构分层(单 `:app` 模块 + 严格分包) ``` com.placeholder.ringhealth ├── RingHealthApplication.kt # 组合根:手动构造 Repository,注入 ViewModel ├── MainActivity.kt # 唯一 Activity,承载 Compose NavHost ├── core/ │ ├── designsystem/ # Theme/Color/Type/Shape + 通用组件 │ │ └── component/ # HealthCard、RingProgress、MetricTile、SectionHeader… │ ├── common/ # 格式化与扩展(步数、时长、心率区间) │ └── navigation/ # Route 常量与 NavGraph ├── domain/ │ ├── model/ # 纯 Kotlin 数据模型(无 Android 依赖) │ └── repository/ # 仓库接口(UI 只依赖这里) ├── data/ │ └── mock/ # Mock 实现 + 造数器(阶段一) │ # M2 将新增 data/sdk/ 作为厂商 SDK 适配层 ├── feature/ # 每个 Tab / 子页一个包:Screen + ViewModel + UiState │ ├── home/ workout/ device/ profile/ detail/ └── vendor/ # 预留:M2 原样存放厂商 com.sr.smartring 通讯类(不改一行) ``` **核心边界原则**:UI(`feature/*`)**只依赖 `domain/repository` 的接口**与 `domain/model`。 阶段一注入 Mock 实现;M2 新增 SDK 适配实现,接口不变则 UI 零改动。 ### 5.1 关键接口(阶段一定稿) ```kotlin // domain/repository interface RingDeviceRepository { val deviceState: StateFlow // 连接态/电量/型号/固件版本 suspend fun refreshDeviceState() } interface HealthDataRepository { fun observeTodaySummary(): Flow fun observeMetricHistory(metric: MetricType, range: DateRange): Flow> fun observeWorkouts(): Flow> } // domain/model(节选) enum class MetricType { STEP, HEART_RATE, SLEEP, BLOOD_OXYGEN, STRESS, TEMPERATURE, BLOOD_PRESSURE } data class RingDeviceState( val connected: Boolean, val name: String, val batteryPercent: Int, val firmwareVersion: String, val charging: Boolean, ) data class HealthSummary( // 今日概览 val steps: Int, val stepGoal: Int, val calories: Int, val distanceKm: Double, val heartRate: Int?, val sleepMinutes: Int?, val bloodOxygen: Int?, val stress: Int?, val temperature: Double?, val bloodPressure: String?, // 血压未开启时为 null ) data class MetricPoint(val timestamp: Long, val value: Double) ``` 约定:`ViewModel` 用 `stateIn` 把 Flow 转成 `StateFlow`;`null` 表示"该指标未开启/无数据", UI 统一走"未开启占位态",不显示 0。 ## 6. 页面地图与路由 底部四 Tab(仿小米运动健康),并按戒指能力裁剪: | Tab | 页面内容 | 路由 | |---|---|---| | **① 健康·首页** | 顶部问候 + 设备连接状态条;今日大环(步数/卡路里/距离);指标卡片网格:**心率 / 睡眠 / 血氧 / 压力 / 体温**(血压显示"未开启/需校准"占位) | `home` | | **② 运动** | "开始运动"入口(跑步/骑行/自由训练占位)+ 运动历史记录列表(静态) | `workout` | | **③ 设备** | 戒指卡片(名称/电量/连接态/固件)+ 设置列表:健康自动监测开关(心率/血氧/血压/压力/睡眠,对应协议 0502 开关位)、查找戒指(050B)、智能提醒/闹钟占位、**固件升级占位**(OTA 17xx)、佩戴检测 | `device` | | **④ 我的** | 头像/昵称(登录占位)、App 设置(单位/通知)、通知权限引导、关于/检查更新、帮助与反馈 | `profile` | **详情子页**(全部静态 + Canvas 图表占位): | 路由 | 内容 | |---|---| | `detail/step` | 今日步数详情:大环 + 7 天/月切换柱状图 + 说明文案 | | `detail/heart_rate` | 心率详情:当日折线/区间统计(静息/运动)+ 历史区间 | | `detail/sleep` | 睡眠详情:深睡/浅睡/清醒分段条 + 入睡/起床时间 | | `detail/blood_oxygen` | 血氧详情:趋势折线 + 正常范围说明 | | `device/settings` | 戒指功能开关详情(静态开关,M2 后写向设备) | | `workout/history` | 运动历史列表 | **明确不做**(小米有但我们戒指不支持):跑步课程、商城、社区、多设备接入、体重秤/体脂秤。 ## 7. 设计系统与视觉(仿小米运动健康) - 浅色为主,白底卡片 + 大圆角 + 轻阴影;大号数字(步数等)用等宽感强的字重。 - 指标色板:步数=绿、心率=红/粉、睡眠=蓝/靛、血氧=青、压力=紫、体温=橙(在 `designsystem/Color.kt` 统一定义)。 - 通用组件沉淀到 `core/designsystem/component/`:`HealthCard`、`RingProgress`(Canvas 环形进度)、 `MetricTile`(图标+数值+单位小卡)、`SectionHeader`、`StatBar`(睡眠分段条/柱状图)。 - 深色模式:阶段一**仅保证浅色**可用,深色留到 M5。 - 所有中文字符串进 `strings.xml`(默认中文,附英文),支持后续多语言;**禁止硬编码文案**。 ## 8. Mock 数据策略 - `data/mock/MockDataProvider.kt` 提供**确定性**演示数据(固定随机种子),保证每次运行截图一致、便于展示。 - Mock 仓库模拟轻微异步延迟(~200ms),让加载态/占位骨架在阶段一就能跑通。 - 开关类设置的状态用内存态(`StateFlow`)保存,重启重置;**阶段一不引入持久化**。 ## 9. API 仓库(阶段一) `ring-health-api` 阶段一只包含: ``` ring-health-api/ ├── README.md # 说明:后端栈待定,M4 落地 ├── .gitignore └── docs/api-contract.md # 接口契约草稿(登录、设备绑定、健康数据同步的字段雏形) ``` 契约草稿仅为对齐未来前后端边界,**不实现**。后端技术栈(Spring Boot / .NET)在 M4 前单独定。 ## 10. 里程碑与验收 | 阶段 | 内容 | 验收标准 | |---|---|---| | **M0 基建** | 建 `docs/`、`repos/` 目录;初始化两个 git 仓库;gitignore/README | 两仓库结构就绪,符合命名规范 | | **M1 静态 App(阶段一)** | Compose 工程骨架、主题、四 Tab 导航、本文档第 6 节全部页面、Mock 仓库 | 真机/模拟器可跑;四 Tab 与全部子页可切换;无崩溃;`assembleDebug` 通过 | | **M2 SDK 接入** | `vendor/` 原样放厂商通讯类;新增 `data/sdk/` 适配层实现既有接口;BLE 权限、扫描、连接 | 真机扫描到戒指、连接成功、读到电量/固件 | | **M3 数据打通** | 协议解析(帧 `DC…LT`)、Mock→真数据、ROOM 本地存储、真实图表 | 首页与详情显示真机数据 | | **M4 云端/账号** | 定后端栈、实现登录与同步接口、App 接入 | 接口联调通过 | | **M5 发布** | 签名、图标、深色模式、多语言、内测包 | 可分发 APK | **本次实施范围 = M0 + M1。** ## 11. 风险与决策记录 | 风险/决策 | 说明与对策 | |---|---| | 厂商 SDK 老旧(Support Library / EventBus)与 Compose 工程共存 | `BluetoothLeClass` 零第三方依赖,风险低;M2 隔离在 `vendor/` 包内,仅当必需才考虑 jetifier | | 占位命名需全局替换 | 替换清单见 §12;集中一处记录,替换时按清单执行 | | BLE 权限(Android 12+) | M2 统一处理 `BLUETOOTH_SCAN/CONNECT` + 位置权限与引导 UI,阶段一不涉及 | | 手绘 Canvas 图表工作量 | 阶段一只做环形 + 简单柱/折线,避免过度实现;复杂图表留 M3 | | 后端栈未定 | 有意延迟到 M4,避免两仓库空转 | | 本机构建环境版本 | 已探明:JDK 17、Android SDK(platform 34/35/36)、build-tools **仅 35.0.0 / 36.1.0**、Gradle 缓存 8.12/8.14、无全局 gradle。故工具链**钉死在已安装版本**,避免构建时联网拉取缺失组件 | **已确认决策**:① 前后端分离,两个仓库;② Jetpack Compose;③ 阶段一单 `:app` 分层模块; ④ 四个 Tab 与全部详情子页都做;⑤ 后端阶段一只留契约;⑥ 占位命名后续统一替换。 ## 12. 占位命名与替换清单 待正式产品名/域名确定后,需统一替换: | 位置 | 占位值 | |---|---| | 仓库名 | `ring-health-app`、`ring-health-api`(含 git 服务器上名称) | | `applicationId` / `namespace` | `com.placeholder.ringhealth` | | 包路径 | `com/placeholder/ringhealth` 目录树 | | `app_name`(strings.xml) | `RingHealth`(占位) | | 文档与 README 中的产品代号 | `ring-health` | 替换为一次性全局操作,不涉及逻辑改动;执行时按上表逐项核对。