11import 'package:flutter/material.dart' ;
22import 'package:google_maps_flutter/google_maps_flutter.dart' ;
33import 'dart:async' ;
4+ import 'dart:math' as math;
45import '../../constants/colors.dart' ;
5-
66class RouteAnimator {
77 List <LatLng > _animatedPoints = [];
88 Set <Polyline > _animatedPolylines = {};
99 Timer ? _routeAnimationTimer;
10+ Timer ? _pulseAnimationTimer;
1011 bool _isAnimatingRoute = false ;
12+ bool _isPulsing = false ;
13+ int _pulseWidth = 5 ;
14+ final int _maxPulseWidth = 8 ;
15+ final int _minPulseWidth = 4 ;
16+ bool _pulseIncreasing = true ;
1117
1218 // Getters
1319 Set <Polyline > get animatedPolylines => _animatedPolylines;
1420 bool get isAnimating => _isAnimatingRoute;
1521
1622 void dispose () {
1723 _routeAnimationTimer? .cancel ();
24+ _pulseAnimationTimer? .cancel ();
1825 }
1926
2027 void clearRoute () {
2128 _animatedPolylines.clear ();
2229 _animatedPoints.clear ();
2330 _isAnimatingRoute = false ;
31+ _isPulsing = false ;
2432 _routeAnimationTimer? .cancel ();
33+ _pulseAnimationTimer? .cancel ();
2534 }
2635
27- void startAnimation (Set <Polyline > polylines, Function (Set <Polyline >) onUpdate, Function () onComplete) {
36+ void startAnimation (
37+ Set <Polyline > polylines,
38+ Function (Set <Polyline >) onUpdate,
39+ Function () onComplete,
40+ [Function (CameraUpdate )? onCameraUpdate] // Optional camera update callback
41+ ) {
2842 // Cancel any existing animation
2943 _routeAnimationTimer? .cancel ();
44+ _pulseAnimationTimer? .cancel ();
3045
3146 if (polylines.isEmpty) return ;
3247
@@ -40,41 +55,184 @@ class RouteAnimator {
4055 int currentPointIndex = 0 ;
4156
4257 // Create a timer that adds points to the animated polyline
43- _routeAnimationTimer = Timer .periodic (const Duration (milliseconds: 50 ), (timer) {
58+ _routeAnimationTimer = Timer .periodic (const Duration (milliseconds: 40 ), (timer) {
4459 if (currentPointIndex >= totalPoints) {
4560 timer.cancel ();
61+ _startPulseAnimation (onUpdate, onComplete);
4662 return ;
4763 }
4864
4965 // Add the next point to our animated points list
5066 _animatedPoints.add (allPoints[currentPointIndex]);
5167
5268 // Create a new polyline with the current points
53- _animatedPolylines = {
54- Polyline (
55- polylineId: const PolylineId ('animated_route' ),
56- points: _animatedPoints,
57- color: EvacuationColors .primaryColor,
58- width: 5 ,
59- patterns: [
60- PatternItem .dash (20 ),
61- PatternItem .gap (5 ),
62- ],
63- startCap: Cap .roundCap,
64- endCap: Cap .roundCap,
65- ),
66- };
69+ _updateAnimatedPolyline (onUpdate);
6770
68- onUpdate (_animatedPolylines);
71+ // Update camera position if callback is provided
72+ if (onCameraUpdate != null && _animatedPoints.isNotEmpty) {
73+ // Determine the camera position based on animation progress
74+ _updateCameraPosition (allPoints, currentPointIndex, onCameraUpdate);
75+ }
6976
7077 currentPointIndex++ ;
7178
7279 // If we've added all points, stop the animation
7380 if (currentPointIndex >= totalPoints) {
81+ timer.cancel ();
82+ _startPulseAnimation (onUpdate, onComplete);
83+ }
84+ });
85+ }
86+
87+ void _updateCameraPosition (List <LatLng > allPoints, int currentIndex, Function (CameraUpdate ) onCameraUpdate) {
88+ // Calculate the appropriate camera position based on the current animation progress
89+
90+ // For the first few points, we want to show the starting point and direction
91+ if (currentIndex < 5 ) {
92+ // Show the first few points with some padding
93+ final bounds = _calculateBounds (allPoints.sublist (0 , math.min (10 , allPoints.length)));
94+ onCameraUpdate (CameraUpdate .newLatLngBounds (bounds, 100 ));
95+ return ;
96+ }
97+
98+ // For the middle of the route, follow the current point with some lookahead
99+ int endIndex = math.min (currentIndex + 5 , allPoints.length - 1 );
100+ int startIndex = math.max (0 , currentIndex - 2 );
101+
102+ // Calculate visible segment bounds
103+ final visibleSegment = allPoints.sublist (startIndex, endIndex + 1 );
104+ final bounds = _calculateBounds (visibleSegment);
105+
106+ // Update camera to show the current segment with padding
107+ onCameraUpdate (CameraUpdate .newLatLngBounds (bounds, 80 ));
108+ }
109+
110+ LatLngBounds _calculateBounds (List <LatLng > points) {
111+ if (points.isEmpty) {
112+ // Default bounds if no points (shouldn't happen)
113+ return LatLngBounds (
114+ southwest: const LatLng (0 , 0 ),
115+ northeast: const LatLng (0 , 0 )
116+ );
117+ }
118+
119+ double minLat = points.first.latitude;
120+ double maxLat = points.first.latitude;
121+ double minLng = points.first.longitude;
122+ double maxLng = points.first.longitude;
123+
124+ for (var point in points) {
125+ if (point.latitude < minLat) minLat = point.latitude;
126+ if (point.latitude > maxLat) maxLat = point.latitude;
127+ if (point.longitude < minLng) minLng = point.longitude;
128+ if (point.longitude > maxLng) maxLng = point.longitude;
129+ }
130+
131+ return LatLngBounds (
132+ southwest: LatLng (minLat, minLng),
133+ northeast: LatLng (maxLat, maxLng)
134+ );
135+ }
136+
137+ void _startPulseAnimation (Function (Set <Polyline >) onUpdate, Function () onComplete) {
138+ _isPulsing = true ;
139+
140+ // Create a pulsing effect by changing the width of the polyline
141+ _pulseAnimationTimer = Timer .periodic (const Duration (milliseconds: 100 ), (timer) {
142+ if (_pulseIncreasing) {
143+ _pulseWidth++ ;
144+ if (_pulseWidth >= _maxPulseWidth) {
145+ _pulseIncreasing = false ;
146+ }
147+ } else {
148+ _pulseWidth-- ;
149+ if (_pulseWidth <= _minPulseWidth) {
150+ _pulseIncreasing = true ;
151+ }
152+ }
153+
154+ _updateAnimatedPolyline (onUpdate);
155+
156+ // After a certain time, stop the pulsing animation
157+ if (! _isAnimatingRoute && timer.tick > 50 ) {
158+ _isPulsing = false ;
74159 _isAnimatingRoute = false ;
75- onComplete ();
76160 timer.cancel ();
161+ onComplete ();
77162 }
78163 });
79164 }
165+
166+ void _updateAnimatedPolyline (Function (Set <Polyline >) onUpdate) {
167+ if (_animatedPoints.isEmpty) return ;
168+
169+ // Create a gradient effect by using two polylines
170+ _animatedPolylines = {
171+ // Main route polyline
172+ Polyline (
173+ polylineId: const PolylineId ('animated_route' ),
174+ points: _animatedPoints,
175+ color: EvacuationColors .primaryColor,
176+ width: _pulseWidth,
177+ patterns: [
178+ PatternItem .dash (20 ),
179+ PatternItem .gap (5 ),
180+ ],
181+ startCap: Cap .roundCap,
182+ endCap: Cap .roundCap,
183+ ),
184+
185+ // Glow effect polyline (slightly wider and more transparent)
186+ Polyline (
187+ polylineId: const PolylineId ('glow_effect' ),
188+ points: _animatedPoints,
189+ color: EvacuationColors .primaryColor.withOpacity (0.3 ),
190+ width: _pulseWidth + 3 , // Remove .toDouble()
191+ startCap: Cap .roundCap,
192+ endCap: Cap .roundCap,
193+ ),
194+
195+ // Direction indicator at the end of the route
196+ if (_animatedPoints.length > 1 )
197+ Polyline (
198+ polylineId: const PolylineId ('direction_indicator' ),
199+ points: [
200+ _animatedPoints.last,
201+ _getDirectionPoint (_animatedPoints),
202+ ],
203+ color: Colors .white,
204+ width: 3 ,
205+ startCap: Cap .roundCap,
206+ endCap: Cap .roundCap,
207+ ),
208+ };
209+
210+ onUpdate (_animatedPolylines);
211+ }
212+
213+ // Helper method to create a point that indicates direction
214+ LatLng _getDirectionPoint (List <LatLng > points) {
215+ if (points.length < 2 ) return points.last;
216+
217+ // Get the last two points to determine direction
218+ final LatLng lastPoint = points.last;
219+ final LatLng secondLastPoint = points[points.length - 2 ];
220+
221+ // Calculate direction vector
222+ double dx = lastPoint.latitude - secondLastPoint.latitude;
223+ double dy = lastPoint.longitude - secondLastPoint.longitude;
224+
225+ // Normalize and scale
226+ double length = sqrt (dx * dx + dy * dy);
227+ dx = dx / length * 0.0005 ; // Small offset for direction indicator
228+ dy = dy / length * 0.0005 ;
229+
230+ // Return a point slightly ahead in the direction of travel
231+ return LatLng (lastPoint.latitude + dx, lastPoint.longitude + dy);
232+ }
233+ }
234+
235+ // Helper method for square root calculation
236+ double sqrt (double value) {
237+ return value <= 0 ? 0 : math.sqrt (value);
80238}
0 commit comments