Offline range and battery estimation app for the TailG F52, built on a physics model (cruise + congestion overhead) with real-trip calibration data collection for future ML refinement.
ev_range_tracker/
├── android/ # generated by `flutter create .`
├── ios/ # generated by `flutter create .`
├── analysis/
│ └── analyze_trips.py # offline: turns exported CSVs into
│ # calibration constants + residual model
├── lib/
│ ├── main.dart # Hive init, theming, 3-tab root shell
│ ├── theme/
│ │ └── app_theme.dart # Material You theme + tank gauge colors
│ ├── models/ # Hive data models
│ │ ├── vehicle_profile.dart # F52 spec + calibration constants
│ │ ├── trip_sample.dart # raw 1Hz GPS samples
│ │ ├── trip_metadata.dart # per-trip rider/passenger/charge state
│ │ └── bar_drop_event.dart # mid-trip "bar just dropped" anchors
│ ├── services/
│ │ ├── range_engine.dart # pure-Dart physics + coulomb counter
│ │ └── trip_logger.dart # GPS logging, start/stop, CSV export
│ ├── screens/
│ │ ├── home_screen.dart # dashboard: tank gauge, quick actions
│ │ ├── active_trip_screen.dart# live ride: speed/distance/bar-drop
│ │ ├── trip_history_screen.dart
│ │ └── vehicle_setup_screen.dart
│ └── widgets/
│ ├── battery_gauge.dart # the signature tank/battery gauge
│ └── stat_tile.dart # reusable labeled stat display
├── test/
│ └── range_engine_test.dart # validates physics against real benchmarks
├── pubspec.yaml
└── README.md
-
Copy
lib/,test/,pubspec.yaml,analysis/into your existingEV_Range_Trackerproject folder (keep your existingandroid//ios/). -
Install dependencies:
flutter pub get
-
Generate Hive adapter code (needed for every
@HiveTypemodel —vehicle_profile.dart,trip_sample.dart,trip_metadata.dart,bar_drop_event.dart):dart run build_runner build --delete-conflicting-outputs
-
Run the tests:
flutter test -
Run the app:
flutter run
Wired: Hive persistence, the 3-tab shell (Home/History/Settings), the
tank gauge UI, start/stop ride flow, mid-trip bar-drop logging, CSV export
from TripLogger.
Deliberately stubbed for you to connect:
ActiveTripScreen's speed/distance display is currently just a UI shell with a stopwatch — it doesn't yet callengine.tick()from live GPS. WireTripLogger's position stream to also callRangeEngine.tick()per sample (they're currently separate so you can test each independently).- A "Share Export" button using
share_plusto send a trip's CSVs off the phone — the export logic exists inTripLogger.exportTripToCsv(), just needs a button calling it +Share.shareXFiles(...). - GPS permission request flow (
geolocator'srequestPermission()) — not yet called anywhere; add it beforeTripLogger.startTrip().
The tank gauge (widgets/battery_gauge.dart) is deliberately shaped
like a battery with a liquid fill level, not a generic circular ring —
it's meant to visually reinforce that this number comes from the
software energy-tank model, not the bike's own (untrustworthy) bars.
Color shifts teal → amber → red via TankColors, matching the
amber/red distance-based alerts from the original feature spec.
Add these inside <manifest> (alongside the location permissions from
before) in android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />And this inside <application> (do not rename the service):
<service
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
android:foregroundServiceType="location"
android:exported="false" />These come straight from flutter_foreground_task 9.2.2's own setup docs — pinned exactly to that version, not guessed.
- Map —
flutter_map+ CartoDB Dark Matter tiles, with a hand-rolled offline-first tile provider (offline_tile_provider.dart) instead of flutter_map_tile_caching. That package depends on ObjectBox (a native database) — one more native-build surface on top of the Kotlin issues you already hit. The plain-file approach only usesFileImage/NetworkImage, both simple and stable. - Offline downloads — Settings > Offline Map, pre-downloads tiles for your Mirpur DOHS <-> Tongi corridor (real coordinates, buffered). Standard Slippy Map tile math, nothing exotic.
- Trip detail screen — tap any trip in History for its route map, distance/duration/avg+max speed/stop count/elevation gain, notes, and its bar-drop + alert timeline.
- Persistent notification —
trip_foreground_task.dart, using the exact current (9.2.2) TaskHandler API confirmed against the package's own docs. - Export format — now 2 files per trip:
_samples.csv(raw GPS, genuinely tabular) and_trip.json(metadata + bar drops + alerts — these fit JSON's nested-list shape far better than forced-flat CSV).