From 5fe0d4a4204bb732f3be7395f2fdcacc89cb1f4d 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 13:11:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E7=BB=88=E5=AE=A1?= =?UTF-8?q?=E5=8F=91=E7=8E=B0=E7=9A=84=E8=AF=A6=E6=83=85=E9=A1=B5=E9=85=8D?= =?UTF-8?q?=E8=89=B2=E3=80=81=E7=A9=BA=E6=80=81=E4=B8=8E=E8=A1=80=E5=8E=8B?= =?UTF-8?q?=E5=8D=A1=E5=8F=AF=E7=82=B9=E5=87=BB=E7=AD=89=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 详情页柱状图改用指标语义色(新增 MetricType→色板映射),此前恒定绿色 - 详情页无数据时渲染空态文案,不再画全 0 柱状图与三行 0.0 - 首页「未开启」指标卡不再可点击,与详情页内容保持一致 - 压力卡单位位显示等级文案(如「34 正常」),接上原先无人调用的 stressLevelLabelRes - NavHost 补 consumeWindowInsets,避免详情页 TopAppBar 重复叠加状态栏内边距 - README 注明 installDebug 需要设备/模拟器 Co-Authored-By: Claude Code --- README.md | 5 +- .../core/designsystem/component/MetricTile.kt | 5 +- .../core/navigation/RingHealthApp.kt | 8 +++- .../feature/detail/MetricDetailScreen.kt | 48 ++++++++++++++++++- .../ringhealth/feature/home/HomeScreen.kt | 11 ++++- app/src/main/res/values/strings.xml | 1 + 6 files changed, 71 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 971739b..821fdc5 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,11 @@ ```bash ./gradlew assembleDebug # 产物在 app/build/outputs/apk/debug/ -./gradlew installDebug # 安装到已连接设备 +./gradlew installDebug # 安装到已连接设备(需接好设备或已启动模拟器) ./gradlew test # 单元测试 ``` +> `installDebug` 需要一台已连接的设备或正在运行的模拟器。M1 使用的机器两者都没有, +> 因此本机实际可执行的是 `./gradlew assembleDebug` 与 `./gradlew :app:testDebugUnitTest`。 + 要求:JDK 17、Android SDK(platform 36 / build-tools 36.1.0)。 diff --git a/app/src/main/java/com/placeholder/ringhealth/core/designsystem/component/MetricTile.kt b/app/src/main/java/com/placeholder/ringhealth/core/designsystem/component/MetricTile.kt index 75eb810..0f559db 100644 --- a/app/src/main/java/com/placeholder/ringhealth/core/designsystem/component/MetricTile.kt +++ b/app/src/main/java/com/placeholder/ringhealth/core/designsystem/component/MetricTile.kt @@ -30,7 +30,8 @@ import com.placeholder.ringhealth.core.designsystem.TextSecondary * @param value 主数值文本。 * @param unit 单位文本,如「步」「次/分」。 * @param modifier 外部修饰符。 - * @param onClick 点击回调,默认空实现(配合 HealthCard 的整卡可点击)。 + * @param onClick 点击回调;为 null 表示卡片不可点击 —— 不挂 clickable,因此既没有水波纹 + * 也不会触发导航,用于「未开启」这类没有数值的指标卡(有值的卡才可跳转详情)。 */ @Composable fun MetricTile( @@ -40,7 +41,7 @@ fun MetricTile( value: String, unit: String, modifier: Modifier = Modifier, - onClick: () -> Unit = {}, + onClick: (() -> Unit)? = null, ) { HealthCard(modifier = modifier, onClick = onClick) { Row(verticalAlignment = Alignment.CenterVertically) { diff --git a/app/src/main/java/com/placeholder/ringhealth/core/navigation/RingHealthApp.kt b/app/src/main/java/com/placeholder/ringhealth/core/navigation/RingHealthApp.kt index 6b7ae6f..32ee89c 100644 --- a/app/src/main/java/com/placeholder/ringhealth/core/navigation/RingHealthApp.kt +++ b/app/src/main/java/com/placeholder/ringhealth/core/navigation/RingHealthApp.kt @@ -1,5 +1,6 @@ package com.placeholder.ringhealth.core.navigation +import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.FitnessCenter @@ -90,7 +91,12 @@ fun RingHealthApp(navController: NavHostController = rememberNavController()) { NavHost( navController = navController, 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) { HomeScreen(onOpenDetail = { metric -> navController.navigate(Routes.detail(metric)) }) diff --git a/app/src/main/java/com/placeholder/ringhealth/feature/detail/MetricDetailScreen.kt b/app/src/main/java/com/placeholder/ringhealth/feature/detail/MetricDetailScreen.kt index a5bc952..0a98df2 100644 --- a/app/src/main/java/com/placeholder/ringhealth/feature/detail/MetricDetailScreen.kt +++ b/app/src/main/java/com/placeholder/ringhealth/feature/detail/MetricDetailScreen.kt @@ -37,7 +37,12 @@ import com.placeholder.ringhealth.R import com.placeholder.ringhealth.core.common.formatClock import com.placeholder.ringhealth.core.common.formatDecimal 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.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.TextSecondary import com.placeholder.ringhealth.core.designsystem.component.HealthCard @@ -75,6 +80,8 @@ fun MetricDetailScreen( /** * 无状态指标详情内容:范围切换 + 柱状趋势图 + 统计摘要(+ 睡眠分期)。 * + * 范围切换常驻;当前范围没有数据点时,只把「趋势图 + 统计摘要」换成一行空态文案。 + * * 标题取自 `state.metric` 而不是外面传进来的 `metric` —— 无状态层只有 `state` 一个数据来源, * 免得出现「标题写心率、数据画睡眠」这种两者不一致的可能。 */ @@ -118,13 +125,35 @@ private fun MetricDetailContent( contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp), ) { + // 范围切换常驻,空数据时反而更要留着:切到另一个范围是用户看到「暂无数据」后的 + // 第一反应,chips 也是判断「哪个范围是空的」的唯一线索。它属于页头,不属于数据区。 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 { HealthCard { SectionHeader(title = stringResource(R.string.detail_trend)) MetricBarChart( 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 } +/** + * 指标类型 → 语义色。 + * + * 与 `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 private fun RangeSelector( diff --git a/app/src/main/java/com/placeholder/ringhealth/feature/home/HomeScreen.kt b/app/src/main/java/com/placeholder/ringhealth/feature/home/HomeScreen.kt index 26bcb1c..06cb811 100644 --- a/app/src/main/java/com/placeholder/ringhealth/feature/home/HomeScreen.kt +++ b/app/src/main/java/com/placeholder/ringhealth/feature/home/HomeScreen.kt @@ -42,6 +42,7 @@ import com.placeholder.ringhealth.core.common.formatCalories import com.placeholder.ringhealth.core.common.formatDistance import com.placeholder.ringhealth.core.common.formatSleepDuration 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.MetricCyan import com.placeholder.ringhealth.core.designsystem.MetricGreen @@ -109,13 +110,16 @@ private fun HomeContent( item { StepsCard(summary = summary, onClick = { onOpenDetail(MetricType.STEP) }) } item { SectionHeader(title = stringResource(R.string.home_metrics)) } items(cards) { card -> + // 没有数值(未开启)的指标卡不可点击:传 null 后 MetricTile/HealthCard 不挂 clickable, + // 无跳转也无水波纹。否则首页显示「未开启」、点进去却是一张完整图表,两页自相矛盾。 + val openDetail = card.value?.let { { onOpenDetail(card.type) } } MetricTile( icon = card.icon, iconTint = card.tint, title = card.title, value = card.value ?: notEnabledText, unit = if (card.value == null) "" else card.unit, - onClick = { onOpenDetail(card.type) }, + onClick = openDetail, ) } } @@ -239,7 +243,10 @@ private fun buildMetricCards(summary: HealthSummary): List = lis tint = MetricPurple, title = stringResource(R.string.metric_stress), value = summary.stress?.toString(), - unit = "", + // 单位位改放压力等级文案(数值落在哪个区间就是放松/正常/中等/偏高), + // 于是卡片读作「34 正常」而不是一个孤零零的数字。无值时留空(上面的 value 分支 + // 会整卡退化成「未开启」,此时单位本来也会被清空)。 + unit = summary.stress?.let { stringResource(stressLevelLabelRes(it)) } ?: "", ), MetricCardSpec( type = MetricType.TEMPERATURE, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5520167..ae6e594 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -71,6 +71,7 @@ 最高 最低 趋势 + 暂无数据 睡眠分期 深睡 浅睡