Refresh the Flutter snapshot: v3 tickets V3-04 through V3-16, plus a fresh installable APK
V3-04 through V3-07, V3-10, V3-16 shipped complete; V3-11/V3-12/V3-14 shipped code-complete pending device/account verification; V3-08/V3-09 deferred behind a new V3-17 (self-hosted OSRM investigation). 316 tests passing, up from 221. The APK is a fresh release build (debug-signed, no release signing config exists yet) with two build fixes applied: core library desugaring enabled for flutter_local_notifications, and sentry_flutter bumped to 9.27.0 (8.14.2's bundled Kotlin plugin was incompatible with this project's Kotlin 2.4.0 toolchain).
This commit is contained in:
86
rippr-flutter-src/docs/v3/V3-02-settings-screen.md
Normal file
86
rippr-flutter-src/docs/v3/V3-02-settings-screen.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# V3-02 — Settings screen
|
||||
|
||||
**Phase** Foundations · **Depends on** nothing (pairs with V3-01, V3-03) · **Size** S · **Status** Done
|
||||
|
||||
## Goal
|
||||
One place for the preferences that currently have nowhere to live.
|
||||
|
||||
## Context
|
||||
`Config` already holds `uploadEndpoint`, `deviceId` and `mapEnabled`, but there is no UI
|
||||
for any of them. The map toggle sits on trip detail because a single switch did not justify
|
||||
a screen. V3-01 and V3-03 both add preferences, which finally does justify one.
|
||||
|
||||
The upload endpoint has **never** had UI, in the native app or the port. This is where it
|
||||
stops being a code-only setting.
|
||||
|
||||
## Design
|
||||
Reached from the record screen. Sections:
|
||||
|
||||
- **Recording** — default activity (V3-01), units (V3-03)
|
||||
- **Map** — render maps on trip detail; later, live map (V3-04)
|
||||
- **Sync** — upload endpoint, with the device id shown read-only and copyable
|
||||
- **About** — version, a link to the privacy policy, licences
|
||||
|
||||
Keep it plain. This is not a screen anyone should spend time in.
|
||||
|
||||
## Implementation
|
||||
1. `SettingsScreen` + a `go_router` route
|
||||
2. Make `Config` reactive — it is currently read once into a provider; settings need writes
|
||||
to propagate. A `ConfigNotifier` over `shared_preferences`.
|
||||
3. Move the map toggle off trip detail
|
||||
4. Endpoint field validates it parses as a URL and is http(s)
|
||||
|
||||
## Acceptance criteria
|
||||
- [ ] Every `Config` value is viewable and editable
|
||||
- [ ] Changing the map toggle takes effect without an app restart
|
||||
- [ ] An invalid endpoint is rejected with a readable message, not silently stored
|
||||
- [ ] Device id is copyable — it is the only way to identify this install to a server
|
||||
|
||||
## Tests
|
||||
- Widget: each control renders and writes through to `Config`
|
||||
- Widget: invalid URL rejected
|
||||
- Changing the map toggle rebuilds trip detail
|
||||
|
||||
## Risks
|
||||
`Config` is currently loaded once at startup into a `StateProvider`. Making it writable
|
||||
without introducing a second source of truth is the only subtle part.
|
||||
|
||||
## Out of scope
|
||||
Account settings (v4). Theme selection (V3-16).
|
||||
|
||||
|
||||
## Outcome
|
||||
|
||||
"Move the map toggle off trip detail" (implementation step 3) turned out to be moot —
|
||||
`mapEnabledProvider` already existed and drove `RideMap`'s visibility, but **no widget
|
||||
anywhere ever offered a control to change it**. There was nothing to move. Settings adds
|
||||
the first one.
|
||||
|
||||
No `ConfigNotifier` was built — see V3-03's outcome for why the existing
|
||||
`mapEnabledProvider`-style `StateProvider` pattern covers reactivity without it, and
|
||||
`unitSystemProvider` was added the same way, in `providers.dart`, ahead of this ticket.
|
||||
|
||||
Two implementation-step items shipped differently than drafted, both to avoid adding a
|
||||
dependency disproportionate to an `S`-sized settings screen:
|
||||
|
||||
- **No `package_info_plus`.** The About section's version string is a static literal
|
||||
matching `pubspec.yaml`'s `1.0.0+1`, not a live package lookup. Fine today; would need
|
||||
revisiting if the version ever needs to be authoritative from inside the running app
|
||||
rather than copied by hand.
|
||||
- **No privacy-policy link.** None is published yet (see `docs/LAUNCH.md`) and linking
|
||||
to one that does not exist would be worse than omitting it. Shows a plain note instead.
|
||||
Licences are still free: Flutter's built-in `showLicensePage` needed no new dependency.
|
||||
|
||||
Endpoint validation accepts `http://` and `https://` with a non-empty host, and treats
|
||||
an **empty** field as valid — that is how upload gets disabled, not an error state. A
|
||||
non-empty invalid value is rejected with inline `errorText` and never reaches `Config`;
|
||||
proven by a widget test that types garbage, taps Save, and asserts `Config.uploadEndpoint`
|
||||
is still empty afterwards.
|
||||
|
||||
`uploaderProvider` needed an explicit `ref.invalidate()` after saving the endpoint, for
|
||||
the identical reason `unitSystemProvider` needed its own `StateProvider` rather than
|
||||
reading through `configProvider` directly — a `Config` write never changes the `Config`
|
||||
instance Riverpod is watching, so nothing downstream rebuilds unless told to.
|
||||
|
||||
10 tests: 9 in `settings_screen_test.dart`, 1 confirming the record screen's settings
|
||||
button is genuinely wired (not just present) in `widget_test.dart`.
|
||||
Reference in New Issue
Block a user