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

93 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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