CarDash is a Flutter/Dart Android home-screen launcher for a car tablet. It has one configurable home page (clock, battery/Wi-Fi, media volume, up to 16 home buttons), launches other apps full-screen, keeps the screen awake by default (configurable), offers a long-press night screen, light/dark/automatic themes, and an optional four-digit PIN requested only on cold start.
Stack: Flutter 3.47.8 / Dart 3.13.7, Android minSdk 29 (Android 10), compile/targetSdk 36, Kotlin bridges, AGP 9.1.0, Gradle 9.3.1, JDK 21. Package com.wasp.cardash_launcher. Landscape-only, tablet-first (1280×800).
You are the implementation agent: keep the app buildable, tested, documented, committed, and pushed. Always call this project CarDash.
Run from the repository root.
flutter pub get
flutter analyze
flutter test
flutter test test/pin_service_test.dart
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
flutter build apk --debug
flutter build apk --release
./tool/check.sh # analyze + tests
./tool/emulator.sh 35 # boot the Android 15 tablet (headless)
./tool/emulator.sh 29 # boot the Android 10 tablet
./tool/install.sh debug # build with JDK 21, install, set HOME role
./tool/install.sh release
ADB lives at /opt/android-sdk/platform-tools/adb; the SDK is at /opt/android-sdk. AVDs: cardash_tablet_api35, cardash_tablet_api29 (1280×800, landscape).
lib/core/ — theme tokens (AppColors, AccentChoice), AppTheme, ThemeController, DisplayController (keep-awake), reusable widgets.lib/data/models/ — AppEntry, SlotType, SlotAssignment, SystemStatus, VolumeInfo, MediaPlayback, OBD/weather models.lib/data/services/ — NativeBridge, TelemetryBridge, WeatherService, UpdateService, PinService, secure-store adapter.lib/data/repositories/ — SettingsRepository, the SSOT for theme, night window, slot assignments, visibility flags, pinned apps, onboarding time, and speed/temperature choices.lib/features/home/ — home screen, LauncherController, SystemController, ObdController, WeatherController, GpsSpeedController, TelemetrySettingsController (speed/temperature visibility and sources), equalizer, night overlay.lib/features/apps/ — All Apps screen and app-picker sheet.lib/features/settings/ — General, Media, OBD-II, Speed, Temperature, Security and Updates cards.lib/features/lock/ — PIN pad, lock screen, cold-start LockController.android/app/src/main/kotlin/com/wasp/cardash_launcher/ — MainActivity, LauncherBridge, SystemBridge, TelemetryBridge, ObdService (Classic SPP ELM327), MediaBridge, CardashNotificationListener; SystemBridge also dispatches media keys and applies the keep-awake flag.tool/ — emulator/install/check scripts. docs/ — implementation plan and design notes.Rules:
SettingsRepository; never add ad-hoc SharedPreferences keys in widgets.ChangeNotifiers consumed with provider; no business logic in widgets.LauncherPlatform so controllers stay testable.Persistence pattern:
_slots[slot] = app.toAssignment();
await _settings.setSlot(slot, app.toAssignment());
notifyListeners();
context.colors / AppTheme tokens, not hard-coded colors in widgets.SlotType for fixed slots or use LauncherController pinned-app APIs; fixed slots stay stylized, pinned apps default to their own icon, and icon edits use style_icon_choices.dart.LauncherPlatform, NativeBridge, the relevant Kotlin bridge, and the test fake if used.LauncherPlatform/SecureStore; SharedPreferences.setMockInitialValues({}); widget tests wrap in MaterialApp(theme: AppTheme.light()). Test files live in test/ and end with _test.dart.dart format lib test before committing. Conventional Commits (feat:, fix:, docs:, chore:). After every commit, push to origin (git push origin main); never leave commits unpushed and never force-push.README.md, and docs/IMPLEMENTATION_PLAN.md in the same commit as any behavior change.SYSTEM_ALERT_WINDOW, kiosk, root, or split-screen feature. The agreed product launches apps full-screen.android/local.properties, keystores, build outputs, APKs, or secrets.JAVA_HOME=/usr/lib/jvm/java-21-openjdk for builds./opt/android-sdk.QUERY_ALL_PACKAGES is required to list every app. It is acceptable for a launcher but is not a Play-Store-distributable choice as-is; document it.resumed lifecycle event; Android may restore system bars after dialogs or role prompts.cmd role add-role-holder; the Settings row uses RoleManager.LauncherController.homeRoleHintShown; do not show it again.onboarding.firstLaunchAt and HomeHint: it is visible only for the first ten minutes after a fresh installation. Upgraded installs are migrated to an expired window and never see it again.StatusCluster and does not consume a slot. Avoid unbounded ListViews inside Columns.tool/emulator.sh starts a headless AVD with the host GPU; SwiftShader segfaulted on this machine's API 35 image. Use CARDASH_GPU=swiftshader_indirect only as a fallback. Use adb exec-out screencap -p for screenshots; there may be multiple adb binaries on PATH, so use the absolute one.MainActivity consumes hardware volume keys and adjusts STREAM_MUSIC so they match the dashboard. Do not remove that override without providing another sync path; otherwise Android controls the ring stream when no media is playing.FLAG_KEEP_SCREEN_ON is controlled by DisplayController from display.keepScreenOn (default on) through SystemBridge; do not hard-code it in MainActivity or move it to a wake lock.AudioManager.isMusicActive. Now-playing title/progress use the notification-listener component and active MediaSession; never read or store notification content.TelemetrySettingsController.speedEnabled is on. OBD-II is preferred when connected and fresh; the GPS fallback is used when OBD speed is off or unavailable. GPS starts only with already-granted fine location and GPS; never prompt from that path, and clamp stationary noise below 2 km/h to 0.TelemetrySettingsController.temperatureEnabled is on. OBD ambient is preferred when connected and fresh; Open-Meteo/location weather is the fallback, and the source switch only appears while an OBD-II adapter is selected.UpdateController.updateAvailable is true.tool/install.sh or use Settings > Home app.Remote: origin = https://git.krasi.net/kkazakov/car-interface-android.git, branch main. Push with git push origin main; never force-push. The host runs Gogs (not Gitea); the public Releases page is https://git.krasi.net/kkazakov/car-interface-android/releases.
Release checklist:
version: in pubspec.yaml and appVersion in lib/core/app_version.dart identical. Increment versionCode../tool/check.sh.JAVA_HOME=/usr/lib/jvm/java-21-openjdk flutter build apk --release --target-platform android-arm,android-arm64,android-x64releases/cardash-android-<version>.apk, write releases/cardash-android-<version>.apk.sha256, update releases/README.md, then commit and push./releases/new: tag v<version>, target main, title/body, link the raw APK:
https://git.krasi.net/kkazakov/car-interface-android/raw/main/releases/cardash-android-<version>.apk.
Gogs attachments cap at 32 MB, so the ~54 MB universal APK is linked from the release body instead of attached.https://git.krasi.net/api/v1/repos/kkazakov/car-interface-android/releases,
selects the highest published semver tag, and downloads
/raw/main/releases/cardash-android-<version>.apk. No app-side release configuration is needed.C2:8E:5B:0B:E0:F8:27:EE:12:22:B0:62:9F:EE:1C:F2:7E:14:3F:78:95:86:1E:3F:34:15:A6:E5:3A:06:BE:ED); never let Gradle generate a fresh debug key for a release, or in-place updates will fail. Android still requires the user to confirm installation and allow unknown-app installs once; never attempt silent installation.