V3-01: activity type per ride
Adds Activity (motorcycle/bicycle/scooter/skateboard/running/walking/other) as a column on Trip, and threads it through as real behaviour rather than a label: ActivityProfile (domain/activity_profile.dart) carries a noise floor, accuracy gate, histogram bucket width, and elevation smoothing window/threshold per activity, consumed by sanitizeSpeedKmh, isUsableFix, computeSummary, Accumulator and speedHistogram. The motorcycle profile reproduces the exact constants the app shipped with before this existed, and a test asserts they never drift apart. This is the port's first real migration: schemaVersion 1 -> 2, m.addColumn(trips, trips.activity) with a motorcycle default so every existing row survives unmodified. Proven with a hand-built v1 SQLite file (raw sqlite3, not drift_dev's schema tooling) that a real database with real rides upgrades and keeps every trip, segment and point. No picker in front of Start: a new trip defaults to whichever activity was most recently used, derived live from the trips table rather than duplicated into a Config field. Editable afterwards on trip detail, which recomputes aggregates under the new profile immediately -- a walking pace that reads as noise under a motorcycle's floor reads as real movement once the activity is corrected, and there's a test proving exactly that transition. The widget-test pass for the activity-picker sheet caught a real overflow bug: seven options overflowed a Column-based bottom sheet the same way the record screen once did. Fixed with a scrollable ListView + isScrollControlled, same shape as that earlier fix. 188 tests (171 -> 188): 2 migration, 5 ActivityProfile, 3 profile-threading proofs in computeSummary, 5 repository, 2 widget. Analyze clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# V3-01 — Activity type per ride
|
||||
|
||||
**Phase** Foundations · **Depends on** nothing · **Size** M · **Status** Not started
|
||||
**Phase** Foundations · **Depends on** nothing · **Size** M · **Status** Done
|
||||
|
||||
## Goal
|
||||
Every ride records what it was done on — motorcycle, bicycle, scooter, skateboard,
|
||||
@@ -67,3 +67,31 @@ Default to the last activity used; make it editable on trip detail next to renam
|
||||
|
||||
## Out of scope
|
||||
Per-activity totals or a stats screen. GPX `<type>` export (see V3-14).
|
||||
|
||||
|
||||
## Outcome
|
||||
|
||||
Shipped as designed, with two deliberate deviations from the ticket text, both
|
||||
recorded here rather than silently:
|
||||
|
||||
- **No `Config.lastActivity` field.** "Default to the last activity used" is instead
|
||||
derived live from the trips table itself (`AppDatabase.mostRecentTrip()` /
|
||||
`TripRepository._lastUsedActivity()`) rather than duplicated into a separate
|
||||
preference. One source of truth, no write path to keep in sync, and it degrades
|
||||
correctly to `motorcycle` when the database is empty.
|
||||
- **`ActivityProfile` lives in `domain/activity_profile.dart`**, not folded into
|
||||
`models.dart` — kept the domain model (`Trip.activity`) separate from the behavioural
|
||||
defaults built on top of it, and avoided a dependency cycle between the domain layer
|
||||
and `stats/`/`recording/`.
|
||||
|
||||
Map-fit zoom (mentioned in the ticket's defaults table) turned out to need no work:
|
||||
`RideMap` already fits to the ride's actual recorded bounds, which is activity-agnostic
|
||||
by construction.
|
||||
|
||||
The widget-test pass caught a real overflow bug independent of activity type: the
|
||||
7-item activity picker sheet overflowed a `Column`-based `showModalBottomSheet` the same
|
||||
way the record screen once did (see `docs/port/PROGRESS.md`, Phase 4). Fixed with a
|
||||
scrollable `ListView` + `isScrollControlled: true`, the same shape as that earlier fix.
|
||||
|
||||
188 tests total (171 → 188): migration (2), `ActivityProfile` (5), `computeSummary`
|
||||
profile-threading proof (3), repository (5), widget (2).
|
||||
|
||||
Reference in New Issue
Block a user