From ab05f13de91aa89b9805fa8f0e5b46dcb420e714 Mon Sep 17 00:00:00 2001 From: Janez T Date: Sun, 22 Mar 2026 15:48:55 +0100 Subject: [PATCH] fix: Remove contact trails from map #123 --- lib/models/config_profile.dart | 5 - lib/providers/map_provider.dart | 60 +------- lib/screens/map_tab.dart | 84 ++++------ lib/screens/settings_screen.dart | 15 -- .../map_workspace_snapshot_service.dart | 1 - lib/widgets/map/trail_controls.dart | 144 ------------------ 6 files changed, 33 insertions(+), 276 deletions(-) diff --git a/lib/models/config_profile.dart b/lib/models/config_profile.dart index ea581f2..837b156 100644 --- a/lib/models/config_profile.dart +++ b/lib/models/config_profile.dart @@ -263,7 +263,6 @@ class MapWorkspaceProfileSection { final bool? showKrasFireZonesOverlay; final bool? showPlaceNamesOverlay; final bool? showMunicipalityBordersOverlay; - final bool? showAllContactTrails; final bool? hideRepeatersOnMap; const MapWorkspaceProfileSection({ @@ -284,7 +283,6 @@ class MapWorkspaceProfileSection { this.showKrasFireZonesOverlay, this.showPlaceNamesOverlay, this.showMunicipalityBordersOverlay, - this.showAllContactTrails, this.hideRepeatersOnMap, }); @@ -306,7 +304,6 @@ class MapWorkspaceProfileSection { showKrasFireZonesOverlay == null && showPlaceNamesOverlay == null && showMunicipalityBordersOverlay == null && - showAllContactTrails == null && hideRepeatersOnMap == null; Map toJson() => { @@ -327,7 +324,6 @@ class MapWorkspaceProfileSection { 'showKrasFireZonesOverlay': showKrasFireZonesOverlay, 'showPlaceNamesOverlay': showPlaceNamesOverlay, 'showMunicipalityBordersOverlay': showMunicipalityBordersOverlay, - 'showAllContactTrails': showAllContactTrails, 'hideRepeatersOnMap': hideRepeatersOnMap, }; @@ -356,7 +352,6 @@ class MapWorkspaceProfileSection { showPlaceNamesOverlay: json['showPlaceNamesOverlay'] as bool?, showMunicipalityBordersOverlay: json['showMunicipalityBordersOverlay'] as bool?, - showAllContactTrails: json['showAllContactTrails'] as bool?, hideRepeatersOnMap: json['hideRepeatersOnMap'] as bool?, ); } diff --git a/lib/providers/map_provider.dart b/lib/providers/map_provider.dart index 91b3ffa..440413f 100644 --- a/lib/providers/map_provider.dart +++ b/lib/providers/map_provider.dart @@ -37,8 +37,6 @@ class MapProvider with ChangeNotifier { MapCoordinateSpace _targetCoordinateSpace = MapCoordinateSpace.geo; String? _targetMapId; - final Set _visibleContactPaths = {}; - LocationTrail? _currentTrail; bool _isTrailVisible = true; final List _trailHistory = []; @@ -55,7 +53,6 @@ class MapProvider with ChangeNotifier { bool _showPlaceNamesOverlay = false; bool _showMunicipalityBordersOverlay = false; - bool _showAllContactTrails = true; bool _hideRepeatersOnMap = false; LocationTrail? _importedTrail; @@ -72,8 +69,6 @@ class MapProvider with ChangeNotifier { bool get shouldAnimate => _shouldAnimate; MapCoordinateSpace get targetCoordinateSpace => _targetCoordinateSpace; String? get targetMapId => _targetMapId; - Set get visibleContactPaths => Set.unmodifiable(_visibleContactPaths); - LocationTrail? get currentTrail => _currentTrail; bool get isTrailVisible => _isTrailVisible; List get trailHistory => List.unmodifiable(_trailHistory); @@ -91,7 +86,6 @@ class MapProvider with ChangeNotifier { bool get showPlaceNamesOverlay => _showPlaceNamesOverlay; bool get showMunicipalityBordersOverlay => _showMunicipalityBordersOverlay; - bool get showAllContactTrails => _showAllContactTrails; bool get hideRepeatersOnMap => _hideRepeatersOnMap; LocationTrail? get importedTrail => _importedTrail; @@ -365,30 +359,6 @@ class MapProvider with ChangeNotifier { notifyListeners(); } - void toggleContactPath(String publicKeyHex) { - if (_visibleContactPaths.contains(publicKeyHex)) { - _visibleContactPaths.remove(publicKeyHex); - } else { - _visibleContactPaths.add(publicKeyHex); - } - notifyListeners(); - } - - bool isContactPathVisible(String publicKeyHex) { - return _visibleContactPaths.contains(publicKeyHex); - } - - void hideAllPaths() { - _visibleContactPaths.clear(); - notifyListeners(); - } - - void showOnlyPath(String publicKeyHex) { - _visibleContactPaths.clear(); - _visibleContactPaths.add(publicKeyHex); - notifyListeners(); - } - void startTrail() { if (_currentTrail != null && _currentTrail!.isActive) { endTrail(); @@ -556,12 +526,17 @@ class MapProvider with ChangeNotifier { Future _loadInitialState() async { await Future.wait([ loadOverlayState(), - loadTrailSettings(), loadRepeaterVisibilitySettings(), _loadCustomMapState(), + _removeDeprecatedSettings(), ]); } + Future _removeDeprecatedSettings() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_scopedKey('map_show_all_contact_trails')); + } + Future _saveOverlayState() async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool( @@ -610,27 +585,6 @@ class MapProvider with ChangeNotifier { ); } - Future toggleAllContactTrails() async { - _showAllContactTrails = !_showAllContactTrails; - notifyListeners(); - await _saveTrailSettings(); - } - - Future loadTrailSettings() async { - final prefs = await SharedPreferences.getInstance(); - _showAllContactTrails = - prefs.getBool(_scopedKey('map_show_all_contact_trails')) ?? true; - notifyListeners(); - } - - Future _saveTrailSettings() async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setBool( - _scopedKey('map_show_all_contact_trails'), - _showAllContactTrails, - ); - } - Future setHideRepeatersOnMap(bool hide) async { if (_hideRepeatersOnMap == hide) return; _hideRepeatersOnMap = hide; @@ -755,7 +709,6 @@ class MapProvider with ChangeNotifier { 'showKrasFireZonesOverlay': _showKrasFireZonesOverlay, 'showPlaceNamesOverlay': _showPlaceNamesOverlay, 'showMunicipalityBordersOverlay': _showMunicipalityBordersOverlay, - 'showAllContactTrails': _showAllContactTrails, 'hideRepeatersOnMap': _hideRepeatersOnMap, }; } @@ -792,7 +745,6 @@ class MapProvider with ChangeNotifier { _showPlaceNamesOverlay = json['showPlaceNamesOverlay'] as bool? ?? false; _showMunicipalityBordersOverlay = json['showMunicipalityBordersOverlay'] as bool? ?? false; - _showAllContactTrails = json['showAllContactTrails'] as bool? ?? true; _hideRepeatersOnMap = json['hideRepeatersOnMap'] as bool? ?? false; notifyListeners(); } diff --git a/lib/screens/map_tab.dart b/lib/screens/map_tab.dart index 6be52be..be3ae27 100644 --- a/lib/screens/map_tab.dart +++ b/lib/screens/map_tab.dart @@ -27,7 +27,6 @@ import '../services/background_location_service.dart'; import '../services/location_tracking_service.dart'; import '../services/map_marker_service.dart'; import '../services/message_destination_preferences.dart'; -import '../services/trail_color_service.dart'; import '../services/profiles_feature_service.dart'; import '../widgets/map_debug_info.dart'; import '../widgets/map/compass_widget.dart'; @@ -873,7 +872,9 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { if (!hasCustomMap) ListTile( leading: Icon(Icons.add_photo_alternate), - title: Text(AppLocalizations.of(context)!.loadFromGallery), + title: Text( + AppLocalizations.of(context)!.loadFromGallery, + ), subtitle: const Text( 'Use a cave map image instead of GPS tiles', ), @@ -905,7 +906,9 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { ), ListTile( leading: Icon(Icons.swap_horizontal_circle), - title: Text(AppLocalizations.of(context)!.replaceImage), + title: Text( + AppLocalizations.of(context)!.replaceImage, + ), subtitle: const Text( 'Pick a different map from the gallery', ), @@ -941,7 +944,9 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { if (customMapConfig.isCalibrated) ListTile( leading: Icon(Icons.clear), - title: Text(AppLocalizations.of(context)!.clearScale), + title: Text( + AppLocalizations.of(context)!.clearScale, + ), onTap: () async { await mapProvider.clearCustomMapCalibration(); if (!context.mounted) return; @@ -1277,9 +1282,11 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { _stopCustomMapCalibration(); if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.customMapScaleSaved))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.customMapScaleSaved), + ), + ); } } @@ -1419,7 +1426,9 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { contentPadding: EdgeInsets.zero, leading: Icon(Icons.chat_bubble_outline), title: Text(AppLocalizations.of(context)!.openMessage), - subtitle: Text(AppLocalizations.of(context)!.jumpToTheRelatedSarMessage), + subtitle: Text( + AppLocalizations.of(context)!.jumpToTheRelatedSarMessage, + ), onTap: () async { Navigator.pop(sheetContext); await _openSarMarkerMessage( @@ -1655,7 +1664,11 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(AppLocalizations.of(context)!.pleaseSelectADestinationToSendSarMarker), + content: Text( + AppLocalizations.of( + context, + )!.pleaseSelectADestinationToSendSarMarker, + ), backgroundColor: Colors.red, ), ); @@ -1785,7 +1798,9 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(AppLocalizations.of(context)!.sarMarkerBroadcastToPublicChannel), + content: Text( + AppLocalizations.of(context)!.sarMarkerBroadcastToPublicChannel, + ), backgroundColor: Colors.orange, duration: Duration(seconds: 2), ), @@ -2514,52 +2529,6 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { ); }, ), - // Contact trail polylines (rendered before user trail and markers) - Consumer( - builder: (context, mapProvider, _) { - // Determine which contacts to show trails for - final contactsToShow = mapProvider.showAllContactTrails - ? contactsWithLocation // Show all when master toggle is ON - : contactsWithLocation.where( - (contact) => mapProvider.isContactPathVisible( - contact.publicKeyHex, - ), - ); // Individual toggles - - return PolylineLayer( - polylines: contactsToShow - .where( - (contact) => contact.advertHistory.length >= 2, - ) - .map((contact) { - // Use TrailColorService for consistent, emoji-based colors - final color = TrailColorService.getTrailColor( - contact, - ); - - return Polyline( - points: contact.advertHistory - .map((advert) => advert.location) - .toList(), - color: color.withValues( - alpha: 0.95, - ), // More opaque for better visibility - strokeWidth: - 4.5, // Thicker for better visibility on all map backgrounds - borderColor: Colors.white.withValues( - alpha: 0.6, - ), // Stronger border contrast - borderStrokeWidth: 2.0, // Wider border - // DASHED pattern to distinguish from solid user trail - pattern: StrokePattern.dashed( - segments: [8, 4], - ), - ); - }) - .toList(), - ); - }, - ), // Location trail layer (rendered after paths, before drawings) const LocationTrailLayer(), ], @@ -2604,7 +2573,8 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { context: context, mapRotation: _getMapRotation(), userPosition: _locationService.currentPosition, - estimatedLocations: contactsProvider.estimatedLocations, + estimatedLocations: + contactsProvider.estimatedLocations, onTap: (contact) { _showDetailedCompassWithContact( context, diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 31968e2..dc4eec4 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -1463,21 +1463,6 @@ class _SettingsScreenState extends State { }, ), ), - Consumer( - builder: (context, mapProvider, child) => SwitchListTile( - secondary: Icon(Icons.timeline), - title: Text( - AppLocalizations.of(context)!.showAllContactTrailsLabel, - ), - subtitle: const Text( - 'Display location trails for all contacts that have history', - ), - value: mapProvider.showAllContactTrails, - onChanged: (value) async { - await mapProvider.toggleAllContactTrails(); - }, - ), - ), Consumer( builder: (context, mapProvider, child) => SwitchListTile( secondary: Icon(Icons.router_outlined), diff --git a/lib/services/map_workspace_snapshot_service.dart b/lib/services/map_workspace_snapshot_service.dart index f65347c..8cf5725 100644 --- a/lib/services/map_workspace_snapshot_service.dart +++ b/lib/services/map_workspace_snapshot_service.dart @@ -64,7 +64,6 @@ class MapWorkspaceSnapshotService { showPlaceNamesOverlay: mapProvider.showPlaceNamesOverlay, showMunicipalityBordersOverlay: mapProvider.showMunicipalityBordersOverlay, - showAllContactTrails: mapProvider.showAllContactTrails, hideRepeatersOnMap: mapProvider.hideRepeatersOnMap, ); } diff --git a/lib/widgets/map/trail_controls.dart b/lib/widgets/map/trail_controls.dart index f2c112b..b37eb73 100644 --- a/lib/widgets/map/trail_controls.dart +++ b/lib/widgets/map/trail_controls.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../../models/contact.dart'; import '../../models/location_trail.dart'; import '../../providers/map_provider.dart'; -import '../../providers/contacts_provider.dart'; import '../../services/gpx_service.dart'; -import '../../services/trail_color_service.dart'; import '../../l10n/app_localizations.dart'; /// Trail management controls widget @@ -14,17 +11,8 @@ class TrailControls extends StatelessWidget { void _showTrailMenu(BuildContext context) { final mapProvider = Provider.of(context, listen: false); - final contactsProvider = Provider.of( - context, - listen: false, - ); final l10n = AppLocalizations.of(context)!; - // Get contacts with trails (advertHistory >= 2 points) - final contactsWithTrails = contactsProvider.contactsWithLocation - .where((c) => c.advertHistory.length >= 2) - .toList(); - showModalBottomSheet( context: context, isScrollControlled: true, @@ -183,110 +171,6 @@ class TrailControls extends StatelessWidget { ), ), - const SizedBox(height: 8), - const Divider(), - const SizedBox(height: 8), - - // Contact Trails Section - Row( - children: [ - const Icon(Icons.people, size: 20), - SizedBox(width: 8), - Text( - l10n.contactTrails, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 12), - - // Show All Contact Trails toggle - SwitchListTile( - secondary: Icon(Icons.route), - title: Text(l10n.showAllContactTrails), - subtitle: Text( - contactsWithTrails.isEmpty - ? l10n.noContactsWithLocationHistory - : mapProvider.showAllContactTrails - ? l10n.showingTrailsForContacts( - contactsWithTrails.length, - ) - : l10n.individualContactTrails, - ), - value: mapProvider.showAllContactTrails, - onChanged: contactsWithTrails.isNotEmpty - ? (value) { - mapProvider.toggleAllContactTrails(); - setModalState(() {}); // Update modal UI - } - : null, // Disable if no contacts with trails - ), - - // Individual contact trails (when "show all" is OFF) - if (!mapProvider.showAllContactTrails && - contactsWithTrails.isNotEmpty) - ExpansionTile( - title: Text(l10n.individualContactTrails), - initiallyExpanded: false, - children: contactsWithTrails.map((contact) { - final trailColor = TrailColorService.getTrailColor( - contact, - ); - final isVisible = mapProvider.isContactPathVisible( - contact.publicKeyHex, - ); - - return SwitchListTile( - // Color indicator with emoji - secondary: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (contact.roleEmoji != null) - Text( - contact.roleEmoji!, - style: const TextStyle(fontSize: 18), - ), - const SizedBox(width: 4), - Container( - width: 16, - height: 16, - decoration: BoxDecoration( - color: trailColor, - border: Border.all( - color: Colors.white, - width: 2, - ), - borderRadius: BorderRadius.circular(3), - ), - ), - ], - ), - title: Row( - children: [ - Expanded(child: Text(contact.displayName)), - IconButton( - tooltip: l10n.exportToClipboard, - icon: const Icon(Icons.file_download_outlined), - onPressed: () => - _exportContactTrail(context, contact), - ), - ], - ), - subtitle: Text( - '${contact.advertHistory.length} points', - ), - value: isVisible, - onChanged: (value) { - mapProvider.toggleContactPath(contact.publicKeyHex); - setModalState(() {}); // Update modal UI - }, - ); - }).toList(), - ), - const SizedBox(height: 8), // Close button @@ -360,34 +244,6 @@ class TrailControls extends StatelessWidget { } } - Future _exportContactTrail(BuildContext context, Contact contact) async { - await _exportTrail( - context, - _buildContactTrail(contact), - customName: '${contact.displayName} Trail', - ); - } - - LocationTrail _buildContactTrail(Contact contact) { - final sortedHistory = [...contact.advertHistory] - ..sort((a, b) => a.timestamp.compareTo(b.timestamp)); - - return LocationTrail( - id: 'contact_${contact.publicKeyHex}', - isActive: false, - startTime: sortedHistory.first.timestamp, - endTime: sortedHistory.last.timestamp, - points: sortedHistory - .map( - (advert) => TrailPoint( - position: advert.location, - timestamp: advert.timestamp, - ), - ) - .toList(), - ); - } - void _showClearConfirmation( BuildContext context, MapProvider mapProvider,