Skip to content

Commit 1174f79

Browse files
committed
HealthStatsDialog: show per-weekday typical sleep values
Mirrors the typical-steps debug section added on fix/typical-steps, showing the four values the watch reads from each <weekday>_sleepData blob's typical tail: duration / deep duration / bedtime → wakeup. Weekdays below the 2-day minimum-history threshold show "--". WeekdaySleepTypicals visibility bumped from internal to public so the new HealthDebugStats field can expose it across modules. The data class is just four Ints and was always going to be reachable by the debug dialog by design.
1 parent e66ca9b commit 1174f79

6 files changed

Lines changed: 58 additions & 3 deletions

File tree

libpebble3/src/commonMain/kotlin/io/rebble/libpebblecommon/connection/FakeLibPebble.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ class FakeLibPebble : LibPebble {
381381
latestDataTimestamp = null,
382382
daysOfData = 0,
383383
weekdayTypicalSteps = emptyMap(),
384+
weekdayTypicalSleep = emptyMap(),
384385
)
385386
}
386387

libpebble3/src/commonMain/kotlin/io/rebble/libpebblecommon/health/Health.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import io.rebble.libpebblecommon.datalogging.HealthDataProcessor
1919
import io.rebble.libpebblecommon.di.LibPebbleCoroutineScope
2020
import io.rebble.libpebblecommon.services.DailySleep
2121
import io.rebble.libpebblecommon.services.calculateHealthAverages
22+
import io.rebble.libpebblecommon.services.computeAllWeekdayTypicalSleep
2223
import io.rebble.libpebblecommon.services.computeAllWeekdayTypicalSteps
2324
import io.rebble.libpebblecommon.services.decodeTypicalStepTotal
2425
import io.rebble.libpebblecommon.services.groupSleepSessions
@@ -98,6 +99,7 @@ class Health(
9899

99100
val weekdayTypicalSteps = computeAllWeekdayTypicalSteps(healthDao, today, timeZone)
100101
.mapValues { (_, payload) -> decodeTypicalStepTotal(payload) }
102+
val weekdayTypicalSleep = computeAllWeekdayTypicalSleep(healthDao, today, timeZone)
101103

102104
return HealthDebugStats(
103105
totalSteps30Days = averages.totalSteps,
@@ -109,6 +111,7 @@ class Health(
109111
latestDataTimestamp = latestTimestamp,
110112
daysOfData = daysOfData,
111113
weekdayTypicalSteps = weekdayTypicalSteps,
114+
weekdayTypicalSleep = weekdayTypicalSleep,
112115
)
113116
}
114117

libpebble3/src/commonMain/kotlin/io/rebble/libpebblecommon/health/HealthRecords.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ class RawOverlayRecord : StructMappable() {
8181
* [weekdayTypicalSteps] carries the per-weekday typical-step totals computed for the
8282
* `<weekday>_steps` BlobDB rows; absent keys are weekdays where the user has insufficient
8383
* same-weekday history (we don't write a row for them, so the watch shows no comparison).
84+
*
85+
* [weekdayTypicalSleep] carries the four typical sleep values per weekday written into the
86+
* tail of each `<weekday>_sleepData` BlobDB row; absent keys are weekdays below the same
87+
* minimum-history threshold.
8488
*/
8589
data class HealthDebugStats(
8690
val totalSteps30Days: Long,
@@ -92,4 +96,5 @@ data class HealthDebugStats(
9296
val latestDataTimestamp: Long?,
9397
val daysOfData: Int,
9498
val weekdayTypicalSteps: Map<kotlinx.datetime.DayOfWeek, Int> = emptyMap(),
99+
val weekdayTypicalSleep: Map<kotlinx.datetime.DayOfWeek, io.rebble.libpebblecommon.services.WeekdaySleepTypicals> = emptyMap(),
95100
)

libpebble3/src/commonMain/kotlin/io/rebble/libpebblecommon/services/HealthStatsSync.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ private val STEP_TYPICAL_KEYS = mapOf(
493493
DayOfWeek.SUNDAY to "sunday_steps",
494494
)
495495

496-
internal data class WeekdaySleepTypicals(
496+
data class WeekdaySleepTypicals(
497497
val sleepDurationSeconds: Int,
498498
val deepSleepDurationSeconds: Int,
499499
val fallAsleepSecondsOfDay: Int,

pebble/src/commonMain/kotlin/coredevices/pebble/ui/HealthStatsDialog.kt

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,37 @@ fun HealthStatsDialog(libPebble: LibPebble, onDismissRequest: () -> Unit) {
201201
)
202202
}
203203
}
204+
205+
Spacer(Modifier.height(4.dp))
206+
207+
Text(
208+
"Typical sleep by weekday",
209+
style = MaterialTheme.typography.bodyMedium,
210+
)
211+
for (wd in DayOfWeek.entries) {
212+
val typ = s.weekdayTypicalSleep[wd]
213+
Row(
214+
modifier = Modifier.fillMaxWidth(),
215+
horizontalArrangement = Arrangement.SpaceBetween
216+
) {
217+
Text(
218+
wd.name.lowercase().replaceFirstChar { it.uppercase() },
219+
style = MaterialTheme.typography.bodySmall,
220+
color = MaterialTheme.colorScheme.onSurfaceVariant,
221+
)
222+
Text(
223+
typ?.let {
224+
"${formatHm(it.sleepDurationSeconds)} / ${formatHm(it.deepSleepDurationSeconds)} ${formatClock(it.fallAsleepSecondsOfDay)}${formatClock(it.wakeupSecondsOfDay)}"
225+
} ?: "--",
226+
style = MaterialTheme.typography.bodySmall,
227+
color = if (typ != null) {
228+
MaterialTheme.colorScheme.onSurface
229+
} else {
230+
MaterialTheme.colorScheme.onSurfaceVariant
231+
},
232+
)
233+
}
234+
}
204235
}
205236
} else {
206237
Text(
@@ -215,4 +246,16 @@ fun HealthStatsDialog(libPebble: LibPebble, onDismissRequest: () -> Unit) {
215246
}
216247

217248
expect fun Double.format(digits: Int): String
218-
fun Float.format(digits: Int): String = toDouble().format(digits)
249+
fun Float.format(digits: Int): String = toDouble().format(digits)
250+
251+
private fun formatHm(totalSeconds: Int): String {
252+
val h = totalSeconds / 3600
253+
val m = (totalSeconds % 3600) / 60
254+
return "${h}h${m.toString().padStart(2, '0')}m"
255+
}
256+
257+
private fun formatClock(secondsOfDay: Int): String {
258+
val h = secondsOfDay / 3600
259+
val m = (secondsOfDay % 3600) / 60
260+
return "${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}"
261+
}

pebble/src/iosMain/kotlin/coredevices/pebble/actions/watch/HealthStats.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@ fun healthDebugStatsToJson(stats: HealthDebugStats): String {
1010
val latestTs = stats.latestDataTimestamp?.toString() ?: "null"
1111
val typicalSteps = stats.weekdayTypicalSteps.entries
1212
.joinToString(prefix = "{", postfix = "}") { (wd, total) -> "\"$wd\":$total" }
13-
return """{"totalSteps30Days":${stats.totalSteps30Days},"averageStepsPerDay":${stats.averageStepsPerDay},"totalSleepSeconds30Days":${stats.totalSleepSeconds30Days},"averageSleepSecondsPerDay":${stats.averageSleepSecondsPerDay},"todaySteps":${stats.todaySteps},"lastNightSleepHours":$lastNight,"latestDataTimestamp":$latestTs,"daysOfData":${stats.daysOfData},"weekdayTypicalSteps":$typicalSteps}"""
13+
val typicalSleep = stats.weekdayTypicalSleep.entries.joinToString(prefix = "{", postfix = "}") { (wd, v) ->
14+
"\"$wd\":{\"sleepDurationSeconds\":${v.sleepDurationSeconds},\"deepSleepDurationSeconds\":${v.deepSleepDurationSeconds},\"fallAsleepSecondsOfDay\":${v.fallAsleepSecondsOfDay},\"wakeupSecondsOfDay\":${v.wakeupSecondsOfDay}}"
15+
}
16+
return """{"totalSteps30Days":${stats.totalSteps30Days},"averageStepsPerDay":${stats.averageStepsPerDay},"totalSleepSeconds30Days":${stats.totalSleepSeconds30Days},"averageSleepSecondsPerDay":${stats.averageSleepSecondsPerDay},"todaySteps":${stats.todaySteps},"lastNightSleepHours":$lastNight,"latestDataTimestamp":$latestTs,"daysOfData":${stats.daysOfData},"weekdayTypicalSteps":$typicalSteps,"weekdayTypicalSleep":$typicalSleep}"""
1417
}

0 commit comments

Comments
 (0)