Skip to content

Commit 5ca5281

Browse files
authored
Merge pull request #23 from SafeEscape-org/optimizations
FEAT:improved polylines animations
2 parents 9f5b2ea + bd5bfe3 commit 5ca5281

File tree

8 files changed

+653
-485
lines changed

8 files changed

+653
-485
lines changed

lib/features/disaster_alerts/widgets/evacuation_map.dart

Lines changed: 104 additions & 485 deletions
Large diffs are not rendered by default.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter/services.dart';
3+
import '../../constants/colors.dart';
4+
5+
class MapControls extends StatelessWidget {
6+
final bool isDarkMode;
7+
final bool trafficEnabled;
8+
final Function() onMapTypePressed;
9+
final Function() onTrafficToggled;
10+
final Function() onDarkModeToggled;
11+
final Function() onMyLocationPressed;
12+
13+
const MapControls({
14+
Key? key,
15+
required this.isDarkMode,
16+
required this.trafficEnabled,
17+
required this.onMapTypePressed,
18+
required this.onTrafficToggled,
19+
required this.onDarkModeToggled,
20+
required this.onMyLocationPressed,
21+
}) : super(key: key);
22+
23+
@override
24+
Widget build(BuildContext context) {
25+
return AnimatedContainer(
26+
duration: const Duration(milliseconds: 200),
27+
decoration: BoxDecoration(
28+
color: Colors.white,
29+
borderRadius: BorderRadius.circular(12),
30+
boxShadow: [
31+
BoxShadow(
32+
color: Colors.black.withOpacity(0.1),
33+
blurRadius: 8,
34+
offset: const Offset(0, 2),
35+
),
36+
],
37+
),
38+
child: Column(
39+
mainAxisSize: MainAxisSize.min,
40+
children: [
41+
// Map type button
42+
IconButton(
43+
icon: const Icon(Icons.layers_outlined),
44+
color: EvacuationColors.primaryColor,
45+
onPressed: () {
46+
HapticFeedback.selectionClick();
47+
onMapTypePressed();
48+
},
49+
),
50+
51+
// Traffic toggle
52+
IconButton(
53+
icon: Icon(
54+
Icons.traffic_outlined,
55+
color: trafficEnabled
56+
? EvacuationColors.primaryColor
57+
: Colors.grey[600],
58+
),
59+
onPressed: () {
60+
HapticFeedback.selectionClick();
61+
onTrafficToggled();
62+
},
63+
),
64+
65+
// Dark mode toggle
66+
IconButton(
67+
icon: Icon(
68+
isDarkMode ? Icons.wb_sunny_outlined : Icons.nights_stay_outlined,
69+
color: isDarkMode
70+
? EvacuationColors.primaryColor
71+
: Colors.grey[600],
72+
),
73+
onPressed: () {
74+
HapticFeedback.selectionClick();
75+
onDarkModeToggled();
76+
},
77+
),
78+
79+
// My location button
80+
IconButton(
81+
icon: const Icon(Icons.my_location),
82+
color: EvacuationColors.primaryColor,
83+
onPressed: () {
84+
HapticFeedback.selectionClick();
85+
onMyLocationPressed();
86+
},
87+
),
88+
],
89+
),
90+
);
91+
}
92+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import 'package:google_maps_flutter/google_maps_flutter.dart';
2+
3+
class MapStyleUtils {
4+
static Future<void> setMapStyle(GoogleMapController controller, bool isDarkMode) async {
5+
if (isDarkMode) {
6+
await controller.setMapStyle('''
7+
[
8+
{
9+
"elementType": "geometry",
10+
"stylers": [{"color": "#242f3e"}]
11+
},
12+
{
13+
"elementType": "labels.text.fill",
14+
"stylers": [{"color": "#746855"}]
15+
},
16+
{
17+
"elementType": "labels.text.stroke",
18+
"stylers": [{"color": "#242f3e"}]
19+
},
20+
{
21+
"featureType": "administrative.locality",
22+
"elementType": "labels.text.fill",
23+
"stylers": [{"color": "#d59563"}]
24+
},
25+
{
26+
"featureType": "poi",
27+
"elementType": "labels.text.fill",
28+
"stylers": [{"color": "#d59563"}]
29+
},
30+
{
31+
"featureType": "poi.park",
32+
"elementType": "geometry",
33+
"stylers": [{"color": "#263c3f"}]
34+
},
35+
{
36+
"featureType": "poi.park",
37+
"elementType": "labels.text.fill",
38+
"stylers": [{"color": "#6b9a76"}]
39+
},
40+
{
41+
"featureType": "road",
42+
"elementType": "geometry",
43+
"stylers": [{"color": "#38414e"}]
44+
},
45+
{
46+
"featureType": "road",
47+
"elementType": "geometry.stroke",
48+
"stylers": [{"color": "#212a37"}]
49+
},
50+
{
51+
"featureType": "road",
52+
"elementType": "labels.text.fill",
53+
"stylers": [{"color": "#9ca5b3"}]
54+
},
55+
{
56+
"featureType": "road.highway",
57+
"elementType": "geometry",
58+
"stylers": [{"color": "#746855"}]
59+
},
60+
{
61+
"featureType": "road.highway",
62+
"elementType": "geometry.stroke",
63+
"stylers": [{"color": "#1f2835"}]
64+
},
65+
{
66+
"featureType": "road.highway",
67+
"elementType": "labels.text.fill",
68+
"stylers": [{"color": "#f3d19c"}]
69+
},
70+
{
71+
"featureType": "transit",
72+
"elementType": "geometry",
73+
"stylers": [{"color": "#2f3948"}]
74+
},
75+
{
76+
"featureType": "transit.station",
77+
"elementType": "labels.text.fill",
78+
"stylers": [{"color": "#d59563"}]
79+
},
80+
{
81+
"featureType": "water",
82+
"elementType": "geometry",
83+
"stylers": [{"color": "#17263c"}]
84+
},
85+
{
86+
"featureType": "water",
87+
"elementType": "labels.text.fill",
88+
"stylers": [{"color": "#515c6d"}]
89+
},
90+
{
91+
"featureType": "water",
92+
"elementType": "labels.text.stroke",
93+
"stylers": [{"color": "#17263c"}]
94+
}
95+
]
96+
''');
97+
} else {
98+
await controller.setMapStyle(null); // Reset to default style
99+
}
100+
}
101+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:google_maps_flutter/google_maps_flutter.dart';
3+
import '../../constants/colors.dart';
4+
5+
class MapTypeSelector {
6+
static void show(BuildContext context, MapType currentMapType, Function(MapType) onMapTypeSelected) {
7+
showModalBottomSheet(
8+
context: context,
9+
shape: const RoundedRectangleBorder(
10+
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
11+
),
12+
builder: (context) => Container(
13+
padding: const EdgeInsets.symmetric(vertical: 20),
14+
child: Column(
15+
mainAxisSize: MainAxisSize.min,
16+
children: [
17+
const Padding(
18+
padding: EdgeInsets.only(bottom: 16),
19+
child: Text(
20+
'Select Map Type',
21+
style: TextStyle(
22+
fontSize: 18,
23+
fontWeight: FontWeight.bold,
24+
),
25+
),
26+
),
27+
_buildMapTypeOption(
28+
context,
29+
'Standard',
30+
Icons.map_outlined,
31+
MapType.normal,
32+
currentMapType,
33+
onMapTypeSelected,
34+
),
35+
_buildMapTypeOption(
36+
context,
37+
'Satellite',
38+
Icons.satellite_outlined,
39+
MapType.satellite,
40+
currentMapType,
41+
onMapTypeSelected,
42+
),
43+
_buildMapTypeOption(
44+
context,
45+
'Terrain',
46+
Icons.terrain_outlined,
47+
MapType.terrain,
48+
currentMapType,
49+
onMapTypeSelected,
50+
),
51+
_buildMapTypeOption(
52+
context,
53+
'Hybrid',
54+
Icons.layers_outlined,
55+
MapType.hybrid,
56+
currentMapType,
57+
onMapTypeSelected,
58+
),
59+
],
60+
),
61+
),
62+
);
63+
}
64+
65+
static Widget _buildMapTypeOption(
66+
BuildContext context,
67+
String title,
68+
IconData icon,
69+
MapType mapType,
70+
MapType currentMapType,
71+
Function(MapType) onMapTypeSelected,
72+
) {
73+
final isSelected = currentMapType == mapType;
74+
75+
return ListTile(
76+
leading: Icon(
77+
icon,
78+
color: isSelected ? EvacuationColors.primaryColor : Colors.grey[600],
79+
),
80+
title: Text(
81+
title,
82+
style: TextStyle(
83+
color: isSelected ? EvacuationColors.primaryColor : Colors.black,
84+
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
85+
),
86+
),
87+
trailing: isSelected
88+
? const Icon(Icons.check, color: EvacuationColors.primaryColor)
89+
: null,
90+
onTap: () {
91+
onMapTypeSelected(mapType);
92+
Navigator.pop(context);
93+
},
94+
);
95+
}
96+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:google_maps_flutter/google_maps_flutter.dart';
3+
import 'dart:async';
4+
import '../../constants/colors.dart';
5+
6+
class RouteAnimator {
7+
List<LatLng> _animatedPoints = [];
8+
Set<Polyline> _animatedPolylines = {};
9+
Timer? _routeAnimationTimer;
10+
bool _isAnimatingRoute = false;
11+
12+
// Getters
13+
Set<Polyline> get animatedPolylines => _animatedPolylines;
14+
bool get isAnimating => _isAnimatingRoute;
15+
16+
void dispose() {
17+
_routeAnimationTimer?.cancel();
18+
}
19+
20+
void clearRoute() {
21+
_animatedPolylines.clear();
22+
_animatedPoints.clear();
23+
_isAnimatingRoute = false;
24+
_routeAnimationTimer?.cancel();
25+
}
26+
27+
void startAnimation(Set<Polyline> polylines, Function(Set<Polyline>) onUpdate, Function() onComplete) {
28+
// Cancel any existing animation
29+
_routeAnimationTimer?.cancel();
30+
31+
if (polylines.isEmpty) return;
32+
33+
_isAnimatingRoute = true;
34+
_animatedPoints = [];
35+
_animatedPolylines = {};
36+
onUpdate(_animatedPolylines);
37+
38+
final allPoints = polylines.first.points;
39+
final totalPoints = allPoints.length;
40+
int currentPointIndex = 0;
41+
42+
// Create a timer that adds points to the animated polyline
43+
_routeAnimationTimer = Timer.periodic(const Duration(milliseconds: 50), (timer) {
44+
if (currentPointIndex >= totalPoints) {
45+
timer.cancel();
46+
return;
47+
}
48+
49+
// Add the next point to our animated points list
50+
_animatedPoints.add(allPoints[currentPointIndex]);
51+
52+
// 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+
};
67+
68+
onUpdate(_animatedPolylines);
69+
70+
currentPointIndex++;
71+
72+
// If we've added all points, stop the animation
73+
if (currentPointIndex >= totalPoints) {
74+
_isAnimatingRoute = false;
75+
onComplete();
76+
timer.cancel();
77+
}
78+
});
79+
}
80+
}

0 commit comments

Comments
 (0)