Files
2026-07-06 08:50:52 +02:00

103 lines
5.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Car Control — Phone App (Flutter)
A Flutter client for the Car Control maintenance tracker. Talks **only** to the
API Server (same contract and JWT auth as the web app). At full feature parity
with the web app (data export/import is the only deliberate omission).
Project name `carcontrol_phone`, package id `com.carcontrole.carcontrol_phone`.
**Android is the supported target** — the older Flutter-web build path is
deprecated.
## Features
- **Login** — email/password against `/api/auth/login`, password show/hide, and a
collapsible **Server settings** section to override the API base URL on-device.
- **Biometric / face sign-in + app lock** — see the dedicated section below.
- **Dashboard** — car list with next-due status badges (date + km, worst-of),
a "shared" chip on cars owned by someone else, pull-to-refresh, **Add car**
FAB, Settings gear, and an admin action (admins only).
- **Car detail** — all spec fields (incl. VIN and transmission / differential /
brake / coolant specs), tabs for **Service history** and **Parts catalog**,
edit car, add/edit/delete service records and parts, a **share** sheet
(owner only), quick odometer update, and delete car (type-to-confirm; cascades).
Actions are gated by the caller's access level (read-only vs write vs owner).
- **Settings** — account (name / email verification / password), appearance
(theme + dark mode, locale, date format, font size), profile (avatar via
`image_picker`, bio), **Security** (biometric toggle), active sessions with
remote logout, and the account-deletion state machine.
- **Admin** — user management screen (list / create / role / reset password /
delete), gated by the admin role.
Sharing/ownership: `Car.access` drives `isOwner` / `canWrite` / `isReadOnly`
getters that gate the UI, mirroring the server's access checks.
## Biometric / face sign-in & app lock
Fingerprint and face-recognition sign-in via `local_auth`, with credentials kept
in Android Keystorebacked secure storage (`flutter_secure_storage`).
- After a successful password login the app offers to **enable biometric login**;
the entered (known-good) credentials are stored securely.
- The login screen then shows **"Sign in with face recognition / fingerprint"**
buttons (labels reflect the enrolled biometric kinds) and auto-prompts once.
On success the stored credentials are replayed against the normal login API, so
each biometric sign-in mints a fresh session. Stale credentials (e.g. after a
password change) auto-disable biometric login.
- **App lock** — the JWT persists, so a valid session normally restores silently.
When biometric login is enabled the app instead starts **locked** (and re-locks
when backgrounded) and shows a lock screen requiring a biometric unlock. A
**30-second grace period** means quick app-switches don't re-lock; a full app
close (process kill) always locks on next launch. "Use password instead" on the
lock screen logs out and returns to the login form.
- Manage it under **Settings → Security** (enabling re-confirms the password).
Android host requirements (already configured, don't revert):
`MainActivity` extends **`FlutterFragmentActivity`** (required by `local_auth`),
and `AndroidManifest.xml` declares `android.permission.USE_BIOMETRIC`.
## Configure the API endpoint
The app talks to `kDefaultApiBase` (see `lib/config.dart`), default
`http://localhost:8080/api`. Override at build time with `--dart-define`, or at
runtime from the login screen's **Server settings** (persisted as `cc_server_url`).
## Run & build
```bash
flutter pub get
# run on a connected device against a LAN server
flutter run -d <device> --dart-define=API_BASE=http://10.2.1.101:8080/api
# build a debug APK for a real phone on the LAN
flutter build apk --debug --dart-define=API_BASE=http://10.2.1.101:8080/api
adb install -r build/app/outputs/flutter-apk/app-debug.apk
adb shell monkey -p com.carcontrole.carcontrol_phone -c android.intent.category.LAUNCHER 1
```
Notes:
- `android/gradle.properties` sets `kotlin.incremental=false` — required because
the project lives on drive `E:` while Gradle/Kotlin caches are on `C:` (the
incremental compiler can't compute cross-root relative paths on Windows).
- `AndroidManifest.xml` sets `android:usesCleartextTraffic="true"` because the API
base is a plain-HTTP LAN URL.
- The API Server must be running and reachable at the configured URL.
## Structure
```
lib/
├── config.dart # default API base URL (kDefaultApiBase)
├── models.dart # Car (+ access getters), ServiceRecord, Part, AuthUser, UserProfile, Session
├── api.dart # ApiClient — the only thing that calls the API Server
├── auth.dart # AuthService (token persistence, app-lock flag, ChangeNotifier)
├── biometric.dart # BiometricAuth — local_auth + secure storage; biometricAuth singleton
├── app_settings.dart # AppSettings (theme/locale/date/font), persisted; drives MaterialApp
├── format.dart # date/km formatting + next-service status (worst-of date/km)
├── main.dart # app root; routes Login / Lock / Dashboard; lifecycle-based re-lock
└── screens/
├── login_screen.dart dashboard_screen.dart car_detail_screen.dart
├── car_form_sheet.dart settings_screen.dart admin_users_screen.dart
└── lock_screen.dart
```