import 'dart:async'; import 'dart:developer'; import 'dart:developer' as dev; import 'dart:math' as math; import 'dart:typed_data'; import 'package:app_settings/app_settings.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:awesome_notifications/awesome_notifications.dart'; import 'package:wanisclient/config/AppSnackbar.dart'; import 'package:wanisclient/config/constants/app_constant.dart'; import 'package:wanisclient/config/custom_loader.dart'; import 'package:wanisclient/config/custom_text.dart'; import 'package:wanisclient/config/enum/ride_enum.dart'; import 'package:wanisclient/config/enum/ride_tracking_state.dart'; import 'package:wanisclient/models/DriverLocationDto.dart'; import 'package:wanisclient/models/RideUpdateRequest.dart'; import 'package:wanisclient/providers/ride_providers.dart'; import 'package:wanisclient/providers/utils_singleton.dart'; import 'package:wanisclient/services/Sockets/PubNubManager.dart'; import 'package:wanisclient/services/audio_service.dart'; import 'package:wanisclient/utils/colors.dart'; import 'package:wanisclient/widgets/ArrivalConfirmationCard.dart'; import 'package:wanisclient/widgets/driver_info_card.dart'; import 'package:wanisclient/widgets/islandDialog.dart'; import 'package:wanisclient/widgets/startRideCardInfo.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get_it/get_it.dart'; import 'package:latlong2/latlong.dart' as latlng; import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart'; import 'package:provider/provider.dart'; import 'package:rxdart/rxdart.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:vibration/vibration.dart'; import '../providers/public_provider.dart'; import '../services/SystemInsetsService.dart'; import '../widgets/NetworkImageCarousel.dart'; import '../widgets/beem_kids_video_call.dart'; import 'NewUiScreen/RideSummary.dart'; import 'cancel_ride_dialog.dart'; extension on Point { latlng.LatLng get toLatLng2 => latlng.LatLng(coordinates.lat.toDouble(), coordinates.lng.toDouble()); } extension on List { List get toMapbox => map((p) => Point(coordinates: Position(p.longitude, p.latitude))).toList(); } class RideTrackingScreen extends StatefulWidget { const RideTrackingScreen({super.key}); @override _RideTrackingScreenState createState() => _RideTrackingScreenState(); } class _RideTrackingScreenState extends State with WidgetsBindingObserver, AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; late final RidesProvider _ridesProvider; Timer? _alertTimer; Timer? _vibrationTimer; final AudioPlayer _alertPlayer = AudioPlayer(); Timer? _speedMonitorTimer; bool _showLowInternetBanner = false; Timer? _bannerHideTimer; bool _isprocessing = false; bool _is_app_in_foreground = true; // final FlutterLocalNotificationsPlugin _localNotifications = FlutterLocalNotificationsPlugin(); // local notif // Mapbox specific variables MapboxMap? _mapboxMap; Point? _initialLocation; Point? _destination; DriverLocationDto? driverLocationDto; String? initiallocationaddress; String? desticationlocationaddress; // Mapbox annotations PointAnnotationManager? _pointAnnotationManager; List _allAnnotations = []; PolylineAnnotationManager? _polylineAnnotationManager; // Socket and other variables late PubNubManager _pubnubManager; StreamSubscription? _rideUpdateSubscription; late int? driverid ; StreamSubscription? _netSub; bool _hasInternet = true; bool _connectionDialogShowing = false; Timer? _locationPollTimer; Timer? _PollTimerRide; Timer? _networkCheckDebouncer; Timer? _routeUpdateDebouncer; bool _isDisposed = false; bool _isPubNubInitialized = false; bool isExpanded = false; Timer? _estimationUpdateTimer; // Better marker management PointAnnotation? _driverMarker; PointAnnotation? _startMarker; PointAnnotation? _destinationMarker; latlng.LatLng? _driverLocation; // Preloaded images for better performance Uint8List? _taxiImage; Uint8List? _startMarkerImage; Uint8List? _destinationMarkerImage; // Add this flag to prevent concurrent updates bool _isUpdatingDriverMarker = false; static const platform = MethodChannel('com.camelsoft.wanisclient/background'); double _systembottompadding = 0; double distanceKm = 0; int etaSeconds = 0; late final PublicProvider _publicProvider; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _ridesProvider = context.read(); _publicProvider = context.read(); // IMMEDIATE FIX: Set destination and initial location right away final ride = _ridesProvider.currentride; if (ride != null) { // Set destination immediately from ride data _destination = Point( coordinates: Position( _ridesProvider.getLongitude(ride.endLocation), _ridesProvider.getLatitude(ride.endLocation), ), ); // Set initial location to start location as fallback // This will be updated with actual driver location when available _initialLocation = Point( coordinates: Position( _ridesProvider.getLongitude(ride.startLocation), _ridesProvider.getLatitude(ride.startLocation), ), ); log('[INIT] Immediately set _destination and _initialLocation from ride data'); } WidgetsBinding.instance.addPostFrameCallback((_) async { if (!mounted) return; _alertPlayer.setVolume(1.0); // Load images and initialize in background await _preloadImages(); await _initialize(); _listenToFlutterConnectivity(); _maybeStartAlerts(); _ridesProvider.addListener(_rideStateListener); }); } Future _preloadImages() async { int maxRetries = 3; int attempt = 0; while (attempt < maxRetries) { try { log('[DEBUG] Preloading images (attempt ${attempt + 1}/$maxRetries)...'); final ByteData taxiBytes = await rootBundle.load('assets/images/taxis2.png'); final ByteData startBytes = await rootBundle.load('assets/icons/markers/markerA.png'); final ByteData destBytes = await rootBundle.load('assets/icons/markers/markerB.png'); _taxiImage = taxiBytes.buffer.asUint8List(); _startMarkerImage = startBytes.buffer.asUint8List(); _destinationMarkerImage = destBytes.buffer.asUint8List(); log('[DEBUG] ✅ All marker images preloaded successfully'); return; } catch (e) { attempt++; log('[ERROR] ❌ Error preloading images (attempt $attempt): $e'); if (attempt < maxRetries) { await Future.delayed(Duration(milliseconds: 500 * attempt)); } } } log('[ERROR] Failed to preload images after $maxRetries attempts'); } Future _handleDriverArrivedBackground() async { try { // Call native method to wake screen and open app await platform.invokeMethod('wakeAndOpenApp', { 'title': 'Driver Arrived!', 'message': 'Your driver is waiting outside', 'rideId': _ridesProvider.currentride?.id.toString() ?? '', }); // Strong vibration if (await Vibration.hasVibrator() ?? false) { Vibration.vibrate( pattern: [0, 1000, 300, 1000, 300, 1000], intensities: [0, 255, 0, 255, 0, 255], ); } } catch (e) { // fallback is already calling _on_driver_arrived_side_effects() // add: if possible, schedule again after a short delay to retry wake await Future.delayed(const Duration(milliseconds: 600)); try { await platform.invokeMethod('wakeAndOpenApp', { 'title': 'Driver Arrived!', 'message': 'Your driver is waiting outside', 'rideId': _ridesProvider.currentride?.id.toString() ?? '', }); } catch (_) { await _on_driver_arrived_side_effects(); } } } void _dismissLowInternetBanner() { if (!_showLowInternetBanner) return; _bannerHideTimer?.cancel(); if (mounted) { setState(() => _showLowInternetBanner = false); } } Future _onMapCreated(MapboxMap mapboxMap) async { log('[MAP] ==========================================='); log('[MAP] _onMapCreated called'); _mapboxMap = mapboxMap; // Create annotation managers try { _pointAnnotationManager = await _mapboxMap!.annotations.createPointAnnotationManager(); log('[MAP] Point annotation manager created: ${_pointAnnotationManager != null}'); _polylineAnnotationManager = await _mapboxMap!.annotations.createPolylineAnnotationManager(); log('[MAP] Polyline annotation manager created: ${_polylineAnnotationManager != null}'); } catch (e) { log('[MAP] ERROR creating annotation managers: $e'); } // Set map style try { await _mapboxMap!.style.setStyleURI(MapboxStyles.MAPBOX_STREETS); log('[MAP] Map style set successfully'); } catch (e) { log('[MAP] ERROR setting map style: $e'); } // Wait longer to ensure everything is ready log('[MAP] Waiting for map to be fully initialized...'); await Future.delayed(const Duration(milliseconds: 2000)); // Increase wait time // Check ride data final ride = _ridesProvider.currentride; log('[MAP] Current ride null: ${ride == null}'); if (ride != null) { log('[MAP] Encoded polyline length: ${ride.encodedpolyline.length}'); log('[MAP] Start location: ${ride.startLocation}'); log('[MAP] End location: ${ride.endLocation}'); } // Initialize markers and polyline await _initializeMapboxMarkers(); // Force polyline drawing with debug if (ride != null && ride.encodedpolyline.isNotEmpty) { log('[MAP] Attempting to draw polyline...'); await _drawFixedPolyline(ride.encodedpolyline); } else { log('[MAP] No polyline data, drawing fallback...'); await _drawFallbackStraightLine(); } log('[MAP] ==========================================='); } Future _initializeMapboxMarkers() async { final ride = _ridesProvider.currentride; log('Initializing markers...'); if (ride == null || _pointAnnotationManager == null || _startMarkerImage == null || _destinationMarkerImage == null) { log('ERROR: Missing required data for marker initialization'); return; } try { await _clearAllAnnotations(); // Create start and destination markers final startPoint = Point( coordinates: Position( _ridesProvider.getLongitude(ride.startLocation), _ridesProvider.getLatitude(ride.startLocation), ), ); final destPoint = Point( coordinates: Position( _ridesProvider.getLongitude(ride.endLocation), _ridesProvider.getLatitude(ride.endLocation), ), ); // Create static markers _startMarker = await _pointAnnotationManager!.create( PointAnnotationOptions( geometry: startPoint, image: _startMarkerImage!, iconSize: 1.5, ), ); _destinationMarker = await _pointAnnotationManager!.create( PointAnnotationOptions( geometry: destPoint, image: _destinationMarkerImage!, iconSize: 1.5, ), ); log('✅ Static markers created successfully'); // Create driver marker if location is available if (_initialLocation != null && _taxiImage != null) { await _createDriverMarker(_initialLocation!); } // Draw polyline if (ride.encodedpolyline.isNotEmpty) { await _drawFixedPolyline(ride.encodedpolyline); } } catch (e, stackTrace) { log('❌ Error creating markers: $e'); log('Stack trace: $stackTrace'); } } Future _createDriverMarker(Point location) async { if (_pointAnnotationManager == null || _taxiImage == null) return; try { // Delete existing driver marker if it exists if (_driverMarker != null) { await _pointAnnotationManager!.delete(_driverMarker!); } // Create new driver marker _driverMarker = await _pointAnnotationManager!.create( PointAnnotationOptions( geometry: location, image: _taxiImage!, iconSize: 0.1, iconOffset: [0.0, -10.0], ), ); log('✅ Driver marker created at: ${location.coordinates}'); } catch (e) { log('❌ Error creating driver marker: $e'); } } Future _updateDriverMarkerOnly(Point newPosition, double? bearing) async { if (!mounted || _pointAnnotationManager == null || _isUpdatingDriverMarker) { log('[DEBUG] Skipping marker update - not ready or already updating'); return; } // Prevent concurrent updates _isUpdatingDriverMarker = true; try { log('[DEBUG] Updating driver marker to: ${newPosition.coordinates}'); // Update driver location in LatLng format _driverLocation = latlng.LatLng( newPosition.coordinates.lat.toDouble(), newPosition.coordinates.lng.toDouble() ); // Ensure we have the taxi image if (_taxiImage == null) { log('[ERROR] Taxi image not loaded yet, skipping marker update'); return; } // Delete existing driver marker if it exists if (_driverMarker != null) { try { await _pointAnnotationManager!.delete(_driverMarker!); log('[DEBUG] Deleted existing driver marker'); } catch (e) { log('[ERROR] Error deleting existing marker: $e'); } _driverMarker = null; } // Create new driver marker at new position _driverMarker = await _pointAnnotationManager!.create( PointAnnotationOptions( geometry: newPosition, image: _taxiImage!, iconSize: 0.1, iconOffset: [0.0, -10.0], symbolSortKey: bearing ?? 0.0, ), ); log('[DEBUG] ✅ Driver marker updated successfully at ${newPosition.coordinates}'); } catch (e, stackTrace) { log('[ERROR] ❌ Error updating driver marker: $e'); log('Stack trace: $stackTrace'); } finally { _isUpdatingDriverMarker = false; } } Future _clearAllAnnotations() async { try { await _pointAnnotationManager?.deleteAll(); await _polylineAnnotationManager?.deleteAll(); // Reset marker references _driverMarker = null; _startMarker = null; _destinationMarker = null; log('✅ All annotations cleared'); } catch (e) { log('❌ Error clearing annotations: $e'); } } void _startPollingTimerRide() { _PollTimerRide?.cancel(); log("Starting ride polling every 3 seconds"); _PollTimerRide = Timer.periodic(const Duration(seconds: 3), (timer) { _pollRideStatus(); }); } void _pollRideStatus() async { if (!mounted || _ridesProvider.currentride == null) { _PollTimerRide?.cancel(); return; } log("Polling for ride status update..."); try { await _ridesProvider.finbyidWithUpdate(_ridesProvider.currentride!.id); } catch (e) { log("Error during ride polling: $e"); } } Future initdriverlocaiton() async { if (_ridesProvider.currentride?.driverId == null) { log('[DEBUG] No driver ID available'); return; } try { log('[DEBUG] Fetching initial driver location for driver: ${_ridesProvider.currentride!.driverId}'); DriverLocationDto? location = await _ridesProvider.getdriverlocaiton( _ridesProvider.currentride!.driverId! ); if (location != null && mounted) { final newPosition = Point( coordinates: Position(location.longitude, location.latitude), ); setState(() { _initialLocation = newPosition; _driverLocation = latlng.LatLng(location.latitude, location.longitude); distanceKm = location.distanceKm??0; etaSeconds = location.etaSeconds??0; }); // Wait for images to be loaded int attempts = 0; while (_taxiImage == null && attempts < 20) { await Future.delayed(const Duration(milliseconds: 100)); attempts++; } if (_taxiImage != null) { await _refreshDriverMarker(); log('[DEBUG] ✅ Initial driver location set and marker created'); } else { log('[ERROR] Taxi image still not loaded after waiting'); } _locationPollTimer?.cancel(); } else { log('[DEBUG] No driver location received or widget not mounted'); } } catch (e) { log('[ERROR] ❌ Error getting initial driver location: $e'); } } void _listenToFlutterConnectivity() { _netSub?.cancel(); const debounceDuration = Duration(seconds: 2); _netSub = Connectivity() .onConnectivityChanged .distinct() .debounceTime(debounceDuration) .listen((List results) async { if (_isDisposed || !mounted) return; try { final nowOnline = results.any((r) => r != ConnectivityResult.none); if (nowOnline != _hasInternet) { setState(() => _hasInternet = nowOnline); if (nowOnline) { _dismissConnectionLostDialog(); await _onBackOnline(); } else { if (!_connectionDialogShowing) { _showConnectionLostDialog(); } } } } catch (e, stackTrace) { dev.log('[NET] Error in connectivity listener: $e', error: e, stackTrace: stackTrace); } }); } void _dismissConnectionLostDialog() { if (_connectionDialogShowing) { Navigator.of(context, rootNavigator: true).pop(); _connectionDialogShowing = false; } } void _showConnectionLostDialog() { if (_connectionDialogShowing) return; _connectionDialogShowing = true; showDialog( context: context, barrierDismissible: false, builder: (_) => SimpleDynamicIslandDialog( title: 'No Internet', content: 'Please check your connection. The app will resume automatically once you are back online.', barrierDismissible: false, actionText: 'Open Settings', onAction: () { AppSettings.openAppSettings(type: AppSettingsType.wifi); }, ), ); } Future _onBackOnline() async { final rideProvider = context.read(); if (rideProvider.currentride != null) { await rideProvider.finbyidWithUpdate(rideProvider.currentride!.id); // Redraw polyline when back online final ride = rideProvider.currentride; if (ride != null && ride.encodedpolyline.isNotEmpty) { await _drawFixedPolyline(ride.encodedpolyline); } } AppSnackbar.showSuccess("Success", "You've reconnected to the internet!"); await initializepubnub(); } Future initializepubnub() async { if (_isPubNubInitialized || _isDisposed) return; try { if (utils.user?.username == null) return; final userContext = UserContext(username: utils.user!.username!); _pubnubManager.init(userContext); _isPubNubInitialized = true; final ridesProvider = Provider.of(context, listen: false); if (ridesProvider.currentride != null) { await ridesProvider.finbyidWithUpdate(ridesProvider.currentride!.id); } } catch (e) { dev.log('Failed to initialize PubNub: $e'); if (!_isDisposed && mounted) { Future.delayed(const Duration(seconds: 5), () { if (!_isDisposed && mounted) { initializepubnub(); } }); } } } void _rideStateListener() { if (mounted) { _maybeStartAlerts(); } } Future _updateDestination(Point newDest) async { if (_destination != newDest) { _destination = newDest; } } void _maybeStartAlerts() { final state = _ridesProvider.currentstate; if (state == RideEnum.driver_arrived && !_ridesProvider.coming) { if (_alertTimer == null || !_alertTimer!.isActive) { _startVibrationAlert(); _alertTimer = Timer.periodic(const Duration(seconds: 3), (_) { if (mounted && _ridesProvider.currentstate == RideEnum.driver_arrived && !_ridesProvider.coming) { _startVibrationAlert(); } else { _stopAlerts("_maybeStartAlerts"); } }); } } else { _stopAlerts("_maybeStartAlerts"); } } Future _startVibrationAlert() async { _vibrationTimer?.cancel(); if (await Vibration.hasVibrator() ?? false) { Vibration.vibrate(duration: 300); } } void _stopAlerts(String name) { if (_alertTimer != null || _vibrationTimer != null) { log("STOPPING ALERTS NOW from $name"); _alertTimer?.cancel(); _alertTimer = null; _vibrationTimer?.cancel(); _vibrationTimer = null; Vibration.cancel(); } } Future _initialize() async { debugPrint("🔄 _initialize() started"); _pubnubManager = PubNubManager.instance; debugPrint("✅ PubNubManager initialized"); final currentRide = _ridesProvider.currentride; debugPrint("📌 Current ride: $currentRide"); if (currentRide?.driverId != null) { driverid = currentRide!.driverId; debugPrint("👨‍✈️ Driver ID set: $driverid"); } final pad = await SystemInsetsService.instance.bottomPadding(context); log("pad : $pad"); if (mounted) setState(() => _systembottompadding = pad); _rideUpdateSubscription = _pubnubManager.rideUpdateStream.listen(_handleRideData); debugPrint("📡 Subscribed to ride update stream"); // Set destination immediately if (currentRide != null) { _destination = Point( coordinates: Position( _ridesProvider.getLongitude(currentRide.endLocation), _ridesProvider.getLatitude(currentRide.endLocation), ), ); debugPrint("📍 Destination set: $_destination"); setState(() { debugPrint("🖼️ UI updated with destination"); }); // Trigger rebuild to show map } // Load driver location in background debugPrint("🚗 Initializing driver location..."); initdriverlocaiton(); // Remove await here _startPollingTimerRide(); debugPrint("⏱️ Ride polling timer started"); debugPrint("✅ _initialize() completed"); } void _handleRideData(Map data) { if (!mounted) return; // Ensure driver ID is set from current ride if (driverid == null) { final currentRide = context.read().currentride; if (currentRide?.driverId != null) { driverid = currentRide!.driverId; log('[PUBNUB] Driver ID set from current ride: $driverid'); } } log('[PUBNUB] Data received: $data'); log('[PUBNUB] Looking for driver ID: $driverid'); if (data.containsKey('driverId') && data.containsKey('latitude')) { final incomingDriverId = data['driverId']; log('[PUBNUB] Incoming driver ID: $incomingDriverId, Expected: $driverid'); if (incomingDriverId == driverid) { // log('[PUBNUB] ✅ Driver location update for our driver!'); _handleDriverLocationUpdate(data); } else { // log('[PUBNUB] ❌ Driver location update for different driver (ID: $incomingDriverId)'); } } else if (data.containsKey('type')) { HapticFeedback.lightImpact(); _handleRideStatusUpdate(data); } } void _handleDriverLocationUpdate(Map data) { log('[DEBUG] Driver location update received: $data'); if (!data.containsKey('latitude') || !data.containsKey('longitude')) { log('[ERROR] Missing coordinates in driver update'); return; } final latValue = data['latitude']; final lngValue = data['longitude']; if (latValue == null || lngValue == null) { log('[ERROR] Null coordinates in driver update'); return; } double lat, lng; try { lat = (latValue as num).toDouble(); lng = (lngValue as num).toDouble(); } catch (e) { log('[ERROR] Invalid coordinate format: $e'); return; } // Validate coordinate ranges if (lat < -90 || lat > 90 || lng < -180 || lng > 180) { log('[ERROR] Invalid coordinate ranges: lat=$lat, lng=$lng'); return; } // Update stored location _initialLocation = Point(coordinates: Position(lng, lat)); _driverLocation = latlng.LatLng(lat, lng); setState(() { distanceKm = data['distanceKm']; etaSeconds = data['etaSeconds']; }); try { driverLocationDto = DriverLocationDto.fromJson(data); } catch (e) { log('[ERROR] Error parsing driver location DTO: $e'); } // FIXED: Use the working approach from RequstRideScreen _refreshDriverMarker(); log('[DEBUG] Driver location processed: $lat, $lng'); } Future _refreshDriverMarker() async { log("[DEBUG] _refreshDriverMarker called"); if (_pointAnnotationManager == null || _initialLocation == null) { log("[ERROR] Annotation manager or driver location is null"); return; } try { log("[DEBUG] Refreshing driver marker"); // Clear existing annotations (like RequstRideScreen does) await _pointAnnotationManager!.deleteAll(); _allAnnotations.clear(); // Ensure we have the taxi image loaded if (_taxiImage == null) { log('[ERROR] Taxi image not loaded, cannot create marker'); return; } final List annotationOptions = []; // Add driver marker if (_initialLocation != null) { annotationOptions.add( PointAnnotationOptions( geometry: _initialLocation!, image: _taxiImage!, iconSize: 0.1, iconOffset: [0.0, -10.0], symbolSortKey: driverLocationDto?.bearing ?? 0.0, ), ); } // Add start marker if available final ride = _ridesProvider.currentride; if (ride != null && _startMarkerImage != null) { final startPoint = Point( coordinates: Position( _ridesProvider.getLongitude(ride.startLocation), _ridesProvider.getLatitude(ride.startLocation), ), ); annotationOptions.add( PointAnnotationOptions( geometry: startPoint, image: _startMarkerImage!, iconSize: 1.5, ), ); } // Add destination marker if available if (ride != null && _destinationMarkerImage != null) { final destPoint = Point( coordinates: Position( _ridesProvider.getLongitude(ride.endLocation), _ridesProvider.getLatitude(ride.endLocation), ), ); annotationOptions.add( PointAnnotationOptions( geometry: destPoint, image: _destinationMarkerImage!, iconSize: 1.5, ), ); } // Create all annotations at once (like RequstRideScreen) if (annotationOptions.isNotEmpty) { final createdAnnotations = await _pointAnnotationManager!.createMulti(annotationOptions); if (createdAnnotations != null) { _allAnnotations = createdAnnotations.whereType().toList(); log("[DEBUG] Created ${_allAnnotations.length} markers successfully"); } } } catch (e, stackTrace) { log('[ERROR] Error refreshing driver marker: $e'); log('Stack trace: $stackTrace'); } } Future _on_driver_arrived_side_effects() async { final ride = _ridesProvider.currentride; if (ride == null) return; // fire a high-priority, full-screen notif that also vibrates on its own await AwesomeNotifications().createNotification( content: NotificationContent( id: ride.id.hashCode, channelKey: 'ride_arrived_channel', // <-- use the vibrating channel title: 'your driver has arrived', body: (ride.licenseplate?.isNotEmpty ?? false) ? 'waiting outside • ${ride.licenseplate}' : 'waiting outside', category: NotificationCategory.Alarm, fullScreenIntent: true, wakeUpScreen: true, criticalAlert: true, autoDismissible: true, locked: true, displayOnBackground: true, displayOnForeground: true, payload: {'ride_id': ride.id.toString()}, ), actionButtons: [ NotificationActionButton( key: 'OPEN', label: 'Open', actionType: ActionType.Default, autoDismissible: true, ), NotificationActionButton( key: 'SNOOZE', label: 'Snooze', actionType: ActionType.SilentAction, autoDismissible: true, ), ], ); // keep your short haptic, but let the notif handle most vibration if (await Vibration.hasVibrator() ?? false) { Vibration.vibrate(duration: 300); } } Future _drawPolylineUsingFetchRouteInfo() async { try { final ride = _ridesProvider.currentride; if (ride == null) { log('[POLYLINE] No current ride for fetchRouteInfo'); return; } final startLocation = latlng.LatLng( _ridesProvider.getLatitude(ride.startLocation), _ridesProvider.getLongitude(ride.startLocation), ); final endLocation = latlng.LatLng( _ridesProvider.getLatitude(ride.endLocation), _ridesProvider.getLongitude(ride.endLocation), ); log('[POLYLINE] Start: ${startLocation.latitude}, ${startLocation.longitude}'); log('[POLYLINE] End: ${endLocation.latitude}, ${endLocation.longitude}'); // Fetch route info using your existing function final routeData = await _ridesProvider.fetchRouteInfo(startLocation, endLocation); log('[POLYLINE] Route data points: ${routeData.points.length}'); if (routeData.points.isEmpty) { log('[POLYLINE] No route points received'); return; } // Convert points to Mapbox Position format (EXACT same as FindingRideScreen) final positions = routeData.points.map((point) => Position(point.lng, point.lat)).toList(); log('[POLYLINE] Converted positions: ${positions.length}'); // Create polyline options (EXACT same as FindingRideScreen) final polylineOptions = PolylineAnnotationOptions( geometry: LineString(coordinates: positions), lineColor: Colors.blue.value, // Use blue like FindingRideScreen for testing lineWidth: 5.0, lineOpacity: 0.8, ); // Create the polyline final createdPolyline = await _polylineAnnotationManager!.create(polylineOptions); log('[POLYLINE] Created polyline: ${createdPolyline != null}'); } catch (e) { log('[POLYLINE] fetchRouteInfo method failed: $e'); } } Future _drawFixedPolyline(String encodedPolyline) async { log('[POLYLINE] ==========================================='); log('[POLYLINE] Starting _drawFixedPolyline'); log('[POLYLINE] encodedPolyline length: ${encodedPolyline.length}'); log('[POLYLINE] encodedPolyline content: $encodedPolyline'); log('[POLYLINE] _polylineAnnotationManager null: ${_polylineAnnotationManager == null}'); log('[POLYLINE] _mapboxMap null: ${_mapboxMap == null}'); if (encodedPolyline.isEmpty || _polylineAnnotationManager == null || _mapboxMap == null) { log('[POLYLINE] ERROR: Cannot draw polyline - missing data or managers'); return; } try { // Clear existing polylines first await _polylineAnnotationManager!.deleteAll(); log('[POLYLINE] Cleared existing polylines'); // FIRST: Try the exact same method as FindingRideScreen log('[POLYLINE] Trying fetchRouteInfo method...'); await _drawPolylineUsingFetchRouteInfo(); // Check if polyline was created if (await _checkPolylineExists()) { log('[POLYLINE] ✅ SUCCESS with fetchRouteInfo method'); return; } // SECOND: Try decoding the encoded polyline log('[POLYLINE] Trying decodePolyline method...'); await _drawPolylineUsingDecoded(encodedPolyline); if (await _checkPolylineExists()) { log('[POLYLINE] ✅ SUCCESS with decodePolyline method'); return; } // THIRD: Draw simple straight line as fallback log('[POLYLINE] Drawing fallback straight line...'); await _drawFallbackStraightLine(); if (await _checkPolylineExists()) { log('[POLYLINE] ✅ SUCCESS with fallback method'); return; } log('[POLYLINE] ❌ All methods failed'); } catch (e, stackTrace) { log('[POLYLINE] ❌ CRITICAL ERROR: $e'); log('[POLYLINE] Stack trace: $stackTrace'); } log('[POLYLINE] ==========================================='); } Future _checkPolylineExists() async { try { // This is a simple check - you might need to adjust based on your Mapbox version return _polylineAnnotationManager != null; } catch (e) { return false; } } Future _drawFallbackStraightLine() async { try { final ride = _ridesProvider.currentride; if (ride == null) return; final startLat = _ridesProvider.getLatitude(ride.startLocation); final startLng = _ridesProvider.getLongitude(ride.startLocation); final endLat = _ridesProvider.getLatitude(ride.endLocation); final endLng = _ridesProvider.getLongitude(ride.endLocation); log('[POLYLINE] Fallback line from ($startLat, $startLng) to ($endLat, $endLng)'); final positions = [ Position(startLng, startLat), Position(endLng, endLat) ]; final polylineOptions = PolylineAnnotationOptions( geometry: LineString(coordinates: positions), lineColor: Colors.green.value, // Use green for testing lineWidth: 8.0, // Make it thicker so it's visible lineOpacity: 1.0, // Make it fully opaque ); final createdPolyline = await _polylineAnnotationManager!.create(polylineOptions); log('[POLYLINE] Created fallback polyline: ${createdPolyline != null}'); } catch (e) { log('[POLYLINE] fallback method failed: $e'); } } Future _drawPolylineUsingDecoded(String encodedPolyline) async { try { final List routePoints = _ridesProvider.decodePolyline(encodedPolyline); log('[POLYLINE] Decoded points: ${routePoints.length}'); if (routePoints.isEmpty) { log('[POLYLINE] No points from decode'); return; } // Print first few points for debugging for (int i = 0; i < math.min(3, routePoints.length); i++) { log('[POLYLINE] Point $i: ${routePoints[i].latitude}, ${routePoints[i].longitude}'); } // Convert to Mapbox Position format final positions = routePoints.map((point) => Position(point.longitude, point.latitude)).toList(); final polylineOptions = PolylineAnnotationOptions( geometry: LineString(coordinates: positions), lineColor: Colors.red.value, // Use red for testing lineWidth: 5.0, lineOpacity: 0.8, ); final createdPolyline = await _polylineAnnotationManager!.create(polylineOptions); log('[POLYLINE] Created decoded polyline: ${createdPolyline != null}'); } catch (e) { log('[POLYLINE] decodePolyline method failed: $e'); } } Future _initializeMapWithPolyline() async { final ride = _ridesProvider.currentride; if (ride == null) { log('[ERROR] No current ride available for polyline'); return; } try { log('[DEBUG] Initializing map with polyline...'); // Wait for all managers to be ready int attempts = 0; while ((_pointAnnotationManager == null || _polylineAnnotationManager == null) && attempts < 20) { await Future.delayed(const Duration(milliseconds: 100)); attempts++; } if (_pointAnnotationManager == null || _polylineAnnotationManager == null) { log('[ERROR] Annotation managers not initialized after waiting'); return; } // Initialize markers first await _initializeMapboxMarkers(); // Then draw polyline if available if (ride.encodedpolyline.isNotEmpty) { log('[DEBUG] Drawing polyline for ride...'); await _drawFixedPolyline(ride.encodedpolyline); await _animateToPolyline(ride.encodedpolyline); } else { log('[WARNING] No encoded polyline available in ride data'); // Fallback: draw a basic route await _drawFallbackRoute(); } } catch (e, stackTrace) { log('[ERROR] Error in _initializeMapWithPolyline: $e'); log('Stack trace: $stackTrace'); } } Future _drawFallbackRoute() async { final ride = _ridesProvider.currentride; if (ride == null || _polylineAnnotationManager == null) return; try { log('[DEBUG] Drawing fallback route...'); final startLat = _ridesProvider.getLatitude(ride.startLocation); final startLng = _ridesProvider.getLongitude(ride.startLocation); final endLat = _ridesProvider.getLatitude(ride.endLocation); final endLng = _ridesProvider.getLongitude(ride.endLocation); final positions = [ Position(startLng, startLat), Position(endLng, endLat) ]; final polylineOptions = PolylineAnnotationOptions( geometry: LineString(coordinates: positions), lineColor: Colors.grey.value, lineWidth: 3.0, lineOpacity: 0.6, ); await _polylineAnnotationManager!.create(polylineOptions); log('[DEBUG] ✅ Fallback route drawn successfully'); } catch (e) { log('[ERROR] Error drawing fallback route: $e'); } } Future _clearPolylines() async { try { await _polylineAnnotationManager?.deleteAll(); log('✅ Cleared all polylines'); } catch (e) { log('❌ Error clearing polylines: $e'); } } Future _animateToPolyline(String encodedPolyline) async { if (encodedPolyline.isEmpty || _mapboxMap == null) return; try { final List routePoints = _ridesProvider.decodePolyline(encodedPolyline); if (routePoints.isNotEmpty) { // Calculate bounds for all route points double minLat = routePoints.first.latitude; double maxLat = routePoints.first.latitude; double minLng = routePoints.first.longitude; double maxLng = routePoints.first.longitude; for (final point in routePoints) { minLat = math.min(minLat, point.latitude); maxLat = math.max(maxLat, point.latitude); minLng = math.min(minLng, point.longitude); maxLng = math.max(maxLng, point.longitude); } // Add padding to bounds final padding = 0.01; final bounds = CoordinateBounds( southwest: Point(coordinates: Position(minLng - padding, minLat - padding)), northeast: Point(coordinates: Position(maxLng + padding, maxLat + padding)), infiniteBounds: false, ); final cameraOptions = await _mapboxMap!.cameraForCoordinateBounds( bounds, MbxEdgeInsets(top: 100, left: 100, bottom: 300, right: 100), 0, 0, null, null, ); await _mapboxMap!.flyTo(cameraOptions, MapAnimationOptions(duration: 1500)); log('✅ Animated camera to show full route'); } } catch (e) { log('❌ Error animating to polyline: $e'); } } Future _handleRideStatusUpdate(Map data) async { log("Ride status update received: $data"); if (!mounted) return; final type = data['type']; final stoplocation = data['stoplocation']; log('$stoplocation stoplocationsock'); final rideProvider = Provider.of(context, listen: false); // Handle all ride status updates if (type == 'DRIVER_ASSIGNED') { rideProvider.updateCurrentridetrackstate(RideTracking.track_drive); rideProvider.updateComing(true); AppSnackbar.showInfo("Info", "A driver has been assigned to your ride."); final ride = rideProvider.currentride; if (ride != null && ride.encodedpolyline.isNotEmpty) { await _drawFixedPolyline(ride.encodedpolyline); await _animateToPolyline(ride.encodedpolyline); } } else if (type == 'DRIVER_ARRIVED') { if (!rideProvider.driverArrivedSoundPlayedForCurrentRide) { GetIt.I().playSound(SoundEvent.driverarriver); rideProvider.setDriverArrivedSoundPlayed(true); } rideProvider.updateCurrentstate(RideEnum.driver_arrived); rideProvider.updateCurrentridetrackstate(RideTracking.driver_arrived); final ride = rideProvider.currentride; if (ride != null && ride.encodedpolyline.isNotEmpty) { await _drawFixedPolyline(ride.encodedpolyline); } rideProvider.updateComing(false); await _handleDriverArrivedBackground(); } else if (type == 'RIDE_START') { rideProvider.updateCurrentstate(RideEnum.start_ride); rideProvider.updateCurrentridetrackstate(RideTracking.track_ride); final ride = rideProvider.currentride!; _destination = Point( coordinates: Position( rideProvider.getLongitude(ride.endLocation), rideProvider.getLatitude(ride.endLocation), ), ); await _drawFixedPolyline(ride.encodedpolyline); await _animateToPolyline(ride.encodedpolyline); } else if (type == 'RIDE_STOP') { rideProvider.updateCurrentstate(RideEnum.start_ride); rideProvider.updateCurrentridetrackstate(RideTracking.track_ride); if (stoplocation == null) { AppSnackbar.showError("Error", "Stops not found"); final destinationpos = Point( coordinates: Position( rideProvider.getLongitude(rideProvider.currentride!.endLocation), rideProvider.getLatitude(rideProvider.currentride!.endLocation), ), ); log('change destination from : $_destination to $destinationpos'); await _updateDestination(destinationpos); } else { final destinationpos = Point( coordinates: Position( rideProvider.getLongitude(stoplocation), rideProvider.getLatitude(stoplocation), ), ); log('change destination from : $_destination to $destinationpos'); await _updateDestination(destinationpos); } } else if (type == 'RIDE_END') { rideProvider.updateComing(false); rideProvider.updateCurrentstate(RideEnum.rate_driver); rideProvider.updateCurrentridetrackstate(RideTracking.rating_driver); rideProvider.setDriverArrivedSoundPlayed(false); await _clearPolylines(); } else if (type == 'RIDE_CANCEL_DRIVER') { GetIt.I().playSound(SoundEvent.driverCancelled); AppSnackbar.showInfo("Info", "Driver has cancel your trip"); rideProvider.updateComing(false); rideProvider.updateCurrentstate(RideEnum.idle); rideProvider.updateCurrentridetrackstate(RideTracking.track_drive); rideProvider.setDriverArrivedSoundPlayed(false); await _clearPolylines(); } else if (type == 'RIDE_CANCEL_USER') { AppSnackbar.showInfo("Info", "you cancel your trip"); rideProvider.updateComing(false); rideProvider.updateCurrentstate(RideEnum.idle); rideProvider.updateCurrentridetrackstate(RideTracking.track_drive); rideProvider.setDriverArrivedSoundPlayed(false); await _clearPolylines(); } if (mounted) { setState(() {}); } } final List imageUrls = [ 'https://images.unsplash.com/photo-1506748686214-e9df14d4d9d0', 'https://images.unsplash.com/photo-1469474968028-56623f02e42e', 'https://images.unsplash.com/photo-1501594907352-04cda38ebc29', 'https://images.unsplash.com/photo-1519681393784-d120267933ba', ]; @override Widget build(BuildContext context) { super.build(context); log("**********************_initialLocation : $_initialLocation | _destination: $_destination"); if (_initialLocation == null || _destination == null) { return const Scaffold( backgroundColor: Colors.white, body: Center( child: CustomLoader( logoSize: 80.0, dotSize: 10.0, dotColor: Colors.red, ), ), ); } List coords = _ridesProvider.currentride?.startLocation.split(',') ?? ["0", "0"]; double lat = double.parse(coords[0]); double lng = double.parse(coords[1]); return SafeArea( child: Scaffold( backgroundColor: Colors.white, body: Stack( children: [ // Mapbox Map Widget MapWidget( cameraOptions: CameraOptions( center: Point( coordinates: Position(lng, lat), ), zoom: 15.0, ), onMapCreated: _onMapCreated, onStyleLoadedListener: (_) { log('Mapbox style loaded'); }, gestureRecognizers: >{ Factory( () => EagerGestureRecognizer(), ) }, onTapListener: (context) { log('Map tapped at: ${context.point.coordinates}'); }, ), // Positioned( // bottom:500, // // child: // ), // Keep all existing UI components Consumer( builder: (context, rideProvider, child) { return Stack( children: [ if (rideProvider.currentridetrackstate == RideTracking.track_drive) Positioned( bottom: 0, left: 0, right: 0, child: Column( mainAxisSize: MainAxisSize.min, children: [ NetworkImageCarousel( offers: _publicProvider.offers, height: 150, autoPlay: true, showIndicators: true, borderRadius: BorderRadius.circular(0), ), DriverInfoCard( driverName: rideProvider.currentride?.drivername ?? "...", paymentmethode: rideProvider.currentride?.paymenttype ?? "...", driverImage: "$mediaUrl${rideProvider.currentride?.driverimageurl}", estimatedArrival: "${(etaSeconds/60).toStringAsFixed(0)} Min", starLocation: rideProvider.startlocationname!, endLocation: rideProvider.destinationlocationname!, tripCost: "${rideProvider.currentride?.totalPrice?.toStringAsFixed(2) ?? 0.0} TND ", distance: "${distanceKm.toStringAsFixed(2) ?? 0.0} Km", carModel: rideProvider.currentride?.drivercarmodel ?? "...", licensePlate: rideProvider.currentride?.licenseplate ?? "...", rating: 4, onCollapse: () => setState(() => isExpanded = false), isExpanded: isExpanded, systembottompadding:_systembottompadding, onCall: () { if (rideProvider.currentride?.driverphonenumber?.isNotEmpty ?? false) { _showContactOptions(context, rideProvider.currentride!.driverphonenumber!); } else { AppSnackbar.showError("Error", "Phone number not available"); } }, onCancel: () => showCancelRideBottomSheet(context, rideProvider), ), ], ), ), if (rideProvider.currentridetrackstate == RideTracking.driver_arrived) Positioned( bottom: 0, left: 0, right: 0, child: Column( mainAxisSize: MainAxisSize.min, children: [ NetworkImageCarousel( offers: _publicProvider.offers, height: 150, autoPlay: true, showIndicators: true, borderRadius: BorderRadius.circular(0), ), ArrivalConfirmationCard( driverName: rideProvider.currentride!.drivername!, driverCurrentLocation: "${_initialLocation?.coordinates.lat},${_initialLocation?.coordinates.lng}", endLocationLatLang: rideProvider.currentride!.endLocation, plateNumber: rideProvider.currentride!.licenseplate!, paymentMethod: rideProvider.currentride!.paymenttype, driverImage: rideProvider.currentride!.driverimageurl!, carModel: rideProvider.currentride!.drivercarmodel!, rating: rideProvider.currentride!.averagedriverrating, starLocation: rideProvider.currentride!.startLocationName, endLocation: rideProvider.currentride!.endLocationName, systembottompadding:_systembottompadding, tripCost: "${rideProvider.currentride?.totalPrice?.toStringAsFixed(2) ?? 0.0} TND ", coming: rideProvider.coming, onCall: () { if (rideProvider.currentride?.driverphonenumber?.isNotEmpty ?? false) { _showContactOptions(context, rideProvider.currentride!.driverphonenumber!); } else { AppSnackbar.showError("Error", "Phone number not available"); } }, onStopAlert: () async { log("COMING BUTTON PRESSED. Calling stop alerts."); _stopAlerts("RideTracking.driver_arrived onStopAlert"); _ridesProvider.updateComing(true); RideUpdateRequest request = RideUpdateRequest( rideId: rideProvider.currentride!.id, type: 'USER_COMING', message: 'user coming', riderusername: rideProvider.currentride!.driverusername, stoplocation: 'NOT_EXIST'); bool result = await rideProvider.send_update_ride(request); if (!result) { _ridesProvider.updateComing(false); } }, onCancel: () => showCancelRideBottomSheet(context, rideProvider), ), ], ), ), if (rideProvider.currentridetrackstate != RideTracking.driver_arrived && rideProvider.currentstate != RideEnum.start_ride && rideProvider.currentridetrackstate != RideTracking.track_drive) Positioned( bottom: 50, left: 25, right: 25, child: ElevatedButton( onPressed: () async { if (_isprocessing) return; setState(() => _isprocessing = true); RideUpdateRequest request = RideUpdateRequest( rideId: rideProvider.currentride!.id, type: 'RIDE_CANCEL_USER', message: 'NO reason the cancel happen in the search screen', riderusername: rideProvider.currentride!.driverusername!, stoplocation: 'NOT_EXIST'); bool result = await rideProvider.send_update_ride(request); if (!result) { AppSnackbar.showError("Error", "Error occurred please try again later"); if (mounted) setState(() => _isprocessing = false); return; } }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.primaryColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)), ), child: const Text("Cancel ride", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), ), ), if (rideProvider.currentridetrackstate != RideTracking.driver_arrived && !(rideProvider.currentridetrackstate == RideTracking.track_drive) && rideProvider.currentridetrackstate != RideTracking.rating_driver) Positioned( top: 30, left: 0, right: 0, child:BeemKidsVideoCall(ride: rideProvider.currentride!, isDriver: false) ), if (rideProvider.currentridetrackstate != RideTracking.driver_arrived && !(rideProvider.currentridetrackstate == RideTracking.track_drive) && rideProvider.currentridetrackstate != RideTracking.rating_driver) Positioned( bottom: 0.0, left: 0.0, right: 0.0, child: Column( mainAxisSize: MainAxisSize.min, children: [ NetworkImageCarousel( offers: _publicProvider.offers, height: 150, autoPlay: true, showIndicators: true, borderRadius: BorderRadius.circular(0), ), startRideCardInfo( estimatedArrival: "${(etaSeconds/60).toStringAsFixed(0)} Min", driverName: rideProvider.currentride?.drivername ?? "...", driverCurrentLocation: "${_initialLocation?.coordinates.lat},${_initialLocation?.coordinates.lng}", endLocationLatLang: rideProvider.currentride!.endLocation, paymentMethod: rideProvider.currentride!.paymenttype, driverImage: "$mediaUrl${rideProvider.currentride?.driverimageurl}", starLocation: rideProvider.startlocationname!, systembottompadding: _systembottompadding, endLocation: rideProvider.destinationlocationname!, tripCost: "${rideProvider.currentride?.totalPrice?.toStringAsFixed(2) ?? 0.0} TND ", distance: "${distanceKm.toStringAsFixed(2) ?? 0.0} Km", carModel: rideProvider.currentride?.drivercarmodel ?? "...", licensePlate: rideProvider.currentride?.licenseplate ?? "...", rating: 4, onCall: () async { final Uri phoneUri = Uri.parse("tel:198"); if (await canLaunchUrl(phoneUri)) { await launchUrl(phoneUri); } else { AppSnackbar.showError("Error", "Cannot make a call"); } // if (rideProvider.currentride?.driverphonenumber?.isNotEmpty ?? false) { // _showContactOptions(context, rideProvider.currentride!.driverphonenumber!); // } else { // AppSnackbar.showError("Error", "Phone number not available"); // } }, onCancel: () => showCancelRideBottomSheet(context, rideProvider), ), ], ), ), if (rideProvider.currentridetrackstate == RideTracking.rating_driver) Positioned( bottom: 0.0, left: 0.0, right: 0.0, child: RideDetailClientScreen( ride: rideProvider.currentride!, systembottompadding: _systembottompadding, driverName: rideProvider.currentride?.drivername ?? "...", driverImage: rideProvider.currentride!.driverimageurl!, isBottomSheet: true, // This makes it behave as a bottom sheet onRatingSelected: (rating) { // Handle rating selection if needed }, onRateRider: (rating) async { await rideProvider.rate_driver_current_ride(rating); rideProvider.updateCurrentstate(RideEnum.idle); }, onClose: () { // Optional: Handle close action if needed // For example, you could navigate away or reset state rideProvider.updateCurrentstate(RideEnum.idle); }, ), ), ], ); }, ), // Low internet banner if (_showLowInternetBanner) Positioned( top: MediaQuery.of(context).padding.top + 10, left: 20, right: 20, child: GestureDetector( onTap: _dismissLowInternetBanner, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: Colors.orange[700]?.withOpacity(0.9), borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.2), blurRadius: 8, offset: const Offset(0, 4) ) ], ), child: Row( children: [ const Icon(Icons.wifi_off, color: Colors.white), const SizedBox(width: 12), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Slow connection', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16)), Text('Some features may not work properly', style: TextStyle( color: Colors.white70, fontSize: 14)), ], ), ), IconButton( icon: const Icon(Icons.close, color: Colors.white), onPressed: _dismissLowInternetBanner, padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), ], ), ), ), ), ], ), ), ); } void showCancelRideBottomSheet(BuildContext context, RidesProvider rideProvider) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) { return CancelRideDialog( onCancelConfirmed: (reason) async { if (_isprocessing) return; if (!mounted) return; setState(() => _isprocessing = true); log("Ride canceled for reason: $reason"); RideUpdateRequest request = RideUpdateRequest( rideId: rideProvider.currentride!.id, type: 'RIDE_CANCEL_USER', message: reason, riderusername: rideProvider.currentride!.driverusername!, stoplocation: 'NOT_EXIST'); bool result = await rideProvider.send_update_ride(request); if (!result) { AppSnackbar.showError("Error", "Error occurred please try again later"); if (!mounted) return; setState(() => _isprocessing = false); return; } }, onCancelDismissed: () { print("User chose to wait for driver"); }, ); }, ); } void _cleanup() { _isDisposed = true; _isPubNubInitialized = false; // Clear annotation references _allAnnotations.clear(); _driverMarker = null; _startMarker = null; _destinationMarker = null; // Cancel all timers _speedMonitorTimer?.cancel(); _locationPollTimer?.cancel(); _alertTimer?.cancel(); _bannerHideTimer?.cancel(); _networkCheckDebouncer?.cancel(); _routeUpdateDebouncer?.cancel(); _PollTimerRide?.cancel(); _estimationUpdateTimer?.cancel(); // Cancel subscriptions _rideUpdateSubscription?.cancel(); _netSub?.cancel(); // Remove listeners _ridesProvider.removeListener(_rideStateListener); // Dispose audio player _alertPlayer.dispose(); _stopAlerts("_cleanup"); } void _debugDriverState() { final currentRide = _ridesProvider.currentride; log('[DEBUG] === Driver State Debug ==='); log('[DEBUG] driverid variable: $driverid'); log('[DEBUG] Current ride driver ID: ${currentRide?.driverId}'); log('[DEBUG] _initialLocation: $_initialLocation'); log('[DEBUG] _taxiImage loaded: ${_taxiImage != null}'); log('[DEBUG] _pointAnnotationManager: ${_pointAnnotationManager != null}'); log('[DEBUG] _allAnnotations count: ${_allAnnotations.length}'); log('[DEBUG] ============================'); } @override void dispose() { _cleanup(); WidgetsBinding.instance.removeObserver(this); _dismissConnectionLostDialog(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (!mounted || _isDisposed) return; _is_app_in_foreground = (state == AppLifecycleState.resumed); switch (state) { case AppLifecycleState.resumed: _pubnubManager.forceReconnect(); if (_ridesProvider.currentride != null) { _ridesProvider.finbyidWithUpdate(_ridesProvider.currentride!.id); } break; case AppLifecycleState.paused: case AppLifecycleState.inactive: break; case AppLifecycleState.detached: _cleanup(); break; default: break; } } void _showContactOptions(BuildContext context, String number) { showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20)), ), builder: (ctx) { return Container( height: 200, padding: const EdgeInsets.symmetric(vertical: 8), child: Column( children: [ ListTile( leading: const Icon(Icons.phone, color: Colors.green), title: const Txt(text: "Call", style: TextStyle()), onTap: () async { Navigator.pop(ctx); final Uri phoneUri = Uri.parse("tel:$number"); if (await canLaunchUrl(phoneUri)) { await launchUrl(phoneUri); } else { AppSnackbar.showError("Error", "Cannot make a call"); } }, ), ListTile( leading: SvgPicture.asset( 'assets/images/icons8-whatsapp.svg', width: 25, color: Colors.green, ), title: const Txt(text: "Open in WhatsApp", style: TextStyle()), onTap: () async { Navigator.pop(ctx); String formattedNumber = number.replaceAll(RegExp(r'\D'), ''); if (!formattedNumber.startsWith('+')) { formattedNumber = '+' + formattedNumber; } if (formattedNumber.isEmpty || formattedNumber.length < 8) { AppSnackbar.showError("Error", "Phone number is too short"); return; } final Uri whatsappUri = Uri.parse("https://wa.me/$formattedNumber"); if (await canLaunchUrl(whatsappUri)) { await launchUrl(whatsappUri, mode: LaunchMode.externalApplication); } else { AppSnackbar.showError("Error", "Cannot open WhatsApp"); } }, ), ], ), ); }, ); } }