Phone App: connected service, charging cost, a tab picker and data export
Both READMEs claimed web parity with data export/import as the only
omission. That was three gaps out of date: the car screen had no
connected-service tab, no per-car charging-cost tab, and no way to say what
a car's page shows - all three of which the web has had since the car view
became a property of the car rather than of the browser.
The tab bar was the thing blocking the rest. It was a fixed list of eight
Tab(text: "Information") literals, so it could neither grow a tab nor read
an arrangement, and it sat outside the translation system that the rest of
the app has used since b6bb6b1. It now builds from the car's own tabOrder
and hiddenTabs, and labels come from car.tabs.* like the web's.
Rather than retype four subtrees of strings in three languages, the shared
ones - car.*, settings.advanced, forms.charging, forms.import and the
common keys the phone was missing - are copied out of the Web App's own
language files, with the phone's existing wording winning every collision.
Polish and Danish therefore arrive complete and cannot drift between the
two apps. Only five strings are genuinely phone-only: the reorder hint, the
saved-file message, the open action and two validation lines.
The connected-service tab mirrors ProviderPanel: headline readings, the
offer to take a provider odometer that is ahead of the stored one, the
vehicle record, and one collapsible card per capability, rendered from the
server's flattened key/value pairs so a provider adding a field surfaces it
without touching this app. Two deliberate differences. The raw-payload
disclosure is dropped - Toyota's eight sections are megabytes of JSON on a
phone screen, and the flattened fields carry the same content. And the
readings cannot be dragged here, though a stored metricOrder is still
honoured, so an arrangement made on the web carries over.
The view picker takes the same line on gestures. The web rearranges by
dragging the tab bar itself and the Information rows themselves; on a touch
screen that gesture belongs to the tab bar, so both arrangements are made
in the picker with a handle instead, and hiddenTabs, hiddenFields, tabOrder
and fieldOrder all save in one PUT. The key catalogues live in
car_view_sheet.dart and mirror hideableCarTabs / arrangeableCarTabs /
hideableCarFields in cars.go, because the server rejects anything else.
arrangeKeys applies a partial stored order the way the API documents it: an
unknown key is dropped and an unnamed one follows the arranged ones, which
is what puts a tab added in a later release at the end of somebody's page
rather than the middle of it.
Charging cost is the electric twin of Fuel and is built as one - the same
stats panel, tile and form shape, measured between full charges. It is the
per-car cost log, not the Charging section in the bottom bar, which remains
the charger network and OCPP control.
Export and import needed a phone answer to two browser affordances. The
export is written to the app's documents directory under the filename the
server's Content-Disposition names, and offered to whatever opens JSON via
open_filex - the same route attachments already take. The import goes
through the system file picker, validates the file locally, and confirms
with the number of cars the file actually holds, because the server always
creates new records and never merges.
The one field worth calling out on the client: _carPayload still leaves the
provider link and the view arrangement out, matching carPayload in
records.go, so saving the car form cannot silently unlink a car or undo an
arrangement.
Known gap, deliberately not closed here: the older sheets in
record_form_sheets.dart and most of car_detail_screen.dart still carry
hardcoded English. Everything added here and every tab label goes through
t(), but translating the rest of the car screen is its own change and would
have buried this one.
Verified by flutter analyze (clean), flutter test - 13 pass, 6 of them new,
covering arrangeKeys against partial, unknown and duplicate keys, the
charging models keeping uncomputed figures null rather than a plausible
zero, the new Car fields, and ProviderSnapshot parsing an unreachable
provider as an answer rather than a failure - and flutter build apk
--debug, which succeeds. The Kotlin Gradle plugin warnings in that build
are pre-existing.
Not verified: nothing was run against a live API Server or on a device, so
the new screens have not been driven end to end - only compiled, analyzed
and unit-tested.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
215c027ada
commit
e249c2f4d8
14 files changed
+3510
-203
No files matched your search
+35
-7
@@ -2,8 +2,7 @@
|
||||
|
||||
A Flutter client for the DriverVault maintenance tracker. Talks **only** to the
|
||||
API Server (same contract and auth as the web app — a PocketBase token relayed by
|
||||
the server, not a JWT the server mints). At full feature parity with the web app
|
||||
(data export/import is the only deliberate omission).
|
||||
the server, not a JWT the server mints). At full feature parity with the web app.
|
||||
|
||||
Project name `drivervault_phone`, package id `com.drivervault.phoneapp`.
|
||||
**Android is the supported target** — the older Flutter-web build path is
|
||||
@@ -24,8 +23,27 @@ navigation bar** — Garage, Charging, Settings, and Users for admins — in an
|
||||
- **Car detail** — all spec fields (incl. VIN and transmission / differential /
|
||||
brake / coolant specs), a **share** sheet (owner only), quick odometer update,
|
||||
edit car, and delete car (type-to-confirm; cascades). Actions are gated by the
|
||||
caller's access level (read-only vs write vs owner). Tabs, in the web app's
|
||||
order:
|
||||
caller's access level (read-only vs write vs owner).
|
||||
|
||||
**Which tabs a car shows, and in what order, belongs to the car** — the same
|
||||
arrangement the web app reads, so everyone it is shared with sees the same
|
||||
page. Edit it under the **tune** icon: switch tabs and Information rows on or
|
||||
off, and drag either list by its handle to reorder it. (The web rearranges by
|
||||
dragging the tab bar itself; on a touch screen that gesture is the tab bar's,
|
||||
so both arrangements are made in the picker instead.) Information cannot be
|
||||
switched off — a page with no tabs left would be a dead end — but it can be
|
||||
moved. The tabs, in their default order:
|
||||
- **Connected service** — everything the manufacturer's own app knows about
|
||||
this car, read live under *your* account: headline readings (odometer, fuel
|
||||
or battery, range, charging state, position), the vehicle record, and one
|
||||
card per capability the plugin exposes, rendered from the server's flattened
|
||||
key/value pairs so a provider adding a field surfaces it without an app
|
||||
change. Offers to take the provider's odometer when it is ahead of the
|
||||
stored one. A car with no link yet gets a **connect** card instead, and the
|
||||
tab is hidden entirely for a user with no manufacturer account connected.
|
||||
Because the read runs under your own credentials, a car shared from someone
|
||||
else shows data here only if that vehicle is on your account too — it says
|
||||
so rather than failing.
|
||||
- **Service history** — date/odometer plus which parts were changed, with the
|
||||
next-due date/km derived by the server.
|
||||
- **Technical check history** — the mandatory roadworthiness inspections
|
||||
@@ -39,6 +57,11 @@ navigation bar** — Garage, Charging, Settings, and Users for admins — in an
|
||||
cost per km, price per litre). Consumption is measured between full tanks, so
|
||||
partial fills roll into the next full one and a flagged missed fill leaves its
|
||||
window uncomputed rather than reporting a fictional figure.
|
||||
- **Charging cost** — charges with a summary panel (average / best / worst
|
||||
kWh/100km, cost per km, price per kWh). The electric twin of Fuel, and
|
||||
derived the same way: consumption is measured between full charges, so a
|
||||
top-up rolls into the next full one. Distinct from the **Charging** section
|
||||
in the bottom bar, which is the charger network and OCPP control.
|
||||
- **Documents** — insurance, registration, road tax and the rest, with a
|
||||
renewal badge driven by the server's expiry assessment.
|
||||
- **Parts catalog** — the per-car parts list.
|
||||
@@ -46,7 +69,7 @@ navigation bar** — Garage, Charging, Settings, and Users for admins — in an
|
||||
**Mark done** action. Reminders the server derived from a document or service
|
||||
record are shown read-only.
|
||||
- **Attachments** — one optional file per service record, technical check,
|
||||
workshop visit, refill, document and part (PDF or image, up to 10MB). Picked
|
||||
workshop visit, refill, charge, document and part (PDF or image, up to 10MB). Picked
|
||||
with `file_picker`, fetched back through the API Server — never a public URL —
|
||||
and opened with the phone's own viewer via `open_filex`.
|
||||
- **Charging** — mirrors the web `Charging.vue`, split into two tabs. **Public**
|
||||
@@ -59,8 +82,11 @@ navigation bar** — Garage, Charging, Settings, and Users for admins — in an
|
||||
(theme + dark mode, **language**, **region**, date format, **currency**, font
|
||||
size), profile (avatar via `image_picker`, bio), **integrations** (Toyota,
|
||||
Anker Solix), **Security** (biometric toggle), **Organization** (create your
|
||||
own — which makes you its admin — or rename/delete the one you administer), and
|
||||
the account-deletion state machine. Auth relays PocketBase's own stateless
|
||||
own — which makes you its admin — or rename/delete the one you administer),
|
||||
**data export/import**, and the account-deletion state machine. Export writes
|
||||
the account JSON into the app's own documents directory and offers to open it;
|
||||
import picks a previously exported file and always creates new records, so it
|
||||
asks first, with the number of cars the file actually holds. Auth relays PocketBase's own stateless
|
||||
tokens, so there is no per-device session list to show or revoke.
|
||||
- **Users (admin)** — user management tab (list / create / role / reset password
|
||||
/ delete), shown only for the admin role.
|
||||
@@ -161,5 +187,7 @@ lib/
|
||||
├── root_shell.dart # bottom-nav shell: Garage, Charging, Settings, Users
|
||||
├── login_screen.dart lock_screen.dart dashboard_screen.dart
|
||||
├── car_detail_screen.dart car_form_sheet.dart record_form_sheets.dart
|
||||
├── car_view_sheet.dart # which tabs/rows a car shows + the key catalogue
|
||||
├── provider_tab.dart # the connected-service tab (MyToyota)
|
||||
└── charging_screen.dart settings_screen.dart admin_users_screen.dart
|
||||
```
|
||||
Reference in new issue
Block a user