import 'dart:async'; import 'dart:developer'; import 'dart:math' as math; import 'dart:ui' as ui; import 'package:wanisclient/config/AppSnackbar.dart'; import 'package:wanisclient/config/CustomPoup.dart'; import 'package:wanisclient/config/custom_loader.dart'; import 'package:wanisclient/config/custom_text.dart'; import 'package:wanisclient/config/enum/orde_type.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import '../config/enum/ride_enum.dart'; import 'package:wanisclient/config/enum/ride_type.dart'; import 'package:wanisclient/config/language/language_enum.dart'; import 'package:wanisclient/providers/language_provider.dart'; import 'package:wanisclient/providers/myAddress_provider.dart'; import 'package:wanisclient/providers/ride_providers.dart'; import 'package:wanisclient/screens/promo_code_input.dart'; import 'package:wanisclient/screens/ride_request_details.dart'; import 'package:wanisclient/services/Sockets/PubNubManager.dart'; import 'package:wanisclient/utils/colors.dart'; import 'package:wanisclient/widgets/SchedulePickupWidget.dart'; import 'package:wanisclient/widgets/order_summary.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:geolocator/geolocator.dart' as geolocator; import 'package:latlong2/latlong.dart' as latlng; import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:sidebarx/sidebarx.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart'; import '../providers/public_provider.dart'; import '../providers/utils_singleton.dart'; import '../services/SystemInsetsService.dart'; import 'TripsScreen.dart'; class RequstRideScreen extends StatefulWidget { const RequstRideScreen({ super.key, required this.controllerSideBar, }); final SidebarXController controllerSideBar; @override State createState() => _RequstRideScreenState(); } class _RequstRideScreenState extends State with WidgetsBindingObserver{ final PanelController _panelController = PanelController(); // --- Mapbox State Variables --- MapboxMap? mapboxMap; PointAnnotationManager? taxiAnnotationManager; PointAnnotationManager? userPinAnnotationManager; // --- Path Markers ------------ PointAnnotationManager? _startAnnotationManager; PointAnnotationManager? _destinationAnnotationManager; PointAnnotation? _startAnnotation; PointAnnotation? _destinationAnnotation; PolylineAnnotationManager? _routePolylineManager; PolylineAnnotation? _routePolyline; Timer? _updateDebounceTimer; bool _isDisposed = false; // Add these variables to track current state latlng.LatLng? _lastStartLocation; latlng.LatLng? _lastDestinationLocation; RideEnum? _lastRideState; bool _isDrawingPolyline = false; double _systembottompadding = 0.0; DateTime _lastLogTime = DateTime.now(); bool _suppressLogs = false; bool isLoading = true; bool permissionsGranted = false; latlng.LatLng? _currentGpsLocation; List _taxiAnnotations = []; latlng.LatLng DEFAULT_MAP_CENTER = latlng.LatLng(36.8065, 10.1815); // Tunis, Tunisia // --- Location Tracking --- StreamSubscription? _positionStreamSubscription; // --- Socket and Driver Data --- final Map _driverLocations = {}; final Map _previousDriverLocations = {}; late PubNubManager _pubnubManager; StreamSubscription? _rideUpdateSubscription; Uint8List? _taxiIconBytes; Timer? _taxiRefreshDebounceTimer; final Map _activeTaxiAnnotations = {}; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _initialize(); WidgetsBinding.instance.addPostFrameCallback((_) async { await _initializeSocket(); }); } // Modify your log statements to be less spammy void _debugLog(String message) { final now = DateTime.now(); if (now.difference(_lastLogTime).inMilliseconds > 1000 || !_suppressLogs) { log(message); _lastLogTime = now; } } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.resumed) { // When the app comes back to the foreground, force a marker refresh log("[DEBUG] App resumed. Forcing taxi marker refresh."); if (mounted) { _refreshSimpleTaxiMarkers(); } } } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _isDisposed = true; _suppressLogs = true; // Stop all logging during dispose _taxiRefreshDebounceTimer?.cancel(); _updateDebounceTimer?.cancel(); _rideUpdateSubscription?.cancel(); _positionStreamSubscription?.cancel(); // Clean up in reverse order of creation _clearRoutePolyline(); _clearRouteMarkers(); _clearTaxiAnnotations(); super.dispose(); } Future _performMapOperations(List operations) async { for (final operation in operations) { if (_isDisposed) break; try { await operation(); // Small delay to prevent UI lag await Future.delayed(const Duration(milliseconds: 16)); } catch (e) { _debugLog('[WARNING] Error in map operation: $e'); } } } Future _clearTaxiAnnotations() async { try { if (taxiAnnotationManager != null && _taxiAnnotations.isNotEmpty) { for (final annotation in _taxiAnnotations) { try { await taxiAnnotationManager?.delete(annotation); } catch (e) { log('[WARNING] Error deleting taxi annotation: $e'); // Continue with other annotations } } _taxiAnnotations.clear(); } } catch (e) { log('[ERROR] Error clearing taxi annotations: $e'); _taxiAnnotations.clear(); } } Future _initializeSocket() async { log("[DEBUG] Initializing Socket..."); _pubnubManager = Provider.of(context, listen: false); _rideUpdateSubscription = _pubnubManager.rideUpdateStream.listen(_handleRideUpdate); log("[DEBUG] Socket Initialized."); final pad = await SystemInsetsService.instance.bottomPadding(context); log("pad : $pad"); if (mounted) setState(() => _systembottompadding = pad); } Future _initialize() async { log("[DEBUG] Starting Initialization..."); if (!mounted) return; setState(() => isLoading = true); // Try to get last known location first final lastLocation = await _getLastKnownLocation(); if (lastLocation != null) { setState(() { _currentGpsLocation = lastLocation; }); } else { // Use a better default location setState(() { _currentGpsLocation = DEFAULT_MAP_CENTER; }); } await _checkPermissions(); if (permissionsGranted) { log("[DEBUG] Permissions granted. Starting location updates."); _startLocationUpdates(); } else { log("[DEBUG] Permissions denied. Stopping loader."); setState(() => isLoading = false); } log("[DEBUG] Loading address history..."); await Provider.of(context, listen: false) .fetchHistorie(); log("[DEBUG] Address history loaded."); } Future _checkPermissions() async { log("[DEBUG] Checking location permissions..."); if (await Permission.location.request().isGranted) { log("[DEBUG] Location permission is granted."); setState(() => permissionsGranted = true); } else { log("[DEBUG] Location permission is NOT granted."); setState(() => permissionsGranted = false); } } void _startLocationUpdates() { log("[DEBUG] _startLocationUpdates called."); const geolocator.LocationSettings locationSettings = geolocator.LocationSettings( accuracy: geolocator.LocationAccuracy.high, distanceFilter: 10, ); _positionStreamSubscription = geolocator.Geolocator.getPositionStream( locationSettings: locationSettings) .listen((geolocator.Position position) { log("[DEBUG] Received new location: Lat: ${position.latitude}, Lng: ${position.longitude}"); if (!mounted) return; final newLocation = latlng.LatLng(position.latitude, position.longitude); final bool isFirstLocation = _currentGpsLocation == null; setState(() { _currentGpsLocation = newLocation; }); // No need to manually update user pin - Mapbox handles it automatically if (isFirstLocation && mapboxMap != null) { _flyToLocationAndStopLoading(newLocation); } }, onError: (error) { log("[DEBUG] Error in location stream: $error"); }); } void _flyToLocationAndStopLoading(latlng.LatLng location) { log("[DEBUG] Flying to initial camera position."); mapboxMap?.flyTo( CameraOptions( center: Point( coordinates: Position(location.longitude, location.latitude)), zoom: 16, ), MapAnimationOptions(duration: 1500, startDelay: 0), ); log("[DEBUG] CRITICAL: Setting isLoading to FALSE."); if (mounted) { setState(() { isLoading = false; }); } } void _recenterMap() { if (_currentGpsLocation != null) { mapboxMap?.flyTo( CameraOptions( center: Point( coordinates: Position(_currentGpsLocation!.longitude, _currentGpsLocation!.latitude)), zoom: 16, ), MapAnimationOptions(duration: 1500, startDelay: 0), ); } } void _onMapCreated(MapboxMap controller) async { log("[DEBUG] onMapCreated called."); mapboxMap = controller; await mapboxMap?.gestures.updateSettings(GesturesSettings( pinchToZoomEnabled: true, scrollEnabled: true, rotateEnabled: true, )); taxiAnnotationManager = await mapboxMap?.annotations.createPointAnnotationManager(); _startAnnotationManager = await mapboxMap?.annotations.createPointAnnotationManager(); _destinationAnnotationManager = await mapboxMap?.annotations.createPointAnnotationManager(); _routePolylineManager = await mapboxMap?.annotations.createPolylineAnnotationManager(); await mapboxMap?.location.updateSettings( LocationComponentSettings( enabled: true, showAccuracyRing: false, pulsingEnabled: false, pulsingColor: 0xFF7B5CFA, accuracyRingColor: 0xFF7B5CFA, accuracyRingBorderColor: 0xFF7B5CFA, ), ); log("[DEBUG] Location component enabled."); if (_currentGpsLocation != null) { _flyToLocationAndStopLoading(_currentGpsLocation!); } _loadMapIcons(); } Future _loadMapIcons() async { try { final ByteData bytes = await rootBundle.load('assets/images/taxis2.png'); if (mounted) { _taxiIconBytes = bytes.buffer.asUint8List(); } } catch (e) { log("[ERROR] Failed to load taxi icon: $e"); } } Future _updateRouteMarkers(RidesProvider rideProvider) async { if (mapboxMap == null || _isDisposed) return; // Check if anything actually changed to avoid unnecessary work final shouldUpdate = _lastStartLocation != rideProvider.startlocation || _lastDestinationLocation != rideProvider.destinationlocation || _lastRideState != rideProvider.currentstate; if (!shouldUpdate) return; // Update tracked state _lastStartLocation = rideProvider.startlocation; _lastDestinationLocation = rideProvider.destinationlocation; _lastRideState = rideProvider.currentstate; try { final shouldShowMarkers = (rideProvider.currentstate == RideEnum.choose_ride || rideProvider.currentstate == RideEnum.order_summary); final hasValidLocations = rideProvider.startlocation != null && rideProvider.destinationlocation != null; if (shouldShowMarkers && hasValidLocations) { await _clearRouteMarkers(); await _clearRoutePolyline(); await _createStartMarker(rideProvider.startlocation!); await _createDestinationMarker(rideProvider.destinationlocation!); await _drawRoutePolyline(rideProvider.startlocation!, rideProvider.destinationlocation!); _focusCameraOnRoute(rideProvider.startlocation!, rideProvider.destinationlocation!); } else { await _clearRouteMarkers(); await _clearRoutePolyline(); // Reset tracked locations when cleared _lastStartLocation = null; _lastDestinationLocation = null; } } catch (e) { _debugLog('[ERROR] Error updating route markers: $e'); await _clearRouteMarkers(); await _clearRoutePolyline(); } } Future _drawRoutePolyline(latlng.LatLng start, latlng.LatLng end) async { if (_routePolylineManager == null || _isDisposed) return; setState(() => _isDrawingPolyline = true); try { final rideProvider = Provider.of(context, listen: false); // Check if we already have route data to avoid unnecessary API calls if (rideProvider.routedata != null && rideProvider.routedata!.geometryHash.isNotEmpty) { // Use existing route data final routeData = rideProvider.routedata!; _createPolylineFromRouteData(routeData); return; } // Fetch route info using your existing function final routeData = await rideProvider.fetchRouteInfo( latlng.LatLng(start.latitude, start.longitude), latlng.LatLng(end.latitude, end.longitude), ); _createPolylineFromRouteData(routeData); } catch (e) { log('[ERROR] Error drawing route polyline: $e'); // Fallback: draw straight line if API call fails _drawFallbackPolyline(start, end); } finally { if (mounted) { setState(() => _isDrawingPolyline = false); } } } Future _createPolylineFromRouteData(RouteData routeData) async { // Convert points to Mapbox Position format final positions = routeData.points.map((point) => Position(point.lng, point.lat)).toList(); // Create polyline options final polylineOptions = PolylineAnnotationOptions( geometry: LineString(coordinates: positions), lineColor: Colors.blue.value, lineWidth: 5.0, lineOpacity: 0.8, ); // Create the polyline _routePolyline = await _routePolylineManager?.create(polylineOptions); log('[DEBUG] Route polyline drawn successfully'); } // Fallback method if Mapbox API fails Future _drawFallbackPolyline(latlng.LatLng start, latlng.LatLng end) async { if (_routePolylineManager == null || _isDisposed) return; try { final positions = [ Position(start.longitude, start.latitude), Position(end.longitude, end.latitude) ]; final polylineOptions = PolylineAnnotationOptions( geometry: LineString(coordinates: positions), lineColor: Colors.grey.value, // Grey for fallback lineWidth: 3.0, lineOpacity: 0.6, ); _routePolyline = await _routePolylineManager?.create(polylineOptions); log('[DEBUG] Fallback polyline drawn'); } catch (e) { log('[ERROR] Error drawing fallback polyline: $e'); } } Future _clearRoutePolyline() async { try { if (_routePolyline != null && _routePolylineManager != null) { try { await _routePolylineManager?.delete(_routePolyline!); } catch (e) { log('[WARNING] Error deleting route polyline: $e'); // Polyline might already be deleted } _routePolyline = null; } } catch (e) { log('[ERROR] Error in _clearRoutePolyline: $e'); _routePolyline = null; } } Future _createStartMarker(latlng.LatLng location) async { if (_startAnnotationManager == null || _isDisposed) return; try { final ByteData bytes = await rootBundle.load('assets/images/DefaultMarker.png'); final Uint8List list = bytes.buffer.asUint8List(); final options = PointAnnotationOptions( geometry: Point(coordinates: Position(location.longitude, location.latitude)), image: list, iconSize: 1.5, iconAnchor: IconAnchor.BOTTOM, ); _startAnnotation = await _startAnnotationManager?.create(options); log('[DEBUG] Start marker created successfully'); } catch (e) { log('[ERROR] Error creating start marker: $e'); _startAnnotation = null; } } Future _createDestinationMarker(latlng.LatLng location) async { if (_destinationAnnotationManager == null || _isDisposed) return; try { final ByteData bytes = await rootBundle.load('assets/images/destinationlocation.png'); final Uint8List list = bytes.buffer.asUint8List(); final options = PointAnnotationOptions( geometry: Point(coordinates: Position(location.longitude, location.latitude)), image: list, iconSize: 1.5, iconAnchor: IconAnchor.BOTTOM, ); _destinationAnnotation = await _destinationAnnotationManager?.create(options); log('[DEBUG] Destination marker created successfully'); } catch (e) { log('[ERROR] Error creating destination marker: $e'); _destinationAnnotation = null; } } Future _clearRouteMarkers() async { try { // Check if managers are still valid before attempting to delete if (_startAnnotation != null && _startAnnotationManager != null) { try { await _startAnnotationManager?.delete(_startAnnotation!); } catch (e) { log('[WARNING] Error deleting start annotation: $e'); // Annotation might already be deleted, just nullify } _startAnnotation = null; } if (_destinationAnnotation != null && _destinationAnnotationManager != null) { try { await _destinationAnnotationManager?.delete(_destinationAnnotation!); } catch (e) { log('[WARNING] Error deleting destination annotation: $e'); // Annotation might already be deleted, just nullify } _destinationAnnotation = null; } } catch (e) { log('[ERROR] Error in _clearRouteMarkers: $e'); // Ensure variables are nullified even if deletion fails _startAnnotation = null; _destinationAnnotation = null; } } void _focusCameraOnRoute(latlng.LatLng start, latlng.LatLng end) async { // Calculate bounds that contain both points final north = math.max(start.latitude, end.latitude); final south = math.min(start.latitude, end.latitude); final east = math.max(start.longitude, end.longitude); final west = math.min(start.longitude, end.longitude); // Add some padding const padding = 0.01; final bounds = CoordinateBounds( southwest: Point(coordinates: Position(west - padding, south - padding)), northeast: Point(coordinates: Position(east + padding, north + padding)), infiniteBounds: false, ); // Use cameraForCoordinateBounds for better control final cameraOptions = await mapboxMap!.cameraForCoordinateBounds( bounds, MbxEdgeInsets(top: 120, left: 120, bottom: 320, right: 120), 0, // bearing 0, // pitch 120, // maxZoom null, // offset ); await mapboxMap?.flyTo(cameraOptions, MapAnimationOptions(duration: 1000)); } Future _handleRideUpdate(Map data) async { if (_suppressLogs) return; log("[DEBUG] _handleRideUpdate called with data: $data"); // Add this check at the very beginning if (taxiAnnotationManager == null) { log("[DEBUG] taxiAnnotationManager is null, skipping update"); return; } if (_currentGpsLocation == null) { log("[ERROR] _currentGpsLocation is null"); return; } if (data.containsKey('driverId') && data.containsKey('latitude')) { final id = data['driverId'] as int; final lat = data['latitude'] as double; final lng = data['longitude'] as double; log("[DEBUG] Processing driver $id at location: $lat, $lng"); if (_driverLocations.containsKey(id)) { _previousDriverLocations[id] = _driverLocations[id]!; } final newLocation = latlng.LatLng(lat, lng); _driverLocations[id] = newLocation; _taxiRefreshDebounceTimer?.cancel(); _taxiRefreshDebounceTimer = Timer(const Duration(milliseconds: 500), () { if (mounted) { _refreshSimpleTaxiMarkers(); } }); } } Future _refreshSimpleTaxiMarkers() async { log("[DEBUG] Intelligent marker refresh initiated..."); if (_taxiIconBytes == null || taxiAnnotationManager == null || _currentGpsLocation == null) { log("[WARNING] Pre-conditions for marker refresh not met. Icon loaded: ${_taxiIconBytes != null}, Manager ready: ${taxiAnnotationManager != null}, GPS ready: ${_currentGpsLocation != null}"); return; } try { const double radiusKm = 3.0; // Using 3km as requested final Map driversInRange = {}; // Step 1: Find all drivers currently within the radius _driverLocations.forEach((driverId, driverLocation) { final distanceInKm = geolocator.Geolocator.distanceBetween( _currentGpsLocation!.latitude, _currentGpsLocation!.longitude, driverLocation.latitude, driverLocation.longitude, ) / 1000; if (distanceInKm <= radiusKm) { driversInRange[driverId] = driverLocation; } }); // Step 2: Remove markers for drivers who are no longer in range final List driverIdsToRemove = []; final List annotationsToDelete = []; _activeTaxiAnnotations.forEach((driverId, annotation) { if (!driversInRange.containsKey(driverId)) { driverIdsToRemove.add(driverId); annotationsToDelete.add(annotation); } }); if (annotationsToDelete.isNotEmpty) { for (final annotation in annotationsToDelete) { try { await taxiAnnotationManager?.delete(annotation); } catch(e) { log('[WARNING] Failed to delete annotation: $e'); } } driverIdsToRemove.forEach(_activeTaxiAnnotations.remove); log("[DEBUG] Removed ${annotationsToDelete.length} out-of-range markers."); } // Step 3: Update existing markers and identify new ones to create final List annotationsToCreate = []; final List driverIdsForCreation = []; driversInRange.forEach((driverId, location) { final bearing = _calculateBearing(_previousDriverLocations[driverId] ?? location, location); if (_activeTaxiAnnotations.containsKey(driverId)) { // UPDATE the geometry and rotation of the existing marker final annotation = _activeTaxiAnnotations[driverId]!; annotation.geometry = Point(coordinates: Position(location.longitude, location.latitude)); annotation.iconRotate = bearing; try { // This is an async call but we don't need to wait for each one taxiAnnotationManager?.update(annotation); } catch(e) { log('[WARNING] Failed to update annotation for driver $driverId: $e'); } } else { // This is a NEW driver in range, add it to the creation list driverIdsForCreation.add(driverId); annotationsToCreate.add(PointAnnotationOptions( geometry: Point(coordinates: Position(location.longitude, location.latitude)), image: _taxiIconBytes!, iconSize: 0.1, iconRotate: bearing, )); } }); // Step 4: Batch-create all new markers at once if (annotationsToCreate.isNotEmpty) { final newAnnotations = await taxiAnnotationManager?.createMulti(annotationsToCreate); if (newAnnotations != null) { // Add the newly created markers to our tracking map for (int i = 0; i < newAnnotations.length; i++) { _activeTaxiAnnotations[driverIdsForCreation[i]] = newAnnotations[i]!; } log("[DEBUG] Created ${newAnnotations.length} new markers."); } } log("[DEBUG] Marker refresh complete. Total active markers: ${_activeTaxiAnnotations.length}"); } catch (e) { log("[ERROR] A critical error occurred in _refreshSimpleTaxiMarkers: $e"); } } @override void didUpdateWidget(RequstRideScreen oldWidget) { super.didUpdateWidget(oldWidget); // Only update if the ride state or locations have actually changed final rideProvider = context.read(); if (mapboxMap != null && !_isDisposed) { _updateRouteMarkers(rideProvider); } } void _debounceUpdateRouteMarkers(RidesProvider rideProvider) { _updateDebounceTimer?.cancel(); _updateDebounceTimer = Timer(const Duration(milliseconds: 300), () { if (mapboxMap != null && !_isDisposed && mounted) { _updateRouteMarkers(rideProvider); } });} @override Widget build(BuildContext context) { final rideProvider = context.watch(); final screen = MediaQuery.of(context).size; final state = rideProvider.currentstate; // Use a debouncer to prevent multiple rapid updates _debounceUpdateRouteMarkers(rideProvider); // Add this debouncer variable at the top of your state class if (_isDrawingPolyline && !isLoading) { Positioned( top: 20, child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.black54, borderRadius: BorderRadius.circular(8), ), child: const Text( 'Calculating route...', style: TextStyle(color: Colors.white), ), ), ); } return SlidingUpPanel( renderPanelSheet: true, panelSnapping: true, isDraggable: false, parallaxEnabled: true, backdropTapClosesPanel: true, controller: _panelController, minHeight: _panelMinHeight(state, screen), maxHeight: _panelMaxHeight(state, screen), panel: _buildPanel(state), borderRadius: BorderRadius.only( topLeft: Radius.circular(state == RideEnum.fill_places ? 0 : 15), topRight: Radius.circular(state == RideEnum.fill_places ? 0 : 15), ), body: _buildMapAndOverlays(state, rideProvider), ); } Future _getLastKnownLocation() async { try { final geolocator.Position? lastPosition = await geolocator.Geolocator.getLastKnownPosition(); if (lastPosition != null) { return latlng.LatLng(lastPosition.latitude, lastPosition.longitude); } } catch (e) { log("[ERROR] Getting last known location: $e"); } return null; } /// Builds the main content stack with the map and UI overlays. Widget _buildMapAndOverlays(RideEnum state, RidesProvider rideProv) { final languageProvider = context.watch(); bool isArabic = languageProvider.currentLangCode == Language.ar; final double positionBottom = _getBottomPosition(state, context); if (_currentGpsLocation != null) { log( "++++_currentGpsLocation!.longitude,_currentGpsLocation!.latitude === > {${_currentGpsLocation! .longitude} ,${_currentGpsLocation!.latitude}}"); } return Stack( alignment: Alignment.center, children: [ // MapWidget is now always in the tree, not wrapped in Opacity. // This allows it to initialize correctly. if (permissionsGranted) MapWidget( key: const ValueKey("mapWidget"), // NEW CODE - FIXES CRASH // cameraOptions: _currentGpsLocation == null // ? null // : CameraOptions( // center: Point( // coordinates: Position( // _currentGpsLocation!.longitude, // _currentGpsLocation!.latitude, // ) // ), // zoom: 15.0, // ), cameraOptions: CameraOptions( center: Point( coordinates: Position( _currentGpsLocation?.longitude ?? DEFAULT_MAP_CENTER.longitude, _currentGpsLocation?.latitude ?? DEFAULT_MAP_CENTER.latitude, ) ), zoom: 15.0, ), styleUri: MapboxStyles.MAPBOX_STREETS, onMapCreated: _onMapCreated, gestureRecognizers: >{ Factory( () => EagerGestureRecognizer(), ), }, ), // The loader is now placed on TOP of the map and is removed // when loading is complete, revealing the map below. if (isLoading) const Center(child: CustomLoader()) else if (!permissionsGranted) const Center( child: Padding( padding: EdgeInsets.all(24.0), child: Text( 'Location permission is required to find rides near you.', textAlign: TextAlign.center, ), )), // The rest of your UI overlays remain the same. if (state != RideEnum.idle && !isLoading) Positioned( bottom: positionBottom+_systembottompadding, right: 20, child: _buidpromocodeandpos(isArabic, rideProv,state), ), if (state == RideEnum.idle && !isLoading) Positioned( bottom: positionBottom+_systembottompadding , left: 16, right: 16, child: Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: [ _buidpromocodeandpos(isArabic, rideProv,state), const SizedBox(height: 25,), ], ), ), ], ); } /// Builds the blurred-background ride type selector. Widget _buidpromocodeandpos(bool isArabic, RidesProvider rideProv,RideEnum state) { return Column( children: [ if (state != RideEnum.order_summary) FloatingActionButton.small( heroTag: 'promo', shape: const CircleBorder(), onPressed: () => rideProv.updateCurrentstate(RideEnum.promo_code), backgroundColor: rideProv.couponcode?.isNotEmpty == true ? AppColors.primaryColor : const Color(0xfffffffff), child: SvgPicture.asset('assets/icons/voucher.svg', colorFilter: ColorFilter.mode(rideProv.couponcode?.isNotEmpty == true ? Colors.white : Colors.black, BlendMode.srcIn,), ), ), if (state != RideEnum.order_summary) const SizedBox(height: 8), if (state != RideEnum.order_summary) FloatingActionButton.small( heroTag: 'location', onPressed: _recenterMap, backgroundColor: Colors.white, shape: const CircleBorder(), child: SvgPicture.asset('assets/icons/my_location.svg'), ), ], ); } // --- Utility and Helper Functions --- double _getBottomPosition(RideEnum state, BuildContext context) { switch (state) { case RideEnum.choose_ride: return MediaQuery.of(context).size.height / 1.9; case RideEnum.promo_code: return MediaQuery.of(context).size.height / 2.9; case RideEnum.choose_ride_schedule: case RideEnum.Update_ride_schedule: return MediaQuery.of(context).size.height / 2.25; case RideEnum.order_summary: return MediaQuery.of(context).size.height / 2.3; default: return MediaQuery.of(context).size.height /7.5; } } double _panelMinHeight(RideEnum state, ui.Size screen) { switch (state) { case RideEnum.fill_places: return screen.height; case RideEnum.choose_ride: return (screen.height+_systembottompadding) / 2; case RideEnum.choose_ride_schedule: case RideEnum.Update_ride_schedule: return screen.height; case RideEnum.promo_code: return screen.height / 3.2; case RideEnum.order_summary: return (screen.height+_systembottompadding) / 2.4; default: return 0; } } double _panelMaxHeight(RideEnum state, ui.Size screen) { switch (state) { case RideEnum.fill_places: return screen.height; case RideEnum.choose_ride: return (screen.height-_systembottompadding) / 2; case RideEnum.choose_ride_schedule: case RideEnum.Update_ride_schedule: return screen.height; case RideEnum.promo_code: return screen.height / 3.2; case RideEnum.order_summary: return (screen.height-_systembottompadding) / 2.4; default: return 0; } } Widget _buildPanel(RideEnum state) { switch (state) { case RideEnum.fill_places: return TripsScreen( andoridbuttonhight: _systembottompadding, controllerSideBar: widget.controllerSideBar, ); case RideEnum.choose_ride: final rp = context.read(); if (rp.geohash == null || rp.geohash!.isEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { if (rp.startlocation != null && rp.destinationlocation != null) { rp.notifyListeners(); } }); return const Center(child: CustomLoader()); } return RideRequestDetailsScreen( startlocation: rp.startlocation, stoplocationlocation: rp.stoplocationlocation, destinationlocation: rp.destinationlocation, ); case RideEnum.choose_ride_schedule: return const SchedulePickupWidget(); case RideEnum.Update_ride_schedule: return const SchedulePickupWidget(); case RideEnum.promo_code: final rp = context.watch(); return PromoCodeInput( onApply: (code) { showDialog( context: context, builder: (ctx) => CustomPopup( imagePath: 'assets/images/voucher_done.png', message: 'Promo Code Applied!', description: 'Your promo code has been successfully applied to your order', loader: false, onOk: () async { rp.updateCoupon(code); rp.updateCurrentstate( rp.startlocation != null && rp.destinationlocation != null ? RideEnum.choose_ride : RideEnum.idle, ); log('promo code input --------------------------------'); if(ridesprovidersingliton.startlocation!=null && ridesprovidersingliton.geohash!=null){ await context.read().fetchRideOptions( ridesprovidersingliton.couponcode ?? "", ridesprovidersingliton.ridestartdatetime != null ? ridesprovidersingliton.ridestartdatetime!.toIso8601String() : "", "${ridesprovidersingliton.startlocation?.longitude},${ridesprovidersingliton.startlocation?.latitude}", ridesprovidersingliton.geohash ?? ""); } }, ), ); }, onDontHave: () { rp.updateCurrentstate( rp.startlocation != null && rp.destinationlocation != null ? RideEnum.choose_ride : RideEnum.idle); }, ); case RideEnum.order_summary: final rp = context.watch(); return OrderSummary( startlocation: rp.startlocationname ?? 'Start Location name', endLocation: rp.destinationlocationname ?? "", time: (rp.routedata?.duration.inMinutes ?? 0).toDouble(), // Use the safely calculated time distance: rp.routedata?.distanceKm ?? 0.0, // Use the safely calculated distance price: rp.price.toString(), feeRate: 0, systembottompadding: _systembottompadding, couponpercentage: rp.couponpercentage??0.0, couponvalue: rp.couponvalue??0.0, couponcode: rp.couponcode??"", paymentMethod: rp.selecteridoption?.name ?? "", onConfirm: () { if (rp.price <= 0) { AppSnackbar.showInfo("Info", "Sorry this service not available please try again"); } if (rp.oncreationridetype == RideType.INSTANT) { rp.updateCurrentstate(RideEnum.searching_ride); } if (rp.oncreationridetype == RideType.SCHEDULE) { showDialog( context: context, barrierDismissible: false, builder: (context) => CustomPopup( imagePath: 'assets/images/wanis.png', loader: rp.isLoadingCreateride, message: 'Your pickup has been scheduled successfully', onOk: () async { final req = await rp.convertToReq(RideType.SCHEDULE); if (req == null) { AppSnackbar.showError("Error", "All wanis driver are busy right now, Please try again"); return; } if (rp.currentride != null) { rp.updateRideScheduledData(req); } else { rp.submitRideScheduledData(req); } }, ), ); } }, onSearch: () => rp.updateCurrentstate(RideEnum.fill_places), ); default: return const SizedBox(); } } double _calculateBearing(latlng.LatLng start, latlng.LatLng end) { double startLat = start.latitudeInRad; double startLng = start.longitudeInRad; double endLat = end.latitudeInRad; double endLng = end.longitudeInRad; double dLng = endLng - startLng; double y = math.sin(dLng) * math.cos(endLat); double x = math.cos(startLat) * math.sin(endLat) - math.sin(startLat) * math.cos(endLat) * math.cos(dLng); return (math.atan2(y, x) * (180 / math.pi) + 360) % 360; } }