mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
fix: Remove contact trails from map #123
This commit is contained in:
@@ -263,7 +263,6 @@ class MapWorkspaceProfileSection {
|
|||||||
final bool? showKrasFireZonesOverlay;
|
final bool? showKrasFireZonesOverlay;
|
||||||
final bool? showPlaceNamesOverlay;
|
final bool? showPlaceNamesOverlay;
|
||||||
final bool? showMunicipalityBordersOverlay;
|
final bool? showMunicipalityBordersOverlay;
|
||||||
final bool? showAllContactTrails;
|
|
||||||
final bool? hideRepeatersOnMap;
|
final bool? hideRepeatersOnMap;
|
||||||
|
|
||||||
const MapWorkspaceProfileSection({
|
const MapWorkspaceProfileSection({
|
||||||
@@ -284,7 +283,6 @@ class MapWorkspaceProfileSection {
|
|||||||
this.showKrasFireZonesOverlay,
|
this.showKrasFireZonesOverlay,
|
||||||
this.showPlaceNamesOverlay,
|
this.showPlaceNamesOverlay,
|
||||||
this.showMunicipalityBordersOverlay,
|
this.showMunicipalityBordersOverlay,
|
||||||
this.showAllContactTrails,
|
|
||||||
this.hideRepeatersOnMap,
|
this.hideRepeatersOnMap,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -306,7 +304,6 @@ class MapWorkspaceProfileSection {
|
|||||||
showKrasFireZonesOverlay == null &&
|
showKrasFireZonesOverlay == null &&
|
||||||
showPlaceNamesOverlay == null &&
|
showPlaceNamesOverlay == null &&
|
||||||
showMunicipalityBordersOverlay == null &&
|
showMunicipalityBordersOverlay == null &&
|
||||||
showAllContactTrails == null &&
|
|
||||||
hideRepeatersOnMap == null;
|
hideRepeatersOnMap == null;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
@@ -327,7 +324,6 @@ class MapWorkspaceProfileSection {
|
|||||||
'showKrasFireZonesOverlay': showKrasFireZonesOverlay,
|
'showKrasFireZonesOverlay': showKrasFireZonesOverlay,
|
||||||
'showPlaceNamesOverlay': showPlaceNamesOverlay,
|
'showPlaceNamesOverlay': showPlaceNamesOverlay,
|
||||||
'showMunicipalityBordersOverlay': showMunicipalityBordersOverlay,
|
'showMunicipalityBordersOverlay': showMunicipalityBordersOverlay,
|
||||||
'showAllContactTrails': showAllContactTrails,
|
|
||||||
'hideRepeatersOnMap': hideRepeatersOnMap,
|
'hideRepeatersOnMap': hideRepeatersOnMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -356,7 +352,6 @@ class MapWorkspaceProfileSection {
|
|||||||
showPlaceNamesOverlay: json['showPlaceNamesOverlay'] as bool?,
|
showPlaceNamesOverlay: json['showPlaceNamesOverlay'] as bool?,
|
||||||
showMunicipalityBordersOverlay:
|
showMunicipalityBordersOverlay:
|
||||||
json['showMunicipalityBordersOverlay'] as bool?,
|
json['showMunicipalityBordersOverlay'] as bool?,
|
||||||
showAllContactTrails: json['showAllContactTrails'] as bool?,
|
|
||||||
hideRepeatersOnMap: json['hideRepeatersOnMap'] as bool?,
|
hideRepeatersOnMap: json['hideRepeatersOnMap'] as bool?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ class MapProvider with ChangeNotifier {
|
|||||||
MapCoordinateSpace _targetCoordinateSpace = MapCoordinateSpace.geo;
|
MapCoordinateSpace _targetCoordinateSpace = MapCoordinateSpace.geo;
|
||||||
String? _targetMapId;
|
String? _targetMapId;
|
||||||
|
|
||||||
final Set<String> _visibleContactPaths = {};
|
|
||||||
|
|
||||||
LocationTrail? _currentTrail;
|
LocationTrail? _currentTrail;
|
||||||
bool _isTrailVisible = true;
|
bool _isTrailVisible = true;
|
||||||
final List<LocationTrail> _trailHistory = [];
|
final List<LocationTrail> _trailHistory = [];
|
||||||
@@ -55,7 +53,6 @@ class MapProvider with ChangeNotifier {
|
|||||||
bool _showPlaceNamesOverlay = false;
|
bool _showPlaceNamesOverlay = false;
|
||||||
bool _showMunicipalityBordersOverlay = false;
|
bool _showMunicipalityBordersOverlay = false;
|
||||||
|
|
||||||
bool _showAllContactTrails = true;
|
|
||||||
bool _hideRepeatersOnMap = false;
|
bool _hideRepeatersOnMap = false;
|
||||||
|
|
||||||
LocationTrail? _importedTrail;
|
LocationTrail? _importedTrail;
|
||||||
@@ -72,8 +69,6 @@ class MapProvider with ChangeNotifier {
|
|||||||
bool get shouldAnimate => _shouldAnimate;
|
bool get shouldAnimate => _shouldAnimate;
|
||||||
MapCoordinateSpace get targetCoordinateSpace => _targetCoordinateSpace;
|
MapCoordinateSpace get targetCoordinateSpace => _targetCoordinateSpace;
|
||||||
String? get targetMapId => _targetMapId;
|
String? get targetMapId => _targetMapId;
|
||||||
Set<String> get visibleContactPaths => Set.unmodifiable(_visibleContactPaths);
|
|
||||||
|
|
||||||
LocationTrail? get currentTrail => _currentTrail;
|
LocationTrail? get currentTrail => _currentTrail;
|
||||||
bool get isTrailVisible => _isTrailVisible;
|
bool get isTrailVisible => _isTrailVisible;
|
||||||
List<LocationTrail> get trailHistory => List.unmodifiable(_trailHistory);
|
List<LocationTrail> get trailHistory => List.unmodifiable(_trailHistory);
|
||||||
@@ -91,7 +86,6 @@ class MapProvider with ChangeNotifier {
|
|||||||
bool get showPlaceNamesOverlay => _showPlaceNamesOverlay;
|
bool get showPlaceNamesOverlay => _showPlaceNamesOverlay;
|
||||||
bool get showMunicipalityBordersOverlay => _showMunicipalityBordersOverlay;
|
bool get showMunicipalityBordersOverlay => _showMunicipalityBordersOverlay;
|
||||||
|
|
||||||
bool get showAllContactTrails => _showAllContactTrails;
|
|
||||||
bool get hideRepeatersOnMap => _hideRepeatersOnMap;
|
bool get hideRepeatersOnMap => _hideRepeatersOnMap;
|
||||||
|
|
||||||
LocationTrail? get importedTrail => _importedTrail;
|
LocationTrail? get importedTrail => _importedTrail;
|
||||||
@@ -365,30 +359,6 @@ class MapProvider with ChangeNotifier {
|
|||||||
notifyListeners();
|
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() {
|
void startTrail() {
|
||||||
if (_currentTrail != null && _currentTrail!.isActive) {
|
if (_currentTrail != null && _currentTrail!.isActive) {
|
||||||
endTrail();
|
endTrail();
|
||||||
@@ -556,12 +526,17 @@ class MapProvider with ChangeNotifier {
|
|||||||
Future<void> _loadInitialState() async {
|
Future<void> _loadInitialState() async {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
loadOverlayState(),
|
loadOverlayState(),
|
||||||
loadTrailSettings(),
|
|
||||||
loadRepeaterVisibilitySettings(),
|
loadRepeaterVisibilitySettings(),
|
||||||
_loadCustomMapState(),
|
_loadCustomMapState(),
|
||||||
|
_removeDeprecatedSettings(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _removeDeprecatedSettings() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.remove(_scopedKey('map_show_all_contact_trails'));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _saveOverlayState() async {
|
Future<void> _saveOverlayState() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setBool(
|
await prefs.setBool(
|
||||||
@@ -610,27 +585,6 @@ class MapProvider with ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> toggleAllContactTrails() async {
|
|
||||||
_showAllContactTrails = !_showAllContactTrails;
|
|
||||||
notifyListeners();
|
|
||||||
await _saveTrailSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> loadTrailSettings() async {
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
_showAllContactTrails =
|
|
||||||
prefs.getBool(_scopedKey('map_show_all_contact_trails')) ?? true;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _saveTrailSettings() async {
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
await prefs.setBool(
|
|
||||||
_scopedKey('map_show_all_contact_trails'),
|
|
||||||
_showAllContactTrails,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setHideRepeatersOnMap(bool hide) async {
|
Future<void> setHideRepeatersOnMap(bool hide) async {
|
||||||
if (_hideRepeatersOnMap == hide) return;
|
if (_hideRepeatersOnMap == hide) return;
|
||||||
_hideRepeatersOnMap = hide;
|
_hideRepeatersOnMap = hide;
|
||||||
@@ -755,7 +709,6 @@ class MapProvider with ChangeNotifier {
|
|||||||
'showKrasFireZonesOverlay': _showKrasFireZonesOverlay,
|
'showKrasFireZonesOverlay': _showKrasFireZonesOverlay,
|
||||||
'showPlaceNamesOverlay': _showPlaceNamesOverlay,
|
'showPlaceNamesOverlay': _showPlaceNamesOverlay,
|
||||||
'showMunicipalityBordersOverlay': _showMunicipalityBordersOverlay,
|
'showMunicipalityBordersOverlay': _showMunicipalityBordersOverlay,
|
||||||
'showAllContactTrails': _showAllContactTrails,
|
|
||||||
'hideRepeatersOnMap': _hideRepeatersOnMap,
|
'hideRepeatersOnMap': _hideRepeatersOnMap,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -792,7 +745,6 @@ class MapProvider with ChangeNotifier {
|
|||||||
_showPlaceNamesOverlay = json['showPlaceNamesOverlay'] as bool? ?? false;
|
_showPlaceNamesOverlay = json['showPlaceNamesOverlay'] as bool? ?? false;
|
||||||
_showMunicipalityBordersOverlay =
|
_showMunicipalityBordersOverlay =
|
||||||
json['showMunicipalityBordersOverlay'] as bool? ?? false;
|
json['showMunicipalityBordersOverlay'] as bool? ?? false;
|
||||||
_showAllContactTrails = json['showAllContactTrails'] as bool? ?? true;
|
|
||||||
_hideRepeatersOnMap = json['hideRepeatersOnMap'] as bool? ?? false;
|
_hideRepeatersOnMap = json['hideRepeatersOnMap'] as bool? ?? false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import '../services/background_location_service.dart';
|
|||||||
import '../services/location_tracking_service.dart';
|
import '../services/location_tracking_service.dart';
|
||||||
import '../services/map_marker_service.dart';
|
import '../services/map_marker_service.dart';
|
||||||
import '../services/message_destination_preferences.dart';
|
import '../services/message_destination_preferences.dart';
|
||||||
import '../services/trail_color_service.dart';
|
|
||||||
import '../services/profiles_feature_service.dart';
|
import '../services/profiles_feature_service.dart';
|
||||||
import '../widgets/map_debug_info.dart';
|
import '../widgets/map_debug_info.dart';
|
||||||
import '../widgets/map/compass_widget.dart';
|
import '../widgets/map/compass_widget.dart';
|
||||||
@@ -873,7 +872,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
if (!hasCustomMap)
|
if (!hasCustomMap)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Icon(Icons.add_photo_alternate),
|
leading: Icon(Icons.add_photo_alternate),
|
||||||
title: Text(AppLocalizations.of(context)!.loadFromGallery),
|
title: Text(
|
||||||
|
AppLocalizations.of(context)!.loadFromGallery,
|
||||||
|
),
|
||||||
subtitle: const Text(
|
subtitle: const Text(
|
||||||
'Use a cave map image instead of GPS tiles',
|
'Use a cave map image instead of GPS tiles',
|
||||||
),
|
),
|
||||||
@@ -905,7 +906,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Icon(Icons.swap_horizontal_circle),
|
leading: Icon(Icons.swap_horizontal_circle),
|
||||||
title: Text(AppLocalizations.of(context)!.replaceImage),
|
title: Text(
|
||||||
|
AppLocalizations.of(context)!.replaceImage,
|
||||||
|
),
|
||||||
subtitle: const Text(
|
subtitle: const Text(
|
||||||
'Pick a different map from the gallery',
|
'Pick a different map from the gallery',
|
||||||
),
|
),
|
||||||
@@ -941,7 +944,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
if (customMapConfig.isCalibrated)
|
if (customMapConfig.isCalibrated)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Icon(Icons.clear),
|
leading: Icon(Icons.clear),
|
||||||
title: Text(AppLocalizations.of(context)!.clearScale),
|
title: Text(
|
||||||
|
AppLocalizations.of(context)!.clearScale,
|
||||||
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await mapProvider.clearCustomMapCalibration();
|
await mapProvider.clearCustomMapCalibration();
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
@@ -1277,9 +1282,11 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
_stopCustomMapCalibration();
|
_stopCustomMapCalibration();
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
context,
|
SnackBar(
|
||||||
).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.customMapScaleSaved)));
|
content: Text(AppLocalizations.of(context)!.customMapScaleSaved),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1419,7 +1426,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
leading: Icon(Icons.chat_bubble_outline),
|
leading: Icon(Icons.chat_bubble_outline),
|
||||||
title: Text(AppLocalizations.of(context)!.openMessage),
|
title: Text(AppLocalizations.of(context)!.openMessage),
|
||||||
subtitle: Text(AppLocalizations.of(context)!.jumpToTheRelatedSarMessage),
|
subtitle: Text(
|
||||||
|
AppLocalizations.of(context)!.jumpToTheRelatedSarMessage,
|
||||||
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
Navigator.pop(sheetContext);
|
Navigator.pop(sheetContext);
|
||||||
await _openSarMarkerMessage(
|
await _openSarMarkerMessage(
|
||||||
@@ -1655,7 +1664,11 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(AppLocalizations.of(context)!.pleaseSelectADestinationToSendSarMarker),
|
content: Text(
|
||||||
|
AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.pleaseSelectADestinationToSendSarMarker,
|
||||||
|
),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1785,7 +1798,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(AppLocalizations.of(context)!.sarMarkerBroadcastToPublicChannel),
|
content: Text(
|
||||||
|
AppLocalizations.of(context)!.sarMarkerBroadcastToPublicChannel,
|
||||||
|
),
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
duration: Duration(seconds: 2),
|
duration: Duration(seconds: 2),
|
||||||
),
|
),
|
||||||
@@ -2514,52 +2529,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// Contact trail polylines (rendered before user trail and markers)
|
|
||||||
Consumer<MapProvider>(
|
|
||||||
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)
|
// Location trail layer (rendered after paths, before drawings)
|
||||||
const LocationTrailLayer(),
|
const LocationTrailLayer(),
|
||||||
],
|
],
|
||||||
@@ -2604,7 +2573,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
context: context,
|
context: context,
|
||||||
mapRotation: _getMapRotation(),
|
mapRotation: _getMapRotation(),
|
||||||
userPosition: _locationService.currentPosition,
|
userPosition: _locationService.currentPosition,
|
||||||
estimatedLocations: contactsProvider.estimatedLocations,
|
estimatedLocations:
|
||||||
|
contactsProvider.estimatedLocations,
|
||||||
onTap: (contact) {
|
onTap: (contact) {
|
||||||
_showDetailedCompassWithContact(
|
_showDetailedCompassWithContact(
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -1463,21 +1463,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Consumer<MapProvider>(
|
|
||||||
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<MapProvider>(
|
Consumer<MapProvider>(
|
||||||
builder: (context, mapProvider, child) => SwitchListTile(
|
builder: (context, mapProvider, child) => SwitchListTile(
|
||||||
secondary: Icon(Icons.router_outlined),
|
secondary: Icon(Icons.router_outlined),
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ class MapWorkspaceSnapshotService {
|
|||||||
showPlaceNamesOverlay: mapProvider.showPlaceNamesOverlay,
|
showPlaceNamesOverlay: mapProvider.showPlaceNamesOverlay,
|
||||||
showMunicipalityBordersOverlay:
|
showMunicipalityBordersOverlay:
|
||||||
mapProvider.showMunicipalityBordersOverlay,
|
mapProvider.showMunicipalityBordersOverlay,
|
||||||
showAllContactTrails: mapProvider.showAllContactTrails,
|
|
||||||
hideRepeatersOnMap: mapProvider.hideRepeatersOnMap,
|
hideRepeatersOnMap: mapProvider.hideRepeatersOnMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../../models/contact.dart';
|
|
||||||
import '../../models/location_trail.dart';
|
import '../../models/location_trail.dart';
|
||||||
import '../../providers/map_provider.dart';
|
import '../../providers/map_provider.dart';
|
||||||
import '../../providers/contacts_provider.dart';
|
|
||||||
import '../../services/gpx_service.dart';
|
import '../../services/gpx_service.dart';
|
||||||
import '../../services/trail_color_service.dart';
|
|
||||||
import '../../l10n/app_localizations.dart';
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
/// Trail management controls widget
|
/// Trail management controls widget
|
||||||
@@ -14,17 +11,8 @@ class TrailControls extends StatelessWidget {
|
|||||||
|
|
||||||
void _showTrailMenu(BuildContext context) {
|
void _showTrailMenu(BuildContext context) {
|
||||||
final mapProvider = Provider.of<MapProvider>(context, listen: false);
|
final mapProvider = Provider.of<MapProvider>(context, listen: false);
|
||||||
final contactsProvider = Provider.of<ContactsProvider>(
|
|
||||||
context,
|
|
||||||
listen: false,
|
|
||||||
);
|
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
// Get contacts with trails (advertHistory >= 2 points)
|
|
||||||
final contactsWithTrails = contactsProvider.contactsWithLocation
|
|
||||||
.where((c) => c.advertHistory.length >= 2)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
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),
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
// Close button
|
// Close button
|
||||||
@@ -360,34 +244,6 @@ class TrailControls extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _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(
|
void _showClearConfirmation(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
MapProvider mapProvider,
|
MapProvider mapProvider,
|
||||||
|
|||||||
Reference in New Issue
Block a user