Aidra Driver 1.3.5+68
Aidra Driver - Your path to green energy
Loading...
Searching...
No Matches
collections_map_view.dart
Go to the documentation of this file.
1import 'package:flutter/material.dart';
2import 'package:flutter_screenutil/flutter_screenutil.dart';
3import 'package:google_maps_flutter/google_maps_flutter.dart';
4import 'package:go_router/go_router.dart';
5import 'package:geolocator/geolocator.dart';
6import 'dart:async';
7import 'dart:ui' as ui;
8
9import '../../../../../../core/common/entities/collection_entity.dart';
10import '../../../../../../core/router/routes.dart';
11import '../../../../../../core/ui/theme/color_palette.dart';
12import '../widgets/map_button.dart';
13import '../widgets/map_details_card.dart';
14
15class CollectionsMapView extends StatefulWidget {
16 final List<CollectionEntity> collections;
17
19 super.key,
20 required this.collections,
21 });
22
23 @override
24 State<CollectionsMapView> createState() => _CollectionsMapViewState();
25}
26
27class _CollectionsMapViewState extends State<CollectionsMapView>
28 with SingleTickerProviderStateMixin {
29 GoogleMapController? _mapController;
30 Map<MarkerId, Marker> _markers = {};
31 bool _isMapLoaded = false;
33 late AnimationController _animationController;
35 [
36 {
37 "featureType": "poi",
38 "elementType": "labels.icon",
39 "stylers": [
40 {
41 "visibility": "off"
42 }
43 ]
44 },
45 {
46 "featureType": "transit",
47 "stylers": [
48 {
49 "visibility": "off"
50 }
51 ]
52 }
53 ]
54 ''';
55
56 @override
57 void initState() {
58 super.initState();
59 _animationController = AnimationController(
60 vsync: this,
61 duration: const Duration(milliseconds: 300),
62 );
64 }
65
66 @override
67 void didUpdateWidget(CollectionsMapView oldWidget) {
68 super.didUpdateWidget(oldWidget);
69 if (widget.collections != oldWidget.collections) {
71 }
72 }
73
74 Future<void> _createMarkers() async {
75 final markers = <MarkerId, Marker>{};
76
77 for (int i = 0; i < widget.collections.length; i++) {
78 final collection = widget.collections[i];
79
80 if (collection.lat == null || collection.lng == null) continue;
81
82 final markerId = MarkerId(collection.id.toString());
83
84 final marker = Marker(
85 markerId: markerId,
86 position: LatLng(
87 double.parse(collection.lat!), double.parse(collection.lng!)),
88 infoWindow: InfoWindow(
89 title: collection.restaurant ?? 'Collection ${i + 1}',
90 snippet: collection.address ?? '',
91 onTap: () {
92 context.push(
93 Routes.collectionDetailsScreen.route,
94 extra: collection,
95 );
96 },
97 ),
98 icon: await _getMarkerBitmap(i + 1),
99 onTap: () {
100 setState(() {
102 });
103 },
104 );
105
106 markers[markerId] = marker;
107 }
108
109 setState(() {
110 _markers = markers;
111 });
112 }
113
114 Future<BitmapDescriptor> _getMarkerBitmap(int number) async {
115 final size = 120.0;
116 final pictureRecorder = ui.PictureRecorder();
117 final canvas = Canvas(pictureRecorder);
118 final paint = Paint()..color = ColorPalette.lightGreen;
119 final textPainter = TextPainter(
120 textDirection: TextDirection.ltr,
121 textAlign: TextAlign.center,
122 );
123
124 canvas.drawCircle(Offset(size / 2, size / 2), size / 2, paint);
125
126 final borderPaint = Paint()
127 ..color = Colors.white
128 ..style = PaintingStyle.stroke
129 ..strokeWidth = 8;
130 canvas.drawCircle(Offset(size / 2, size / 2), size / 2 - 4, borderPaint);
131
132 textPainter.text = TextSpan(
133 text: number.toString(),
134 style: const TextStyle(
135 fontSize: 50,
136 fontWeight: FontWeight.bold,
137 color: Colors.white,
138 ),
139 );
140
141 textPainter.layout();
142 textPainter.paint(
143 canvas,
144 Offset(
145 (size - textPainter.width) / 2,
146 (size - textPainter.height) / 2,
147 ),
148 );
149
150 final img = await pictureRecorder.endRecording().toImage(
151 size.toInt(),
152 size.toInt(),
153 );
154 final data = await img.toByteData(format: ui.ImageByteFormat.png);
155
156 return BitmapDescriptor.bytes(data!.buffer.asUint8List(), height: 35.sp);
157 }
158
159 @override
160 Widget build(BuildContext context) {
161 LatLng initialPosition = const LatLng(51.5074, -0.1278);
162
163 for (final collection in widget.collections) {
164 if (collection.lat != null && collection.lng != null) {
165 initialPosition = LatLng(
166 double.parse(collection.lat!), double.parse(collection.lng!));
167 break;
168 }
169 }
170
171 return Stack(
172 children: [
173 GoogleMap(
174 style: mapStyle,
175 initialCameraPosition: CameraPosition(
176 target: initialPosition,
177 zoom: 10,
178 ),
179 markers: Set<Marker>.of(_markers.values),
180 onMapCreated: (GoogleMapController controller) {
181 _mapController = controller;
182
183 setState(() {
184 _isMapLoaded = true;
185 });
186 },
187 myLocationEnabled: true,
188 myLocationButtonEnabled: false,
189 zoomControlsEnabled: false,
190 mapToolbarEnabled: false,
191 compassEnabled: true,
192 onTap: (_) {
193 if (_selectedMarkerIndex != -1) {
194 setState(() {
196 });
197 }
198 },
199 ),
200 if (!_isMapLoaded)
201 Container(
202 color: Colors.white.withValues(alpha: 0.7),
203 child: const Center(
204 child: CircularProgressIndicator(),
205 ),
206 ),
207 Positioned(
208 right: 16,
209 bottom: 16,
210 child: Column(
211 mainAxisSize: MainAxisSize.min,
212 children: [
213 MapButton(
214 icon: Icons.my_location,
215 tooltip: 'My Location',
217 ),
218 const SizedBox(height: 8),
219 MapButton(
220 icon: Icons.add,
221 tooltip: 'Zoom In',
222 onPressed: () {
223 _mapController?.animateCamera(CameraUpdate.zoomIn());
224 },
225 ),
226 const SizedBox(height: 8),
227 MapButton(
228 icon: Icons.remove,
229 tooltip: 'Zoom Out',
230 onPressed: () {
231 _mapController?.animateCamera(CameraUpdate.zoomOut());
232 },
233 ),
234 ],
235 ),
236 ),
237 if (_selectedMarkerIndex >= 0 &&
238 _selectedMarkerIndex < widget.collections.length)
239 Positioned(
240 bottom: 16,
241 left: 16,
242 right: 80,
243 child: AnimatedBuilder(
244 animation: _animationController,
245 builder: (context, child) {
246 if (_selectedMarkerIndex != -1 &&
247 !_animationController.isAnimating) {
248 _animationController.forward();
249 }
250 return Transform.translate(
251 offset: Offset(0, 20 * (1 - _animationController.value)),
252 child: Opacity(
253 opacity: _animationController.value,
254 child: child,
255 ),
256 );
257 },
258 child: MapDetailsCard(
259 collection: widget.collections[_selectedMarkerIndex],
260 index: _selectedMarkerIndex + 1,
261 ),
262 ),
263 ),
264 ],
265 );
266 }
267
268 void _goToUserLocation() async {
269 if (_mapController == null) return;
270
271 try {
272 bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
273 if (!serviceEnabled) {
274 if (mounted) {
275 ScaffoldMessenger.of(context).showSnackBar(
276 const SnackBar(
277 content: Text(
278 'Location services are disabled. Please enable them in settings.'),
279 duration: Duration(seconds: 3),
280 ),
281 );
282 }
283 return;
284 }
285
286 LocationPermission permission = await Geolocator.checkPermission();
287 if (permission == LocationPermission.denied) {
288 permission = await Geolocator.requestPermission();
289 if (permission == LocationPermission.denied) {
290 if (mounted) {
291 ScaffoldMessenger.of(context).showSnackBar(
292 const SnackBar(
293 content: Text('Location permission denied'),
294 duration: Duration(seconds: 2),
295 ),
296 );
297 }
298 return;
299 }
300 }
301
302 if (permission == LocationPermission.deniedForever) {
303 if (mounted) {
304 ScaffoldMessenger.of(context).showSnackBar(
305 const SnackBar(
306 content: Text(
307 'Location permissions are permanently denied. Please enable them in app settings.'),
308 duration: Duration(seconds: 3),
309 ),
310 );
311 }
312 return;
313 }
314
315 final position = await Geolocator.getCurrentPosition(
316 locationSettings: const LocationSettings(
317 accuracy: LocationAccuracy.high,
318 timeLimit: Duration(seconds: 10),
319 ),
320 );
321
322 await _mapController!.animateCamera(
323 CameraUpdate.newCameraPosition(
324 CameraPosition(
325 target: LatLng(position.latitude, position.longitude),
326 zoom: 15,
327 ),
328 ),
329 );
330 } catch (e) {
331 if (mounted) {
332 ScaffoldMessenger.of(context).showSnackBar(
333 SnackBar(
334 content: Text('Unable to get your location: ${e.toString()}'),
335 duration: const Duration(seconds: 3),
336 ),
337 );
338 }
339 }
340 }
341
342 @override
343 void dispose() {
344 _animationController.dispose();
345 _mapController?.dispose();
346 super.dispose();
347 }
348}
override void initState()
class App extends StatefulWidget build(BuildContext context)
Definition app.dart:31
AuthGuard _()
static const lightGreen
final List< CollectionEntity > collections
override State< CollectionsMapView > createState()
const CollectionsMapView({ super.key, required this.collections, })
override void didUpdateWidget(CollectionsMapView oldWidget)
late AnimationController _animationController
void _goToUserLocation() async
int _selectedMarkerIndex
Future< void > _createMarkers() async
Future< BitmapDescriptor > _getMarkerBitmap(int number) async
override void dispose()
final Widget child
override void paint(Canvas canvas, Size size)
class Partner String
final String mapStyle
class EndLocationChooserScreen extends StatefulWidget _mapController
final Color color
Definition failures.dart:1
final VoidCallback onPressed
const MapButton({ super.key, required this.icon, required this.onPressed, this.tooltip, })
final String tooltip
final VoidCallback onTap
final String title
Set< Marker > _markers
class SearchWeeklyCollectionsEvent extends WeeklyCollectionsEvent collection
style Text( '${ 'scheduling.reference'.tr()}:${collection.internalCode}', style:Theme.of(context).textTheme.bodySmall,)
style SizedBox(height:2.h)
style Column(crossAxisAlignment:CrossAxisAlignment.end, children:[Container(padding:EdgeInsets.symmetric(horizontal:8.w, vertical:4.h), decoration:BoxDecoration(color:ColorPalette.tiffanyBlue.withValues(alpha:0.1), borderRadius:BorderRadius.circular(12),), child:Text(collection.type ?? '', style:Theme.of(context).textTheme.bodySmall?.copyWith(color:ColorPalette.tiffanyBlue, fontWeight:FontWeight.bold,),),),],)