Aidra Driver 1.3.5+68
Aidra Driver - Your path to green energy
Loading...
Searching...
No Matches
end_location_chooser_screen.dart
Go to the documentation of this file.
1import 'package:easy_localization/easy_localization.dart';
2import 'package:flutter/material.dart';
3import 'package:flutter_bloc/flutter_bloc.dart';
4import 'package:flutter_screenutil/flutter_screenutil.dart';
5import 'package:google_maps_flutter/google_maps_flutter.dart';
6import 'package:geolocator/geolocator.dart';
7import 'package:geocoding/geocoding.dart';
8import 'package:go_router/go_router.dart';
9import 'package:fluentui_system_icons/fluentui_system_icons.dart';
10import 'dart:async';
11
12import '../../../../../core/ui/theme/color_palette.dart';
13import '../../../../../core/ui/widgets/custom_scaffold.dart';
14import '../../../../../core/router/routes.dart';
15import '../../bloc/planning_bloc/routing_planning_bloc.dart';
16import '../../../../auth/presentation/bloc/authentication_bloc/authentication_bloc.dart';
17import 'models/saved_location.dart';
18import 'widgets/location_search_bar.dart';
19import 'widgets/saved_locations_list.dart';
20import 'widgets/custom_pin_widget.dart';
21import 'widgets/address_info_widget.dart';
22import 'widgets/map_overlay_buttons.dart';
23import 'widgets/confirm_location_button.dart';
24
25class EndLocationChooserScreen extends StatefulWidget {
26 const EndLocationChooserScreen({super.key});
27
28 @override
29 State<EndLocationChooserScreen> createState() => _EndLocationChooserScreenState();
30}
31
32class _EndLocationChooserScreenState extends State<EndLocationChooserScreen> {
33 GoogleMapController? _mapController;
34 bool _isMapLoaded = false;
35 final TextEditingController _searchController = TextEditingController();
36 LatLng _initialPosition = const LatLng(51.5074, -0.1278);
37 LatLng _currentCameraPosition = const LatLng(51.5074, -0.1278);
40 String _currentAddress = 'scheduling.choose_end_location'.tr();
41 bool _isLoadingAddress = false;
42 bool _isSearchVisible = false;
44
45 final String mapStyle = '''
46 [
47 {
48 "featureType": "poi",
49 "elementType": "labels.icon",
50 "stylers": [
51 {
52 "visibility": "off"
53 }
54 ]
55 },
56 {
57 "featureType": "transit",
58 "stylers": [
59 {
60 "visibility": "off"
61 }
62 ]
63 }
64 ]
65 ''';
66
67 final List<SavedLocation> _savedLocations = [
69 name: 'Dammam Warehouse',
70 address: 'Al-Dammam, Saudi Arabia',
71 latLng: LatLng(26.251011, 49.971146),
72 icon: FluentIcons.building_16_filled,
73 ),
75 name: 'Jeddah Warehouse',
76 address: 'Prince Abdulmajeed, Jeddah Saudi Arabia',
77 latLng: LatLng(21.418327917324838, 39.25912328519525),
78 icon: FluentIcons.building_16_filled,
79 ),
81 name: 'Riyadh Warehouse',
82 address: 'Al Misfat, Riyad Arabie saoudite',
83 latLng: LatLng(24.5518913, 46.8818212),
84 icon: FluentIcons.building_16_filled,
85 ),
86 ];
87
88 @override
89 void initState() {
90 super.initState();
92 }
93
94 Future<void> _getCurrentLocationForInit() async {
95 try {
96 bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
97 if (!serviceEnabled) return;
98
99 LocationPermission permission = await Geolocator.checkPermission();
100 if (permission == LocationPermission.denied) {
101 permission = await Geolocator.requestPermission();
102 if (permission == LocationPermission.denied) return;
103 }
104
105 if (permission == LocationPermission.deniedForever) return;
106
107 final position = await Geolocator.getCurrentPosition(
108 locationSettings: const LocationSettings(
109 accuracy: LocationAccuracy.high,
110 timeLimit: Duration(seconds: 10),
111 ),
112 );
113
114 setState(() {
115 _initialPosition = LatLng(position.latitude, position.longitude);
117 });
118
119 // Get initial address
121
122 if (_mapController != null) {
123 await _mapController!.animateCamera(
124 CameraUpdate.newCameraPosition(
125 CameraPosition(
126 target: _initialPosition,
127 zoom: 15,
128 ),
129 ),
130 );
131 }
132 } catch (e) {
133 // Handle error silently, keep default position
134 }
135 }
136
137 void _onCameraMove(CameraPosition position) {
138 _currentCameraPosition = position.target;
139 setState(() {
140 _isLoadingAddress = true;
141 });
142
143 // Cancel previous timer
144 _debounceTimer?.cancel();
145
146 // Set new timer to debounce the address lookup
147 _debounceTimer = Timer(const Duration(milliseconds: 500), () {
148 _getAddressFromLocation(position.target);
149 });
150 }
151
153 setState(() {
154 _isBottomSheetExpanded = false; // Collapse bottom sheet when map is moved
155 _isBottomSheetVisible = false; // Hide bottom sheet when map is moved
156 });
157 }
158
159 Future<void> _getAddressFromLocation(LatLng location) async {
160 setState(() {
161 _isLoadingAddress = true;
162 });
163
164 try {
165 List<Placemark> placemarks = await placemarkFromCoordinates(
166 location.latitude,
167 location.longitude,
168 );
169
170 if (placemarks.isNotEmpty && mounted) {
171 final placemark = placemarks.first;
172 String address = '';
173
174 if (placemark.street != null && placemark.street!.isNotEmpty) {
175 address += placemark.street!;
176 }
177
178 if (placemark.locality != null && placemark.locality!.isNotEmpty) {
179 if (address.isNotEmpty) address += ', ';
180 address += placemark.locality!;
181 }
182
183 if (placemark.administrativeArea != null &&
184 placemark.administrativeArea!.isNotEmpty) {
185 if (address.isNotEmpty) address += ', ';
186 address += placemark.administrativeArea!;
187 }
188
189 if (placemark.country != null && placemark.country!.isNotEmpty) {
190 if (address.isNotEmpty) address += ', ';
191 address += placemark.country!;
192 }
193
194 setState(() {
195 _currentAddress = address.isNotEmpty ? address : 'scheduling.selected_location'.tr();
196 _isLoadingAddress = false;
197 });
198 }
199 } catch (e) {
200 if (mounted) {
201 setState(() {
202 _currentAddress = 'scheduling.selected_location'.tr();
203 _isLoadingAddress = false;
204 });
205 }
206 }
207 }
208
210 if (_mapController != null) {
211 await _mapController!.animateCamera(
212 CameraUpdate.newCameraPosition(
213 CameraPosition(
214 target: location.latLng,
215 zoom: 15,
216 ),
217 ),
218 );
219 }
220 // Close bottom sheet after selecting location
221 setState(() {
222 _isBottomSheetVisible = false;
224 });
225 }
226
228 // Set end location in routing planning bloc
229 context.read<RoutingPlanningBloc>().add(
233 ),
234 );
235
236 // Get authenticated user and create routing
237 final authState = context.read<AuthenticationBloc>().state;
238 if (authState is AuthenticatedState) {
239 context.read<RoutingPlanningBloc>().add(
240 CreateRoutingEvent(
241 driverId: (authState.session.uid)?.toInt() ?? 0,
242 duration: 60,
243 ),
244 );
245 }
246 }
247
248 @override
249 Widget build(BuildContext context) {
250 return BlocListener<RoutingPlanningBloc, RoutingPlanningState>(
251 listener: (context, state) {
252 if (state is RoutingPlanningError) {
253 ScaffoldMessenger.of(context).showSnackBar(
254 SnackBar(
255 content: Text(state.failure.message),
256 backgroundColor: state.failure.color,
257 ),
258 );
259 } else if (state is RoutingPlanningSuccess) {
260 // Clear the navigation stack and navigate to schedule confirmation
261 context.go(Routes.scheduleConfirmationScreen.route);
262 }
263 },
264 child: CustomScaffold(
265 title: 'scheduling.choose_end_location'.tr(),
266 backgroundColor: ColorPalette.antiFlashWhite,
267 body: Column(
268 children: [
269 Expanded(
270 child: Stack(
271 children: [
272 GoogleMap(
273 style: mapStyle,
274 initialCameraPosition: CameraPosition(
275 target: _initialPosition,
276 zoom: 10,
277 ),
278 onMapCreated: (GoogleMapController controller) {
279 _mapController = controller;
280 setState(() {
281 _isMapLoaded = true;
282 });
283 },
284 onCameraMove: _onCameraMove,
285 onCameraIdle: _onCameraIdle,
286 myLocationEnabled: true,
287 myLocationButtonEnabled: false,
288 zoomControlsEnabled: false,
289 zoomGesturesEnabled: true,
290 mapToolbarEnabled: false,
291 compassEnabled: true,
292 ),
295 // Search bar overlay (when visible)
297 Positioned(
298 top: 20.h,
299 left: 16.w,
300 right: 16.w,
301 child: LocationSearchBar(
302 controller: _searchController,
303 isVisible: _isSearchVisible,
304 onLocationSelected: (location) {
305 if (_mapController != null) {
306 _mapController!.animateCamera(
307 CameraUpdate.newCameraPosition(
308 CameraPosition(
309 target: location,
310 zoom: 15,
311 ),
312 ),
313 );
314 }
315 },
316 onClose: () {
317 setState(() {
318 _isSearchVisible = false;
319 });
320 },
321 ),
322 ),
323 if (!_isSearchVisible)
324 Positioned(
325 top: 20.h,
326 left: 16.w,
327 right: 16.w,
328 child: AddressInfoWidget(
331 ),
332 ),
333 if (!_isMapLoaded)
334 Container(
335 color: Colors.white.withValues(alpha: 0.7),
336 child: const Center(
337 child: CircularProgressIndicator(),
338 ),
339 ),
340 Positioned(
341 right: 16,
342 bottom: 115.h,
343 child: MapOverlayButtons(
344 onSearchPressed: () {
345 setState(() {
347 });
348 },
349 onSavedLocationsPressed: () {
350 setState(() {
354 }
355 });
356 },
357 ),
358 ),
359 Positioned(
360 bottom: 20.h,
361 left: 16.w,
362 right: 16.w,
363 child: BlocBuilder<RoutingPlanningBloc, RoutingPlanningState>(
364 builder: (context, state) {
365 final isLoading = state is RoutingPlanningLoading;
366 return ConfirmLocationButton(
369 );
370 },
371 ),
372 ),
373 ],
374 ),
375 ),
377 AnimatedContainer(
378 duration: const Duration(milliseconds: 300),
379 curve: Curves.easeInOut,
380 height: _isBottomSheetExpanded ? 280.h : 60.h,
381 child: SavedLocationsList(
382 savedLocations: _savedLocations,
383 onLocationSelected: _onSavedLocationSelected,
384 isExpanded: _isBottomSheetExpanded,
385 onToggle: () {
386 setState(() {
388 });
389 },
390 onClose: () {
391 setState(() {
392 _isBottomSheetVisible = false;
394 });
395 },
396 ),
397 ),
398 ],
399 ),
400 ),
401 );
402 }
403
405 return Center(
406 child: Transform.translate(
407 offset: Offset(0, -24.5.sp),
408 child: AnimatedContainer(
409 duration: const Duration(milliseconds: 200),
410 transform:
411 Matrix4.translationValues(0, _isLoadingAddress ? -5 : 0, 0),
412 child: CustomPinWidget(
414 size: 35.sp,
415 hasShadow: _isLoadingAddress,
416 ),
417 ),
418 ),
419 );
420 }
421
423 return Center(
424 child: Container(
425 width: 4.sp,
426 height: 4.sp,
427 decoration: BoxDecoration(
430 : ColorPalette.lightGreen.withValues(alpha: 0.6),
431 shape: BoxShape.circle,
432 boxShadow: _isLoadingAddress
433 ? [
434 BoxShadow(
435 color: Colors.black.withValues(alpha: 0.3),
436 offset: const Offset(0, 2),
437 blurRadius: 2,
438 spreadRadius: 2,
439 ),
440 ]
441 : [],
442 ),
443 ),
444 );
445 }
446
447 @override
448 void dispose() {
449 _mapController?.dispose();
450 _searchController.dispose();
451 _debounceTimer?.cancel();
452 super.dispose();
453 }
454}
override void initState()
override void dispose()
class App extends StatefulWidget build(BuildContext context)
Definition app.dart:31
bool isLoading
static const lightGreen
static const antiFlashWhite
override State< EndLocationChooserScreen > createState()
const EndLocationChooserScreen({super.key})
final Widget child
class Partner String
final String address
final List< SavedLocation > _savedLocations
void _onSavedLocationSelected(SavedLocation location) async
Widget _buildCenterPin()
Future< void > _getCurrentLocationForInit() async
final String mapStyle
final TextEditingController _searchController
Widget _buildCenterDot()
class EndLocationChooserScreen extends StatefulWidget _mapController
LatLng _initialPosition
Future< void > _getAddressFromLocation(LatLng location) async
LatLng _currentCameraPosition
void _onCameraMove(CameraPosition position)
bool _isBottomSheetExpanded
final Color color
Definition failures.dart:1
class UpdateLocationEvent extends LocationSelectionEvent location
final VoidCallback onPressed
Routes
Definition routes.dart:30
const SetEndLocationEvent({ required this.location, required this.address, })
const RoutingPlanningError({ required this.failure, this.startLocation, this.startAddress, this.selectedCollections, this.collectionsToSchedule, this.endLocation, this.endAddress, })
final String title
style Text( '${ 'scheduling.reference'.tr()}:${collection.internalCode}', style:Theme.of(context).textTheme.bodySmall,)
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,),),),],)