MO-Fuel-Tax-Back/README.md
2026-08-08 08:22:14 -05:00

4.5 KiB
Raw Permalink Blame History

Fuel Tax Tracker

A Flutter app (Android + iOS) for logging fuel purchases per vehicle. Snap a photo of a gas receipt, it OCRs the gallons/price-per-gallon/total on-device, you confirm or correct the numbers, and it's saved alongside the receipt photo to a data file in a folder you choose.

Features

  • Manage a list of vehicles (make, model, color, license plate).
  • Capture a fuel receipt photo per vehicle and OCR it on-device with Google ML Kit — no internet connection or API key required.
  • Review/edit the parsed gallons, price per gallon, and total cost before saving (OCR on printed receipts is usually good but not perfect).
  • Per-vehicle fuel log with running gallons total, tap-to-zoom receipt photos, and delete.
  • Settings screen to choose where receipt photos and the data file are stored (defaults to the app's own documents folder; existing data is copied over when you change it).

Project layout

lib/
  models/       Vehicle, FuelEntry — plain data classes with JSON (de)serialization
  services/
    app_state.dart        In-memory state + CRUD, backed by StorageService, exposed via Provider
    storage_service.dart  Owns the data.json file + receipts/ folder and the configurable save path
    ocr_service.dart      Thin wrapper around google_mlkit_text_recognition
    receipt_parser.dart   Regex-based extraction of gallons/price/total from OCR text
  screens/      One file per screen (vehicle list, add/edit vehicle, vehicle detail,
                confirm fuel entry, receipt viewer, settings)

Data is stored as a single fuel_tax_data.json file plus a receipts/ subfolder of photos, both inside whatever directory Settings points at.

Running it

flutter pub get
flutter run            # with a device/emulator connected or a simulator booted

To build release artifacts:

flutter build apk --release      # Android
flutter build ios --release      # iOS (requires a full Xcode install + signing setup)

This was scaffolded and verified with Flutter 3.44.9. flutter analyze and flutter test are clean, and flutter build apk --debug has been confirmed to produce a working APK.

Permissions

  • Camera: NSCameraUsageDescription (iOS, ios/Runner/Info.plist) and android.permission.CAMERA (Android, AndroidManifest.xml) are already set up for receipt capture.
  • Storage: no explicit storage permission is declared. The default save location is inside the app's own sandbox (no permission needed). If you point Settings at a location outside the sandbox, the OS-native folder picker (via file_picker) is what grants access — see the caveat below.

Known caveats / things to revisit

  • file_picker is pinned to 10.3.10, not the latest release. Versions 11.0.0–11.0.3 skip applying the Kotlin Gradle plugin when they detect AGP 9+ (assuming AGP's built-in Kotlin support handles it), but that isn't actually wired up for library modules in this Flutter/AGP combination yet, so the plugin's own Kotlin sources never get compiled and the build fails with cannot find symbol: FilePickerPlugin. 10.3.11 fixes that but is retracted on pub.dev, hence 10.3.10. Worth revisiting this pin next time you bump dependencies — check the package's CHANGELOG for when this is properly resolved upstream.
  • iOS folder picking and app restarts: file_picker's directory picker on iOS uses UIDocumentPickerViewController, which hands back a security-scoped URL. This app does not currently persist a security-scoped bookmark for that URL, so if you pick a folder outside the app's own sandbox (e.g. an iCloud Drive folder) on iOS, continued write access after an app restart is not guaranteed. Picking the default in-sandbox location, or a folder on Android, does not have this limitation. If cross-restart external storage on iOS matters for your use case, this needs a proper bookmark implementation (NSURL bookmarkData + startAccessingSecurityScopedResource).
  • Receipt parsing is best-effort regex matching on the OCR'd text (lib/services/receipt_parser.dart), tuned against common receipt phrasing ("GALLONS", "PRICE/GAL", "PPG", "TOTAL", etc.). Unusual receipt layouts may parse partially or not at all — the confirm screen always lets you fill in or correct whatever wasn't found.
  • No automated tests exercise the OCR or camera capture path itself (that requires a real device/emulator with a camera); receipt_parser_test.dart covers the parsing logic against fixed OCR text.