# 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 --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 ```