feat(import): import dives directly from a connected Garmin device (USB) - #813
feat(import): import dives directly from a connected Garmin device (USB)#813etlami wants to merge 3 commits into
Conversation
A Garmin Descent isn't a libdivecomputer serial/BLE device -- plugged in by cable it mounts as a USB drive whose activities are FIT files under GARMIN/Activity. Until now users had to find that folder and pick the files by hand. Add a desktop-only "Import from Garmin Device" button to the import wizard's file step. It detects the mounted volume (by looking for a non-empty GARMIN/Activity folder, regardless of volume name), keeps only dive FITs -- non-dive activities (runs/rides) and corrupt files parse to null and are skipped -- and feeds them into the existing single/batch triage -> duplicate-check -> import pipeline. No new credentials, network, or backend: this is pure filesystem access, reusing FitParserService for the dive filter. Falls back to the existing "Choose Folder" button when no device is detected. Tests: - garmin_device_detector_test: volume detection (name-agnostic, empty folders ignored, multi-volume) and .fit listing. - universal_import_garmin_test: single/batch import from a real dive FIT fixture, corrupt-file skipping, and the no-device / no-dives errors.
The new import-button key was only in the English template; add real translations so the arb_parity guard passes.
|
📦 Build artifacts for this PR · commit
Artifacts expire in 7 days. Downloading requires being signed in to GitHub. macOS needs two extractions: unzip the downloaded artifact, then unzip the Updated automatically on each push. |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
@etlami Instead of including the "Import from Garmin Device" button on everyone's import dialog, what about we scan for Garmin volumes automatically when the import wizard initializes, and only show the option when a Garmin is detected? Functionally everything stays the same, and it keeps the UI cleaner for everyone who doesn't own a Garmin. |
Resolves conflicts in the 10 translated ARB files. Both branches appended a new key immediately after the opening brace, so every locale collided on the same line: - PR side: universalImport_action_importFromGarmin - main side: diveSites_list_menu_select Both keys are independent additions, so each conflict is resolved as a union (keep both). Verified per locale that the resolved file is valid JSON and contains both keys, and that `flutter gen-l10n` reproduces the committed app_localizations_*.dart byte-for-byte.
Problem
A Garmin Descent isn't a libdivecomputer serial/BLE device — plugged in by cable it mounts as a USB mass-storage drive, and its activities live as FIT files under
GARMIN/Activity. The FIT import already parses these perfectly, but users had to manually locate that folder and pick the files. There was no "plug in → import" affordance.This is a no-backend step toward the roadmap's Garmin items (
docs/FEATURE_ROADMAP.md§"Garmin Dive sync" / "Automatic conversion from Garmin Descent"): it needs no Garmin Connect account, credentials, or network — just the filesystem.Changes
GarminDeviceDetector(universal_import/data/services/): scans mounted-volume roots (macOS/Volumes, Linux/media+/run/media, Windows drive letters) for a non-emptyGARMIN/Activityfolder. Recognises the device by that folder, regardless of volume name. Mount-point scan is injectable so it's unit-testable against a temp tree.UniversalImportNotifier.importFromGarminDevice(): detects the device, lists its.fitfiles, and keeps only dive activities — non-dive FITs (runs/rides) and corrupt files parse tonullvia the existingFitParserServiceand are skipped — then hands the result to the existing single/batch triage → duplicate-check → import pipeline. Falls back with a clear message pointing at "Choose Folder" when nothing is detected.No changes to the parse/save path — this only adds a new source into the existing flow.
Scope / notes
Tests
garmin_device_detector_test.dart: name-agnostic detection, empty/absentGARMIN/Activityignored, multi-volume,.fit-only listing.universal_import_garmin_test.dart: single- and multi-dive import from a real dive FIT fixture, corrupt-file skipping, and the no-device / no-dives error states.Verified locally (Flutter 3.44.8 / Dart 3.12.2):
flutter analyze libclean,dart formatclean, fulltest/features/universal_importsuite green.