Initial commit
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
# 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 Keystore–backed 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
|
||||
```
|
||||
Reference in New Issue
Block a user