7.6 KiB
Advertisement Path Tracking Implementation
Overview
Contacts now automatically track their advertisement location history, allowing visualization of movement paths on the map.
Implementation Details
1. Data Model (lib/models/)
AdvertLocation Model
File: lib/models/advert_location.dart
- Stores a single GPS point with timestamp
- Provides
timeAgodisplay formatting - Immutable value object with proper equality implementation
Contact Model Updates
File: lib/models/contact.dart
New fields:
advertHistory: List<AdvertLocation>- Stores up to 100 most recent location points
New methods:
addAdvertLocation(LatLng, DateTime)- Intelligently adds location to history- Deduplicates points (skips if <5m apart and <60s interval)
- Maintains max 100 points (oldest removed first)
- Uses Haversine formula for distance calculation
2. State Management (lib/providers/)
ContactsProvider Updates
File: lib/providers/contacts_provider.dart
Modified: addOrUpdateContact() method
- Automatically adds advertisement location to history when contact is updated
- Preserves existing history when updating contacts
- Timestamp extracted from
lastAdvertfield (Unix seconds)
MapProvider Updates
File: lib/providers/map_provider.dart
New state:
_visibleContactPaths: Set<String>- Tracks which paths are currently visible
New methods:
toggleContactPath(publicKeyHex)- Show/hide path for specific contactisContactPathVisible(publicKeyHex)- Check path visibilityhideAllPaths()- Clear all visible pathsshowOnlyPath(publicKeyHex)- Isolate single contact's path
3. Map Rendering (lib/screens/)
MapTab Updates
File: lib/screens/map_tab.dart
Added: PolylineLayer before MarkerLayer
- Renders blue polylines with white borders (3px stroke + 1px border)
- Only renders paths for contacts marked as visible in MapProvider
- Only renders if contact has ≥2 location points
- Uses
Consumer<MapProvider>to reactively update when visibility changes
Visual styling:
- Color:
Colors.bluewith 70% opacity - Border:
Colors.whitewith 50% opacity - Stroke width: 3px (main), 1px (border)
4. User Interface (lib/widgets/)
DetailedCompassDialog Updates
File: lib/widgets/map/detailed_compass_dialog.dart
Added: Path toggle button in contact detail view
- Only visible when contact has ≥2 location points
- Shows route icon (filled when active, outlined when inactive)
- Tooltip displays current state and point count
- Primary color when path is visible
- Uses
Consumer<MapProvider>for reactivity
User flow:
- Tap contact marker on map → opens detailed compass dialog
- If contact has movement history (≥2 points), path toggle button appears
- Tap button → path appears on map as blue polyline
- Tap again → path disappears
5. Data Flow
BLE Device → PUSH_CODE_NEW_ADVERT (0x8A)
↓
FrameParser.parseContact()
↓
ContactsProvider.addOrUpdateContact()
↓
Contact.addAdvertLocation() [auto-deduplication]
↓
advertHistory updated (max 100 points)
↓
MapTab.PolylineLayer renders if path visible
6. Storage Behavior
Persistence: Advertisement history is persisted via ContactStorageService
- Uses
contact_storage.jsonin app documents directory - Automatically saved when contacts are updated
- Loaded on app startup
Capacity: Each contact stores up to 100 location points
- Oldest points automatically removed when limit reached
- Prevents unbounded memory growth
Usage Example
-
Automatic tracking - No user action required:
Contact broadcasts location → History automatically updated -
View path on map:
Tap contact marker → Path toggle button → Tap to show → Blue line appears -
Multiple paths:
Each contact has independent path visibility Can show multiple paths simultaneously
Performance Considerations
-
Deduplication: Prevents excessive data accumulation for stationary contacts
- Skip if <5 meters apart AND <60 seconds interval
-
Bounded history: Max 100 points per contact
- Typical SAR operation: 1 point/minute × 8 hours = 480 points (trimmed to 100)
-
Conditional rendering: Polylines only rendered when:
- Path visibility enabled via MapProvider
- Contact has ≥2 location points
-
Memory efficiency:
- Each AdvertLocation: ~48 bytes (2 doubles + DateTime)
- Max per contact: 100 × 48 = ~4.8KB
- 50 contacts: ~240KB total
Future: GPX Export (Not Implemented)
Rationale for Deferring
GPX export was intentionally NOT implemented in this iteration to:
- Validate path tracking UX first
- Gather user feedback on data granularity needs
- Determine preferred export formats (GPX vs KML vs GeoJSON)
Implementation Considerations
When implementing GPX export, consider:
-
Track segmentation:
<trk> <name>Contact Name - YYYY-MM-DD</name> <trkseg> <trkpt lat="46.0569" lon="14.5058"> <time>2025-01-15T10:30:00Z</time> </trkpt> <!-- More points --> </trkseg> </trk> -
Metadata:
- Contact name
- Date range of track
- Device type (from contact telemetry)
- Total distance traveled
- Duration
-
Gap handling:
- Break into segments if gap >15 minutes between points
- Prevents drawing straight lines across large time gaps
-
Multi-contact export:
- Option to export all visible paths as separate tracks
- Single GPX file with multiple
<trk>elements
-
UI integration:
- Add "Export Path" button in detailed compass dialog
- Share sheet for exporting GPX file
- Option to select date range
Recommended Package
dependencies:
gpx: ^2.2.0 # GPX file generation and parsing
Sample Implementation (Future)
import 'package:gpx/gpx.dart';
String exportContactPathToGpx(Contact contact) {
final gpx = Gpx();
gpx.creator = 'MeshCore SAR';
final track = Trk();
track.name = '${contact.advName} - ${DateTime.now().toIso8601String()}';
final segment = Trkseg();
for (final point in contact.advertHistory.reversed) {
segment.trkpts.add(Wpt(
lat: point.location.latitude,
lon: point.location.longitude,
time: point.timestamp,
));
}
track.trksegs.add(segment);
gpx.trks.add(track);
return GpxWriter().asString(gpx, pretty: true);
}
Testing Checklist
- Advertisement locations automatically tracked when contact updates
- Deduplication prevents duplicate points for stationary contacts
- History limited to 100 points per contact
- Path toggle button appears only when ≥2 points exist
- Polyline renders correctly on map
- Path visibility persists across dialog open/close
- Multiple contact paths can be visible simultaneously
- GPX export (deferred to future iteration)
Known Limitations
-
No manual path clearing: Users cannot manually clear a contact's path history
- Workaround: Path auto-trims to 100 points
-
No date range filtering: Cannot view path for specific time period
- All points always rendered (up to 100)
-
No distance/duration display: Path metadata not calculated
- Future enhancement: Show "Total: 2.4km over 3h"
Migration Notes
Existing contacts: No migration required
- Existing contacts start with empty
advertHistory - History begins accumulating from first update after app upgrade
- No data loss or corruption risk
Storage format: JSON-compatible
advertHistoryserialized as array of objects- Standard DateTime ISO-8601 strings
- LatLng as lat/lon decimal degrees