import 'dart:async'; import 'dart:convert'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:http/http.dart' as http; import 'package:url_launcher/url_launcher.dart'; import 'package:supplier_new/common/settings.dart'; import 'otp_start_unloading.dart'; import 'unloading_inprogress.dart'; import 'collect_money.dart'; class DeliveryUpdatesPage extends StatefulWidget { var details; final String orderId; final String initialStatus; DeliveryUpdatesPage({ super.key, this.details, required this.orderId, required this.initialStatus, }); @override State createState() => _DeliveryUpdatesPageState(); } class _DeliveryUpdatesPageState extends State { GoogleMapController? _mapController; Timer? _timer; final PolylinePoints _polylinePoints = PolylinePoints(); static const String googleApiKey = "YOUR_GOOGLE_KEY"; bool isLoading=true; bool mapReady=false; String currentStatus=''; String etaText='--'; String distanceText='--'; String durationText='--'; String driverName=''; String driverPhone=''; String tankerName=''; String orderAddress=''; double? driverLat; double? driverLng; double? destinationLat; double? destinationLng; Set markers={}; Set polylines={}; List routePoints=[]; LatLng? _lastDriverPosition; bool firstCameraMove=true; @override void initState(){ super.initState(); currentStatus=_beautifyStatus(widget.initialStatus); _loadTracking(showLoader:true); _timer=Timer.periodic( Duration(seconds:5), (_){ _loadTracking(); }); } @override void dispose(){ _timer?.cancel(); _mapController?.dispose(); super.dispose(); } Future _loadTracking({bool showLoader=false}) async{ try{ final uri=Uri.parse( '${AppSettings.host}trackOrder/${widget.orderId}' ); final response=await http.get(uri); final decoded=jsonDecode(response.body); final data=decoded["data"]??decoded; driverLat=_toDouble(data["driverLat"]); driverLng=_toDouble(data["driverLng"]); destinationLat=_toDouble(data["destinationLat"]); destinationLng=_toDouble(data["destinationLng"]); driverName=(data["driverName"]??"").toString(); driverPhone=(data["driverPhone"]??"").toString(); tankerName=(data["tankerName"]??"").toString(); orderAddress=(data["address"]??"").toString(); currentStatus=_beautifyStatus( (data["status"]??widget.initialStatus).toString() ); if(driverLat!=null && driverLng!=null && destinationLat!=null && destinationLng!=null){ await _buildMarkers(); if(currentStatus.toLowerCase().contains("out for delivery")){ await _fetchGoogleRouteAndEta(); } await _moveCameraToBounds(); } setState(() { isLoading=false; }); } catch(e){ setState(() { isLoading=false; }); } } double _calculateDistanceKm( double startLat, double startLng, double endLat, double endLng){ const double earthRadius=6371; final dLat=_degToRad(endLat-startLat); final dLng=_degToRad(endLng-startLng); final a= sin(dLat/2)*sin(dLat/2)+ cos(_degToRad(startLat))* cos(_degToRad(endLat))* sin(dLng/2)* sin(dLng/2); final c=2*atan2(sqrt(a),sqrt(1-a)); return earthRadius*c; } double _degToRad(double deg){ return deg*pi/180; } Future _buildMarkers() async{ if(driverLat==null || driverLng==null || destinationLat==null || destinationLng==null){ return; } LatLng newDriverPos= LatLng(driverLat!,driverLng!); /// CHECK MOVEMENT DISTANCE bool moved=true; if(_lastDriverPosition!=null){ double diff= _calculateDistanceKm( _lastDriverPosition!.latitude, _lastDriverPosition!.longitude, newDriverPos.latitude, newDriverPos.longitude ); /// UPDATE ROUTE ONLY IF DRIVER MOVED > 30m moved=diff>0.03; } _lastDriverPosition=newDriverPos; markers={ Marker( markerId:MarkerId("driver"), position:newDriverPos, rotation:0, icon:BitmapDescriptor.defaultMarkerWithHue( BitmapDescriptor.hueAzure ) ), Marker( markerId:MarkerId("destination"), position:LatLng(destinationLat!,destinationLng!), icon:BitmapDescriptor.defaultMarkerWithHue( BitmapDescriptor.hueRed ) ) }; /// FOLLOW DRIVER CAMERA if(_mapController!=null){ if(firstCameraMove){ _mapController!.animateCamera( CameraUpdate.newLatLngZoom( newDriverPos, 15 ) ); firstCameraMove=false; } else{ _mapController!.animateCamera( CameraUpdate.newLatLng( newDriverPos ) ); } } } Future _fetchGoogleRouteAndEta() async{ if(driverLat==null || driverLng==null || destinationLat==null || destinationLng==null){ return; } final origin='${driverLat!},${driverLng!}'; final destination='${destinationLat!},${destinationLng!}'; final url=Uri.parse( "https://maps.googleapis.com/maps/api/directions/json?" "origin=$origin" "&destination=$destination" "&departure_time=now" "&traffic_model=best_guess" "&mode=driving" "&key=$googleApiKey" ); try{ final res=await http.get(url); final data=jsonDecode(res.body); if(data["routes"]==null || data["routes"].isEmpty)return; final route=data["routes"][0]; final leg=route["legs"][0]; /// DISTANCE distanceText= (leg["distance"]["text"]??"--").toString(); /// DURATION durationText= (leg["duration"]["text"]??"--").toString(); /// ETA WITH TRAFFIC if(leg["duration_in_traffic"]!=null){ etaText= leg["duration_in_traffic"]["text"].toString(); } else{ etaText=durationText; } /// ROUTE POLYLINE final poly= route["overview_polyline"]["points"]; final decoded= _polylinePoints.decodePolyline(poly); routePoints= decoded.map((e)=> LatLng(e.latitude,e.longitude) ).toList(); polylines={ Polyline( polylineId:PolylineId("route"), points:routePoints, width:5, color:Color(0XFF8270DB) ) }; } catch(e){ print("Route error $e"); } } Future _moveCameraToBounds() async{ if(_mapController==null)return; LatLngBounds bounds=LatLngBounds( southwest:LatLng( min(driverLat!,destinationLat!), min(driverLng!,destinationLng!) ), northeast:LatLng( max(driverLat!,destinationLat!), max(driverLng!,destinationLng!) ) ); _mapController!.animateCamera( CameraUpdate.newLatLngBounds(bounds,70) ); } double? _toDouble(val){ if(val==null)return null; return double.tryParse(val.toString()); } String _beautifyStatus(String s){ return s.replaceAll("_"," ").toUpperCase(); } /// STATUS FLOW BUTTON Widget _buildActionButton(){ String status=currentStatus .replaceAll("_"," ") .toLowerCase() .trim(); if(status.contains("arrived")){ return _actionButton( "Start Unloading", (){ Navigator.push( context, MaterialPageRoute( builder:(_)=>UnloadArrivalScreen( details:widget.details ) ) ); }); } if(status.contains("arrived")){ etaText="Reached destination"; } if(status.contains("unloading started")){ return _actionButton( "Unloading Progress", (){ Navigator.push( context, MaterialPageRoute( builder:(_)=>UnloadingInProgressScreen( details:widget.details ) ) ); }); } if(status.contains("unloading stopped")){ return _actionButton( "Collect Payment", (){ Navigator.push( context, MaterialPageRoute( builder:(_)=>CollectMoney( details:widget.details ) ) ); }); } if(status.contains("payment pending")){ return _actionButton( "Complete Delivery", (){ Navigator.push( context, MaterialPageRoute( builder:(_)=>CollectMoney( details:widget.details ) ) ); }); } return SizedBox(); } Widget _actionButton( String text, VoidCallback onTap){ return SizedBox( width:double.infinity, child:ElevatedButton( style:ElevatedButton.styleFrom( backgroundColor:Color(0XFF8270DB), padding:EdgeInsets.symmetric(vertical:15), shape:RoundedRectangleBorder( borderRadius:BorderRadius.circular(20) ) ), onPressed:onTap, child:Text( text, style:TextStyle( color:Colors.white, fontSize:16, fontWeight:FontWeight.w600 ) ) ), ); } Future _callDriver() async{ if(widget.details.delivery_agent_mobile.isEmpty)return; final uri=Uri.parse("tel:${widget.details.delivery_agent_mobile}"); await launchUrl(uri); } @override Widget build(BuildContext context){ return Scaffold( backgroundColor:Color(0XFFF2F2F2), appBar:AppBar( title:Text("Track Delivery"), backgroundColor:Colors.white, ), body:isLoading? Center(child:CircularProgressIndicator()) : Column( children:[ Expanded( child:GoogleMap( initialCameraPosition: CameraPosition( target:LatLng( driverLat??17.385, driverLng??78.486 ), zoom:14 ), markers:markers, polylines:polylines, onMapCreated:(c){ _mapController=c; mapReady=true; } ) ), Container( padding:EdgeInsets.all(16), decoration:BoxDecoration( color:Colors.white, borderRadius:BorderRadius.vertical( top:Radius.circular(25) ) ), child:Column( children:[ Row( children:[ Expanded( child:Text( "${widget.details.delivery_agent_name}", style:TextStyle( fontSize:16, fontWeight:FontWeight.bold ) ) ), IconButton( onPressed:_callDriver, icon:Icon(Icons.call) ) ], ), SizedBox(height:10), Text( "Status : $currentStatus" ), SizedBox(height:8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "ETA", style: TextStyle( fontSize:12, color:Colors.grey ), ), Text( etaText, style: TextStyle( fontSize:14, fontWeight:FontWeight.bold ), ) ], ), Column( children: [ Text( "Distance", style: TextStyle( fontSize:12, color:Colors.grey ), ), Text( distanceText, style: TextStyle( fontSize:14, fontWeight:FontWeight.bold ), ) ], ), Column( children: [ Text( "Duration", style: TextStyle( fontSize:12, color:Colors.grey ), ), Text( durationText, style: TextStyle( fontSize:14, fontWeight:FontWeight.bold ), ) ], ) ], ), SizedBox(height:15), SizedBox(height:20), _buildActionButton(), SizedBox(height:10), ], ), ) ], ), ); } }