part of 'deliveries.dart'; // ------------------------------------------------------------------------- // SCREEN 1: DELIVERY MAP PREVIEW (Shows route, has "Start" button) // ------------------------------------------------------------------------- class _DeliveryMapScreen extends StatefulWidget { final Map delivery; final _MyDeliveriesState? parentState; const _DeliveryMapScreen({required this.delivery, this.parentState}); @override State<_DeliveryMapScreen> createState() => _DeliveryMapScreenState(); } class _DeliveryMapScreenState extends State<_DeliveryMapScreen> { DoormileMapController? _mapController; late final LatLng _pickupLocation; late final LatLng _dropLocation; List _markers = const []; List _polylines = const []; bool _isNavigating = false; // Prevent multiple clicks @override void initState() { super.initState(); _resolveLocationsFromDelivery(); _setMarkers(); _createRealRoute(); } double _parseD(dynamic v) { if (v == null) return 0.0; if (v is num) return v.toDouble(); return double.tryParse(v.toString()) ?? 0.0; } void _resolveLocationsFromDelivery() { final d = widget.delivery; final double pickLat = _parseD(d['pickuplat'] ?? d['PickupLat']); final double pickLon = _parseD(d['pickuplon'] ?? d['PickupLon']); final double dropLat = _parseD( d['droplat'] ?? d['DropLat'] ?? d['deliverylat'], ); final double dropLon = _parseD( d['droplon'] ?? d['DropLon'] ?? d['deliverylong'], ); final double riderLat = _parseD(d['riderslat']); final double riderLon = _parseD(d['riderslon']); final bool hasPickup = pickLat != 0 && pickLon != 0; final bool hasDrop = dropLat != 0 && dropLon != 0; final LatLng pickup = hasPickup ? LatLng(pickLat, pickLon) : (riderLat != 0 && riderLon != 0 ? LatLng(riderLat, riderLon) : const LatLng(10.998356, 76.977596)); final LatLng drop = hasDrop ? LatLng(dropLat, dropLon) : const LatLng(11.004556, 76.967696); _pickupLocation = pickup; _dropLocation = drop; } Future _setMarkers() async { final pickupIcon = await MapMarkerIcons.pin(Colors.red); final dropIcon = await MapMarkerIcons.pin(Colors.green); if (!mounted) return; setState(() { _markers = [ DoormileMapMarker( id: 'pickup', position: _pickupLocation, icon: pickupIcon, ), DoormileMapMarker(id: 'drop', position: _dropLocation, icon: dropIcon), ]; }); } Future _createRealRoute() async { final route = await RoutingService.getRoute( origin: _pickupLocation, destination: _dropLocation, ); if (!mounted) return; // Fall back to a straight line so the screen still shows the connection // when routing is unavailable. final points = (route != null && route.points.isNotEmpty) ? route.points : [_pickupLocation, _dropLocation]; if (route == null) debugPrint('[MAP_PREVIEW] No route found'); setState(() { _polylines = [ DoormileMapPolyline( id: 'real_route', points: points, color: ColorConstants.primaryColor, width: 6, ), ]; }); _fitMapToRoute(); } Future _fitMapToRoute() async { final controller = _mapController; if (!mounted || controller == null) return; await controller.fitBounds( LatLngBounds( southwest: LatLng( math.min(_pickupLocation.latitude, _dropLocation.latitude), math.min(_pickupLocation.longitude, _dropLocation.longitude), ), northeast: LatLng( math.max(_pickupLocation.latitude, _dropLocation.latitude), math.max(_pickupLocation.longitude, _dropLocation.longitude), ), ), padding: 80, ); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ SizedBox.expand( child: DoormileMap( initialCameraPosition: CameraPosition( target: _pickupLocation, zoom: 14.5, ), markers: _markers, polylines: _polylines, onMapReady: (controller) { _mapController = controller; _fitMapToRoute(); }, ), ), Positioned( top: 50, left: 16, child: CircleAvatar( backgroundColor: Colors.white, child: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black), onPressed: () => Navigator.pop(context), ), ), ), Align( alignment: Alignment.bottomCenter, child: Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), boxShadow: [ BoxShadow( color: Colors.black26, blurRadius: 8, offset: Offset(0, -2), ), ], ), child: SafeArea( top: false, child: LayoutBuilder( builder: (ctx, constraints) { final double maxSheetHeight = MediaQuery.of(ctx).size.height * 0.35; // ignore: unused_local_variable final double allowedHeight = math.min( constraints.maxHeight, maxSheetHeight, ); return ListView( padding: EdgeInsets.zero, shrinkWrap: true, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Customer Details', style: TextStyle( fontSize: FontConstants.xxLarge(context), fontWeight: FontWeight.bold, fontFamily: FontConstants.fontFamily, ), ), InkWell( onTap: () async { // Just launch dialer; PiP is handled only from navigation screen final phone = (widget.delivery['deliverycontactno'] ?? '') .toString(); final bool success = await launchPhoneDialer( phone.isNotEmpty ? phone : '9876543210', ); if (!success && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Could not launch dialer'), ), ); } }, child: Image.asset( 'assets/images/phone-call .png', height: 27, width: 27, errorBuilder: (c, e, s) => const Icon( Icons.phone, size: 27, color: Colors.green, ), ), ), ], ), const SizedBox(height: 8), const Divider(thickness: 1), const SizedBox(height: 8), _buildCustomerInfo(), const SizedBox(height: 20), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: ColorConstants.primaryColor, minimumSize: const Size(double.infinity, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), onPressed: _isNavigating ? null : () async { // Prevent multiple clicks if (_isNavigating || !mounted || !context.mounted) return; setState(() { _isNavigating = true; }); try { // Capture screen size before navigation final screenSize = MediaQuery.of(context).size * MediaQuery.of(context).devicePixelRatio; final dc = Get.put( DeliveriesController(), permanent: true, ); final d = widget.delivery; final int deliveryId = int.tryParse( '${d['deliveryid'] ?? d['DeliveryId'] ?? 0}', ) ?? 0; final String orderId = (d['orderid'] ?? d['OrderId'] ?? '') .toString(); final int orderHeaderId = int.tryParse( '${d['orderheaderid'] ?? d['OrderHeaderId'] ?? 0}', ) ?? 0; // Save ridertime start at the moment navigation is started try { if (deliveryId > 0) { final prefs = await SharedPreferences.getInstance(); // 1) Save rider time start (existing behaviour) await prefs.setString( 'ridertime_start_$deliveryId', DateTime.now().toIso8601String(), ); // 2) Save ETA end time for this order so PiP timer can resume correctly final rawEta = d['eta']; int etaMinutes = 0; if (rawEta != null) { etaMinutes = int.tryParse(rawEta.toString()) ?? 0; } if (etaMinutes > 0) { final now = DateTime.now(); final endTime = now .add( Duration( minutes: etaMinutes, ), ) .millisecondsSinceEpoch ~/ 1000; // store seconds await prefs.setInt( 'eta_endtime_$orderId', endTime, ); debugPrint( '[ACTIVE][ETA] Saved eta_endtime_$orderId -> $endTime (eta=$etaMinutes min)', ); } debugPrint( '[ACTIVE] Saved ridertime_start for deliveryId=$deliveryId', ); } } catch (e) { debugPrint( '[ACTIVE] Error saving ridertime_start: $e', ); } final parentState = widget.parentState ?? context .findAncestorStateOfType< _MyDeliveriesState >(); // ✅ CRITICAL: Check active delivery BEFORE navigation if (parentState != null) { final currentOrderId = (d['orderid'] ?? d['OrderId'] ?? '') .toString(); final activeOrderIds = parentState ._activeDeliveries .map( (del) => (del['orderid'] ?? del['OrderId'] ?? '') .toString(), ) .where((id) => id.isNotEmpty) .toSet(); // Block if there's a different active delivery if (parentState ._activeDeliveries .isNotEmpty && !activeOrderIds.contains( currentOrderId, )) { if (mounted) { setState(() { _isNavigating = false; }); showDialog( context: context, barrierDismissible: true, builder: (BuildContext dialogContext) { return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: Container( padding: const EdgeInsets.all( 24, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular( 20, ), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Colors.orange.shade400, Colors.red.shade500, ], ), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Icon Container( padding: const EdgeInsets.all( 16, ), decoration: BoxDecoration( color: Colors .white .withOpacity( 0.2, ), shape: BoxShape .circle, ), child: const Icon( Icons.warning_rounded, color: Colors.white, size: 48, ), ), const SizedBox( height: 20, ), // Title Text( 'Active Delivery in Progress', textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: FontConstants.xxLarge( context, ), fontWeight: FontWeight.bold, fontFamily: FontConstants .fontFamily, ), ), const SizedBox( height: 12, ), // Message Text( 'Please complete your active delivery first before starting another delivery.', textAlign: TextAlign.center, style: TextStyle( color: Colors.white .withOpacity( 0.95, ), fontSize: FontConstants.medium( context, ), height: 1.4, fontFamily: FontConstants .fontFamily, ), ), const SizedBox( height: 24, ), // Action Button SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () { Navigator.of( dialogContext, ).pop(); // Navigate to active delivery if (parentState ._activeDeliveries .isNotEmpty) { parentState .startDelivery( parentState ._activeDeliveries .first, ); } }, style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: Colors .red .shade600, padding: const EdgeInsets.symmetric( vertical: 16, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 12, ), ), elevation: 2, ), child: Row( mainAxisAlignment: MainAxisAlignment .center, children: [ const Icon( Icons .two_wheeler, size: 22, ), const SizedBox( width: 8, ), Text( 'View Active Delivery', style: TextStyle( fontSize: FontConstants.regular( context, ), fontWeight: FontWeight .bold, fontFamily: FontConstants .fontFamily, ), ), ], ), ), ), const SizedBox( height: 12, ), // Close Button TextButton( onPressed: () { Navigator.of( dialogContext, ).pop(); }, child: Text( 'Close', style: TextStyle( color: Colors.white .withOpacity( 0.9, ), fontSize: 15, fontWeight: FontWeight.w500, fontFamily: FontConstants .fontFamily, ), ), ), ], ), ), ); }, ); } return; // Return early to prevent starting another delivery } } if (deliveryId > 0 && orderId.isNotEmpty) { String riderLatStr = '0'; String riderLngStr = '0'; try { final position = await Geolocator.getCurrentPosition( locationSettings: const LocationSettings( accuracy: LocationAccuracy.medium, timeLimit: Duration( seconds: 3, ), ), ).timeout( const Duration(seconds: 3), ); riderLatStr = position.latitude .toStringAsFixed(6); riderLngStr = position.longitude .toStringAsFixed(6); } catch (e) { debugPrint( '[ACTIVE] Error getting rider location: $e', ); try { final lastPos = await Geolocator.getLastKnownPosition(); if (lastPos != null) { riderLatStr = lastPos.latitude .toStringAsFixed(6); riderLngStr = lastPos.longitude .toStringAsFixed(6); } } catch (_) {} } // ✅ CRITICAL: Navigate FIRST, then handle status updates if (!mounted || !context.mounted) { setState(() { _isNavigating = false; }); return; } // Navigate immediately - this must happen Navigator.of(context).push( MaterialPageRoute( builder: (context) => _RiderNavigationScreen( delivery: widget.delivery, parentState: widget.parentState, ), ), ); // Reset navigation state after navigation completes setState(() { _isNavigating = false; }); // Continue with status updates and PiP in background debugPrint( '[ACTIVE] Updating status for deliveryId=$deliveryId orderId=$orderId lat=$riderLatStr lng=$riderLngStr', ); final ok = await dc.updateActiveStatus( deliveryId: deliveryId, orderHeaderId: orderHeaderId, ridersLat: riderLatStr, ridersLng: riderLngStr, orderId: orderId, ); debugPrint( '[ACTIVE] Status update result: $ok', ); // ✅ CRITICAL: ENFORCE PiP when delivery becomes active (compulsory) if (ok && !dc.isPipEnabled.value) { try { debugPrint( '[ACTIVE] Delivery is now active - Enforcing PiP mode', ); final floating = Floating(); const rational = Rational.landscape(); final height = (screenSize.height * 0.5).toInt(); final width = (screenSize.width * 0.9) .toInt(); final arguments = ImmediatePiP( aspectRatio: rational, sourceRectHint: math.Rectangle( ((screenSize.width - width) ~/ 2) .toInt(), ((screenSize.height - height) ~/ 2) .toInt(), width, height, ), ); await floating.enable(arguments); dc.isPipEnabled.value = true; debugPrint( '[ACTIVE] PiP enabled successfully', ); // Also try method channel as backup try { const channel = MethodChannel( 'doormile/pip', ); await channel.invokeMethod( 'enterPip', ); } catch (_) {} } catch (e) { debugPrint( '[ACTIVE] Error enabling PiP: $e', ); } } if (parentState != null) { final previousActive = parentState._activeDeliveryOrderId; if (previousActive != null && previousActive != orderId) { parentState._stopDeliveryPosting( previousActive, ); } parentState._activeDeliveryOrderId = orderId; d['orderstatus'] = 'active'; final orderKey = parentState ._getOrderKey(d); if (parentState._skippedOrdersCache .containsKey(orderKey)) { parentState._skippedOrdersCache .remove(orderKey); parentState._skippedOrderTimestamps .remove(orderKey); await parentState ._saveSkippedOrdersCache(); debugPrint( '[ACTIVE] Removed from skipped cache (resumed): $orderKey', ); } await parentState._startDeliveryPosting( d, ); try { final prefs = await SharedPreferences.getInstance(); await prefs.setString( 'active_delivery_order_id', orderId, ); } catch (e) { debugPrint( '[ACTIVE] Error saving active delivery ID: $e', ); } // ignore: invalid_use_of_protected_member parentState.setState(() {}); } } else { debugPrint( '[ACTIVE] Invalid deliveryId: $deliveryId or orderId: $orderId', ); setState(() { _isNavigating = false; }); } } catch (e) { debugPrint( '[ACTIVE] Error in navigation flow: $e', ); if (mounted) { setState(() { _isNavigating = false; }); } } }, child: _isNavigating ? Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( Colors.white, ), ), ), const SizedBox(width: 12), Text( 'Starting...', style: TextStyle( fontSize: FontConstants.xLarge(context), fontWeight: FontWeight.bold, fontFamily: FontConstants.fontFamily, color: Colors.white, ), ), ], ) : Text( 'Start Navigation', style: TextStyle( fontSize: FontConstants.xLarge(context), fontWeight: FontWeight.bold, fontFamily: FontConstants.fontFamily, color: Colors.white, ), ), ), ], ); }, ), ), ), ), ], ), ); } Widget _buildCustomerInfo() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildInfoRow( 'Name:', (widget.delivery['deliverycustomer'] ?? 'Customer').toString(), ), const SizedBox(height: 10), _buildInfoRow( 'Address:', (widget.delivery['deliveryaddress'] ?? 'Address not available') .toString(), isExpanded: true, ), const SizedBox(height: 10), _buildInfoRow( 'Order ID:', '#${(widget.delivery['orderid'] ?? '').toString()}', ), const SizedBox(height: 10), _buildInfoRow( 'Distance:', '${(widget.delivery['kms'] ?? '0').toString()} km', valueColor: Colors.red, ), ], ); } Widget _buildInfoRow( String label, String value, { bool isExpanded = false, Color? valueColor, }) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: FontConstants.xLarge(context), fontWeight: FontWeight.w600, fontFamily: FontConstants.fontFamily, ), ), const SizedBox(width: 15), isExpanded ? Expanded( child: Text( value, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: FontConstants.large(context), fontFamily: FontConstants.fontFamily, color: valueColor ?? Colors.black87, ), ), ) : Text( value, style: TextStyle( fontSize: FontConstants.large(context), fontFamily: FontConstants.fontFamily, color: valueColor ?? Colors.black87, ), ), ], ); } }