Skip to content

Android: Sleep screen — in-app bed/wake time editing with live metric recompute#260

Closed
ujix wants to merge 4 commits into
NoopApp:mainfrom
ujix:android/sleep-time-editing
Closed

Android: Sleep screen — in-app bed/wake time editing with live metric recompute#260
ujix wants to merge 4 commits into
NoopApp:mainfrom
ujix:android/sleep-time-editing

Conversation

@ujix

@ujix ujix commented Jun 13, 2026

Copy link
Copy Markdown

What this PR does

Executive summary: Adds in-app editing of bed and wake times from the Sleep nav header — an AlertDialog selects which time to edit, a TimePickerDialog sets the new value, and all metrics recompute instantly against the edited window with no DB round-trip wait.

Details:

Sleep screen — time editing

  • Added: Tapping the edit icon in NightNavHeader opens a Strand-styled AlertDialog with two rows (Bedtime / Wake-up), each showing the current time and an accent edit icon; choosing either row opens its own TimePickerDialog
  • Fixed: After confirming a change the header clock re-renders immediately via an optimistic in-memory sleeps list update; no DB round-trip wait required
  • Fixed: Stage Breakdown subtitle derives "X in bed" from session.endTs − session.startTs so it reflects the edited window instantly rather than the stale stored value
  • Fixed: All metrics (Rest %, Hours vs Needed, Sleep Debt, trend chart) recompute against the edited window: buildSleepModel now builds a metricsWindow that substitutes sessionDurationMin for the selected night's totalSleepMin, while keeping typicalTotalMin from the unmodified historical window

Data layer

  • Added: WhoopDao.deleteSleepSession(deviceId, startTs)@Query DELETE used as part of the delete-then-upsert update pattern
  • Added: WhoopRepository.updateSleepSessionTimes(session, newStartTs, newEndTs) — deletes the old row and upserts the corrected copy
  • Added: AppViewModel.updateSleepSessionTimes — thin suspend wrapper with runCatching so a DB error never surfaces as an unhandled exception

Validation: :app:assembleFullDebug, testFullDebugUnitTest, and testDemoDebugUnitTest all green.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • CI / tooling

How it was tested

Android 16. Real Samsung device. WHOOP 4.0. Tested: editing bed time independently, editing wake time independently, both in the same session, header clock updates immediately, all metric cards reflect edited duration, navigation position preserved after edit, data persists across app restart.

Checklist

  • Swift package tests pass for any package I touched (swift test in Packages/<name>)
  • Android unit tests pass if I touched android/ (./gradlew testFullDebugUnitTest)
  • No new build warnings introduced
  • UI changes use only StrandDesign tokens — no hardcoded colors, fonts, or spacing
  • No hardcoded hex frame bytes; protocol facts live in the schema / decoders
  • Follows the conventions in docs/CONTRIBUTING.md
  • I did not commit generated output (Strand.xcodeproj/) or any secrets/keystores

Related issues

…ckerDialog

Redesigns NightNavHeader to match DayNavBar: left/right chevrons flanking
an accent-tinted center block showing the night label and date. The time
range moves to a separate row below. Tapping the block opens a
DatePickerDialog to jump to any recorded night by calendar date.

Also fixes nightOffset reset: moves it into LaunchedEffect(days) so it only
resets on a real sync/import, not on every optimistic sleeps update.
@NoopApp

NoopApp commented Jun 13, 2026

Copy link
Copy Markdown
Owner

This is genuinely impressive work — and it's the one PR in this set that's a product-direction call, not a code-review call, so I want to flag it honestly rather than quietly fold it in.

In-app editing of recorded bed/wake times touches a core NOOP promise: your data is yours, on-device, and correctable. Letting people fix a mis-detected night fits that ethos well. But it also means the app now lets you rewrite recorded sleep, and it adds a DELETE path to the sleep store — so it deserves a deliberate yes, plus a plan to bring the same capability to iOS/macOS (which have no sleep editing today) so the platforms don't diverge on something this central.

The implementation itself is solid: delete-then-upsert is safe and backward-compatible, the optimistic in-memory update avoids UI lag with the DB write wrapped in runCatching, and buildSleepModel correctly substitutes the edited duration so the metrics recompute consistently.

