Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77de7808cb | ||
|
|
5fe0d4a420 |
@@ -24,8 +24,11 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
./gradlew assembleDebug # 产物在 app/build/outputs/apk/debug/
|
./gradlew assembleDebug # 产物在 app/build/outputs/apk/debug/
|
||||||
./gradlew installDebug # 安装到已连接设备
|
./gradlew installDebug # 安装到已连接设备(需接好设备或已启动模拟器)
|
||||||
./gradlew test # 单元测试
|
./gradlew test # 单元测试
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> `installDebug` 需要一台已连接的设备或正在运行的模拟器。M1 使用的机器两者都没有,
|
||||||
|
> 因此本机实际可执行的是 `./gradlew assembleDebug` 与 `./gradlew :app:testDebugUnitTest`。
|
||||||
|
|
||||||
要求:JDK 17、Android SDK(platform 36 / build-tools 36.1.0)。
|
要求:JDK 17、Android SDK(platform 36 / build-tools 36.1.0)。
|
||||||
|
|||||||
+3
-2
@@ -30,7 +30,8 @@ import com.placeholder.ringhealth.core.designsystem.TextSecondary
|
|||||||
* @param value 主数值文本。
|
* @param value 主数值文本。
|
||||||
* @param unit 单位文本,如「步」「次/分」。
|
* @param unit 单位文本,如「步」「次/分」。
|
||||||
* @param modifier 外部修饰符。
|
* @param modifier 外部修饰符。
|
||||||
* @param onClick 点击回调,默认空实现(配合 HealthCard 的整卡可点击)。
|
* @param onClick 点击回调;为 null 表示卡片不可点击 —— 不挂 clickable,因此既没有水波纹
|
||||||
|
* 也不会触发导航,用于「未开启」这类没有数值的指标卡(有值的卡才可跳转详情)。
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MetricTile(
|
fun MetricTile(
|
||||||
@@ -40,7 +41,7 @@ fun MetricTile(
|
|||||||
value: String,
|
value: String,
|
||||||
unit: String,
|
unit: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: () -> Unit = {},
|
onClick: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
HealthCard(modifier = modifier, onClick = onClick) {
|
HealthCard(modifier = modifier, onClick = onClick) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.placeholder.ringhealth.core.navigation
|
package com.placeholder.ringhealth.core.navigation
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.FitnessCenter
|
import androidx.compose.material.icons.filled.FitnessCenter
|
||||||
@@ -90,7 +91,12 @@ fun RingHealthApp(navController: NavHostController = rememberNavController()) {
|
|||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = Routes.HOME,
|
startDestination = Routes.HOME,
|
||||||
modifier = Modifier.padding(innerPadding),
|
// consumeWindowInsets 必须跟着 padding 一起写:外层 Scaffold 没有 topBar,
|
||||||
|
// 它的 innerPadding.top 就是状态栏高度,若只 padding 不消费,这个 inset 会继续
|
||||||
|
// 往下传,详情页内层 Scaffold 的 TopAppBar 会再加一次状态栏内边距 ——
|
||||||
|
// AppBar 被推低约两个状态栏,上方露出一条背景色带,与四个 Tab 页明显不同。
|
||||||
|
// 消费后内层拿到的是已扣减的 inset,两个方向都安全(若外层早已消费,这里是 no-op)。
|
||||||
|
modifier = Modifier.padding(innerPadding).consumeWindowInsets(innerPadding),
|
||||||
) {
|
) {
|
||||||
composable(Routes.HOME) {
|
composable(Routes.HOME) {
|
||||||
HomeScreen(onOpenDetail = { metric -> navController.navigate(Routes.detail(metric)) })
|
HomeScreen(onOpenDetail = { metric -> navController.navigate(Routes.detail(metric)) })
|
||||||
|
|||||||
@@ -37,7 +37,12 @@ import com.placeholder.ringhealth.R
|
|||||||
import com.placeholder.ringhealth.core.common.formatClock
|
import com.placeholder.ringhealth.core.common.formatClock
|
||||||
import com.placeholder.ringhealth.core.common.formatDecimal
|
import com.placeholder.ringhealth.core.common.formatDecimal
|
||||||
import com.placeholder.ringhealth.core.designsystem.MetricBlue
|
import com.placeholder.ringhealth.core.designsystem.MetricBlue
|
||||||
|
import com.placeholder.ringhealth.core.designsystem.MetricCyan
|
||||||
import com.placeholder.ringhealth.core.designsystem.MetricGreen
|
import com.placeholder.ringhealth.core.designsystem.MetricGreen
|
||||||
|
import com.placeholder.ringhealth.core.designsystem.MetricOrange
|
||||||
|
import com.placeholder.ringhealth.core.designsystem.MetricPurple
|
||||||
|
import com.placeholder.ringhealth.core.designsystem.MetricRed
|
||||||
|
import com.placeholder.ringhealth.core.designsystem.MetricYellow
|
||||||
import com.placeholder.ringhealth.core.designsystem.RingHealthTheme
|
import com.placeholder.ringhealth.core.designsystem.RingHealthTheme
|
||||||
import com.placeholder.ringhealth.core.designsystem.TextSecondary
|
import com.placeholder.ringhealth.core.designsystem.TextSecondary
|
||||||
import com.placeholder.ringhealth.core.designsystem.component.HealthCard
|
import com.placeholder.ringhealth.core.designsystem.component.HealthCard
|
||||||
@@ -75,6 +80,8 @@ fun MetricDetailScreen(
|
|||||||
/**
|
/**
|
||||||
* 无状态指标详情内容:范围切换 + 柱状趋势图 + 统计摘要(+ 睡眠分期)。
|
* 无状态指标详情内容:范围切换 + 柱状趋势图 + 统计摘要(+ 睡眠分期)。
|
||||||
*
|
*
|
||||||
|
* 范围切换常驻;当前范围没有数据点时,只把「趋势图 + 统计摘要」换成一行空态文案。
|
||||||
|
*
|
||||||
* 标题取自 `state.metric` 而不是外面传进来的 `metric` —— 无状态层只有 `state` 一个数据来源,
|
* 标题取自 `state.metric` 而不是外面传进来的 `metric` —— 无状态层只有 `state` 一个数据来源,
|
||||||
* 免得出现「标题写心率、数据画睡眠」这种两者不一致的可能。
|
* 免得出现「标题写心率、数据画睡眠」这种两者不一致的可能。
|
||||||
*/
|
*/
|
||||||
@@ -118,13 +125,35 @@ private fun MetricDetailContent(
|
|||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
) {
|
) {
|
||||||
|
// 范围切换常驻,空数据时反而更要留着:切到另一个范围是用户看到「暂无数据」后的
|
||||||
|
// 第一反应,chips 也是判断「哪个范围是空的」的唯一线索。它属于页头,不属于数据区。
|
||||||
item { RangeSelector(current = state.range, onSelect = onSelectRange) }
|
item { RangeSelector(current = state.range, onSelect = onSelectRange) }
|
||||||
|
|
||||||
|
// 只有图与统计被替换为空态。判空必须在 loading 之后:Mock 有约 200ms 延迟,
|
||||||
|
// 加载期间 points 也是空的,先判空会在数据到达前闪一下空态(loading 分支已提前 return)。
|
||||||
|
if (state.points.isEmpty()) {
|
||||||
|
// 不再渲染「全 0 高度的柱状图 + 三行 0.0」,那看着像页面坏了而不是「没有数据」。
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(vertical = 48.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.detail_empty),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = TextSecondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@LazyColumn
|
||||||
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
HealthCard {
|
HealthCard {
|
||||||
SectionHeader(title = stringResource(R.string.detail_trend))
|
SectionHeader(title = stringResource(R.string.detail_trend))
|
||||||
MetricBarChart(
|
MetricBarChart(
|
||||||
values = state.points.map { it.value.toFloat() },
|
values = state.points.map { it.value.toFloat() },
|
||||||
color = MetricGreen,
|
color = metricTint(state.metric),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,6 +257,23 @@ private fun metricTitleRes(type: MetricType): Int = when (type) {
|
|||||||
MetricType.BLOOD_PRESSURE -> R.string.metric_blood_pressure
|
MetricType.BLOOD_PRESSURE -> R.string.metric_blood_pressure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指标类型 → 语义色。
|
||||||
|
*
|
||||||
|
* 与 `HomeScreen` 里各指标卡的 tint 一一对应(步数=绿、心率=红、睡眠=蓝、血氧=青、
|
||||||
|
* 压力=紫、体温=橙、血压=黄)。色板的唯一来源是设计文档 §指标色板与
|
||||||
|
* `core/designsystem/Color.kt`,本函数只做映射,不在页面里另造颜色。
|
||||||
|
*/
|
||||||
|
private fun metricTint(type: MetricType): Color = when (type) {
|
||||||
|
MetricType.STEP -> MetricGreen
|
||||||
|
MetricType.HEART_RATE -> MetricRed
|
||||||
|
MetricType.SLEEP -> MetricBlue
|
||||||
|
MetricType.BLOOD_OXYGEN -> MetricCyan
|
||||||
|
MetricType.STRESS -> MetricPurple
|
||||||
|
MetricType.TEMPERATURE -> MetricOrange
|
||||||
|
MetricType.BLOOD_PRESSURE -> MetricYellow
|
||||||
|
}
|
||||||
|
|
||||||
/** 周/月切换。 */
|
/** 周/月切换。 */
|
||||||
@Composable
|
@Composable
|
||||||
private fun RangeSelector(
|
private fun RangeSelector(
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import com.placeholder.ringhealth.core.common.formatCalories
|
|||||||
import com.placeholder.ringhealth.core.common.formatDistance
|
import com.placeholder.ringhealth.core.common.formatDistance
|
||||||
import com.placeholder.ringhealth.core.common.formatSleepDuration
|
import com.placeholder.ringhealth.core.common.formatSleepDuration
|
||||||
import com.placeholder.ringhealth.core.common.formatSteps
|
import com.placeholder.ringhealth.core.common.formatSteps
|
||||||
|
import com.placeholder.ringhealth.core.common.stressLevelLabelRes
|
||||||
import com.placeholder.ringhealth.core.designsystem.MetricBlue
|
import com.placeholder.ringhealth.core.designsystem.MetricBlue
|
||||||
import com.placeholder.ringhealth.core.designsystem.MetricCyan
|
import com.placeholder.ringhealth.core.designsystem.MetricCyan
|
||||||
import com.placeholder.ringhealth.core.designsystem.MetricGreen
|
import com.placeholder.ringhealth.core.designsystem.MetricGreen
|
||||||
@@ -109,13 +110,16 @@ private fun HomeContent(
|
|||||||
item { StepsCard(summary = summary, onClick = { onOpenDetail(MetricType.STEP) }) }
|
item { StepsCard(summary = summary, onClick = { onOpenDetail(MetricType.STEP) }) }
|
||||||
item { SectionHeader(title = stringResource(R.string.home_metrics)) }
|
item { SectionHeader(title = stringResource(R.string.home_metrics)) }
|
||||||
items(cards) { card ->
|
items(cards) { card ->
|
||||||
|
// 没有数值(未开启)的指标卡不可点击:传 null 后 MetricTile/HealthCard 不挂 clickable,
|
||||||
|
// 无跳转也无水波纹。否则首页显示「未开启」、点进去却是一张完整图表,两页自相矛盾。
|
||||||
|
val openDetail = card.value?.let { { onOpenDetail(card.type) } }
|
||||||
MetricTile(
|
MetricTile(
|
||||||
icon = card.icon,
|
icon = card.icon,
|
||||||
iconTint = card.tint,
|
iconTint = card.tint,
|
||||||
title = card.title,
|
title = card.title,
|
||||||
value = card.value ?: notEnabledText,
|
value = card.value ?: notEnabledText,
|
||||||
unit = if (card.value == null) "" else card.unit,
|
unit = if (card.value == null) "" else card.unit,
|
||||||
onClick = { onOpenDetail(card.type) },
|
onClick = openDetail,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,7 +243,10 @@ private fun buildMetricCards(summary: HealthSummary): List<MetricCardSpec> = lis
|
|||||||
tint = MetricPurple,
|
tint = MetricPurple,
|
||||||
title = stringResource(R.string.metric_stress),
|
title = stringResource(R.string.metric_stress),
|
||||||
value = summary.stress?.toString(),
|
value = summary.stress?.toString(),
|
||||||
unit = "",
|
// 单位位改放压力等级文案(数值落在哪个区间就是放松/正常/中等/偏高),
|
||||||
|
// 于是卡片读作「34 正常」而不是一个孤零零的数字。无值时留空(上面的 value 分支
|
||||||
|
// 会整卡退化成「未开启」,此时单位本来也会被清空)。
|
||||||
|
unit = summary.stress?.let { stringResource(stressLevelLabelRes(it)) } ?: "",
|
||||||
),
|
),
|
||||||
MetricCardSpec(
|
MetricCardSpec(
|
||||||
type = MetricType.TEMPERATURE,
|
type = MetricType.TEMPERATURE,
|
||||||
|
|||||||
@@ -71,6 +71,7 @@
|
|||||||
<string name="detail_max">最高</string>
|
<string name="detail_max">最高</string>
|
||||||
<string name="detail_min">最低</string>
|
<string name="detail_min">最低</string>
|
||||||
<string name="detail_trend">趋势</string>
|
<string name="detail_trend">趋势</string>
|
||||||
|
<string name="detail_empty">暂无数据</string>
|
||||||
<string name="detail_sleep_stages">睡眠分期</string>
|
<string name="detail_sleep_stages">睡眠分期</string>
|
||||||
<string name="detail_sleep_deep">深睡</string>
|
<string name="detail_sleep_deep">深睡</string>
|
||||||
<string name="detail_sleep_light">浅睡</string>
|
<string name="detail_sleep_light">浅睡</string>
|
||||||
|
|||||||
@@ -39,6 +39,22 @@ List of devices attached
|
|||||||
Preview 面板,**这一项与人工验收一样是未执行的** —— 不要把它写进「已执行」段落
|
Preview 面板,**这一项与人工验收一样是未执行的** —— 不要把它写进「已执行」段落
|
||||||
(旧稿把它列在上面这一节里,那等于声称验证过一件没做过的事)。
|
(旧稿把它列在上面这一节里,那等于声称验证过一件没做过的事)。
|
||||||
|
|
||||||
|
### 终审修复轮(`5fe0d4a`)各项改动的可验证性
|
||||||
|
|
||||||
|
终审后又有一轮修复,共 6 处改动。**不要把这 6 处一律当成「已验证」** —— 它们能验到什么程度
|
||||||
|
差别很大,逐项列明:
|
||||||
|
|
||||||
|
| 改动 | 实际验到了什么 |
|
||||||
|
|---|---|
|
||||||
|
| 详情页按指标取色(原先一律绿) | `when` 穷尽七个 `MetricType`,代码可确认;**配色是否好看/是否与首页一致,未经渲染验证** |
|
||||||
|
| 详情页空数据态(原先渲染全 0 柱状图 + 三行 `0.0`) | **无测试覆盖**,仅代码推理。且血压卡改为不可点后,该路径在当前 Mock 下实际不可达(`metricHistory` 恒返回 7 或 30 个点)—— 属防御性代码 |
|
||||||
|
| 血压卡不可点击(原先显示「未开启」却能点进一张有数据的图表) | `onClick` 改为可空并传 `null`,代码可确认;**点击涟漪是否真的消失,未经渲染验证** |
|
||||||
|
| 压力卡 `unit` 接线(4 条字符串此前无调用点) | 调用点已存在,代码可确认;**文字是否在卡片里溢出,未经渲染验证** |
|
||||||
|
| `RingHealthApp` NavHost 补 `consumeWindowInsets(innerPadding)` | **推理得出,未经设备验证** —— 见下方人工验收第 1 项 |
|
||||||
|
| README 注明 `installDebug` 需设备 | 文档,无需验证 |
|
||||||
|
|
||||||
|
结论不变:**这几处里凡带「视觉」二字的,本机都没验过。**
|
||||||
|
|
||||||
## 人工验收(未执行 —— 无设备)
|
## 人工验收(未执行 —— 无设备)
|
||||||
|
|
||||||
下表**全部未打勾**。这份清单已原样保留,交给有设备的人执行;不要在没有实测的情况下填入结果。
|
下表**全部未打勾**。这份清单已原样保留,交给有设备的人执行;不要在没有实测的情况下填入结果。
|
||||||
@@ -60,6 +76,14 @@ List of devices attached
|
|||||||
|
|
||||||
### 需在有设备时重点确认的项(原清单细目,逐条保留)
|
### 需在有设备时重点确认的项(原清单细目,逐条保留)
|
||||||
|
|
||||||
|
- **【第 1 优先】内边距是否正确(终审 fix 5,本表最可能出问题的一项)**:
|
||||||
|
`RingHealthApp` 的 NavHost 现为 `Modifier.padding(innerPadding).consumeWindowInsets(innerPadding)`。
|
||||||
|
原因是详情页/设备设置页**自带** `Scaffold` + `TopAppBar`,而外层的 `Modifier.padding`
|
||||||
|
**不消费** window insets,于是状态栏高度被上下叠加两次。补 `consumeWindowInsets` 是标准修法,
|
||||||
|
但**这一改动从未在设备上看过一眼**。请优先确认两件事:
|
||||||
|
① 详情页、设备设置页的返回箭头与标题**没有被状态栏遮住**;
|
||||||
|
② 顶部也**没有多出一条无意义的空白**。
|
||||||
|
若仍有多余留白,问题在 insets 的消费方式而不在这行代码本身
|
||||||
- 应用启动无崩溃,底部四个 Tab 显示为 健康 / 运动 / 设备 / 我的
|
- 应用启动无崩溃,底部四个 Tab 显示为 健康 / 运动 / 设备 / 我的
|
||||||
- 四个 Tab 可自由切换,切换后状态保留、无闪烁
|
- 四个 Tab 可自由切换,切换后状态保留、无闪烁
|
||||||
- **重按已高亮的父 Tab 的行为**:实现为 `popUpTo(start){saveState}` + `restoreState`,
|
- **重按已高亮的父 Tab 的行为**:实现为 `popUpTo(start){saveState}` + `restoreState`,
|
||||||
@@ -82,6 +106,35 @@ List of devices attached
|
|||||||
- 睡眠分期条与入睡/起床时间是演示值(固定比例 + 最后一个数据点往前 8 小时),无真实分期数据
|
- 睡眠分期条与入睡/起床时间是演示值(固定比例 + 最后一个数据点往前 8 小时),无真实分期数据
|
||||||
- 详情页统计摘要一律保留一位小数(步数因此显示为「平均 7512.3」这类精度过剩的形式)
|
- 详情页统计摘要一律保留一位小数(步数因此显示为「平均 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 交接提示」一节。
|
||||||
|
|
||||||
## 覆盖缺口声明
|
## 覆盖缺口声明
|
||||||
|
|
||||||
@@ -95,3 +148,7 @@ List of devices attached
|
|||||||
睡眠分期卡片是否只在睡眠指标下出现、次要文字灰度是否统一 —— 这些全部未经运行时检验。
|
睡眠分期卡片是否只在睡眠指标下出现、次要文字灰度是否统一 —— 这些全部未经运行时检验。
|
||||||
单元测试覆盖的是 `Formatters` 的纯函数、`MockDataProvider` 的数据形状,以及七个 ViewModel
|
单元测试覆盖的是 `Formatters` 的纯函数、`MockDataProvider` 的数据形状,以及七个 ViewModel
|
||||||
的状态流;**它们不经过 Compose 渲染管线,也不能发现布局、主题或导航层面的错误。**
|
的状态流;**它们不经过 Compose 渲染管线,也不能发现布局、主题或导航层面的错误。**
|
||||||
|
|
||||||
|
终审修复轮新改的几处**尤其如此**:按指标取色、血压卡不可点、空数据态、压力卡的单位接线、
|
||||||
|
以及 `consumeWindowInsets` —— 这五处**全部是界面行为**,而 21 项单测**一项都没有、也不可能**
|
||||||
|
碰到它们。它们的依据是代码阅读与 Compose 的既有语义,不是运行结果。
|
||||||
|
|||||||
Reference in New Issue
Block a user