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>
4.6 KiB
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
RecordViewModelexposesRecordUiStatederived fromobserveActiveTrip().- Map
TripStateto the control layout above. - Dispatch service intents for each action.
- Add
ConfirmDialogfor Discard. - Reuse
Telemetry.formatDurationfor both duration fields. - Keep the battery-optimisation prompt.
- Add a top-bar action to reach the Trips list.
Acceptance criteria
- All five transitions work from the UI
- Discard is unavailable while actively recording
- Discard confirms, and states what will be lost
- Stats reset between trips — no bleed from the previous ride
- Distance appears and increases during a ride
- 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 dumpfor 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.