Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions lib/features/disaster_alerts/widgets/evacuation_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ class EvacuationMap extends StatefulWidget {
final Function(GoogleMapController) onMapCreated;
final Set<Polyline> polylines;
final Set<Marker> markers;
final bool isExpanded; // Add this parameter
final bool isExpanded;
final Function(bool)? onNavigationStarted; // Add this callback

const EvacuationMap({
Key? key,
Expand All @@ -27,7 +28,8 @@ class EvacuationMap extends StatefulWidget {
required this.polylines,
this.markers = const {},
required this.onMapCreated,
this.isExpanded = false, // Default to false
this.isExpanded = false,
this.onNavigationStarted, // Add this parameter
}) : super(key: key);

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

@override
void initState() {
Expand All @@ -58,6 +61,9 @@ class _EvacuationMapState extends State<EvacuationMap>
);

_routeAnimator = RouteAnimator();

// Initialize markers on startup
_localMarkers = _createMarkers();

// Start route animation if polylines exist
if (widget.polylines.isNotEmpty) {
Expand Down Expand Up @@ -103,9 +109,6 @@ class _EvacuationMapState extends State<EvacuationMap>
// Map
SizedBox(
width: MediaQuery.of(context).size.width,
height: widget.isExpanded
? MediaQuery.of(context).size.height
: MediaQuery.of(context).size.height * 0.4,
child: GoogleMap(
initialCameraPosition: CameraPosition(
target: widget.currentPosition,
Expand All @@ -125,7 +128,7 @@ class _EvacuationMapState extends State<EvacuationMap>
}
});
},
markers: widget.markers.isEmpty ? _createMarkers() : widget.markers,
markers: widget.markers.isEmpty ? _localMarkers : widget.markers,
polylines: _isAnimatingRoute
? _routeAnimator.animatedPolylines
: widget.polylines,
Expand Down Expand Up @@ -206,7 +209,7 @@ class _EvacuationMapState extends State<EvacuationMap>
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.max,
children: [
Text(
'Route to Destination',
Expand All @@ -233,6 +236,9 @@ class _EvacuationMapState extends State<EvacuationMap>
ElevatedButton.icon(
onPressed: () {
// Start navigation logic
if (widget.onNavigationStarted != null) {
widget.onNavigationStarted!(true); // Request map expansion
}
},
icon: const Icon(Icons.navigation, size: 16),
label: const Text('Navigate'),
Expand Down
196 changes: 177 additions & 19 deletions lib/features/disaster_alerts/widgets/evacuation_map/route_animation.dart
Original file line number Diff line number Diff line change
@@ -1,32 +1,47 @@
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'dart:async';
import 'dart:math' as math;
import '../../constants/colors.dart';

class RouteAnimator {
List<LatLng> _animatedPoints = [];
Set<Polyline> _animatedPolylines = {};
Timer? _routeAnimationTimer;
Timer? _pulseAnimationTimer;
bool _isAnimatingRoute = false;
bool _isPulsing = false;
int _pulseWidth = 5;
final int _maxPulseWidth = 8;
final int _minPulseWidth = 4;
bool _pulseIncreasing = true;

// Getters
Set<Polyline> get animatedPolylines => _animatedPolylines;
bool get isAnimating => _isAnimatingRoute;

void dispose() {
_routeAnimationTimer?.cancel();
_pulseAnimationTimer?.cancel();
}

void clearRoute() {
_animatedPolylines.clear();
_animatedPoints.clear();
_isAnimatingRoute = false;
_isPulsing = false;
_routeAnimationTimer?.cancel();
_pulseAnimationTimer?.cancel();
}

void startAnimation(Set<Polyline> polylines, Function(Set<Polyline>) onUpdate, Function() onComplete) {
void startAnimation(
Set<Polyline> polylines,
Function(Set<Polyline>) onUpdate,
Function() onComplete,
[Function(CameraUpdate)? onCameraUpdate] // Optional camera update callback
) {
// Cancel any existing animation
_routeAnimationTimer?.cancel();
_pulseAnimationTimer?.cancel();

if (polylines.isEmpty) return;

Expand All @@ -40,41 +55,184 @@ class RouteAnimator {
int currentPointIndex = 0;

// Create a timer that adds points to the animated polyline
_routeAnimationTimer = Timer.periodic(const Duration(milliseconds: 50), (timer) {
_routeAnimationTimer = Timer.periodic(const Duration(milliseconds: 40), (timer) {
if (currentPointIndex >= totalPoints) {
timer.cancel();
_startPulseAnimation(onUpdate, onComplete);
return;
}

// Add the next point to our animated points list
_animatedPoints.add(allPoints[currentPointIndex]);

// Create a new polyline with the current points
_animatedPolylines = {
Polyline(
polylineId: const PolylineId('animated_route'),
points: _animatedPoints,
color: EvacuationColors.primaryColor,
width: 5,
patterns: [
PatternItem.dash(20),
PatternItem.gap(5),
],
startCap: Cap.roundCap,
endCap: Cap.roundCap,
),
};
_updateAnimatedPolyline(onUpdate);

onUpdate(_animatedPolylines);
// Update camera position if callback is provided
if (onCameraUpdate != null && _animatedPoints.isNotEmpty) {
// Determine the camera position based on animation progress
_updateCameraPosition(allPoints, currentPointIndex, onCameraUpdate);
}

currentPointIndex++;

// If we've added all points, stop the animation
if (currentPointIndex >= totalPoints) {
timer.cancel();
_startPulseAnimation(onUpdate, onComplete);
}
});
}

void _updateCameraPosition(List<LatLng> allPoints, int currentIndex, Function(CameraUpdate) onCameraUpdate) {
// Calculate the appropriate camera position based on the current animation progress

// For the first few points, we want to show the starting point and direction
if (currentIndex < 5) {
// Show the first few points with some padding
final bounds = _calculateBounds(allPoints.sublist(0, math.min(10, allPoints.length)));
onCameraUpdate(CameraUpdate.newLatLngBounds(bounds, 100));
return;
}

// For the middle of the route, follow the current point with some lookahead
int endIndex = math.min(currentIndex + 5, allPoints.length - 1);
int startIndex = math.max(0, currentIndex - 2);

// Calculate visible segment bounds
final visibleSegment = allPoints.sublist(startIndex, endIndex + 1);
final bounds = _calculateBounds(visibleSegment);

// Update camera to show the current segment with padding
onCameraUpdate(CameraUpdate.newLatLngBounds(bounds, 80));
}

LatLngBounds _calculateBounds(List<LatLng> points) {
if (points.isEmpty) {
// Default bounds if no points (shouldn't happen)
return LatLngBounds(
southwest: const LatLng(0, 0),
northeast: const LatLng(0, 0)
);
}

double minLat = points.first.latitude;
double maxLat = points.first.latitude;
double minLng = points.first.longitude;
double maxLng = points.first.longitude;

for (var point in points) {
if (point.latitude < minLat) minLat = point.latitude;
if (point.latitude > maxLat) maxLat = point.latitude;
if (point.longitude < minLng) minLng = point.longitude;
if (point.longitude > maxLng) maxLng = point.longitude;
}

return LatLngBounds(
southwest: LatLng(minLat, minLng),
northeast: LatLng(maxLat, maxLng)
);
}

void _startPulseAnimation(Function(Set<Polyline>) onUpdate, Function() onComplete) {
_isPulsing = true;

// Create a pulsing effect by changing the width of the polyline
_pulseAnimationTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
if (_pulseIncreasing) {
_pulseWidth++;
if (_pulseWidth >= _maxPulseWidth) {
_pulseIncreasing = false;
}
} else {
_pulseWidth--;
if (_pulseWidth <= _minPulseWidth) {
_pulseIncreasing = true;
}
}

_updateAnimatedPolyline(onUpdate);

// After a certain time, stop the pulsing animation
if (!_isAnimatingRoute && timer.tick > 50) {
_isPulsing = false;
_isAnimatingRoute = false;
onComplete();
timer.cancel();
onComplete();
}
});
}

void _updateAnimatedPolyline(Function(Set<Polyline>) onUpdate) {
if (_animatedPoints.isEmpty) return;

// Create a gradient effect by using two polylines
_animatedPolylines = {
// Main route polyline
Polyline(
polylineId: const PolylineId('animated_route'),
points: _animatedPoints,
color: EvacuationColors.primaryColor,
width: _pulseWidth,
patterns: [
PatternItem.dash(20),
PatternItem.gap(5),
],
startCap: Cap.roundCap,
endCap: Cap.roundCap,
),

// Glow effect polyline (slightly wider and more transparent)
Polyline(
polylineId: const PolylineId('glow_effect'),
points: _animatedPoints,
color: EvacuationColors.primaryColor.withOpacity(0.3),
width: _pulseWidth + 3, // Remove .toDouble()
startCap: Cap.roundCap,
endCap: Cap.roundCap,
),

// Direction indicator at the end of the route
if (_animatedPoints.length > 1)
Polyline(
polylineId: const PolylineId('direction_indicator'),
points: [
_animatedPoints.last,
_getDirectionPoint(_animatedPoints),
],
color: Colors.white,
width: 3,
startCap: Cap.roundCap,
endCap: Cap.roundCap,
),
};

onUpdate(_animatedPolylines);
}

// Helper method to create a point that indicates direction
LatLng _getDirectionPoint(List<LatLng> points) {
if (points.length < 2) return points.last;

// Get the last two points to determine direction
final LatLng lastPoint = points.last;
final LatLng secondLastPoint = points[points.length - 2];

// Calculate direction vector
double dx = lastPoint.latitude - secondLastPoint.latitude;
double dy = lastPoint.longitude - secondLastPoint.longitude;

// Normalize and scale
double length = sqrt(dx * dx + dy * dy);
dx = dx / length * 0.0005; // Small offset for direction indicator
dy = dy / length * 0.0005;

// Return a point slightly ahead in the direction of travel
return LatLng(lastPoint.latitude + dx, lastPoint.longitude + dy);
}
}

// Helper method for square root calculation
double sqrt(double value) {
return value <= 0 ? 0 : math.sqrt(value);
}
2 changes: 1 addition & 1 deletion lib/features/disaster_alerts/widgets/expanded_actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ExpandedActions extends StatelessWidget {
return Padding(
padding: const EdgeInsets.all(24),
child: ElevatedButton(
onPressed: onNavigationStart,
onPressed: onNavigationStart, // This should set isExpanded to true
style: ElevatedButton.styleFrom(
backgroundColor: EvacuationColors.primaryColor,
foregroundColor: Colors.white,
Expand Down