You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

742 lines
15 KiB

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<DeliveryUpdatesPage> createState() => _DeliveryUpdatesPageState();
}
class _DeliveryUpdatesPageState extends State<DeliveryUpdatesPage> {
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<Marker> markers={};
Set<Polyline> polylines={};
List<LatLng> 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<void> _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<void> _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<void> _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<void> _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<void> _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),
],
),
)
],
),
);
}
}