import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; import 'package:geolocator/geolocator.dart'; import 'package:latlong2/latlong.dart'; class NavigationMap extends StatefulWidget { final Stream> waypointsCoordinates; final Stream currentWaypointIndexStream; const NavigationMap({ required this.waypointsCoordinates, required this.currentWaypointIndexStream, Key? key, }) : super(key: key); @override State createState() => _NavigationMapState(); } class _NavigationMapState extends State { /// Lines drawn on the map to indicate the path List _polylines = []; /// Used to differentiate the path before and after the user int _currentWaypointIndex = 0; final List> _streamsSubscription = []; late CenterOnLocationUpdate _centerOnLocationUpdate; late StreamController _centerCurrentLocationStreamController; @override void initState() { super.initState(); _centerOnLocationUpdate = CenterOnLocationUpdate.always; _centerCurrentLocationStreamController = StreamController(); _streamsSubscription.add(widget.waypointsCoordinates.listen(_updatePolylines)); _streamsSubscription.add(widget.currentWaypointIndexStream.listen(_updateWaypointIndex)); } void _updatePolylines(List waypointsCoordinates) { setState(() { _polylines = waypointsCoordinates.map((waypoint) => LatLng(waypoint.latitude, waypoint.longitude)).toList(); }); } void _updateWaypointIndex(int newWaypointIndex) { setState(() { _currentWaypointIndex = newWaypointIndex; }); } @override Widget build(BuildContext context) { return SizedBox( height: MediaQuery.of(context).size.height * 0.8, child: FlutterMap( options: MapOptions( center: LatLng(0, 0), zoom: 15, minZoom: 10, maxZoom: 18, onPositionChanged: (MapPosition position, bool hasGesture) { if (hasGesture) { setState(() => _centerOnLocationUpdate = CenterOnLocationUpdate.never); } }, ), children: [ TileLayerWidget( options: TileLayerOptions( urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", subdomains: ['a', 'b', 'c'], ), ), LocationMarkerLayerWidget( plugin: LocationMarkerPlugin( centerOnLocationUpdate: _centerOnLocationUpdate, centerCurrentLocationStream: _centerCurrentLocationStreamController.stream, ), options: LocationMarkerLayerOptions( marker: const DefaultLocationMarker( color: Colors.blue, ), positionStream: const LocationMarkerDataStreamFactory().geolocatorPositionStream( stream: Geolocator.getPositionStream( locationSettings: const LocationSettings( accuracy: LocationAccuracy.bestForNavigation, distanceFilter: 1, ), ), ), ), ), PolylineLayerWidget( options: PolylineLayerOptions( polylines: [ Polyline( points: _polylines.sublist(max(0, _currentWaypointIndex - 1)), color: Colors.red, strokeWidth: 10, ), Polyline( points: _polylines.sublist(0, max(0, _currentWaypointIndex)), color: Colors.green, strokeWidth: 10, ), ], ), ), ], ), ); } @override void dispose() { for (final streamSubscription in _streamsSubscription) { streamSubscription.cancel(); } super.dispose(); } }