Skip to content

Android: interactive charts, Sleep overhaul, Explore redesign, axis labels, battery/streak strip, live-HR notification sparkline#210

Closed
ujix wants to merge 8 commits into
NoopApp:mainfrom
ujix:main
Closed

Android: interactive charts, Sleep overhaul, Explore redesign, axis labels, battery/streak strip, live-HR notification sparkline#210
ujix wants to merge 8 commits into
NoopApp:mainfrom
ujix:main

Conversation

@ujix

@ujix ujix commented Jun 13, 2026

Copy link
Copy Markdown

Android: interactive charts, Sleep overhaul, Explore redesign, axis labels, battery/streak strip, live-HR notification sparkline

What this PR does

Executive summary: A broad Android UX pass covering interactive chart inspection across all screens, a fully redesigned Sleep page with night-by-night browsing and in-app time editing, Y/X axis labels on every line chart, a redesigned Explore metric picker, a live HR sparkline in the foreground notification, and a battery/streak status strip on Today.

Details:

Charts & data visualization

  • Added: selectionEnabled on every LineChart across Today (HeartRateTrendCard), Sleep (metric detail), Stress (StressTrendSection), Trends (HeroChartCard), Explore, and Vital Signs — users can tap or smoothly drag to read exact values on any trend
  • Added: Y-axis labels (max / avg / min) and an X-axis date row to all line charts in Explore, Trends, and Vital Signs screens
  • Fixed: HeartRateTrendCard X-axis: labels now show correct HH:mm wall-clock times with an interpolated midpoint; Y-axis shows avg as the middle label

Today screen

  • Updated: ThreeDaySelectorBar replaced with DayNavBar — left/right chevrons flanking an accent-tinted center block; tapping the block opens a DatePickerDialog for any past date
  • Added: Compact status strip (top-right of content area) shows live strap battery % and total-nights-recorded streak with a fire icon (red when streak ≥ 2, gray otherwise)

Sleep screen — night browsing

  • Added: ◀ / ▶ chevron navigation walks every recorded sleep block (including naps); a DatePickerDialog jumps to any night by calendar date
  • Updated: Night nav header redesigned to match DayNavBar style — accent center block shows the night label ("Last night") and the date ("Wed 4 Jun"); the time range ("22:50–06:48") sits below with an edit icon
  • Fixed: nightOffset no longer resets to 0 on optimistic state updates — it only resets on a real sync or import (LaunchedEffect(days)), so the user stays on the night they navigated to

Sleep screen — time editing

  • Added: Tapping the edit icon opens a Strand-styled AlertDialog with two rows (Bedtime / Wake-up), each showing the current time and an accent edit icon; choosing one opens its own independent TimePickerDialog
  • Fixed: After confirming a change the header clock re-renders immediately via an optimistic in-memory sleeps update; no DB round-trip wait
  • Fixed: Stage Breakdown subtitle derives "X in bed" from session.endTs − session.startTs so it reflects the edited window instantly
  • 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

Sleep screen — analytics cards

  • Added: "Hours vs Needed" card — score %, trend arrow, gradient progress bar, stacked Healthy/Strain/Debt component bar, slept/needed/debt footer
  • Added: "Sleep Consistency" card — Canvas-drawn vertical bar chart with bed-time at top / wake-time at bottom, Y-axis time labels, X-axis day labels, dashed typical overlay lines; score counts nights where both bed and wake fall within 45 min of the user's typical (previous SD-based formula always returned 0 %)
  • Updated: Consistency card Y-axis flipped to match natural night flow (early evening → morning top-to-bottom)

Sleep screen — metric detail

  • Updated: Tapping any metric tile in Night Detail slides up a ModalBottomSheet (W/M/3M/6M/1Y/ALL range selector, Y-axis, line chart, X-axis dates, min/avg/max footer) instead of pushing a new screen

Explore screen

  • Updated: Metric selector fully redesigned as an ExposedDropdownMenuBox with category headers, accent dot per metric, selected-item checkmark, and dividers between groups — replaces the horizontal chip row

Notification

  • Added: Foreground-service notification accumulates a rolling 60-sample HR history and renders it as a Bitmap sparkline (800×200, accent fill + stroke on dark background) attached via NotificationCompat.BigPictureStyle; expanded notification shows an HR trend instead of just a number

Intelligence screen

  • Added: W / M / 3M / 6M / 1Y / ALL SegmentedPillControl to the "By Day" section; list is filtered to the chosen window (defaults to M) with a day-count footnote and an empty-window nudge card

Bug fixes

  • Fixed: String? vs String compile errors in HealthScreen.kt and TrendsScreen.kt x-axis date label paths (.getOrNull() returning String? passed to LocalDate.parse(String))
  • Fixed: SleepConsistencyCardtypicalBed / typicalWake declared after the block that referenced them; moved before the consistentNights count

Validation: :app:assembleFullDebug, testFullDebugUnitTest, and testDemoDebugUnitTest all green after every commit.

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: night navigation and date-jump picker, time editing (bed and wake independently), navigation position preservation after editing, metric tile bottom sheets, consistency card with real multi-night data, HR sparkline in expanded notification, all chart drag interactions, Explore dropdown, Intelligence range filter, Today battery/streak strip.

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

ujix and others added 8 commits June 13, 2026 05:24
…ication sparkline

Enables chart tap/swipe interaction on three more screens, replaces the
unbounded Intelligence day list with a windowed range picker, and draws
a real-time HR line into the foreground-service notification.

- Enable selectionEnabled on LineChart in TodayScreen (HeartRateTrendCard),
  StressScreen (StressTrendSection), and TrendsExploreScreen (HeroChartCard)
  so users can tap/drag to read exact values — matching the existing Trends behaviour
- Add W / M / 3M / 6M / 1Y / ALL SegmentedPillControl to Intelligence "By Day"
  section; list is filtered to the chosen window (defaults to M), with a day-count
  footnote and an empty-window nudge card
- Accumulate rolling 60-sample HR history in WhoopConnectionService and render it
  as a Bitmap sparkline (800×200, accent fill + stroke on dark background) attached
  via NotificationCompat.BigPictureStyle so the expanded notification shows an HR
  trend instead of just a number

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…charts

Adds morning journal prompt, sleep-time editing, two new sleep analytics
cards, an improved day selector, battery/streak status strip, HR chart
axes, and a dropdown metric picker in Explore.

- Sleep: pen icon below the night clock label opens sequential bedtime →
  wake TimePickerDialogs; times are persisted via delete+upsert so the
  Room PK constraint is preserved (WhoopDao.deleteSleepSession + repo
  updateSleepSessionTimes + AppViewModel method)
- Sleep: morning journal prompt — ModalBottomSheet fires once per day
  when the latest sleep session ended within 12 hours; tapping "Open
  Journal" navigates to InsightsScreen (gated by NoopPrefs key
  KEY_LAST_JOURNAL_PROMPT so it never re-fires the same day)
- Sleep: new "Hours vs Needed" card — score %, trend arrow, gradient
  progress bar, stacked Healthy-Min/Strain/Debt component bar, footer
  with slept/needed/debt values
- Sleep: new "Sleep Consistency" card — score % from bedtime+waketime
  SD across the last 14 nights; Canvas-drawn vertical bar chart with
  Y-axis time labels (20:00/00:00/04:00/08:00/12:00), X-axis day labels,
  and dashed typical bed/wake overlay lines
- Today: ThreeDaySelectorBar replaced by DayNavBar (StrandComponents) —
  left/right chevron + calendar icon that opens DatePickerDialog for any
  past date, accent-tinted center block
- Today: HR chart now shows Y-axis min/max labels and an X-axis time row
- Today: compact status strip (top-right of content) shows live strap
  battery % and total-nights streak with a fire icon (red ≥2 consecutive,
  gray otherwise)
- Explore: horizontal chip row replaced by a grouped dropdown
  (ExposedDropdownMenuBox) with category headers and accent dot per metric
…design

Refines data visualization and navigation across Today, Sleep, Explore, Trends, and Vital Signs pages to improve readability, consistency, and interactivity.

- Today / DayNavBar: remove calendar icon; tapping the accent center block opens the date picker instead
- Today / HeartRateTrendCard: fix X-axis time labels (correct HH:MM format, midpoint interpolated); add avg as middle Y-axis label
- Sleep / NightNavHeader: redesign to match DayNavBar style — accent center block hosts night label, sleep/wake times, and inline edit pen; tapping opens date picker
- Sleep / MetricGrid: make all SparkTile cards clickable; each opens a new SleepMetricDetailScreen
- Sleep / SleepMetricDetailScreen: new screen mirroring VitalDetailScreen — W/M/3M/6M/1Y/ALL range filter, Y-axis (max/avg/min), X-axis date labels, min/avg/max footer
- Sleep / SleepConsistencyCard: redesign header (overline + title + subtitle + score); extend Y range to 14 h; clamp bar coordinates to prevent overflow; add SCORE / TYPICAL / NIGHTS footer stats
- Explore / MetricDropdown: full redesign using ExposedDropdownMenuBox — category headers with accent dot, selected-item checkmark, dividers between groups
- Explore + Trends + Vital Signs: add Y-axis (max/avg/min) and X-axis (date labels) to all line charts
…etric bottom sheet

Refines the Sleep page's night navigation, sleep schedule chart, and metric detail UX.

- NightNavHeader: move sleep/wake time + edit pen below the accent nav block (cleaner, less crowded center block)
- SleepConsistencyCard: flip chart Y-axis so bedtime is at the top and wake time progresses downward, matching natural sleep-night flow
- SleepConsistencyCard: fix consistency score — now counts nights where both bed and wake time are within 45 min of typical (was always 0% due to strict SD threshold)
- Night detail tiles: tapping any SparkTile now slides up a ModalBottomSheet with the metric history chart, range selector, and min/avg/max footer instead of navigating to a separate screen
…ime edit, live update

- NightNavHeader: split clock label — date ("Wed 4 Jun") shown inside the accent center block below the night label; only the time range ("22:50–06:48") shown below with the edit icon
- Time edit: replaced the sequential auto-open Bedtime→Wake-up pickers with a Strand-styled AlertDialog letting the user choose which time to edit (Bedtime or Wake-up); each opens its own TimePickerDialog independently
- Immediate reflection: `onUpdateTimes` now does an optimistic in-memory update of the `sleeps` list so the header clock re-renders instantly without waiting for a DB round-trip
- Fix navigation reset: nightOffset no longer snaps to 0 on every optimistic update (LaunchedEffect key moved from sleeps to days), so the user stays on the edited night
- Fix stale/duplicate data: reload sleeps from DB after the write completes, eliminating any computed sessions overlapping the edited night
- updateSleepSessionTimes is now suspend so the reload waits for the write before querying (removes race condition)
- Stage breakdown in-bed duration now derives from session endTs-startTs so it reflects edits immediately
- Time-edit dialog: 14dp/16dp padding, headline-size time text, surfaceOverlay background for comfortable touch targets
After editing sleep/wake times the sleeps list was reloaded from DB and
re-sorted by startTs. If the new startTs sorted the session to a different
index, nightOffset (unchanged) pointed to the wrong session; if that
session had no DailyMetric data the entire night appeared to vanish.

The reload was added to prevent phantom computed sessions, but there are
no actual duplicates: the repository delete+upsert is correct. The
original navigation bug (old data on previous day) was already fixed by
moving the nightOffset reset into LaunchedEffect(days). The reload was
unnecessary, so remove it and keep only optimistic update + fire-and-forget.
buildSleepModel now derives a sessionDurationMin from the session's
endTs-startTs whenever the session matches the selected night. A
metricsWindow replaces that night's totalSleepMin with the session-based
value so performance, hoursVsNeeded, sleepDebt, and the trend chart all
reflect the edited window immediately after an optimistic update.

typicalTotalMin intentionally keeps the unmodified windowDays so the
personal baseline mean is not skewed by a single edited night.
@NoopApp

NoopApp commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Beautiful work, @ujix — this is a lot of thoughtful Android polish. I've taken the chart-axis subset into v2.8.0 (the Today + Trends time/value axis labels, reimplemented under the project identity, credited to you). The bigger pieces — the Sleep night-browsing overhaul, the Explore dropdown redesign, the battery/streak strip and the interactive-drag charts — are each substantial enough that I'd rather land them deliberately than fold them into a mixed release. If you're up for it, a focused follow-up PR on (say) just the Sleep overhaul would be very welcome and I'll prioritise reviewing it. Thank you — genuinely strong contribution. 🙏

@NoopApp NoopApp closed this Jun 13, 2026
@ujix

ujix commented Jun 13, 2026

Copy link
Copy Markdown
Author

Thanks so much for the kind words and for landing the axis-label work — really appreciate it! We've gone ahead and split everything into dedicated focused PRs as you suggested:

  • Sleep overhaul — night-by-night chevron navigation + redesigned nav header (#257), Hours vs Needed + Sleep Consistency analytics cards (#258), metric detail bottom sheets (#259), and in-app bed/wake time editing with live metric recompute (#260)
  • Explore redesign — ExposedDropdownMenuBox metric picker + interactive chart drag (#253)
  • Today screen — DayNavBar replacing ThreeDaySelectorBar (#255), battery % + streak fire strip (#256), and interactive HR trend chart with HH:mm axis labels (#254)
  • Interactive charts — selectionEnabled on Stress screen (#249)
  • Intelligence range filter — W/M/3M/6M/1Y/ALL SegmentedPillControl (#252)

Each one is a single-concern commit rebased onto current main, green on assembleFullDebug and unit tests. Happy to adjust scope or split further if anything still feels too broad. 🙏

@NoopApp

NoopApp commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Thank you — this is exactly the right way to split it, and it made the review dramatically cleaner. I've gone through every one of the focused PRs:

One thing from this PR's title I didn't see carried into the split: the live-HR notification sparkline. If you'd like that considered, a focused PR for it would be welcome.

On mechanics: we re-land contributions under the project account (keeps commit authorship uniform and lands the iOS/macOS hand-ports in lockstep), so you'll see adopted PRs close as "adopted" with credit rather than via merge — the work absolutely ships either way. Really appreciate both the volume and the quality here.

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