Skip to content

Commit dcd00b7

Browse files
authored
Merge pull request #24 from SafeEscape-org/optimizations
feat:added haptics feedback over move
2 parents 5ca5281 + 762599d commit dcd00b7

File tree

3 files changed

+191
-27
lines changed

3 files changed

+191
-27
lines changed

lib/features/disaster_alerts/widgets/evacuation_map.dart

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ class EvacuationMap extends StatefulWidget {
1818
final Function(GoogleMapController) onMapCreated;
1919
final Set<Polyline> polylines;
2020
final Set<Marker> markers;
21-
final bool isExpanded; // Add this parameter
21+
final bool isExpanded;
22+
final Function(bool)? onNavigationStarted; // Add this callback
2223

2324
const EvacuationMap({
2425
Key? key,
@@ -27,7 +28,8 @@ class EvacuationMap extends StatefulWidget {
2728
required this.polylines,
2829
this.markers = const {},
2930
required this.onMapCreated,
30-
this.isExpanded = false, // Default to false
31+
this.isExpanded = false,
32+
this.onNavigationStarted, // Add this parameter
3133
}) : super(key: key);
3234

3335
@override
@@ -48,6 +50,7 @@ class _EvacuationMapState extends State<EvacuationMap>
4850
MapType _currentMapType = MapType.normal;
4951
bool _trafficEnabled = false;
5052
late RouteAnimator _routeAnimator;
53+
Set<Marker> _localMarkers = {}; // Add this line to store local markers
5154

5255
@override
5356
void initState() {
@@ -58,6 +61,9 @@ class _EvacuationMapState extends State<EvacuationMap>
5861
);
5962

6063
_routeAnimator = RouteAnimator();
64+
65+
// Initialize markers on startup
66+
_localMarkers = _createMarkers();
6167

6268
// Start route animation if polylines exist
6369
if (widget.polylines.isNotEmpty) {
@@ -103,9 +109,6 @@ class _EvacuationMapState extends State<EvacuationMap>
103109
// Map
104110
SizedBox(
105111
width: MediaQuery.of(context).size.width,
106-
height: widget.isExpanded
107-
? MediaQuery.of(context).size.height
108-
: MediaQuery.of(context).size.height * 0.4,
109112
child: GoogleMap(
110113
initialCameraPosition: CameraPosition(
111114
target: widget.currentPosition,
@@ -125,7 +128,7 @@ class _EvacuationMapState extends State<EvacuationMap>
125128
}
126129
});
127130
},
128-
markers: widget.markers.isEmpty ? _createMarkers() : widget.markers,
131+
markers: widget.markers.isEmpty ? _localMarkers : widget.markers,
129132
polylines: _isAnimatingRoute
130133
? _routeAnimator.animatedPolylines
131134
: widget.polylines,
@@ -206,7 +209,7 @@ class _EvacuationMapState extends State<EvacuationMap>
206209
padding: const EdgeInsets.symmetric(horizontal: 12),
207210
child: Column(
208211
crossAxisAlignment: CrossAxisAlignment.start,
209-
mainAxisSize: MainAxisSize.min,
212+
mainAxisSize: MainAxisSize.max,
210213
children: [
211214
Text(
212215
'Route to Destination',
@@ -233,6 +236,9 @@ class _EvacuationMapState extends State<EvacuationMap>
233236
ElevatedButton.icon(
234237
onPressed: () {
235238
// Start navigation logic
239+
if (widget.onNavigationStarted != null) {
240+
widget.onNavigationStarted!(true); // Request map expansion
241+
}
236242
},
237243
icon: const Icon(Icons.navigation, size: 16),
238244
label: const Text('Navigate'),
Lines changed: 177 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,47 @@
11
import 'package:flutter/material.dart';
22
import 'package:google_maps_flutter/google_maps_flutter.dart';
33
import 'dart:async';
4+
import 'dart:math' as math;
45
import '../../constants/colors.dart';
5-
66
class 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
}

lib/features/disaster_alerts/widgets/expanded_actions.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class ExpandedActions extends StatelessWidget {
1515
return Padding(
1616
padding: const EdgeInsets.all(24),
1717
child: ElevatedButton(
18-
onPressed: onNavigationStart,
18+
onPressed: onNavigationStart, // This should set isExpanded to true
1919
style: ElevatedButton.styleFrom(
2020
backgroundColor: EvacuationColors.primaryColor,
2121
foregroundColor: Colors.white,

0 commit comments

Comments
 (0)