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
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),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} |