import 'dart:async'; import 'dart:developer'; import 'dart:math' as math; import 'dart:typed_data'; import 'package:beemwidget/text_styles/beem_text_styles.dart'; import 'package:wanisclient/config/custom_loader.dart'; import 'package:wanisclient/config/custom_text.dart'; import 'package:wanisclient/utils/colors.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_svg/svg.dart'; import 'package:latlong2/latlong.dart' as latlng; import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart'; import 'package:provider/provider.dart'; import '../config/AppSnackbar.dart'; import '../config/enum/ride_enum.dart'; import '../config/enum/ride_type.dart'; import '../providers/ride_providers.dart'; import '../providers/utils_singleton.dart'; import '../services/Sockets/PubNubManager.dart'; import '../services/SystemInsetsService.dart'; import '../widgets/custom_button.dart'; class FindingRideScreen extends StatefulWidget { final String title; final String price; final String endLocation; final double time; final double distance; final String paymentMethod; final VoidCallback onCancel; const FindingRideScreen({ Key? key, required this.title, required this.price, required this.endLocation, required this.time, required this.distance, required this.paymentMethod, required this.onCancel, }) : super(key: key); @override _FindingRideScreenState createState() => _FindingRideScreenState(); } class _FindingRideScreenState extends State with WidgetsBindingObserver, AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; // Providers late final RidesProvider _ridesProvider; late final PubNubManager _pubnubManager; // State StreamSubscription? _rideUpdateSubscription; Timer? _pollTimer; Timer? _connectionCheckTimer; String _statusText = 'Finding your ride…'; bool _isLoading = true; bool _rideRequested = false; // Map MapboxMap? _mapboxController; PointAnnotationManager? _pointAnnotationManager; final double _currentZoomLevel = 14.0; bool _driverFound = false; Timer? _timeoutTimer; // Message deduplication - smaller cache for memory efficiency final Set _processedRideIds = {}; final Set _processedMessageIds = {}; static const int _maxProcessedIds = 50; double _systembottompadding = 0.0; //----------------------------------------------- PointAnnotationManager? _startAnnotationManager; PointAnnotationManager? _destinationAnnotationManager; PointAnnotation? _startAnnotation; PointAnnotation? _destinationAnnotation; PolylineAnnotationManager? _routePolylineManager; PolylineAnnotation? _routePolyline; bool _isDisposed = false; bool _mapInitialized = false; int nbDriver = -1; //******************************* // Add these methods to your _FindingRideScreenState class Future _clearRouteMarkers() async { try { // Clear start marker if (_startAnnotation != null && _startAnnotationManager != null && !_isDisposed) { try { await _startAnnotationManager?.delete(_startAnnotation!); log('[DEBUG] Start marker deleted successfully'); } catch (e) { log('[INFO] Start marker already removed: ${e.toString()}'); } _startAnnotation = null; } // Clear destination marker if (_destinationAnnotation != null && _destinationAnnotationManager != null && !_isDisposed) { try { await _destinationAnnotationManager?.delete(_destinationAnnotation!); log('[DEBUG] Destination marker deleted successfully'); } catch (e) { log('[INFO] Destination marker already removed: ${e.toString()}'); } _destinationAnnotation = null; } } catch (e) { log('[WARNING] Error in _clearRouteMarkers: ${e.toString()}'); _startAnnotation = null; _destinationAnnotation = null; } } Future _clearRoutePolyline() async { try { if (_routePolyline != null && _routePolylineManager != null && !_isDisposed) { try { await _routePolylineManager?.delete(_routePolyline!); log('[DEBUG] Route polyline deleted successfully'); } catch (e) { log('[INFO] Polyline already removed: ${e.toString()}'); } } } catch (e) { log('[WARNING] Error in _clearRoutePolyline: ${e.toString()}'); } finally { _routePolyline = null; } } Future _cleanupMapElements() async { if (_isDisposed) return; try { await Future.wait([ _clearRoutePolyline(), _clearRouteMarkers(), ], eagerError: false); log('[DEBUG] All map elements cleaned up'); } catch (e) { log('[WARNING] Error during map cleanup: ${e.toString()}'); } } // Add these marker creation methods 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.toString()}'); _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.toString()}'); _destinationAnnotation = null; } } // Add these polyline methods Future _drawRoutePolyline(latlng.LatLng start, latlng.LatLng end) async { if (_routePolylineManager == null || _isDisposed) return; try { final rideProvider = Provider.of(context, listen: false); // Fetch route info using your existing function final routeData = await rideProvider.fetchRouteInfo( latlng.LatLng(start.latitude, start.longitude), latlng.LatLng(end.latitude, end.longitude), ); // 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'); } catch (e) { log('[ERROR] Error drawing route polyline: $e'); // Fallback: draw straight line if API call fails _drawFallbackPolyline(start, end); } } 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, 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'); } } // Add camera focus method void _focusCameraOnRoute(latlng.LatLng start, latlng.LatLng end) async { if (_mapboxController == null) return; try { // 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 _mapboxController!.cameraForCoordinateBounds( bounds, MbxEdgeInsets(top: 120, left: 120, bottom: 320, right: 120), 0, // bearing 0, // pitch 120, // maxZoom null, // offset ); await _mapboxController?.flyTo(cameraOptions, MapAnimationOptions(duration: 1000)); } catch (e) { log('[ERROR] Error focusing camera on route: $e'); } } Future _addRouteMarkersAndPolyline() async { if (!_mapInitialized || _isDisposed) return; final rideProvider = Provider.of(context, listen: false); if (rideProvider.startlocation != null && rideProvider.destinationlocation != null) { // Clear any existing markers and polyline await _cleanupMapElements(); // Create markers await _createStartMarker(rideProvider.startlocation!); await _createDestinationMarker(rideProvider.destinationlocation!); // Draw route polyline await _drawRoutePolyline(rideProvider.startlocation!, rideProvider.destinationlocation!); // Focus camera on the route _focusCameraOnRoute(rideProvider.startlocation!, rideProvider.destinationlocation!); } } //******************************* //----------------------------------------------- @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); // Get providers immediately _ridesProvider = context.read(); // CRITICAL FIX: Use singleton instance _pubnubManager = PubNubManager.instance; // Validate essential data if (_ridesProvider.startlocation == null) { log("FATAL: FindingRideScreen requires a start location."); WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { Navigator.of(context).pop(); AppSnackbar.showError("Error", "Your location could not be determined."); } }); return; } // Defer setup to ensure context is ready WidgetsBinding.instance.addPostFrameCallback((_) { _initializeScreen(); }); } // Update your dispose method to include map cleanup @override void dispose() { _isDisposed = true; WidgetsBinding.instance.removeObserver(this); _cancelSubscriptionsAndTimers(); // Clean up map elements _cleanupMapElements(); _mapboxController = null; super.dispose(); } void _cancelSubscriptionsAndTimers() { log('Cancelling subscriptions and timers for FindingRideScreen.'); _rideUpdateSubscription?.cancel(); _pollTimer?.cancel(); _timeoutTimer?.cancel(); _connectionCheckTimer?.cancel(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (!mounted) return; if (state == AppLifecycleState.resumed) { log('App Resumed: Re-establishing listeners for FindingRideScreen.'); // Check PubNub health and reconnect if needed _checkAndRestorePubNubConnection(); } else if (state == AppLifecycleState.paused) { log('App Paused: Cleaning up timers (keeping PubNub alive).'); // Don't kill PubNub connection, just clean up local timers _pollTimer?.cancel(); _connectionCheckTimer?.cancel(); } } Future _checkAndRestorePubNubConnection() async { final status = _pubnubManager.getStatus(); log('PubNub Status on resume: $status'); if (!status['isConnected'] || !status['subscriptionHealthy']) { log('PubNub unhealthy, forcing reconnection...'); await _pubnubManager.forceReconnect(); // Wait a bit for reconnection await Future.delayed(const Duration(seconds: 2)); } // Always re-setup listener _setupPubNubListener(); // Restart polling and connection monitoring _startPolling(); _startConnectionMonitoring(); } // Add this to your _initializeScreen method after map initialization Future _initializeScreen() async { if (!mounted) return; setState(() => _isLoading = true); // Setup the listener first _setupPubNubListener(); // Initialize the map and request the ride await _initializeMap(); await _requestRide(); final pad = await SystemInsetsService.instance.bottomPadding(context); log("pad : $pad"); if (mounted) setState(() => _systembottompadding = pad); _startPolling(); _startConnectionMonitoring(); _startTimeout(); if (mounted) setState(() => _isLoading = false); // Add route markers after initialization if (_mapInitialized) { _addRouteMarkersAndPolyline(); } } // NEW: Monitor PubNub connection health periodically void _startConnectionMonitoring() { _connectionCheckTimer?.cancel(); _connectionCheckTimer = Timer.periodic(const Duration(seconds: 30), (timer) { if (!mounted || _driverFound) { timer.cancel(); return; } final status = _pubnubManager.getStatus(); if (!status['isConnected'] || !status['subscriptionHealthy']) { log('⚠️ PubNub connection unhealthy during monitoring, forcing reconnect...'); _pubnubManager.forceReconnect(); if (mounted) { setState(() { _statusText = 'Reconnecting to driver updates...'; }); } } }); } void _setupPubNubListener() { // Cancel any existing subscription _rideUpdateSubscription?.cancel(); log('[PubNub] Cancelled any existing subscription'); log('[PubNub] Setting up PubNub listener...'); log('[PubNub] Driver found status: $_driverFound'); try { _rideUpdateSubscription = _pubnubManager.rideUpdateStream .where((data) { log('[PubNub] Received data, checking if driver is already found: $_driverFound'); return !_driverFound; // Stop processing if driver found }) .listen( _handleRideUpdate, onError: (error, stackTrace) { log('[PubNub] ERROR: Error on rideUpdateStream', error: error); log('[PubNub] ERROR: StackTrace: $stackTrace'); log('[PubNub] ERROR: Error type: ${error.runtimeType}'); // Log additional error details if available if (error is Exception) { log('[PubNub] ERROR: Exception message: $error'); } if (mounted) { log('[PubNub] Widget is mounted, updating status text'); setState(() { _statusText = 'Connection issue. Reconnecting...'; }); } else { log('[PubNub] WARNING: Widget not mounted, cannot update UI'); } // Force reconnect after error log('[PubNub] Scheduling reconnect in 2 seconds'); Future.delayed(const Duration(seconds: 2), () { log('[PubNub] Attempting reconnect now'); log('[PubNub] Current state - mounted: $mounted, driverFound: $_driverFound'); if (mounted && !_driverFound) { log('[PubNub] Conditions met, forcing reconnect'); _pubnubManager.forceReconnect(); } else { if (!mounted) { log('[PubNub] Skipping reconnect - widget not mounted'); } if (_driverFound) { log('[PubNub] Skipping reconnect - driver already found'); } } }); }, cancelOnError: false, ); log('[PubNub] Listener setup completed successfully'); log('[PubNub] Subscription active: ${_rideUpdateSubscription != null}'); } catch (e, stackTrace) { log('[PubNub] CRITICAL ERROR: Failed to setup listener', error: e); log('[PubNub] CRITICAL ERROR: StackTrace: $stackTrace'); if (mounted) { setState(() { _statusText = 'Failed to setup connection. Please restart.'; }); } } } Future _handleRideUpdate(Map data) async { if (!mounted || _driverFound) return; log("Handling message update: $data"); // Clean up processed IDs periodically if (_processedMessageIds.length > _maxProcessedIds) { _processedMessageIds.clear(); log('🧹 Cleared processed message IDs cache'); } if (_processedRideIds.length > _maxProcessedIds) { _processedRideIds.clear(); log('🧹 Cleared processed ride IDs cache'); } // Handle system messages first if (data.containsKey('heartbeat')) { return; // Ignore heartbeat messages } if (data.containsKey('connected') && data['connected'] == true) { if (mounted) { setState(() => _statusText = 'Finding your ride…'); } return; } if (data.containsKey('reconnecting') && data['reconnecting'] == true) { if (mounted) { setState(() => _statusText = data['message'] ?? 'Reconnecting...'); } return; } if (data.containsKey('error') && data['error'] == true) { if (mounted) { setState(() => _statusText = data['message'] ?? 'Connection issue...'); } return; } // Message deduplication for actual ride messages final messageId = data['_messageId'] as String?; if (messageId != null && _processedMessageIds.contains(messageId)) { log('⚠️ Duplicate message ignored in FindingRideScreen: $messageId'); return; } if (messageId != null) { _processedMessageIds.add(messageId); } log('βœ… PubNub Ride Update Received ➜ $data'); // ENHANCED: Check for driver assignment in multiple ways final messageType = data['type'] as String?; final channel = data['_channel'] as String?; // Method 1: Check for explicit DRIVER_ASSIGNED message if (messageType == 'DRIVER_ASSIGNED') { log('🎯 DRIVER_ASSIGNED message detected!'); final rideId = data['rideId']; if (rideId != null) { await _handleDriverAssigned(rideId); return; } } // Method 6: Check Firebase/FCM style messages (from your logs) if (data.containsKey('notification_type')) { final notificationType = data['notification_type']; log('🎯 FCM notification type: $notificationType'); if (notificationType == 'LIVE_ACTIVITY_START' || notificationType == 'DRIVER_ASSIGNED' || notificationType == 'RIDE_ACCEPTED') { log('🎯 Driver assignment detected via FCM notification!'); final rideId = data['rideId'] ?? _ridesProvider.currentride?.id; if (rideId != null) { await _handleDriverAssigned(rideId); return; } } } // Update status message if provided if (data['message'] != null && data['message'] is String) { if (mounted) { setState(() => _statusText = data['message']); } } // Handle search status updates if (messageType == 'SEARCHING') { if (mounted) { setState( () => _statusText = data['message'] ?? 'Searching for drivers...'); } } // DEBUGGING: Log any unhandled message types for analysis if (messageType != null && messageType != 'SEARCHING') { log('πŸ” UNHANDLED MESSAGE TYPE: $messageType - Data: $data'); } } // ENHANCED: More robust driver assignment handler Future _handleDriverAssigned(dynamic rideId) async { if (_driverFound) { log('🎯 Driver already found, ignoring duplicate assignment'); return; } HapticFeedback.lightImpact(); log('πŸŽ‰ Driver assignment detected! Checking ride: $rideId'); try { // First, check current ride without API call if (_ridesProvider.currentride?.driverId != null) { log('πŸŽ‰ Driver already in current ride data! Transitioning screen.'); _driverFound = true; _cancelSubscriptionsAndTimers(); _ridesProvider.updateCurrentstate(RideEnum.tracking_ride); return; } // Fetch latest ride data from API await _ridesProvider.finbyid(rideId); if (_ridesProvider.currentride?.driverId != null) { log('πŸŽ‰ Driver confirmed via API! Driver ID: ${_ridesProvider.currentride?.driverId}'); log('πŸŽ‰ Driver name: ${_ridesProvider.currentride?.drivername}'); log('πŸŽ‰ Car model: ${_ridesProvider.currentride?.drivercarmodel}'); _driverFound = true; _cancelSubscriptionsAndTimers(); _ridesProvider.updateCurrentstate(RideEnum.tracking_ride); } else { log('⚠️ Driver assignment received but no driver ID in ride data'); log('⚠️ Current ride data: ${_ridesProvider.currentride?.toJson()}'); // Try polling as backup log('πŸ”„ Falling back to polling for driver assignment...'); _startImmediatePolling(); } } catch (e, stackTrace) { log('πŸ”΄ Error fetching ride details: $e'); log('πŸ”΄ StackTrace: $stackTrace'); // Try polling as backup log('πŸ”„ Error occurred, falling back to polling...'); _startImmediatePolling(); } } void _startImmediatePolling() { // Cancel existing timer _pollTimer?.cancel(); // Start more frequent polling for 60 seconds int pollCount = 0; const maxPolls = 12; // 12 x 5 seconds = 60 seconds _pollTimer = Timer.periodic(const Duration(seconds: 5), (timer) async { pollCount++; if (!mounted || _driverFound || pollCount > maxPolls) { timer.cancel(); if (pollCount > maxPolls) { log('⏰ Immediate polling timeout reached'); // Resume normal polling _startPolling(); } return; } log('πŸ”„ Immediate polling attempt $pollCount/$maxPolls...'); try { await _ridesProvider.get_my_latest_ride_request(); if (_ridesProvider.currentride?.driverId != null) { log('πŸŽ‰ Driver found via immediate polling! Transitioning screen.'); _driverFound = true; timer.cancel(); _cancelSubscriptionsAndTimers(); _ridesProvider.updateCurrentstate(RideEnum.tracking_ride); } } catch (e) { log('πŸ”΄ Immediate polling error: $e'); } }); } void _startTimeout() { _timeoutTimer?.cancel(); _timeoutTimer = Timer(const Duration(minutes: 5), () { if (!mounted || _driverFound) return; log('⏰ Ride request timeout reached'); _updateStatus('No drivers available. Please try again.'); _cancelSubscriptionsAndTimers(); }); } // IMPROVED: Better polling with PubNub health awareness void _startPolling() { _pollTimer?.cancel(); _pollTimer = Timer.periodic(const Duration(seconds: 3), (timer) async { if (!mounted) { timer.cancel(); return; } // Only poll if we don't have a driver yet if (_ridesProvider.currentride?.driverId == null && !_driverFound) { log('Polling for ride status...'); // Check PubNub health during polling final pubnubStatus = _pubnubManager.getStatus(); if (!pubnubStatus['isConnected'] || !pubnubStatus['subscriptionHealthy']) { log('PubNub unhealthy during polling, forcing reconnect...'); _pubnubManager.forceReconnect(); } try { await _ridesProvider.get_my_latest_ride_request(); if(_ridesProvider.currentride != null && nbDriver < 0){ nbDriver = await _ridesProvider.get_nbDriver(_ridesProvider.currentride!.id); setState(() {}); } if (_ridesProvider.currentride?.driverId != null) { log('πŸŽ‰ Driver found via Polling! Transitioning screen.'); _driverFound = true; _cancelSubscriptionsAndTimers(); _ridesProvider.updateCurrentstate(RideEnum.tracking_ride); } } catch (e, st) { log('Polling error ➜ $e\n$st'); if (mounted) { setState(() { _statusText = 'Checking for updates...'; }); } } } else { timer.cancel(); } }); } Future _requestRide() async { if (_rideRequested) { log('Ride request already in progress, skipping.'); return; } setState(() { _rideRequested = true; _statusText = 'Contacting nearby drivers...'; }); final req = await _ridesProvider.convertToReq(RideType.INSTANT); if (req == null) { _updateStatus('Could not create ride request. Please try again.'); return; } final success = await _ridesProvider.request_ride(req); if (!success && mounted) { _updateStatus( 'Failed to send ride request. Please check your connection.'); } } void _updateStatus(String status) { if (mounted) { setState(() { _statusText = status; _rideRequested = false; }); AppSnackbar.showError("Error", status); } } Future _initializeMap() async { // Map will be initialized in the onMapCreated callback } Future _onMapCreated(MapboxMap mapboxMap) async { _mapboxController = mapboxMap; // Initialize all annotation managers _pointAnnotationManager = await mapboxMap.annotations.createPointAnnotationManager(); _startAnnotationManager = await mapboxMap.annotations.createPointAnnotationManager(); _destinationAnnotationManager = await mapboxMap.annotations.createPointAnnotationManager(); _routePolylineManager = await mapboxMap.annotations.createPolylineAnnotationManager(); _mapInitialized = true; // Add route markers and polyline if we have locations _addRouteMarkersAndPolyline(); } Future _addPickupMarker() async { if (_pointAnnotationManager == null || _ridesProvider.startlocation == null) return; try { // Load custom marker image final ByteData data = await rootBundle.load('assets/icons/pin-map.png'); final Uint8List bytes = data.buffer.asUint8List(); final loc = _ridesProvider.startlocation!; await _pointAnnotationManager!.create( PointAnnotationOptions( geometry: Point( coordinates: Position(loc.longitude, loc.latitude), ), image: bytes, iconSize: 0.2, ), ); } catch (e) { log('Error adding pickup marker: $e'); // Fallback: Add marker without custom image final loc = _ridesProvider.startlocation!; await _pointAnnotationManager!.create( PointAnnotationOptions( geometry: Point( coordinates: Position(loc.longitude, loc.latitude), ), ), ); } } @override Widget build(BuildContext context) { super.build(context); if (_isLoading) { return const Scaffold( body: Center(child: CustomLoader()), ); } return Scaffold( body: Column( children: [ // Map Expanded( child: Stack( children: [ MapWidget( cameraOptions: CameraOptions( center: Point( coordinates: Position( _ridesProvider.startlocation!.longitude, _ridesProvider.startlocation!.latitude, ), ), zoom: _currentZoomLevel, ), styleUri: MapboxStyles.MAPBOX_STREETS, textureView: true, onMapCreated: _onMapCreated, gestureRecognizers: >{ Factory( () => EagerGestureRecognizer(), ), }, ), // Driver Loading Overlay Positioned( top: 190, left: 100, right: 10, child: Container( padding: EdgeInsets.symmetric(horizontal: 14, vertical: 12.h), decoration: BoxDecoration( color: AppColors.white, borderRadius: BorderRadius.circular(30), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, spreadRadius: 2, offset: const Offset(0, 2), ), ], ), child: Row( children: [ // Car Icon Image.asset( 'assets/images/New/Image.png', width: 40, height: 40, fit: BoxFit.fill, ), SizedBox(width: 8.w), const Text( "Looking for your driver.....", style: TextStyle( fontSize: 12, fontFamily: 'Inter', fontWeight: FontWeight.w600, height: 1.33, ), ), ], ), ), ), ], ), ), // Details Panel Container( decoration: const BoxDecoration( color: AppColors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), ), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 10, spreadRadius: 2), ], ), padding: EdgeInsets.only(top: 15.sp, left: 5.sp,right: 5.sp, bottom: _ridesProvider.currentride == null ? 50.sp: 10.sp ), // padding: EdgeInsets.symmetric(vertical: 15.sp, horizontal: 5.sp), child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ driver_info_card( title: "Soon you will get a drive", subtitle: "Your driver is on the way and will be there shortly....", nbDriver:nbDriver <0 ? "..." : nbDriver.toString(), image_asset: 'assets/images/wanis.png'), ], ), ), ], ), bottomNavigationBar: (_ridesProvider.currentride != null) ? _buildConfirmButton(context) : const SizedBox.shrink(), ); } Widget _buildConfirmButton(BuildContext context) { return SafeArea( top: false, child: Container( padding: EdgeInsets.fromLTRB(20, 15, 20, _systembottompadding + 20), // no extra bottom gap decoration: const BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Color(0x0F000000), blurRadius: 10, offset: Offset(0, 2), ), ], border: Border(top: BorderSide(color: Colors.black12, width: 1)), ), child: CustomButton( onClick: widget.onCancel, width: double.infinity, fontSize: 18, height: 50, borderRadius: 12, txtColor: Colors.white, title: "Cancel ride", backgroundColor: AppNewColors.primaryBtnColor, borderColor: AppNewColors.primaryBtnColor, loadercolor: Colors.white, ), ), ); } Widget driver_info_card({ required String title, required String subtitle, required String nbDriver, required String image_asset, }) { return Column( children: [ Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all( // use your app color here if you have one color: const Color(0xFF7C3AED), // purple like the screenshot width: 2, ), boxShadow: const [ BoxShadow( color: Color(0x0F000000), blurRadius: 10, offset: Offset(0, 2), ) ], ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( image_asset, width: 72, height: 48, fit: BoxFit.contain, ), const SizedBox(width: 12), Expanded( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Txt( text: title, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: Colors.black, ), ), const SizedBox(height: 4), Txt( text: subtitle, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w400, color: Color(0xFF6B7280), // grey like the mock height: 1.25, ), ), ], ), ), ], ), ), Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all( // use your app color here if you have one color: const Color(0xFFE0E0E0), // purple like the screenshot width: 2, ), boxShadow: const [ BoxShadow( color: Color(0x0F000000), blurRadius: 10, offset: Offset(0, 2), ) ], ), child: Row( children: [ Txt( text: nbDriver, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: Colors.black, ), ), const SizedBox(width: 5,), const Flexible( child: Txt( text: "drivers are looking for your request", maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: Colors.black, ), ), ), ], ), ), ], ); } }