# 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.