feat: 睡眠详情页支持按分期染色(演示仅昨晚带分期序列)
- SleepSession 增加可选 stages: List<SleepSegment>?;为 null 走原行为 - SleepSegment(stage, startOffsetMinute, endOffsetMinute) —— 相对 sleepStart 的分钟偏移 - MockDataProvider.demoLastNight() 填入真实分期序列,深睡/浅睡/清醒各期时长与累计值严格对齐 - SleepWindowChart 新增 StageTints(deep, light, awake, track);stages 非空时画轨道 + 子矩形 - AWAKE 跳出 MetricBlue 色族用暖粉 Color(0xFFE8B4A8),中途醒显眼;track 用极浅蓝作 rail - 头图分期条 / 图例自动跟着 AWAKE_TINT 走,两处口径一致 - 仅昨晚一条带 stages,其他 6 晚单色原样保留 —— 这是为了演示视觉效果,不是生产数据形态;M2 接入真机后逐步补全其他夜晚
This commit is contained in:
+74
-5
@@ -26,8 +26,11 @@ import androidx.compose.ui.unit.dp
|
|||||||
import com.placeholder.ringhealth.core.common.formatSleepAnchorOffset
|
import com.placeholder.ringhealth.core.common.formatSleepAnchorOffset
|
||||||
import com.placeholder.ringhealth.core.common.minutesSinceSleepAnchor
|
import com.placeholder.ringhealth.core.common.minutesSinceSleepAnchor
|
||||||
import com.placeholder.ringhealth.core.designsystem.DividerColor
|
import com.placeholder.ringhealth.core.designsystem.DividerColor
|
||||||
|
import com.placeholder.ringhealth.core.designsystem.MetricBlue
|
||||||
import com.placeholder.ringhealth.core.designsystem.TextSecondary
|
import com.placeholder.ringhealth.core.designsystem.TextSecondary
|
||||||
import com.placeholder.ringhealth.domain.model.SleepSession
|
import com.placeholder.ringhealth.domain.model.SleepSession
|
||||||
|
import com.placeholder.ringhealth.domain.model.SleepSegment
|
||||||
|
import com.placeholder.ringhealth.domain.model.SleepStage
|
||||||
|
|
||||||
/** 每行日期标签的宽度,够放下「周三」两个字。 */
|
/** 每行日期标签的宽度,够放下「周三」两个字。 */
|
||||||
private val ROW_LABEL_WIDTH = 40.dp
|
private val ROW_LABEL_WIDTH = 40.dp
|
||||||
@@ -41,6 +44,39 @@ private val AXIS_LABEL_GAP = 6.dp
|
|||||||
/** 竖向网格线的粗细。比柱图的基线还细一档:它是背景参照,不该和时段条抢注意力。 */
|
/** 竖向网格线的粗细。比柱图的基线还细一档:它是背景参照,不该和时段条抢注意力。 */
|
||||||
private val GRID_WIDTH = 1.dp
|
private val GRID_WIDTH = 1.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时段图圆角条的默认「底色」(分期模式下的轨道)。极浅蓝,与 MetricBlue 同色系,
|
||||||
|
* 让深睡/浅睡子矩形自然浮起来;AWAKE 用另一族暖色,更显眼。给 [StageTints.track] 用作兜底值。
|
||||||
|
*/
|
||||||
|
internal val DefaultStageTrackTint: Color = MetricBlue.copy(alpha = 0.10f)
|
||||||
|
|
||||||
|
/** 一分钟对应的毫秒数。本地常量,不引用 `MockDataProvider` 那份以免引入循环依赖。 */
|
||||||
|
private const val MILLIS_PER_MINUTE = 60_000L
|
||||||
|
|
||||||
|
/** 分期 → 着色。单色模式下没有 `stageTints`,仍用 [color];分期模式按 stage 取色。 */
|
||||||
|
private fun colorForStage(stage: SleepStage, tints: StageTints?, fallback: Color): Color = when (stage) {
|
||||||
|
SleepStage.DEEP -> tints?.deep ?: fallback
|
||||||
|
SleepStage.LIGHT -> tints?.light ?: fallback
|
||||||
|
SleepStage.AWAKE -> tints?.awake ?: fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时段图内单条睡眠条的分期配色。**为 null 表示该图不画分期**,所有夜晚都按单色画。
|
||||||
|
*
|
||||||
|
* 三色通常取自同一色系的两个深浅(DEEP / LIGHT)+ 一个跳出该色系的 AWAKE 暖色 ——
|
||||||
|
* 「中途醒」是用户最该被告知的信息之一,让它和浅睡同色系会让它和「睡得浅」混淆;
|
||||||
|
* 暖色与睡眠的冷色调形成对比,眼睛会先抓到这里。颜色同时驱动头图分期条与时段图。
|
||||||
|
*
|
||||||
|
* `track` 是时段图内圆角条的底色(rail),子矩形落在这条底色之上 —— 与 AWAKE 区分开来。
|
||||||
|
* 不传则用 [DefaultStageTrackTint]。
|
||||||
|
*/
|
||||||
|
data class StageTints(
|
||||||
|
val deep: Color,
|
||||||
|
val light: Color,
|
||||||
|
val awake: Color,
|
||||||
|
val track: Color = DefaultStageTrackTint,
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 睡眠时段图:**每晚一行**,一根圆角条从入睡时刻横到醒来时刻,横轴是钟点。
|
* 睡眠时段图:**每晚一行**,一根圆角条从入睡时刻横到醒来时刻,横轴是钟点。
|
||||||
*
|
*
|
||||||
@@ -51,14 +87,16 @@ private val GRID_WIDTH = 1.dp
|
|||||||
* 横轴以晚上 20:00 为锚点跨过零点:一晚从 23:43 到 07:37,用「当天第几分钟」表示会得到
|
* 横轴以晚上 20:00 为锚点跨过零点:一晚从 23:43 到 07:37,用「当天第几分钟」表示会得到
|
||||||
* 1423 → 457 这样一对反过来的数,画出来入睡点跑到起床点右边。换算见 `minutesSinceSleepAnchor`。
|
* 1423 → 457 这样一对反过来的数,画出来入睡点跑到起床点右边。换算见 `minutesSinceSleepAnchor`。
|
||||||
*
|
*
|
||||||
* 只用一个颜色,**不画睡眠分期**:手头只有「深睡共 96 分钟」这样的累计时长,
|
* **分期染色**:[SleepSession.stages] 非空的那几晚,条会被拆成多个染色子矩形;
|
||||||
* 没有「第 47 分钟进入深睡」这种分期序列。按累计时长的比例铺成色块,
|
* 传 [stageTints] 启用这一支。不传则所有夜晚走单色原行为 —— 多数 Mock 数据没有分期序列,
|
||||||
* 画的是一段并不存在的分期位置 —— 那比不画更容易让人当真。
|
* 这是默认状态;有这层数据的夜晚再单独着色。M2 接入真机后会逐步补全序列,
|
||||||
|
* 把每一晚都变成可分期着色的版本。
|
||||||
*
|
*
|
||||||
* @param sessions 睡眠时段,按时间正序。
|
* @param sessions 睡眠时段,按时间正序。
|
||||||
* @param color 时段条颜色。
|
* @param color 单色模式下的时段条颜色;分期模式下充当未指定分期的兜底色。
|
||||||
* @param rowLabel 每行左侧的标签(如「周三」)。由调用方从 [SleepSession.wakeDate] 取,
|
* @param rowLabel 每行左侧的标签(如「周三」)。由调用方从 [SleepSession.wakeDate] 取,
|
||||||
* 组件本身不做资源查找;标成 `@Composable` 是因为调用方多半要 `stringResource`。
|
* 组件本身不做资源查找;标成 `@Composable` 是因为调用方多半要 `stringResource`。
|
||||||
|
* @param stageTints 为非 null 时启用分期染色;为 null 时全部夜晚走单色。
|
||||||
* @param modifier 外部修饰符。
|
* @param modifier 外部修饰符。
|
||||||
* @param rowHeight 时段条的高度。
|
* @param rowHeight 时段条的高度。
|
||||||
* @param rowGap 行距。
|
* @param rowGap 行距。
|
||||||
@@ -68,6 +106,7 @@ fun SleepWindowChart(
|
|||||||
sessions: List<SleepSession>,
|
sessions: List<SleepSession>,
|
||||||
color: Color,
|
color: Color,
|
||||||
rowLabel: @Composable (SleepSession) -> String,
|
rowLabel: @Composable (SleepSession) -> String,
|
||||||
|
stageTints: StageTints? = null,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
rowHeight: Dp = 16.dp,
|
rowHeight: Dp = 16.dp,
|
||||||
rowGap: Dp = 10.dp,
|
rowGap: Dp = 10.dp,
|
||||||
@@ -131,12 +170,42 @@ fun SleepWindowChart(
|
|||||||
val start = xOf(minutesSinceSleepAnchor(session.sleepStart)).coerceIn(0f, size.width)
|
val start = xOf(minutesSinceSleepAnchor(session.sleepStart)).coerceIn(0f, size.width)
|
||||||
val end = xOf(minutesSinceSleepAnchor(session.sleepEnd)).coerceIn(0f, size.width)
|
val end = xOf(minutesSinceSleepAnchor(session.sleepEnd)).coerceIn(0f, size.width)
|
||||||
if (end <= start) return@forEachIndexed
|
if (end <= start) return@forEachIndexed
|
||||||
|
|
||||||
|
val stages = session.stages
|
||||||
|
if (stages.isNullOrEmpty() || stageTints == null) {
|
||||||
|
// 单色原行为:所有夜晚(默认)或没拿到分期数据的夜晚。
|
||||||
|
drawRoundRect(
|
||||||
|
color = color,
|
||||||
|
topLeft = Offset(x = start, y = top),
|
||||||
|
size = Size(width = end - start, height = rowHeightPx),
|
||||||
|
cornerRadius = CornerRadius(rowHeightPx / 2f, rowHeightPx / 2f),
|
||||||
|
)
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分期模式:先画整条 track 浅色圆角条作底色(保住两端圆角 + 给未覆盖区域兜底),
|
||||||
|
// 再把每个分期子矩形用方角 `drawRect` 叠在上面 —— 子矩形落在圆角范围内,
|
||||||
|
// 视觉上左右两端仍是 pill 形。AWAKE 子矩形此时是独立暖色,从 track 上明显浮出,
|
||||||
|
// 不再是上一版的「看起来像渲染瑕疵的窄缝」。
|
||||||
|
val baseTint = stageTints.track
|
||||||
drawRoundRect(
|
drawRoundRect(
|
||||||
color = color,
|
color = baseTint,
|
||||||
topLeft = Offset(x = start, y = top),
|
topLeft = Offset(x = start, y = top),
|
||||||
size = Size(width = end - start, height = rowHeightPx),
|
size = Size(width = end - start, height = rowHeightPx),
|
||||||
cornerRadius = CornerRadius(rowHeightPx / 2f, rowHeightPx / 2f),
|
cornerRadius = CornerRadius(rowHeightPx / 2f, rowHeightPx / 2f),
|
||||||
)
|
)
|
||||||
|
stages.forEach { segment ->
|
||||||
|
val segStartTs = session.sleepStart + segment.startOffsetMinute * MILLIS_PER_MINUTE
|
||||||
|
val segEndTs = session.sleepStart + segment.endOffsetMinute * MILLIS_PER_MINUTE
|
||||||
|
val segStart = xOf(minutesSinceSleepAnchor(segStartTs)).coerceIn(0f, size.width)
|
||||||
|
val segEnd = xOf(minutesSinceSleepAnchor(segEndTs)).coerceIn(0f, size.width)
|
||||||
|
if (segEnd <= segStart) return@forEach
|
||||||
|
drawRect(
|
||||||
|
color = colorForStage(segment.stage, stageTints, color),
|
||||||
|
topLeft = Offset(x = segStart, y = top),
|
||||||
|
size = Size(width = segEnd - segStart, height = rowHeightPx),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刻度文字画在画布内,而不是另起一行等宽 Text —— 等宽的文本框中心落在
|
// 刻度文字画在画布内,而不是另起一行等宽 Text —— 等宽的文本框中心落在
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import com.placeholder.ringhealth.domain.model.HealthSummary
|
|||||||
import com.placeholder.ringhealth.domain.model.MetricPoint
|
import com.placeholder.ringhealth.domain.model.MetricPoint
|
||||||
import com.placeholder.ringhealth.domain.model.MetricType
|
import com.placeholder.ringhealth.domain.model.MetricType
|
||||||
import com.placeholder.ringhealth.domain.model.RingDeviceState
|
import com.placeholder.ringhealth.domain.model.RingDeviceState
|
||||||
|
import com.placeholder.ringhealth.domain.model.SleepSegment
|
||||||
import com.placeholder.ringhealth.domain.model.SleepSession
|
import com.placeholder.ringhealth.domain.model.SleepSession
|
||||||
|
import com.placeholder.ringhealth.domain.model.SleepStage
|
||||||
import com.placeholder.ringhealth.domain.model.WorkoutRecord
|
import com.placeholder.ringhealth.domain.model.WorkoutRecord
|
||||||
import com.placeholder.ringhealth.domain.model.valueOf
|
import com.placeholder.ringhealth.domain.model.valueOf
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
@@ -125,6 +127,11 @@ class MockDataProvider(
|
|||||||
* 它是一个**写死的演示值**,和步数 8426、心率 72 同一性质;随机生成的那几晚负责
|
* 它是一个**写死的演示值**,和步数 8426、心率 72 同一性质;随机生成的那几晚负责
|
||||||
* 「作息有波动」的观感,昨晚这一条负责让第一屏的数字好看且固定。
|
* 「作息有波动」的观感,昨晚这一条负责让第一屏的数字好看且固定。
|
||||||
* 时长(452 分钟)就是 `demoLastNight().sleepMinutes`,见 [todaySummary]。
|
* 时长(452 分钟)就是 `demoLastNight().sleepMinutes`,见 [todaySummary]。
|
||||||
|
*
|
||||||
|
* 此外**只有这一晚带 [SleepSession.stages]** —— 24 小时采样里 6 晚没有这层数据,
|
||||||
|
* 时段图按单色画;昨晚给了它,时段图把这一行拆成多个染色子矩形,让用户能直观看到
|
||||||
|
* 「夜里深睡 / 浅睡 / 中途醒是怎么交替的」。各段时长加起来正好等于上面的累计三件套,
|
||||||
|
* 否则头图分期条与时段图条内分期的颜色块会对不上。
|
||||||
*/
|
*/
|
||||||
private fun demoLastNight(): SleepSession {
|
private fun demoLastNight(): SleepSession {
|
||||||
val wake = localTimeAt(daysAgo = 0, hour = DEMO_WAKE_HOUR, minute = DEMO_WAKE_MINUTE)
|
val wake = localTimeAt(daysAgo = 0, hour = DEMO_WAKE_HOUR, minute = DEMO_WAKE_MINUTE)
|
||||||
@@ -135,6 +142,7 @@ class MockDataProvider(
|
|||||||
deepMinutes = DEMO_DEEP_MINUTES,
|
deepMinutes = DEMO_DEEP_MINUTES,
|
||||||
lightMinutes = DEMO_LIGHT_MINUTES,
|
lightMinutes = DEMO_LIGHT_MINUTES,
|
||||||
awakeMinutes = DEMO_AWAKE_MINUTES,
|
awakeMinutes = DEMO_AWAKE_MINUTES,
|
||||||
|
stages = DEMO_LAST_NIGHT_STAGES,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,5 +281,41 @@ class MockDataProvider(
|
|||||||
|
|
||||||
/** 昨晚卧床时长 = 睡着 452 + 清醒 22,由 07:37 倒推得入睡时刻 23:43。 */
|
/** 昨晚卧床时长 = 睡着 452 + 清醒 22,由 07:37 倒推得入睡时刻 23:43。 */
|
||||||
private const val DEMO_IN_BED_MINUTES = DEMO_DEEP_MINUTES + DEMO_LIGHT_MINUTES + DEMO_AWAKE_MINUTES
|
private const val DEMO_IN_BED_MINUTES = DEMO_DEEP_MINUTES + DEMO_LIGHT_MINUTES + DEMO_AWAKE_MINUTES
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 昨晚的分期序列(相对 `sleepStart` 的分钟偏移)。
|
||||||
|
*
|
||||||
|
* 三段时间加起来严格等于 [DEMO_DEEP_MINUTES] / [DEMO_LIGHT_MINUTES] / [DEMO_AWAKE_MINUTES]:
|
||||||
|
* 深睡 48 + 35 + 13 = 96
|
||||||
|
* 浅睡 30 + 60 + 60 + 44 + 57 + 105 = 356
|
||||||
|
* 清醒 5 + 5 + 7 + 5 = 22
|
||||||
|
* 否则头图分期条与时段图条内分期的色块会对不上。形态上模拟「入睡后两轮深睡 + 一段长浅睡
|
||||||
|
* (含 REM)+ 中途两段短暂清醒」的常见曲线。
|
||||||
|
*/
|
||||||
|
private val DEMO_LAST_NIGHT_STAGES: List<SleepSegment> = listOf(
|
||||||
|
// 入睡后短暂清醒 → 浅睡入睡
|
||||||
|
SleepSegment(SleepStage.AWAKE, 0, 5),
|
||||||
|
SleepSegment(SleepStage.LIGHT, 5, 35),
|
||||||
|
// 第一轮深睡
|
||||||
|
SleepSegment(SleepStage.DEEP, 35, 83),
|
||||||
|
// 第一轮长浅睡
|
||||||
|
SleepSegment(SleepStage.LIGHT, 83, 143),
|
||||||
|
// 第二轮深睡(比第一轮短)
|
||||||
|
SleepSegment(SleepStage.DEEP, 143, 178),
|
||||||
|
// 第二轮浅睡
|
||||||
|
SleepSegment(SleepStage.LIGHT, 178, 238),
|
||||||
|
// 第一次中途醒(5 分钟)
|
||||||
|
SleepSegment(SleepStage.AWAKE, 238, 243),
|
||||||
|
SleepSegment(SleepStage.LIGHT, 243, 287),
|
||||||
|
// 第三轮深睡(很短)
|
||||||
|
SleepSegment(SleepStage.DEEP, 287, 300),
|
||||||
|
SleepSegment(SleepStage.LIGHT, 300, 357),
|
||||||
|
// 第二次中途醒(7 分钟)
|
||||||
|
SleepSegment(SleepStage.AWAKE, 357, 364),
|
||||||
|
// 临近醒来的长浅睡(含 REM 密集期)
|
||||||
|
SleepSegment(SleepStage.LIGHT, 364, 469),
|
||||||
|
// 最终清醒
|
||||||
|
SleepSegment(SleepStage.AWAKE, 469, 474),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,23 @@ fun HealthSummary.valueOf(metric: MetricType): Double? = when (metric) {
|
|||||||
/** 一条历史数据点。 */
|
/** 一条历史数据点。 */
|
||||||
data class MetricPoint(val timestamp: Long, val value: Double)
|
data class MetricPoint(val timestamp: Long, val value: Double)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一段睡眠分期:起点到终点(相对于 [SleepSession.sleepStart] 的分钟数)+ 哪一期。
|
||||||
|
*
|
||||||
|
* 时段图渲染时按这一份序列把一根条拆成多个染色子矩形。没有这层序列时只能画
|
||||||
|
* 「累计深睡 96 分钟」这样的比例块 —— 那是一段并不存在的分期位置,比不画更容易让人当真
|
||||||
|
* (见 `SleepWindowChart` 的 KDoc)。有这份序列才能画「23:43–23:48 清醒、23:48–00:18 浅睡」这样的真顺序。
|
||||||
|
*
|
||||||
|
* @param stage 这一段是哪个分期。
|
||||||
|
* @param startOffsetMinute 起点相对 [SleepSession.sleepStart] 的分钟数。
|
||||||
|
* @param endOffsetMinute 终点相对 [SleepSession.sleepStart] 的分钟数。必须大于 [startOffsetMinute]。
|
||||||
|
*/
|
||||||
|
data class SleepSegment(
|
||||||
|
val stage: SleepStage,
|
||||||
|
val startOffsetMinute: Int,
|
||||||
|
val endOffsetMinute: Int,
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一晚睡眠。
|
* 一晚睡眠。
|
||||||
*
|
*
|
||||||
@@ -90,6 +107,8 @@ data class MetricPoint(val timestamp: Long, val value: Double)
|
|||||||
* @param deepMinutes 深睡时长。
|
* @param deepMinutes 深睡时长。
|
||||||
* @param lightMinutes 浅睡时长。
|
* @param lightMinutes 浅睡时长。
|
||||||
* @param awakeMinutes 夜里清醒的累计时长(中途醒来)。
|
* @param awakeMinutes 夜里清醒的累计时长(中途醒来)。
|
||||||
|
* @param stages 分期序列(相对 [sleepStart] 的分钟偏移)。**为 null 表示该晚不画分期染色**,
|
||||||
|
* 时段图按单色画这根条 —— 大多数 Mock 数据没有这层序列,M2 接入真机后会逐步补上。
|
||||||
*/
|
*/
|
||||||
data class SleepSession(
|
data class SleepSession(
|
||||||
val wakeDate: Long,
|
val wakeDate: Long,
|
||||||
@@ -98,6 +117,7 @@ data class SleepSession(
|
|||||||
val deepMinutes: Int,
|
val deepMinutes: Int,
|
||||||
val lightMinutes: Int,
|
val lightMinutes: Int,
|
||||||
val awakeMinutes: Int,
|
val awakeMinutes: Int,
|
||||||
|
val stages: List<SleepSegment>? = null,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* 实际睡着的时长 = 深睡 + 浅睡,**不含夜里清醒**。
|
* 实际睡着的时长 = 深睡 + 浅睡,**不含夜里清醒**。
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import com.placeholder.ringhealth.core.designsystem.TextSecondary
|
|||||||
import com.placeholder.ringhealth.core.designsystem.component.HealthCard
|
import com.placeholder.ringhealth.core.designsystem.component.HealthCard
|
||||||
import com.placeholder.ringhealth.core.designsystem.component.SectionHeader
|
import com.placeholder.ringhealth.core.designsystem.component.SectionHeader
|
||||||
import com.placeholder.ringhealth.core.designsystem.component.SleepWindowChart
|
import com.placeholder.ringhealth.core.designsystem.component.SleepWindowChart
|
||||||
|
import com.placeholder.ringhealth.core.designsystem.component.StageTints
|
||||||
import com.placeholder.ringhealth.core.designsystem.component.StatBar
|
import com.placeholder.ringhealth.core.designsystem.component.StatBar
|
||||||
import com.placeholder.ringhealth.core.designsystem.component.StatSegment
|
import com.placeholder.ringhealth.core.designsystem.component.StatSegment
|
||||||
import com.placeholder.ringhealth.domain.model.DateRange
|
import com.placeholder.ringhealth.domain.model.DateRange
|
||||||
@@ -61,10 +62,15 @@ internal fun LazyListScope.sleepDetail(state: MetricDetailUiState) {
|
|||||||
/** 演示用的目标睡眠时长:8 小时,与设计文档 §7 的口径一致。 */
|
/** 演示用的目标睡眠时长:8 小时,与设计文档 §7 的口径一致。 */
|
||||||
private const val SLEEP_TARGET_MINUTES = 480
|
private const val SLEEP_TARGET_MINUTES = 480
|
||||||
|
|
||||||
/** 分期配色:同一色系的三个深浅,深浅即「睡得沉不沉」,不必再引入第二种颜色。 */
|
/**
|
||||||
|
* 分期配色:DEEP/LIGHT 走 MetricBlue 的两档深浅(深浅即「睡得沉不沉」),
|
||||||
|
* **AWAKE 故意跳出蓝色族**,用一枚暖灰偏粉 —— 睡眠是蓝色调,「醒了」要明显是另一件事。
|
||||||
|
* 「中途醒」是用户最该被告知的信息之一,不能让它和「睡着了但浅」混淆:
|
||||||
|
* 在时段图里它会落成一段独立暖色子矩形,在头图分期条里它会落成独立暖色段。
|
||||||
|
*/
|
||||||
private val DEEP_TINT = MetricBlue
|
private val DEEP_TINT = MetricBlue
|
||||||
private val LIGHT_TINT = MetricBlue.copy(alpha = 0.45f)
|
private val LIGHT_TINT = MetricBlue.copy(alpha = 0.45f)
|
||||||
private val AWAKE_TINT = MetricBlue.copy(alpha = 0.2f)
|
private val AWAKE_TINT = Color(0xFFE8B4A8)
|
||||||
|
|
||||||
/** 头图:昨晚时长 + 入睡/起床时刻 + 分期条与分期时长。 */
|
/** 头图:昨晚时长 + 入睡/起床时刻 + 分期条与分期时长。 */
|
||||||
@Composable
|
@Composable
|
||||||
@@ -199,6 +205,14 @@ private fun SleepWindowCard(state: MetricDetailUiState, modifier: Modifier = Mod
|
|||||||
SleepWindowChart(
|
SleepWindowChart(
|
||||||
sessions = state.sleepSessions,
|
sessions = state.sleepSessions,
|
||||||
color = MetricBlue,
|
color = MetricBlue,
|
||||||
|
// 启用分期染色:少数带 [SleepSession.stages] 的夜晚会把条拆成多色子矩形,
|
||||||
|
// 与头图分期条共用同一套色系(深浅 = 睡得沉不沉)。其他夜晚无 `stages` 字段,
|
||||||
|
// 组件会自己退回单色模式 —— 调用方不需要关心「哪些晚有分期」。
|
||||||
|
stageTints = StageTints(
|
||||||
|
deep = DEEP_TINT,
|
||||||
|
light = LIGHT_TINT,
|
||||||
|
awake = AWAKE_TINT,
|
||||||
|
),
|
||||||
rowLabel = { session ->
|
rowLabel = { session ->
|
||||||
if (state.range == DateRange.WEEK) {
|
if (state.range == DateRange.WEEK) {
|
||||||
stringResource(weekdayResFor(session.wakeDate))
|
stringResource(weekdayResFor(session.wakeDate))
|
||||||
|
|||||||
Reference in New Issue
Block a user