So: holding this open as the canonical version of your Sleep-screen series. If we go ahead, it supersedes #258/#259 (same analytics cards, without the editing). I'll come back with a decision. Two small notes for whenever it lands: the analytics chart uses a hardcoded android.graphics.Color.argb(...) gray — we'd swap that for a Palette token to keep the no-hardcoded-color rule — and the chart elements could use contentDescription labels. Thank you for this.

… card

HoursVsNeededCard: score %, trend arrow, gradient progress bar, stacked
component bar (Healthy / Strain / Debt), slept/needed/debt footer.

SleepConsistencyCard: Canvas vertical bar chart (bed-time top, wake-time
bottom), dashed typical overlay lines, Y-axis time labels, X-axis day
labels. Score is count-based (nights where both bed and wake are within 45
min of the user's typical); the previous SD formula always returned 0 %.
@ujix ujix force-pushed the android/sleep-time-editing branch 3 times, most recently from 4a7c4de to f62c8cb Compare June 13, 2026 15:16
ujix added 2 commits June 13, 2026 18:18
Each SparkTile in MetricGrid now accepts an onClick lambda; tapping any
tile sets detailMetricKey and slides up a ModalBottomSheet scoped to
SleepScreen. SparkTile gains an onClick param — when set, the card
modifier gets .clickable so the whole tile is the touch target.
AppRoot adds SleepMetricDetail as a nav destination (kept for deep-link
compatibility). MainActivity adds KEY_LAST_JOURNAL_PROMPT to NoopPrefs.
Tapping the edit icon in NightNavHeader opens a Strand-styled AlertDialog
showing current bed and wake times; choosing either opens a TimePickerDialog.
Optimistic in-memory sleeps update re-renders the header immediately; the
DB write goes via WhoopRepository.updateSleepSessionTimes (delete + re-insert).
Stage breakdown derives inBedMin from session.endTs - session.startTs so it
reflects edits instantly. buildSleepModel adds metricsWindow: the selected
night is substituted with sessionDurationMin so all per-tile metrics recompute
against the edited window without skewing typicalTotalMin. WhoopDao gains
deleteSleepSession; WhoopRepository and AppViewModel wire up the new method.
@ujix ujix force-pushed the android/sleep-time-editing branch from f62c8cb to f18076f Compare June 13, 2026 15:19
@ujix

ujix commented Jun 13, 2026

Copy link
Copy Markdown
Author

Both issues addressed:

  • Hardcoded color: Palette.textTertiary.toArgb() fix propagates from Android: Sleep screen — Hours vs Needed and Sleep Consistency analytics cards #258 through the rebased stack — no raw argb() calls remain.
  • Chart contentDescription labels: all three chart elements now carry accessibility labels via Modifier.semantics { contentDescription = "..." }:
    • Hours vs Needed progress bar: "Hours vs Needed progress chart"
    • 14-day sleep hours trend LineChart: "Sleep hours trend chart"
    • Sleep Consistency nightly bar chart (Canvas): "Sleep Consistency nightly chart"

On the product-direction point — fully understood, and appreciate the honest framing. The delete-then-upsert data path and the iOS/macOS parity question are real considerations worth deliberating. Happy to wait on that call.

Branch updated and force-pushed.

@NoopApp

NoopApp commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Thanks — Palette.textTertiary.toArgb() across the rebased stack plus the chart contentDescription labels both look right, so the code nits are cleared. Just to be clear about what's holding this open: it isn't the code — it's the product decision I flagged. In-app editing of recorded sleep, plus the new DB delete path, is a trust/honesty call for the maintainer, and it needs an iOS/macOS parity plan since those platforms have no sleep editing today. That's the deliberate yes we need; the implementation itself is in good shape. Keeping it open for that call.

@NoopApp

NoopApp commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Adopted ✅ — the Sleep-screen umbrella shipped in v2.9.0: in-app bed/wake editing with live recompute, Hours-vs-Needed + Consistency cards, night navigation, detail sheets. Re-authored under the project account (hardcoded paint swapped for a Palette token, a11y labels added). Thanks @ujix — this was a big one! Closing as adopted.

@NoopApp NoopApp closed this Jun 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants