Files
samplez/rippr-src/docs/v2/09-record-screen.md
uhryniuk 280fd7f988 Add Rippr source snapshot and full-history bundle
Two copies for two jobs. rippr-src/ is a browsable git archive export of
the tracked tree at 2f76983 - no build outputs, no local.properties, no
nested .git - which is convenient to read in gitea but carries no history
and will drift.

rippr-full-history.bundle is the real backup: all 18 commits, verified as
"records a complete history" and test-cloned before committing. This
matters because ~/dojo/rippr has no git remote and otherwise exists only
on one machine.

rippr-src/SNAPSHOT.md explains the difference and how to restore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 08:30:25 -05:00

115 lines
4.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# T09 — Record screen v2
**Phase** 4 · **Depends on** T06, T08 · **Status** Done
## Goal
Add Pause/Resume, Stop, and Discard to the recording screen while keeping the clean
numeric readout intact, and show per-trip rather than lifetime stats. Done when a ride
can be fully controlled from the screen and the numbers reflect the current trip only.
## Context
Direct feedback: *"app is simple and clean. I like that but it's missing stuff."* The
missing stuff is controls, not decoration. **Resist adding visual weight.**
v1's screen shows `MAX SPEED` as a large monospace figure, then a card with points
captured, avg speed, duration, and pending upload, then one full-width button. That
layout stays; it gains controls and loses its lifetime scope.
The current single button toggles start/stop via `RecordingState.isRecording`. v2 has
three states and needs a different control arrangement.
## Design
### Control layout by state
| State | Primary | Secondary |
|---|---|---|
| Idle | **START RECORDING** (full width) | — |
Service actions to dispatch: `TrackingService.ACTION_START` / `ACTION_PAUSE` /
`ACTION_RESUME` / `ACTION_STOP` / `ACTION_DISCARD`. Only START uses
`startForegroundService`; the rest are plain `startService` on the running service.
| Recording | **PAUSE** | STOP |
| Paused | **RESUME** | STOP · DISCARD |
Discard appears **only when paused** — deliberately. Offering a destructive action next
to Pause during an active ride invites a gloved mis-tap at 100 km/h. Pausing first is a
natural speed bump.
Discard shows a confirmation dialog stating what is lost ("Delete this ride? N points
recorded over X km will be permanently deleted."). Stop and Pause need no confirmation.
### Replacing T02 scaffolding
`MainActivity` derives stats with `flatMapLatest` over `TripRepository.observeActiveTrip()`
into `observeTripStats(tripId)`, and `isRecording` from `observeActiveTrip().map { it != null }`.
Upload errors come from the `UploadStatus` object. All of that moves into `RecordViewModel`,
and once T07 lands the screen reads persisted aggregates off the `Trip` row rather than
recomputing in SQL.
`onToggleClicked` is now a `lifecycleScope.launch` that queries `trips.activeTrip()` — there
is no synchronous recording flag any more. The ViewModel should expose state instead so the
screen never has to suspend to decide what a button does.
### Stats become per-trip
Driven by `observeActiveTrip()` from T03, showing the live aggregates from T07:
- Max speed (the big figure — it is the number Dylan cares about)
- Distance — **new in v2**, read straight off `Trip.distanceM` (no SQL recomputation)
- Duration, moving time
- Points captured
- Pending upload (only when non-zero, and only if an endpoint is configured)
When idle, the screen shows a resting state rather than stale numbers from the last ride.
## Implementation
1. `RecordViewModel` exposes `RecordUiState` derived from `observeActiveTrip()`.
2. Map `TripState` to the control layout above.
3. Dispatch service intents for each action.
4. Add `ConfirmDialog` for Discard.
5. Reuse `Telemetry.formatDuration` for both duration fields.
6. Keep the battery-optimisation prompt.
7. Add a top-bar action to reach the Trips list.
## Acceptance criteria
- [x] All five transitions work from the UI
- [x] Discard is unavailable while actively recording
- [x] Discard confirms, and states what will be lost
- [ ] Stats reset between trips — no bleed from the previous ride
- [x] Distance appears and increases during a ride
- [x] Idle state is visibly distinct from a zeroed ride
- [ ] Screen still reads at a glance; no added clutter
> **Not verified.** The unchecked items above, and the Compose UI tests below, were
> not done. Verification for T08–T11 was manual on the emulator (screenshots through
> the full flow) plus the existing unit and instrumented suites. Compose UI tests are
> outstanding — tracked in T18.
## Tests
Compose UI tests:
- Each state renders the right controls
- Discard dialog appears, cancel is non-destructive
- State survives rotation mid-ride
Emulator end-to-end: drive the full lifecycle through taps, then verify the database
matches what the screen claimed.
## Risks / gotchas
- **Tap targets.** These get used in gloves. Keep the 64–72dp button height v1 uses.
- **Wait for the UI before tapping in tests.** v1's cold start took 8.7 s; a fixed sleep
produced a silently-missed tap and a false "service didn't start" conclusion. Poll
`uiautomator dump` for the button text instead.
- **Do not show "Pending upload" when no endpoint is configured** — it reads as an error
when it is just an unused feature.
## Out of scope
Trips list (T10), any map, live path preview.