2200 lines
81 KiB
Dart
Executable File
2200 lines
81 KiB
Dart
Executable File
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'dart:math';
|
|
import 'dart:math' as math;
|
|
import 'package:sefer_driver/controller/home/captin/behavior_controller.dart';
|
|
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
|
|
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
|
import 'package:bubble_head/bubble.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:geolocator/geolocator.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
|
|
import '../../../constant/api_key.dart';
|
|
import '../../../constant/box_name.dart';
|
|
import '../../../constant/colors.dart';
|
|
import '../../../constant/links.dart';
|
|
import '../../../constant/table_names.dart';
|
|
import '../../../main.dart';
|
|
import '../../../print.dart';
|
|
import '../../../views/Rate/rate_passenger.dart';
|
|
import '../../../views/home/Captin/home_captain/home_captin.dart';
|
|
import '../../firebase/firbase_messge.dart';
|
|
import '../../functions/crud.dart';
|
|
import '../../functions/encrypt_decrypt.dart';
|
|
import '../../functions/location_controller.dart';
|
|
import '../../functions/tts.dart';
|
|
|
|
class MapDriverController extends GetxController {
|
|
bool isLoading = true;
|
|
final formKey1 = GlobalKey<FormState>();
|
|
final formKey2 = GlobalKey<FormState>();
|
|
final formKeyCancel = GlobalKey<FormState>();
|
|
final messageToPassenger = TextEditingController();
|
|
final sosEmergincyNumberCotroller = TextEditingController();
|
|
final cancelTripCotroller = TextEditingController();
|
|
List data = [];
|
|
List dataDestination = [];
|
|
LatLngBounds? boundsData;
|
|
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
|
|
BitmapDescriptor passengerIcon = BitmapDescriptor.defaultMarker;
|
|
BitmapDescriptor startIcon = BitmapDescriptor.defaultMarker;
|
|
BitmapDescriptor endIcon = BitmapDescriptor.defaultMarker;
|
|
final List<LatLng> polylineCoordinates = [];
|
|
final List<LatLng> polylineCoordinatesDestination = [];
|
|
List<Polyline> polyLines = [];
|
|
List<Polyline> polyLinesDestination = [];
|
|
Set<Marker> markers = {};
|
|
late String passengerLocation;
|
|
late String passengerDestination;
|
|
late String step0;
|
|
late String step1;
|
|
late String step2;
|
|
late String step3;
|
|
late String step4;
|
|
late String passengerWalletBurc;
|
|
late String timeOfOrder;
|
|
late String duration;
|
|
late String totalCost;
|
|
late String distance;
|
|
late String passengerName;
|
|
late String passengerEmail;
|
|
late String totalPricePassenger;
|
|
late String passengerPhone;
|
|
late String rideId;
|
|
late String isHaveSteps;
|
|
late String paymentAmount;
|
|
late String paymentMethod;
|
|
late String passengerId;
|
|
late String driverId;
|
|
late String tokenPassenger;
|
|
late String durationToPassenger;
|
|
late String walletChecked;
|
|
late String direction;
|
|
late String durationOfRideValue;
|
|
late String status;
|
|
int timeWaitingPassenger = 5; //5 miniute
|
|
bool isPassengerInfoWindow = false;
|
|
bool isBtnRideBegin = false;
|
|
bool isArrivedSend = true;
|
|
bool isdriverWaitTimeEnd = false;
|
|
bool isRideFinished = false;
|
|
bool isRideStarted = false;
|
|
bool isPriceWindow = false;
|
|
double passengerInfoWindowHeight = Get.height * .38;
|
|
double driverEndPage = 100;
|
|
double progress = 0;
|
|
double progressToPassenger = 0;
|
|
double progressInPassengerLocationFromDriver = 0;
|
|
bool isRideBegin = false;
|
|
int progressTimerToShowPassengerInfoWindowFromDriver = 25;
|
|
int remainingTimeToShowPassengerInfoWindowFromDriver = 25;
|
|
int remainingTimeToPassenger = 60;
|
|
int remainingTimeInPassengerLocatioWait = 60;
|
|
bool isDriverNearPassengerStart = false;
|
|
GoogleMapController? mapController;
|
|
late LatLng myLocation;
|
|
int remainingTimeTimerRideBegin = 60;
|
|
String stringRemainingTimeRideBegin = '';
|
|
String stringRemainingTimeRideBegin1 = '';
|
|
double progressTimerRideBegin = 0;
|
|
late Timer timer;
|
|
String? mapAPIKEY;
|
|
final zones = <Zone>[];
|
|
String canelString = 'yet';
|
|
LatLng latLngPassengerLocation = LatLng(0, 0);
|
|
late LatLng latLngPassengerDestination = LatLng(0, 0);
|
|
|
|
List<Map<String, dynamic>> routeSteps = [];
|
|
String currentInstruction = "";
|
|
int currentStepIndex = 0;
|
|
|
|
@override
|
|
void onClose() {
|
|
print("--- Controller is closing. Cleaning up listener. ---");
|
|
|
|
// This will stop the listener when you leave the page
|
|
_posSub?.cancel();
|
|
_navigationTimer?.cancel();
|
|
// It's also good practice to dispose the map controller
|
|
mapController?.dispose();
|
|
|
|
super.onClose();
|
|
}
|
|
|
|
void onMapCreated(GoogleMapController controller) async {
|
|
myLocation = Get.find<LocationController>().myLocation;
|
|
// myLocation = myLocation;
|
|
mapController = controller;
|
|
controller.getVisibleRegion();
|
|
// LatLngBounds bounds = await controller.getVisibleRegion();
|
|
controller.animateCamera(
|
|
CameraUpdate.newLatLng(Get.find<LocationController>().myLocation),
|
|
);
|
|
update();
|
|
// Set up a timer or interval to trigger the marker update every 3 seconds.
|
|
timer = Timer.periodic(const Duration(seconds: 1), (_) {
|
|
updateMarker();
|
|
});
|
|
}
|
|
|
|
void changeStatusDriver() {
|
|
status = 'On';
|
|
update();
|
|
}
|
|
|
|
void changeDriverEndPage() {
|
|
remainingTimeTimerRideBegin < 60 ? driverEndPage = 160 : 100;
|
|
update();
|
|
}
|
|
|
|
takeSnapMap() {
|
|
mapController!.takeSnapshot();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
mapController!.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future openGoogleMapFromDriverToPassenger() async {
|
|
var endLat = latLngPassengerLocation.latitude;
|
|
var endLng = latLngPassengerLocation.longitude;
|
|
|
|
var startLat = Get.find<LocationController>().myLocation.latitude;
|
|
var startLng = Get.find<LocationController>().myLocation.longitude;
|
|
|
|
String url =
|
|
'https://www.google.com/maps/dir/$startLat,$startLng/$endLat,$endLng/&directionsmode=driving';
|
|
if (await canLaunchUrl(Uri.parse(url))) {
|
|
await launchUrl(Uri.parse(url));
|
|
} else {
|
|
throw 'Could not launch google maps';
|
|
}
|
|
}
|
|
|
|
void clearPolyline() {
|
|
polyLines = [];
|
|
polyLinesDestination = [];
|
|
polylineCoordinates.clear();
|
|
polylineCoordinatesDestination.clear();
|
|
update();
|
|
}
|
|
|
|
void changeRideToBeginToPassenger() {
|
|
isRideBegin = true;
|
|
passengerInfoWindowHeight = Get.height * .22;
|
|
update();
|
|
}
|
|
|
|
cancelTripFromDriverAfterApplied() async {
|
|
if (formKeyCancel.currentState!.validate()) {
|
|
box.write(BoxName.statusDriverLocation, 'off');
|
|
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
|
|
"Cancel Trip from driver",
|
|
"Trip Cancelled from driver. We are looking for a new driver. Please wait."
|
|
.tr,
|
|
tokenPassenger,
|
|
[],
|
|
'cancel.wav',
|
|
);
|
|
await CRUD().post(
|
|
link: "${AppLink.seferCairoServer}/ride/rides/update.php",
|
|
payload: {
|
|
"id": (rideId).toString(), // Convert to String
|
|
"status": 'CancelFromDriverAfterApply'
|
|
});
|
|
CRUD().postFromDialogue(
|
|
link: '${AppLink.seferCairoServer}/driver_order/add.php',
|
|
payload: {
|
|
'driver_id': box.read(BoxName.driverID).toString(),
|
|
// box.read(BoxName.driverID).toString(),
|
|
'order_id': (rideId).toString(),
|
|
'status': 'CancelFromDriverAfterApply'
|
|
});
|
|
await CRUD().post(
|
|
link:
|
|
"${AppLink.seferCairoServer}/ride/cancelRide/addCancelTripFromDriverAfterApplied.php",
|
|
payload: {
|
|
"order_id": (rideId).toString(),
|
|
"driver_id": box.read(BoxName.driverID).toString(),
|
|
"status": 'reject After Applied',
|
|
"notes": (cancelTripCotroller.text).toString()
|
|
});
|
|
if (AppLink.endPoint != AppLink.seferCairoServer) {
|
|
CRUD()
|
|
.post(link: "${AppLink.endPoint}/ride/rides/update.php", payload: {
|
|
"id": (rideId).toString(), // Convert to String
|
|
"status": 'CancelFromDriverAfterApply'
|
|
});
|
|
CRUD().postFromDialogue(
|
|
link: '${AppLink.endPoint}/driver_order/add.php',
|
|
payload: {
|
|
'driver_id': box.read(BoxName.driverID).toString(),
|
|
// box.read(BoxName.driverID).toString(),
|
|
'order_id': (rideId).toString(),
|
|
'status': 'CancelFromDriverAfterApply'
|
|
});
|
|
CRUD().post(
|
|
link:
|
|
"${AppLink.endPoint}/ride/cancelRide/addCancelTripFromDriverAfterApplied.php",
|
|
payload: {
|
|
"order_id": (rideId).toString(),
|
|
"driver_id": box.read(BoxName.driverID).toString(),
|
|
"status": 'reject After Applied',
|
|
"notes": (cancelTripCotroller.text).toString()
|
|
});
|
|
}
|
|
|
|
sql.insertData({
|
|
'order_id': (rideId),
|
|
'created_at': DateTime.now().toString(),
|
|
'driver_id': box.read(BoxName.driverID).toString(),
|
|
}, TableName.driverOrdersRefuse);
|
|
box.write(BoxName.rideStatus, 'Cancel');
|
|
Log.print('rideStatus from map 240 : ${box.read(BoxName.rideStatus)}');
|
|
Get.find<HomeCaptainController>().getRefusedOrderByCaptain();
|
|
Get.offAll(() => HomeCaptain());
|
|
}
|
|
}
|
|
|
|
void startTimerToShowPassengerInfoWindowFromDriver() async {
|
|
if (box.read(BoxName.rideStatus) == 'Begin') {
|
|
Log.print('rideStatus from map 248 : ${box.read(BoxName.rideStatus)}');
|
|
isPassengerInfoWindow = false;
|
|
} else {
|
|
isPassengerInfoWindow = true;
|
|
for (int i = 0; i <= int.parse(durationToPassenger); i++) {
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
progressToPassenger = i / int.parse(durationToPassenger);
|
|
remainingTimeToPassenger = int.parse(durationToPassenger) - i;
|
|
if (remainingTimeToPassenger == 0) {
|
|
isBtnRideBegin = true;
|
|
update();
|
|
}
|
|
|
|
int minutes = (remainingTimeToPassenger / 60).floor();
|
|
int seconds = remainingTimeToPassenger % 60;
|
|
stringRemainingTimeToPassenger =
|
|
'$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
|
|
update();
|
|
}
|
|
}
|
|
|
|
// update();
|
|
// startTimerToShowDriverToPassengerDuration();
|
|
}
|
|
|
|
String stringRemainingTimeToPassenger = '';
|
|
|
|
String stringRemainingTimeWaitingPassenger = '';
|
|
|
|
void startTimerToShowDriverWaitPassengerDuration() async {
|
|
for (int i = 0; i <= timeWaitingPassenger * 60; i++) {
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
progressInPassengerLocationFromDriver = i / (timeWaitingPassenger * 60);
|
|
remainingTimeInPassengerLocatioWait = (timeWaitingPassenger * 60) - i;
|
|
if (isRideBegin == true) {
|
|
remainingTimeInPassengerLocatioWait = 0;
|
|
update();
|
|
}
|
|
if (remainingTimeInPassengerLocatioWait == 0) {
|
|
isdriverWaitTimeEnd = true;
|
|
update();
|
|
}
|
|
'remainingTimeInPassengerLocatioWait $remainingTimeInPassengerLocatioWait';
|
|
|
|
int minutes = (remainingTimeInPassengerLocatioWait / 60).floor();
|
|
int seconds = remainingTimeInPassengerLocatioWait % 60;
|
|
stringRemainingTimeWaitingPassenger =
|
|
'$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
|
|
update();
|
|
}
|
|
}
|
|
|
|
void driverGoToPassenger() async {
|
|
changeRideToBeginToPassenger();
|
|
box.write(BoxName.rideStatus, 'Applied');
|
|
Log.print('rideStatus from map 304 : ${box.read(BoxName.rideStatus)}');
|
|
update();
|
|
await CRUD().post(
|
|
link: "${AppLink.seferCairoServer}/ride/rides/update.php",
|
|
payload: {
|
|
'id': (rideId),
|
|
'driverGoToPassengerTime': DateTime.now().toString(),
|
|
'status': 'Applied'
|
|
});
|
|
if (AppLink.endPoint != AppLink.seferCairoServer) {
|
|
CRUD().post(link: "${AppLink.endPoint}/ride/rides/update.php", payload: {
|
|
'id': (rideId),
|
|
'driverGoToPassengerTime': DateTime.now().toString(),
|
|
'status': 'Applied'
|
|
});
|
|
}
|
|
// Get.find<HomeCaptainController>().changeToAppliedRide('Applied');
|
|
|
|
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
|
|
'Driver Is Going To Passenger'.tr,
|
|
box.read(BoxName.nameDriver).toString(), //todo name driver
|
|
tokenPassenger,
|
|
[],
|
|
'start.wav');
|
|
}
|
|
|
|
bool isSocialPressed = false;
|
|
driverCallPassenger() async {
|
|
String scam = await getDriverScam();
|
|
if (scam != 'failure') {
|
|
if (int.parse(scam) > 3) {
|
|
box.write(BoxName.statusDriverLocation, 'on');
|
|
Get.find<LocationController>().stopLocationUpdates();
|
|
await CRUD().post(link: AppLink.addNotificationCaptain, payload: {
|
|
'driverID': box.read(BoxName.driverID),
|
|
'title': 'scams operations'.tr,
|
|
'body':
|
|
'you have connect to passengers and let them cancel the order'.tr,
|
|
});
|
|
} else if (isSocialPressed == true) {
|
|
box.write(BoxName.statusDriverLocation, 'off');
|
|
await CRUD().post(link: AppLink.addDriverScam, payload: {
|
|
'driverID': box.read(BoxName.driverID),
|
|
'passengerID': passengerId,
|
|
'rideID': rideId,
|
|
'isDriverCalledPassenger': '$isSocialPressed'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<String> getDriverScam() async {
|
|
var res = await CRUD().post(link: AppLink.getDriverScam, payload: {
|
|
'driverID': box.read(BoxName.driverID),
|
|
});
|
|
if (res == 'failure') {
|
|
box.write(BoxName.statusDriverLocation, 'off');
|
|
return '0';
|
|
}
|
|
var d = jsonDecode(res);
|
|
return d['message'][0]['count'];
|
|
}
|
|
|
|
void startRideFromStartApp() {
|
|
// if (box.read(BoxName.rideStatus) == 'Begin') {
|
|
changeRideToBeginToPassenger();
|
|
isPassengerInfoWindow = false;
|
|
isRideStarted = true;
|
|
isRideFinished = false;
|
|
remainingTimeInPassengerLocatioWait = 0;
|
|
timeWaitingPassenger = 0;
|
|
box.write(BoxName.statusDriverLocation, 'on');
|
|
update();
|
|
// }
|
|
|
|
// rideIsBeginPassengerTimer();
|
|
}
|
|
|
|
Position? currentPosition;
|
|
startRideFromDriver() async {
|
|
double _distance =
|
|
await calculateDistanceBetweenDriverAndPassengerLocation();
|
|
|
|
if (_distance < 400) {
|
|
changeRideToBeginToPassenger();
|
|
isPassengerInfoWindow = false;
|
|
isRideStarted = true;
|
|
isRideFinished = false;
|
|
remainingTimeInPassengerLocatioWait = 0;
|
|
timeWaitingPassenger = 0;
|
|
box.write(BoxName.statusDriverLocation, 'on');
|
|
|
|
// // ابدأ التتبع الحيّ لخطوات الملاحة
|
|
await startListeningStepNavigation();
|
|
// box.write(BoxName.rideStatus, 'Begin'); //
|
|
// todo ride details
|
|
// Get.find<HomeCaptainController>().changeToAppliedRide('Begin');
|
|
box.write(BoxName.rideStatus, 'Begin');
|
|
Log.print('rideStatus from map 399 : ${box.read(BoxName.rideStatus)}');
|
|
// Get.find<HomeCaptainController>().update();
|
|
update();
|
|
await CRUD().post(link: AppLink.updateRides, payload: {
|
|
'id': (rideId),
|
|
'rideTimeStart': DateTime.now().toString(),
|
|
'status': 'Begin',
|
|
});
|
|
CRUD().post(link: AppLink.addDriverOrder, payload: {
|
|
'driver_id': box.read(BoxName.driverID).toString(),
|
|
'order_id': (rideId).toString(),
|
|
'status': 'Begin'
|
|
});
|
|
|
|
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
|
|
'Trip is Begin'.tr,
|
|
box.read(BoxName.nameDriver).toString(),
|
|
tokenPassenger,
|
|
[],
|
|
'start.wav');
|
|
rideIsBeginPassengerTimer();
|
|
|
|
update();
|
|
} else {
|
|
Get.back();
|
|
MyDialog().getDialog('Your are far from passenger location'.tr,
|
|
'go to your passenger location before\nPassenger cancel trip'.tr, () {
|
|
Get.back();
|
|
});
|
|
}
|
|
}
|
|
|
|
calculateDistanceInMeter(LatLng prev, LatLng current) async {
|
|
double distance2 = Geolocator.distanceBetween(
|
|
prev.latitude,
|
|
prev.longitude,
|
|
current.latitude,
|
|
current.longitude,
|
|
);
|
|
return distance2;
|
|
}
|
|
|
|
double speedoMeter = 0;
|
|
void updateLocation() async {
|
|
try {
|
|
for (var i = 0; i < remainingTimeTimerRideBegin; i++) {
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
|
|
mapController!.animateCamera(
|
|
CameraUpdate.newCameraPosition(
|
|
CameraPosition(
|
|
bearing: Get.find<LocationController>().heading,
|
|
target: myLocation,
|
|
zoom: 17, // Adjust zoom level as needed
|
|
),
|
|
),
|
|
);
|
|
// });
|
|
update();
|
|
}
|
|
|
|
// Stop listening after ride finishes
|
|
if (!isRideBegin) {}
|
|
} catch (error) {
|
|
debugPrint('Error listening to GPS: $error');
|
|
// Handle GPS errors gracefully
|
|
}
|
|
|
|
// Periodically call updateLocation again
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
updateLocation();
|
|
}
|
|
|
|
calculateDistanceBetweenDriverAndPassengerLocation() async {
|
|
Get.put(LocationController());
|
|
var res = await CRUD().get(
|
|
link: AppLink.getLatestLocationPassenger,
|
|
payload: {'rideId': (rideId)});
|
|
if (res != 'failure') {
|
|
var passengerLatestLocationString = jsonDecode(res)['message'];
|
|
|
|
double distance2 = Geolocator.distanceBetween(
|
|
double.parse(passengerLatestLocationString[0]['lat'].toString()),
|
|
double.parse(passengerLatestLocationString[0]['lng'].toString()),
|
|
Get.find<LocationController>().myLocation.latitude,
|
|
Get.find<LocationController>().myLocation.longitude,
|
|
);
|
|
return distance2;
|
|
} else {
|
|
double distance2 = Geolocator.distanceBetween(
|
|
latLngPassengerLocation.latitude,
|
|
latLngPassengerLocation.longitude,
|
|
Get.find<LocationController>().myLocation.latitude,
|
|
Get.find<LocationController>().myLocation.longitude,
|
|
);
|
|
return distance2;
|
|
}
|
|
}
|
|
|
|
addWaitingTimeCostFromPassengerToDriverWallet() async {
|
|
double distance2 =
|
|
await calculateDistanceBetweenDriverAndPassengerLocation();
|
|
|
|
if (distance2 > 60) {
|
|
MyDialog().getDialog('Your are far from passenger location'.tr,
|
|
'go to your passenger location before\nPassenger cancel trip'.tr, () {
|
|
Get.back();
|
|
});
|
|
} else {
|
|
double costOfWaiting5Minute = box.read(BoxName.countryCode) == 'Egypt'
|
|
? (distanceBetweenDriverAndPassengerWhenConfirm * .08) + (5 * 1)
|
|
: (distanceBetweenDriverAndPassengerWhenConfirm * .06) +
|
|
(5 * .06); //for Eygpt other like jordan .06 per minute
|
|
await CRUD().post(link: AppLink.updateRides, payload: {
|
|
'id': (rideId),
|
|
'rideTimeStart': DateTime.now().toString(),
|
|
'status': 'CancelAfterWait',
|
|
});
|
|
CRUD().post(link: AppLink.addDriverOrder, payload: {
|
|
'driver_id': box.read(BoxName.driverID).toString(),
|
|
'order_id': (rideId).toString(),
|
|
'status': 'CancelAfterWait'
|
|
});
|
|
|
|
if (AppLink.endPoint != AppLink.seferCairoServer) {
|
|
CRUD().post(link: "${AppLink.endPoint}/rides/update.php", payload: {
|
|
'id': (rideId),
|
|
'rideTimeStart': DateTime.now().toString(),
|
|
'status': 'CancelAfterWait',
|
|
});
|
|
CRUD().post(link: "${AppLink.endPoint}/rides/update.php", payload: {
|
|
'driver_id': box.read(BoxName.driverID).toString(),
|
|
'order_id': (rideId).toString(),
|
|
'status': 'CancelAfterWait'
|
|
});
|
|
}
|
|
var paymentTokenWait =
|
|
await generateTokenDriver(costOfWaiting5Minute.toString());
|
|
var res = await CRUD().post(link: AppLink.addDrivePayment, payload: {
|
|
'rideId': (rideId),
|
|
'amount': (costOfWaiting5Minute.toString()),
|
|
'payment_method': 'wait-cancel',
|
|
'passengerID': (passengerId),
|
|
'token': paymentTokenWait,
|
|
'driverID': box.read(BoxName.driverID).toString(),
|
|
});
|
|
var paymentTokenWait1 =
|
|
await generateTokenDriver(costOfWaiting5Minute.toString());
|
|
var res1 =
|
|
await CRUD().post(link: AppLink.addDriversWalletPoints, payload: {
|
|
'paymentID': 'rideId${(rideId)}',
|
|
'amount': (costOfWaiting5Minute).toStringAsFixed(0),
|
|
'paymentMethod': 'wait',
|
|
'token': paymentTokenWait1,
|
|
'driverID': box.read(BoxName.driverID).toString(),
|
|
});
|
|
if (res != 'failure') {
|
|
Get.snackbar(
|
|
'You will get cost of your work for this trip'.tr,
|
|
'${'you gain'.tr} $costOfWaiting5Minute \$${' in your wallet'.tr}',
|
|
backgroundColor: AppColor.deepPurpleAccent,
|
|
);
|
|
}
|
|
var paymentTokenWaitPassenger1 =
|
|
await generateTokenPassenger((costOfWaiting5Minute * -1).toString());
|
|
await CRUD().post(link: AppLink.addPassengersWallet, payload: {
|
|
'passenger_id': (passengerId),
|
|
'balance': (costOfWaiting5Minute * -1).toString(),
|
|
'token': paymentTokenWaitPassenger1,
|
|
});
|
|
box.write(BoxName.statusDriverLocation, 'off');
|
|
Get.offAll(HomeCaptain());
|
|
}
|
|
}
|
|
|
|
Future<void> finishRideFromDriver() async {
|
|
// double distanceToDestination = Geolocator.distanceBetween(
|
|
// latLngPassengerDestination.latitude,
|
|
// latLngPassengerDestination.longitude,
|
|
// Get.find<LocationController>().myLocation.latitude,
|
|
// Get.find<LocationController>().myLocation.longitude,
|
|
// );
|
|
final originalDistanceM = double.parse(distance.toString()) * 1000;
|
|
|
|
// 2. احسب المسافة التي قطعها السائق حتى الآن
|
|
final movedDistanceM = Geolocator.distanceBetween(
|
|
Get.find<LocationController>().myLocation.latitude,
|
|
Get.find<LocationController>().myLocation.longitude,
|
|
latLngPassengerLocation.latitude,
|
|
latLngPassengerLocation.longitude,
|
|
);
|
|
// originalDistanceM - distanceToDestination;
|
|
|
|
// 3. عتبة ثلث المسافة
|
|
final oneThirdDistanceM = originalDistanceM / 5;
|
|
|
|
// Logging للتتبع
|
|
Log.print('originalDistanceM: $originalDistanceM');
|
|
// Log.print('distanceToDestinationM: $distanceToDestination');
|
|
Log.print('movedDistanceM: $movedDistanceM');
|
|
Log.print('oneThirdDistanceM: $oneThirdDistanceM');
|
|
|
|
// 4. إذا لم يقطع السائق ثلث المسافة، نعرض التأكيد
|
|
if (movedDistanceM > oneThirdDistanceM) {
|
|
MyDialog().getDialog(
|
|
'Are you sure to exit ride?'.tr,
|
|
'',
|
|
() {
|
|
Get.back();
|
|
finishRideFromDriver1();
|
|
},
|
|
);
|
|
} /////
|
|
|
|
else {
|
|
final textToSpeechController = Get.put(TextToSpeechController());
|
|
MyDialog().getDialog(
|
|
"You haven't moved sufficiently!".tr,
|
|
'',
|
|
() => Get.back(),
|
|
);
|
|
await textToSpeechController
|
|
.speakText("You haven't moved sufficiently!".tr);
|
|
}
|
|
}
|
|
|
|
String paymentToken = '';
|
|
Future<String> generateTokenDriver(String amount) async {
|
|
var res =
|
|
await CRUD().postWallet(link: AppLink.addPaymentTokenDriver, payload: {
|
|
'driverID': box.read(BoxName.driverID).toString(),
|
|
'amount': amount.toString(),
|
|
});
|
|
var d = (res);
|
|
return d['message'];
|
|
}
|
|
|
|
String paymentTokenPassenger = '';
|
|
Future<String> generateTokenPassenger(String amount) async {
|
|
var res = await CRUD()
|
|
.postWallet(link: AppLink.addPaymentTokenPassenger, payload: {
|
|
'passengerId': passengerId,
|
|
'amount': amount.toString(),
|
|
});
|
|
var d = (res);
|
|
return d['message'];
|
|
}
|
|
|
|
void finishRideFromDriver1() async {
|
|
isRideFinished = true;
|
|
isRideStarted = false;
|
|
isPriceWindow = false;
|
|
box.write(BoxName.rideStatus, 'Finished');
|
|
Log.print('rideStatus from map 664 : ${box.read(BoxName.rideStatus)}');
|
|
|
|
// Calculate totalCost more concisely
|
|
if (price < 16000) {
|
|
totalCost = (carType == 'Comfort' ||
|
|
carType == 'Mishwar Vip' ||
|
|
carType == 'Lady')
|
|
? '20000'
|
|
: '16000';
|
|
} else if (price < double.parse(totalPricePassenger)) {
|
|
totalCost = totalPricePassenger;
|
|
} else {
|
|
totalCost = (carType == 'Comfort' ||
|
|
carType == 'Mishwar Vip' ||
|
|
carType == 'Lady')
|
|
? price.toStringAsFixed(2)
|
|
: totalPricePassenger;
|
|
}
|
|
paymentAmount = totalCost;
|
|
box.write(BoxName.statusDriverLocation, 'off');
|
|
|
|
// Prepare data for API calls
|
|
final nowString = DateTime.now().toString();
|
|
final basePayload = {
|
|
'id': (rideId),
|
|
'rideTimeFinish': nowString,
|
|
'status': 'Finished',
|
|
'price': totalCost,
|
|
};
|
|
final driverOrderPayload = {
|
|
'order_id': (rideId.toString()),
|
|
'status': 'Finished'
|
|
};
|
|
|
|
// List to hold all asynchronous operations
|
|
List<Future<dynamic>> futures = [];
|
|
|
|
// API calls that can run in parallel
|
|
futures.add(CRUD().post(
|
|
link: "${AppLink.seferCairoServer}/ride/rides/update.php",
|
|
payload: basePayload,
|
|
));
|
|
futures.add(CRUD().post(
|
|
link: "${AppLink.seferCairoServer}/ride/driver_order/update.php",
|
|
payload: driverOrderPayload,
|
|
));
|
|
|
|
// Wallet transactions (can potentially be parallelized if independent)
|
|
if (walletChecked == 'true') {
|
|
paymentToken = await generateTokenPassenger(
|
|
((-1) * double.parse(paymentAmount)).toString());
|
|
futures
|
|
.add(CRUD().postWallet(link: AppLink.addPassengersWallet, payload: {
|
|
'passenger_id': (passengerId),
|
|
'balance': ((-1) * double.parse(paymentAmount)).toString(),
|
|
'token': paymentToken,
|
|
}));
|
|
}
|
|
|
|
paymentToken = await generateTokenDriver(paymentAmount.toString());
|
|
futures.add(CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
|
|
'rideId': (rideId),
|
|
'amount': paymentAmount,
|
|
'payment_method':
|
|
walletChecked == 'true' ? "${paymentMethod}Ride" : paymentMethod,
|
|
'passengerID': (passengerId),
|
|
'token': paymentToken,
|
|
'driverID': box.read(BoxName.driverID).toString(),
|
|
}));
|
|
|
|
if (double.parse(passengerWalletBurc) < 0) {
|
|
final paymentToken1 = await generateTokenPassenger(
|
|
((-1) * double.parse(passengerWalletBurc)).toString());
|
|
futures
|
|
.add(CRUD().postWallet(link: AppLink.addPassengersWallet, payload: {
|
|
'passenger_id': (passengerId),
|
|
'token': paymentToken1,
|
|
'balance': ((-1) * double.parse(passengerWalletBurc)).toString()
|
|
}));
|
|
}
|
|
|
|
double pointsSubtraction = double.parse(paymentAmount) * (-1) * 0.1;
|
|
final paymentToken2 =
|
|
await generateTokenDriver((pointsSubtraction).toStringAsFixed(0));
|
|
futures
|
|
.add(CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: {
|
|
'paymentID': 'rideId${(rideId)}',
|
|
'amount': (pointsSubtraction).toStringAsFixed(0),
|
|
'paymentMethod': paymentMethod,
|
|
'token': paymentToken2,
|
|
'driverID': box.read(BoxName.driverID).toString(),
|
|
}));
|
|
|
|
// Wait for all independent API calls to complete
|
|
await Future.wait(futures);
|
|
Get.put(DriverBehaviorController()).sendSummaryToServer(driverId, rideId);
|
|
|
|
// Send notification (this likely depends on previous steps)
|
|
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
|
|
"Driver Finish Trip".tr,
|
|
'${'you will pay to Driver'.tr} $paymentAmount \$',
|
|
tokenPassenger,
|
|
[
|
|
box.read(BoxName.driverID),
|
|
rideId,
|
|
box.read(BoxName.tokenDriver),
|
|
paymentAmount.toString()
|
|
],
|
|
'ding.wav');
|
|
|
|
// Navigate to the next screen (likely depends on previous steps being done)
|
|
Get.to(() => RatePassenger(), arguments: {
|
|
'passengerId': passengerId,
|
|
'rideId': rideId,
|
|
'price': paymentAmount.toString(),
|
|
'walletChecked': walletChecked
|
|
});
|
|
}
|
|
// void finishRideFromDriver1() async {
|
|
// // if (carType != 'Comfort' || carType != 'Free Ride') {
|
|
|
|
// isRideFinished = true;
|
|
// isRideStarted = false;
|
|
// isPriceWindow = false;
|
|
// box.write(BoxName.rideStatus, 'Finished');
|
|
// // Get.find<HomeCaptainController>().changeToAppliedRide('Finished');
|
|
// // Get.find<HomeCaptainController>().update();
|
|
// totalCost = price < 20
|
|
// ? carType != 'Comfort' && carType != 'Mishwar Vip' && carType != 'Lady'
|
|
// ? '20'
|
|
// : '30'
|
|
// : price < double.parse(totalPricePassenger)
|
|
// ? totalPricePassenger
|
|
// : carType != 'Comfort' &&
|
|
// carType != 'Mishwar Vip' &&
|
|
// carType != 'Lady'
|
|
// ? totalPricePassenger
|
|
// : price.toStringAsFixed(2);
|
|
// paymentAmount = totalCost;
|
|
// box.write(BoxName.statusDriverLocation, 'off');
|
|
// // changeRideToBeginToPassenger();
|
|
// await CRUD().post(
|
|
// link: "${AppLink.seferCairoServer}/ride/rides/update.php",
|
|
// payload: {
|
|
// 'id': rideId,
|
|
// 'rideTimeFinish': DateTime.now().toString(),
|
|
// 'status': 'Finished',
|
|
// 'price': totalCost,
|
|
// });
|
|
// CRUD().post(
|
|
// link: "${AppLink.seferCairoServer}/ride/driver_order/update.php",
|
|
// payload: {
|
|
// // 'driver_id': box.read(BoxName.driverID).toString(),
|
|
// 'order_id': rideId.toString(),
|
|
// 'status': 'Finished'
|
|
// });
|
|
// if (AppLink.endPoint != AppLink.seferCairoServer) {
|
|
// CRUD().post(
|
|
// link: "${AppLink.endPoint}/ride/rides/update.php",
|
|
// payload: {
|
|
// 'id': rideId,
|
|
// 'rideTimeFinish': DateTime.now().toString(),
|
|
// 'status': 'Finished',
|
|
// 'price': totalCost,
|
|
// },
|
|
// );
|
|
// CRUD().post(
|
|
// link: "${AppLink.endPoint}/ride/driver_order/update.php",
|
|
// payload: {
|
|
// // 'driver_id': box.read(BoxName.driverID).toString(),
|
|
// 'order_id': rideId.toString(),
|
|
// 'status': 'Finished'
|
|
// });
|
|
// }
|
|
// if (walletChecked == 'true') {
|
|
// paymentToken = await generateTokenPassenger(
|
|
// ((-1) * double.parse(paymentAmount)).toString());
|
|
// await CRUD().post(link: AppLink.addPassengersWallet, payload: {
|
|
// 'passenger_id': passengerId,
|
|
// 'balance': ((-1) * double.parse(paymentAmount)).toString(),
|
|
// 'token': paymentToken,
|
|
// });
|
|
// }
|
|
// paymentToken = await generateTokenDriver(paymentAmount.toString());
|
|
// await CRUD().post(link: AppLink.addDrivePayment, payload: {
|
|
// 'rideId': rideId,
|
|
// 'amount': paymentAmount,
|
|
// 'payment_method':
|
|
// walletChecked == 'true' ? "${paymentMethod}Ride" : paymentMethod,
|
|
// 'passengerID': passengerId,
|
|
// 'token': paymentToken,
|
|
// 'driverID': box.read(BoxName.driverID).toString(),
|
|
// });
|
|
// if (double.parse(passengerWalletBurc) < 0) {
|
|
// // for zero passenger
|
|
// var paymentToken1 = await generateTokenPassenger(
|
|
// ((-1) * double.parse(passengerWalletBurc)).toString());
|
|
// await CRUD().post(link: AppLink.addPassengersWallet, payload: {
|
|
// 'passenger_id': passengerId,
|
|
// 'token': paymentToken1,
|
|
// 'balance': ((-1) * double.parse(passengerWalletBurc)).toString()
|
|
// });
|
|
// }
|
|
|
|
// double pointsSubtraction = 0;
|
|
// pointsSubtraction =
|
|
// double.parse(paymentAmount) * (-1) * .08; //for 300 from 3000
|
|
// var paymentToken2 =
|
|
// await generateTokenDriver((pointsSubtraction).toStringAsFixed(0));
|
|
// var res = await CRUD().post(link: AppLink.addDriversWalletPoints, payload: {
|
|
// 'paymentID': 'rideId$rideId',
|
|
// 'amount': (pointsSubtraction).toStringAsFixed(0),
|
|
// 'paymentMethod': paymentMethod,
|
|
// 'token': paymentToken2,
|
|
// 'driverID': box.read(BoxName.driverID).toString(),
|
|
// });
|
|
// Future.delayed(const Duration(milliseconds: 300));
|
|
// Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
|
|
// "Driver Finish Trip".tr,
|
|
// '${'you will pay to Driver'.tr} $paymentAmount \$',
|
|
// tokenPassenger,
|
|
// [
|
|
// box.read(BoxName.driverID),
|
|
// rideId,
|
|
// box.read(BoxName.tokenDriver),
|
|
// // carType == 'Comfort' || carType == 'Mishwar Vip'
|
|
// // ? price.toStringAsFixed(2)
|
|
// // : totalPassenger
|
|
// paymentAmount.toString()
|
|
// ],
|
|
// 'ding.wav');
|
|
// Get.to(() => RatePassenger(), arguments: {
|
|
// 'passengerId': passengerId,
|
|
// 'rideId': rideId,
|
|
// 'price': paymentAmount.toString(), //price
|
|
// 'walletChecked': walletChecked
|
|
// });
|
|
// // Get.back();
|
|
// }
|
|
|
|
void cancelCheckRideFromPassenger() async {
|
|
var res = await CRUD().get(
|
|
link: "${AppLink.endPoint}/ride/driver_order/getOrderCancelStatus.php",
|
|
payload: {
|
|
'order_id': (rideId),
|
|
}); //.then((value) {
|
|
var response = jsonDecode(res);
|
|
canelString = response['data']['status'];
|
|
update();
|
|
if (canelString == 'Cancel') {
|
|
remainingTimeTimerRideBegin = 0;
|
|
remainingTimeToShowPassengerInfoWindowFromDriver = 0;
|
|
remainingTimeToPassenger = 0;
|
|
isRideStarted = false;
|
|
isRideFinished = false;
|
|
isPassengerInfoWindow = false;
|
|
clearPolyline();
|
|
update();
|
|
MyDialog().getDialog(
|
|
'Order Cancelled'.tr,
|
|
'Order Cancelled by Passenger'.tr,
|
|
() {
|
|
Get.offAll(HomeCaptain());
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
int rideTimerFromBegin = 0;
|
|
double price = 0;
|
|
DateTime currentTime = DateTime.now();
|
|
|
|
/// أثناء الرحلة: نعرض السعر لحظياً بدون مضاعفة العمولة في كل ثانية.
|
|
/// - نستخدم سعر الدقيقة حسب الوقت، مع قواعد الرحلات البعيدة:
|
|
/// >25كم أو >35كم => دقيقة = 600، سقف 60 دقيقة، ومع >35كم عفو 10 دقائق.
|
|
/// - سرعة طويلة: لو المسافة المخططة > 40كم نستخدم 2600 ل.س/كم للـ Speed،
|
|
//// ونطبق نفس نسبة التخفيض على Comfort/Electric/Van.
|
|
/// - نضيف فقط "الزيادة" فوق التسعيرة المقتبسة (وقت زائد + كم زائد).
|
|
/// - نعكس العمولة kazán مرة واحدة على الزيادة (وليس كل ثانية).
|
|
|
|
void rideIsBeginPassengerTimer() async {
|
|
// === مراجع عامة ===
|
|
final hc = Get.find<HomeCaptainController>();
|
|
final loc = Get.find<LocationController>();
|
|
|
|
// أسعار/كم من السيرفر
|
|
final double perKmSpeedBase = hc.speedPrice; // مثال: 2900
|
|
final double perKmComfortRaw = hc.comfortPrice; // مثال: 3600
|
|
final double perKmDelivery = hc.deliveryPrice; // للدليفري
|
|
final double perKmVanRaw = hc.familyPrice; // فان
|
|
const double electricUpliftKm = 400; // كهرباء = كومفورت + 400/كم
|
|
final double perKmElectricRaw = perKmComfortRaw + electricUpliftKm;
|
|
|
|
// أسعار الدقيقة (ل.س/دقيقة)
|
|
final double perMinNature = hc.naturePrice; // طبيعي
|
|
final double perMinLate = hc.latePrice; // ليل
|
|
final double perMinHeavy = hc.heavyPrice; // ذروة
|
|
|
|
// Long/بعيد
|
|
const double longSpeedThresholdKm = 40.0;
|
|
const double longSpeedPerKm = 2600.0;
|
|
|
|
const double mediumDistThresholdKm = 25.0; // >25كم
|
|
const double longDistThresholdKm = 35.0; // >35كم
|
|
const double longTripPerMin = 600.0;
|
|
const int minuteCapMedium = 60; // سقف 60 دقيقة
|
|
const int minuteCapLong = 60; // سقف 60 دقيقة
|
|
const int freeMinutesLong = 10; // عفو 10 دقائق عند >35كم
|
|
|
|
const double extraReduction100 = 0.07; // +7% فوق تخفيض >40كم عند >100كم
|
|
const double maxReductionCap = 0.35; // سقف إجمالي للتخفيض
|
|
|
|
// ——— الأساسات المقتبسة من شاشة التسعير ———
|
|
final double basePassengerQuote = double.tryParse(totalCost) ?? 0.0;
|
|
|
|
// الدقائق المقتبسة (من نفس المصدر الذي تستخدمه مسبقًا)
|
|
final int quotedMinutes =
|
|
int.tryParse(duration) ?? int.tryParse(durationOfRideValue) ?? 0;
|
|
|
|
// ——— المسافة المخططة من Notification/OnInit ———
|
|
// حاول أولًا من متغيّر الرحلة (تمريره عند onInit من النوتيفيكيشن):
|
|
double plannedKm = 0.0;
|
|
try {
|
|
// أمثلة أسماء: غيّر وفق ما عندك
|
|
// 1) إن كانت محفوظة في هذا الكنترولر: this.plannedDistanceKm
|
|
plannedKm = (int.parse(distance) as num).toDouble();
|
|
} catch (_) {}
|
|
final double startKm = loc.totalDistance;
|
|
if (plannedKm <= 0) {
|
|
// fallback ثالث: خذ المسافة الحيّة وقت البدء كأساس
|
|
plannedKm = (startKm > 0) ? startKm : 0.0;
|
|
}
|
|
|
|
// ——— سياق المطار (لا نضيف 20,000 هنا مرة ثانية) ———
|
|
bool _isAirport(String s) =>
|
|
s.toLowerCase().contains('airport') ||
|
|
s.contains('مطار') ||
|
|
s.contains('المطار');
|
|
final bool airportCtx =
|
|
_isAirport(startNameLocation) || _isAirport(endNameLocation);
|
|
|
|
// عمولة الراكب (kazan ٪)
|
|
final double kazanPct = (double.tryParse(kazan) ?? 0.0) / 100.0;
|
|
|
|
// ——— أدوات ———
|
|
double _perMinuteByTime(DateTime now) {
|
|
final h = now.hour;
|
|
if (airportCtx) return perMinLate; // مطار = دقيقة ليل (مثل منطقك القديم)
|
|
if (h >= 21 || h < 1) return perMinLate; // ليل
|
|
if (h >= 14 && h <= 17) return perMinHeavy; // ذروة
|
|
return perMinNature; // طبيعي
|
|
}
|
|
|
|
bool _isLongSpeed(double km) => km > longSpeedThresholdKm;
|
|
|
|
double _distanceReductionPct(double km) {
|
|
double r40 = 0.0;
|
|
if (perKmSpeedBase > 0) {
|
|
r40 = (1.0 - (longSpeedPerKm / perKmSpeedBase))
|
|
.clamp(0.0, maxReductionCap);
|
|
}
|
|
if (km > 100.0)
|
|
return (r40 + extraReduction100).clamp(0.0, maxReductionCap);
|
|
if (km > 40.0) return r40;
|
|
return 0.0;
|
|
}
|
|
|
|
// فلترة اهتزاز GPS
|
|
const double jitterMeters = 10.0;
|
|
double lastKmForNoise = startKm;
|
|
|
|
// تحديث كل 5 ثواني
|
|
final int loopCapSec = (quotedMinutes + 3600) * 60; // أمان
|
|
|
|
for (int sec = 0; sec <= loopCapSec; sec += 5) {
|
|
await Future.delayed(const Duration(seconds: 5));
|
|
final now = DateTime.now();
|
|
|
|
// كم حي (مع فلترة الاهتزاز)
|
|
double liveKm = loc.totalDistance;
|
|
final double deltaMeters = (liveKm - lastKmForNoise) * 1000.0;
|
|
if (deltaMeters < jitterMeters) {
|
|
liveKm = lastKmForNoise;
|
|
} else {
|
|
lastKmForNoise = liveKm;
|
|
}
|
|
|
|
// كم معتمد لقواعد التسعير
|
|
final double plannedForRulesKm = (plannedKm > 0) ? plannedKm : liveKm;
|
|
|
|
// LongSpeed لسعر/كم Speed
|
|
final bool isLong = _isLongSpeed(plannedForRulesKm);
|
|
|
|
// نسبة التخفيض للفئات الأخرى (Comfort/Electric/Van) في كل الأوقات
|
|
final double reductionPct = _distanceReductionPct(plannedForRulesKm);
|
|
|
|
// سعر/كم فعّال لكل فئة
|
|
final double perKmSpeed = isLong ? longSpeedPerKm : perKmSpeedBase;
|
|
final double perKmBalash = (perKmSpeed - 500).clamp(0, double.infinity);
|
|
final double perKmComfort =
|
|
(perKmComfortRaw * (1.0 - reductionPct)).clamp(0, double.infinity);
|
|
final double perKmElectric =
|
|
(perKmElectricRaw * (1.0 - reductionPct)).clamp(0, double.infinity);
|
|
final double perKmVan =
|
|
(perKmVanRaw * (1.0 - reductionPct)).clamp(0, double.infinity);
|
|
|
|
final double perKmForType = () {
|
|
switch (carType) {
|
|
case 'Speed':
|
|
return perKmSpeed;
|
|
case 'Awfar Car':
|
|
return perKmBalash;
|
|
case 'Comfort':
|
|
case 'Mishwar Vip':
|
|
case 'RayehGaiComfort':
|
|
case 'Lady':
|
|
return perKmComfort;
|
|
case 'Electric':
|
|
return perKmElectric;
|
|
case 'Van':
|
|
return perKmVan;
|
|
case 'Delivery':
|
|
return perKmDelivery;
|
|
default:
|
|
return perKmSpeed;
|
|
}
|
|
}();
|
|
|
|
// قواعد الدقيقة (تطبّق على كل الأوقات بما فيها المطار)
|
|
double perMinEff = _perMinuteByTime(now);
|
|
int capMinutes = 1 << 30; // لا سقف افتراضيًا
|
|
int freeMinutes = 0;
|
|
|
|
if (plannedForRulesKm > longDistThresholdKm) {
|
|
perMinEff = longTripPerMin; // 600/د
|
|
capMinutes = minuteCapLong; // سقف 60
|
|
freeMinutes = freeMinutesLong; // عفو 10
|
|
} else if (plannedForRulesKm > mediumDistThresholdKm) {
|
|
perMinEff = longTripPerMin; // 600/د
|
|
capMinutes = minuteCapMedium; // سقف 60
|
|
}
|
|
|
|
// دقائق منقضية
|
|
final int elapsedMinutes = (sec ~/ 60);
|
|
|
|
// دقائق إضافية فوق المقتبس (مع السقف/العفو)
|
|
int extraMinutes = 0;
|
|
final int rawExtra = elapsedMinutes - quotedMinutes - freeMinutes;
|
|
if (rawExtra > 0) {
|
|
extraMinutes = rawExtra;
|
|
if (capMinutes < (1 << 30)) {
|
|
final int maxBill = (capMinutes - quotedMinutes - freeMinutes);
|
|
if (extraMinutes > maxBill)
|
|
extraMinutes = maxBill.clamp(0, extraMinutes);
|
|
}
|
|
}
|
|
|
|
// كيلومترات إضافية: إن لم يكن لدينا plannedKm «حقيقي»، نقيس على لحظة البدء
|
|
double extraKm = liveKm - plannedForRulesKm;
|
|
if (plannedKm <= 0) {
|
|
extraKm = (liveKm - startKm);
|
|
}
|
|
if (extraKm < 0) extraKm = 0;
|
|
|
|
// زيادات قبل العمولة (بدون إضافة مطار ثابتة — فهي مضافة في التسعير الأولي)
|
|
final double extraBefore =
|
|
(extraMinutes * perMinEff) + (extraKm * perKmForType);
|
|
|
|
// السعر المعروض = المقتبس + الزيادات * (1 + kazan)
|
|
price = basePassengerQuote + (extraBefore * (1.0 + kazanPct));
|
|
|
|
// تحديث واجهة
|
|
speed = loc.speed * 3.6;
|
|
remainingTimeTimerRideBegin =
|
|
(quotedMinutes * 60 - sec).clamp(0, 1 << 30);
|
|
progressTimerRideBegin = (quotedMinutes == 0)
|
|
? 0
|
|
: (sec / (quotedMinutes * 60)).clamp(0.0, 1.0);
|
|
remainingTimeTimerRideBegin < 60 ? driverEndPage = 160 : 100;
|
|
|
|
updateMarker();
|
|
update();
|
|
}
|
|
} // int durationOfRide = int.parse(durationOfRideValue);
|
|
// double latePrice = Get.find<HomeCaptainController>().latePrice;
|
|
// update();
|
|
// int infinity = 40000;
|
|
// if (carType == 'Comfort' ||
|
|
// carType == 'Mishwar Vip' ||
|
|
// carType == 'RayehGaiComfort') {
|
|
// durationOfRide = infinity;
|
|
// update();
|
|
// }
|
|
|
|
// price = double.parse(totalCost);
|
|
|
|
// for (int i = 0; i <= durationOfRide; i++) {
|
|
// await Future.delayed(const Duration(seconds: 1));
|
|
// recentDistanceToDash = Get.find<LocationController>().totalDistance;
|
|
// // rideTimerFromBegin = i;
|
|
// if (int.parse(duration) + 300 > i) {
|
|
// price = double.parse(totalCost);
|
|
// } else {
|
|
// if (startNameLocation.toLowerCase().contains('airport') ||
|
|
// endNameLocation.toLowerCase().contains('airport') ||
|
|
// startNameLocation.contains('مطار') ||
|
|
// startNameLocation.contains('المطار') ||
|
|
// endNameLocation.contains('مطار') ||
|
|
// endNameLocation.contains('المطار')) {
|
|
// price = carType == 'Comfort' // || carType == 'Free Ride'
|
|
// ? price + ((i ~/ 60) - int.parse(duration)) * latePrice
|
|
// : carType == 'Lady'
|
|
// ? price + ((i ~/ 60) - int.parse(duration)) * latePrice
|
|
// : carType == 'RayehGaiComfort'
|
|
// ? (i ~/ 60) * latePrice +
|
|
// (recentDistanceToDash *
|
|
// Get.find<HomeCaptainController>().comfortPrice)
|
|
// : (i ~/ 60) * latePrice +
|
|
// (recentDistanceToDash *
|
|
// Get.find<HomeCaptainController>().mashwariPrice);
|
|
// } else if (currentTime.hour >= 21 && currentTime.hour < 0) {
|
|
// price = carType == 'Comfort' // || carType == 'Free Ride'
|
|
// ? price + ((i ~/ 60) - int.parse(duration)) * latePrice
|
|
// : carType == 'Lady'
|
|
// ? price + ((i ~/ 60) - int.parse(duration)) * latePrice
|
|
// : carType == 'RayehGaiComfort'
|
|
// ? (i ~/ 60) * latePrice +
|
|
// (recentDistanceToDash *
|
|
// Get.find<HomeCaptainController>().comfortPrice)
|
|
// : (i ~/ 60) * latePrice +
|
|
// (recentDistanceToDash *
|
|
// Get.find<HomeCaptainController>().mashwariPrice);
|
|
// } else if (currentTime.hour >= 1 && currentTime.hour < 5) {
|
|
// if (startNameLocation.contains('club') ||
|
|
// startNameLocation.contains('nightclub') ||
|
|
// startNameLocation.contains('ديسكو') ||
|
|
// startNameLocation.contains('ملهى ليلي') ||
|
|
// startNameLocation.contains('Night club')) {
|
|
// price = carType == 'Comfort' // || carType == 'Free Ride'
|
|
// ? (i ~/ 60) * (latePrice + .5) * 2 +
|
|
// (price) -
|
|
// int.parse(duration) * (latePrice + .5) * 2
|
|
// : carType == 'Lady'
|
|
// ? (i ~/ 60) * (latePrice + .5) * 2 +
|
|
// (price) -
|
|
// int.parse(duration) * (latePrice + .5) * 2
|
|
// : carType == 'RayehGaiComfort'
|
|
// ? (i ~/ 60) * (latePrice + .5) * 2 + (price)
|
|
// : (i ~/ 60) * (latePrice + .5) * 2 + (price);
|
|
// }
|
|
// price = carType == 'Comfort' // || carType == 'Free Ride'
|
|
// ? (latePrice + 0.5) * ((i ~/ 60) - int.parse(duration)) + price
|
|
// : carType == 'Lady'
|
|
// ? (latePrice + 0.5) * ((i ~/ 60) - int.parse(duration)) +
|
|
// price
|
|
// : carType == 'RayehGaiComfort'
|
|
// ? (latePrice + 0.5) * ((i ~/ 60) - int.parse(duration)) +
|
|
// price
|
|
// : price;
|
|
// } else if (currentTime.hour >= 14 && currentTime.hour <= 17) {
|
|
// price = carType == 'Comfort' // || carType == 'Free Ride'
|
|
// ? Get.find<HomeCaptainController>().heavyPrice *
|
|
// ((i ~/ 60) - int.parse(duration)) +
|
|
// price -
|
|
// (0.5 * int.parse(duration))
|
|
// : carType == 'Lady'
|
|
// ? Get.find<HomeCaptainController>().heavyPrice *
|
|
// ((i ~/ 60) - int.parse(duration)) +
|
|
// price -
|
|
// (0.5 * int.parse(duration))
|
|
// : carType == 'RayehGaiComfort'
|
|
// ? (i ~/ 60) *
|
|
// (Get.find<HomeCaptainController>().heavyPrice) +
|
|
// (recentDistanceToDash *
|
|
// Get.find<HomeCaptainController>().comfortPrice)
|
|
// : (i ~/ 60) *
|
|
// (Get.find<HomeCaptainController>().heavyPrice) +
|
|
// (recentDistanceToDash *
|
|
// Get.find<HomeCaptainController>().mashwariPrice);
|
|
// } else {
|
|
// price = carType == 'Comfort' // || carType == 'Free Ride'
|
|
// ? (i ~/ 60) + (price) - int.parse(duration)
|
|
// : carType == 'Lady'
|
|
// ? (i ~/ 60) + (price) - int.parse(duration)
|
|
// : carType == 'RayehGaiComfort'
|
|
// ? (i ~/ 60) +
|
|
// (recentDistanceToDash *
|
|
// Get.find<HomeCaptainController>().comfortPrice)
|
|
// : (i ~/ 60) +
|
|
// (recentDistanceToDash *
|
|
// Get.find<HomeCaptainController>().mashwariPrice);
|
|
// }
|
|
// }
|
|
|
|
// // $1 for each minute + $4 for each km
|
|
// price = (price * double.parse(kazan)) + price; // Add 10% tax
|
|
// speed = Get.find<LocationController>().speed * 3.6;
|
|
// progressTimerRideBegin = i / durationOfRide;
|
|
// remainingTimeTimerRideBegin = durationOfRide - i;
|
|
// remainingTimeTimerRideBegin < 60 ? driverEndPage = 160 : 100;
|
|
// updateMarker();
|
|
// if (remainingTimeTimerRideBegin < 60) {
|
|
// // to make driver available on last 2 minute in his trip
|
|
// box.write(BoxName.statusDriverLocation, 'off');
|
|
// }
|
|
// int minutes = (remainingTimeTimerRideBegin / 60).floor();
|
|
// int seconds = remainingTimeTimerRideBegin % 60;
|
|
// stringRemainingTimeRideBegin =
|
|
// '$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
// int minutes1 = (i / 60).floor();
|
|
// int seconds1 = i % 60;
|
|
// stringRemainingTimeRideBegin1 =
|
|
// '$minutes1:${seconds1.toString().padLeft(2, '0')}';
|
|
// update();
|
|
// }
|
|
// }
|
|
|
|
double recentDistanceToDash = 0;
|
|
double recentAngelToMarker = 0;
|
|
double speed = 0;
|
|
void updateMarker() async {
|
|
// Remove the existing marker with the ID `MyLocation`.
|
|
markers.remove(MarkerId('MyLocation'));
|
|
|
|
// Add a new marker with the ID `MyLocation` at the current location of the user.
|
|
LocationController locationController = Get.find<LocationController>();
|
|
myLocation = locationController.myLocation;
|
|
|
|
markers.add(
|
|
Marker(
|
|
markerId: MarkerId('MyLocation'.tr),
|
|
position: myLocation,
|
|
draggable: true,
|
|
icon: carIcon,
|
|
rotation: locationController.heading,
|
|
// infoWindow: const InfoWindow(
|
|
// title: 'Time',
|
|
// ),
|
|
),
|
|
);
|
|
|
|
// Animate camera only once after updating the marker
|
|
// mapController!.animateCamera(
|
|
// CameraUpdate.newLatLng(myLocation),
|
|
// );
|
|
update();
|
|
}
|
|
|
|
void addCustomCarIcon() {
|
|
ImageConfiguration config = ImageConfiguration(
|
|
size: const Size(30, 35), devicePixelRatio: Get.pixelRatio);
|
|
BitmapDescriptor.asset(
|
|
config,
|
|
'assets/images/car.png',
|
|
// mipmaps: false,
|
|
).then((value) {
|
|
carIcon = value;
|
|
update();
|
|
});
|
|
}
|
|
|
|
void addCustomStartIcon() async {
|
|
// Create the marker with the resized image
|
|
|
|
ImageConfiguration config = ImageConfiguration(
|
|
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
|
|
BitmapDescriptor.asset(
|
|
config,
|
|
'assets/images/A.png',
|
|
).then((value) {
|
|
startIcon = value;
|
|
update();
|
|
});
|
|
}
|
|
|
|
void addCustomEndIcon() {
|
|
ImageConfiguration config = ImageConfiguration(
|
|
size: const Size(25, 25), devicePixelRatio: Get.pixelRatio);
|
|
BitmapDescriptor.asset(
|
|
config,
|
|
'assets/images/b.png',
|
|
).then((value) {
|
|
endIcon = value;
|
|
update();
|
|
});
|
|
}
|
|
|
|
void addCustomPassengerIcon() {
|
|
ImageConfiguration config = ImageConfiguration(
|
|
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio
|
|
// scale: 1.0,
|
|
);
|
|
BitmapDescriptor.asset(
|
|
config,
|
|
'assets/images/picker.png',
|
|
).then((value) {
|
|
passengerIcon = value;
|
|
update();
|
|
});
|
|
}
|
|
|
|
var activeRouteSteps = <Map<String, dynamic>>[];
|
|
var traveledPathPoints = <LatLng>[]; // المسار المقطوع (رمادي)
|
|
var upcomingPathPoints = <LatLng>[]; // المسار المتبقي (أزرق/أحمر)
|
|
|
|
// --- متغيرات الأيقونات والمواقع ---
|
|
var heading = 0.0;
|
|
// ... يمكنك إضافة أيقونات البداية والنهاية هنا
|
|
|
|
// --- متغيرات قياس الأداء الذكي ---
|
|
final List<int> _performanceReadings = [];
|
|
final int _readingsToCollect = 10; // اجمع 10 قراءات
|
|
bool _hasMadeDecision = false;
|
|
var updateInterval = 5.obs; // القيمة الافتراضية
|
|
|
|
// --- متغيرات داخلية للملاحة ---
|
|
var _stepBounds = <LatLngBounds>[];
|
|
var _stepEndPoints = <LatLng>[];
|
|
var _allPointsForActiveRoute = <LatLng>[];
|
|
|
|
// دالة موحّدة وقوية لجلب أي مسار
|
|
Future<void> getRoute({
|
|
required LatLng origin,
|
|
required LatLng destination,
|
|
required Color routeColor,
|
|
}) async {
|
|
// إظهار مؤشر التحميل لو رغبت
|
|
// isLoading.value = true;
|
|
|
|
var url =
|
|
('${AppLink.googleMapsLink}directions/json?language=ar&destination=${destination.latitude},${destination.longitude}&origin=${origin.latitude},${origin.longitude}&key=${AK.mapAPIKEY}');
|
|
var response = await CRUD().getGoogleApi(link: url, payload: {});
|
|
|
|
if (response == null || response['routes'].isEmpty) {
|
|
// isLoading.value = false;
|
|
// أظهر رسالة خطأ
|
|
return;
|
|
}
|
|
|
|
_resetRouteState(); // تنظيف الحالة القديمة قبل رسم الجديد
|
|
|
|
final route = response['routes'][0];
|
|
final leg = route['legs'][0];
|
|
|
|
// استخراج النقاط ورسم المسار المبدئي بالكامل
|
|
final pointsString = route["overview_polyline"]["points"];
|
|
_allPointsForActiveRoute = decodePolylineToLatLng(pointsString);
|
|
upcomingPathPoints.assignAll(_allPointsForActiveRoute);
|
|
|
|
// استخراج خطوات الملاحة
|
|
activeRouteSteps.assignAll(List<Map<String, dynamic>>.from(leg['steps']));
|
|
_prepareStepData(activeRouteSteps);
|
|
|
|
// تحديث التعليمات الأولية
|
|
if (activeRouteSteps.isNotEmpty) {
|
|
currentInstruction =
|
|
_parseInstruction(activeRouteSteps[0]['html_instructions']);
|
|
Get.find<TextToSpeechController>().speakText(currentInstruction);
|
|
}
|
|
|
|
// تحديث الكاميرا لتناسب المسار الجديد
|
|
final boundsData = route["bounds"];
|
|
_fitToBounds(LatLngBounds(
|
|
northeast: LatLng(
|
|
boundsData['northeast']['lat'], boundsData['northeast']['lng']),
|
|
southwest: LatLng(
|
|
boundsData['southwest']['lat'], boundsData['southwest']['lng']),
|
|
));
|
|
|
|
// isLoading.value = false;
|
|
update(); // تحديث الواجهة مرة واحدة بعد كل العمليات
|
|
}
|
|
|
|
// الدالة التي يتم استدعاؤها من خدمة الموقع كل 5 ثوان (أو حسب الفترة المحددة)
|
|
void onLocationUpdated(Position newPosition) {
|
|
myLocation = LatLng(newPosition.latitude, newPosition.longitude);
|
|
heading = newPosition.heading;
|
|
|
|
// -->> منطق قياس الأداء يبدأ هنا <<--
|
|
final stopwatch = Stopwatch()..start();
|
|
|
|
// -->> منطق الملاحة وتحديث المسار <<--
|
|
_onLocationTick(myLocation);
|
|
|
|
stopwatch.stop();
|
|
|
|
// -->> تحليل الأداء واتخاذ القرار <<--
|
|
if (!_hasMadeDecision) {
|
|
_performanceReadings.add(stopwatch.elapsedMilliseconds);
|
|
if (_performanceReadings.length >= _readingsToCollect) {
|
|
_analyzePerformance();
|
|
_hasMadeDecision = true;
|
|
}
|
|
}
|
|
}
|
|
// =================================================================
|
|
// 3. منطق الملاحة الداخلي (Internal Navigation Logic)
|
|
// =================================================================
|
|
|
|
void _onLocationTick(LatLng pos) {
|
|
if (activeRouteSteps.isEmpty || currentStepIndex >= _stepBounds.length) {
|
|
return;
|
|
}
|
|
|
|
final double dToEnd =
|
|
_distanceMeters(pos, _stepEndPoints[currentStepIndex]);
|
|
if (dToEnd <= 35) {
|
|
// 35 متر عتبة للوصول لنهاية الخطوة
|
|
_advanceStep();
|
|
}
|
|
}
|
|
|
|
void _advanceStep() {
|
|
if (currentStepIndex >= _stepBounds.length - 1) {
|
|
// وصل للنهاية
|
|
currentInstruction = "لقد وصلت إلى وجهتك";
|
|
return;
|
|
}
|
|
|
|
currentStepIndex++;
|
|
currentInstruction = _parseInstruction(
|
|
activeRouteSteps[currentStepIndex]['html_instructions']);
|
|
Get.find<TextToSpeechController>().speakText(currentInstruction);
|
|
|
|
// -->> هنا يتم تحديث لون المسار <<--
|
|
_updateTraveledPath();
|
|
|
|
_fitToBounds(_stepBounds[currentStepIndex],
|
|
padding: 80); // تقريب الكاميرا على الخطوة التالية
|
|
update();
|
|
}
|
|
|
|
void _updateTraveledPath() {
|
|
// استخراج كل النقاط للخطوات التي تم اجتيازها
|
|
List<LatLng> pointsForTraveledSteps = [];
|
|
for (int i = 0; i < currentStepIndex; i++) {
|
|
final stepPolyline = activeRouteSteps[i]['polyline']['points'];
|
|
pointsForTraveledSteps.addAll(decodePolylineToLatLng(stepPolyline));
|
|
}
|
|
traveledPathPoints.assignAll(pointsForTraveledSteps);
|
|
}
|
|
|
|
void _prepareStepData(List<Map<String, dynamic>> steps) {
|
|
_stepBounds.clear();
|
|
_stepEndPoints.clear();
|
|
|
|
for (final s in steps) {
|
|
// 1. استخراج نقطة النهاية (الكود الحالي سليم)
|
|
final end = s['end_location'];
|
|
_stepEndPoints.add(LatLng(
|
|
(end['lat'] as num).toDouble(),
|
|
(end['lng'] as num).toDouble(),
|
|
));
|
|
|
|
// 2. فك تشفير البوليلاين الخاص بالخطوة وتحويله إلى LatLng
|
|
// -->> هنا تم التصحيح <<--
|
|
List<LatLng> pts = decodePolyline(s['polyline']['points'])
|
|
.map((point) => LatLng(point[0].toDouble(), point[1].toDouble()))
|
|
.toList();
|
|
|
|
// أضف نقاط البداية والنهاية إذا لم تكن موجودة في البوليلاين لضمان دقة الحدود
|
|
if (pts.isNotEmpty) {
|
|
final start = s['start_location'];
|
|
final startLatLng = LatLng(
|
|
(start['lat'] as num).toDouble(), (start['lng'] as num).toDouble());
|
|
if (pts.first != startLatLng) {
|
|
pts.insert(0, startLatLng);
|
|
}
|
|
}
|
|
|
|
_stepBounds.add(_boundsFromPoints(pts));
|
|
}
|
|
}
|
|
|
|
// A helper function to decode and convert the polyline string
|
|
List<LatLng> decodePolylineToLatLng(String polylineString) {
|
|
// 1. Decode the string into a list of number lists (e.g., [[lat, lng], ...])
|
|
List<List<num>> decodedPoints = decodePolyline(polylineString);
|
|
|
|
// 2. Map each [lat, lng] pair to a LatLng object, ensuring conversion to double
|
|
List<LatLng> latLngPoints = decodedPoints
|
|
.map((point) => LatLng(point[0].toDouble(), point[1].toDouble()))
|
|
.toList();
|
|
|
|
return latLngPoints;
|
|
}
|
|
|
|
// =================================================================
|
|
// 4. منطق الأداء الذكي (Smart Performance Logic)
|
|
// =================================================================
|
|
void _analyzePerformance() {
|
|
final int sum = _performanceReadings.reduce((a, b) => a + b);
|
|
final double averageTime = sum / _performanceReadings.length;
|
|
if (averageTime > 1000) {
|
|
// إذا كانت العملية تستغرق أكثر من ثانية
|
|
_suggestOptimization();
|
|
}
|
|
}
|
|
|
|
void _suggestOptimization() {
|
|
Get.snackbar(
|
|
"تحسين أداء التطبيق",
|
|
"لضمان أفضل تجربة، نقترح تعديل الإعدادات لتناسب جهازك. هل تود المتابعة؟",
|
|
duration: const Duration(seconds: 15),
|
|
mainButton: TextButton(
|
|
child: const Text("نعم، قم بالتحسين"),
|
|
onPressed: () {
|
|
updateInterval.value = 8; // غير الفترة إلى 8 ثوانٍ
|
|
// save setting to shared_preferences
|
|
box.write(BoxName.updateInterval, 8);
|
|
Get.back();
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
// =================================================================
|
|
// 5. دوال مساعدة (Helper Functions)
|
|
// =================================================================
|
|
void _resetRouteState() {
|
|
activeRouteSteps.clear();
|
|
traveledPathPoints.clear();
|
|
upcomingPathPoints.clear();
|
|
_allPointsForActiveRoute.clear();
|
|
currentStepIndex = 0;
|
|
}
|
|
|
|
String _parseInstruction(String html) =>
|
|
html.replaceAll(RegExp(r'<[^>]*>'), '');
|
|
|
|
Future<void> _fitToBounds(LatLngBounds b, {double padding = 60}) async {
|
|
await mapController
|
|
?.animateCamera(CameraUpdate.newLatLngBounds(b, padding));
|
|
}
|
|
|
|
double distanceBetweenDriverAndPassengerWhenConfirm = 0;
|
|
getMap(String origin, destination) async {
|
|
isLoading = false;
|
|
|
|
update();
|
|
|
|
var url =
|
|
('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}');
|
|
|
|
var response = await CRUD().getGoogleApi(link: url, payload: {});
|
|
Log.print('response: ${response}');
|
|
data = response['routes'][0]['legs'];
|
|
distanceBetweenDriverAndPassengerWhenConfirm =
|
|
(data[0]['distance']['value']) / 1000;
|
|
final points =
|
|
decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
|
|
for (int i = 0; i < points.length; i++) {
|
|
double lat = points[i][0].toDouble();
|
|
double lng = points[i][1].toDouble();
|
|
polylineCoordinates.add(LatLng(lat, lng));
|
|
}
|
|
if (polyLines.isNotEmpty) {
|
|
clearPolyline();
|
|
var polyline = Polyline(
|
|
polylineId: PolylineId(response["routes"][0]["summary"]),
|
|
points: polylineCoordinates,
|
|
width: 10,
|
|
color: AppColor.blueColor,
|
|
);
|
|
polyLines.add(polyline);
|
|
// rideConfirm = false;
|
|
update();
|
|
} else {
|
|
var polyline = Polyline(
|
|
polylineId: PolylineId(response["routes"][0]["summary"]),
|
|
points: polylineCoordinates,
|
|
width: 10,
|
|
color: AppColor.blueColor,
|
|
);
|
|
// final dataBounds = response["routes"][0]["bounds"];
|
|
|
|
// updateCameraFromBoundsAfterGetMap(dataBounds);
|
|
|
|
// Fit the camera to the bounds
|
|
|
|
polyLines.add(polyline);
|
|
// rideConfirm = false;
|
|
// Define the northeast and southwest coordinates
|
|
final bounds = response["routes"][0]["bounds"];
|
|
LatLng northeast =
|
|
LatLng(bounds['northeast']['lat'], bounds['northeast']['lng']);
|
|
LatLng southwest =
|
|
LatLng(bounds['southwest']['lat'], bounds['southwest']['lng']);
|
|
|
|
// Create the LatLngBounds object
|
|
boundsData = LatLngBounds(northeast: northeast, southwest: southwest);
|
|
|
|
// Fit the camera to the bounds
|
|
var cameraUpdate = CameraUpdate.newLatLngBounds(boundsData!, 140);
|
|
mapController!.animateCamera(cameraUpdate);
|
|
update();
|
|
}
|
|
}
|
|
|
|
void checkForNextStep(LatLng currentPosition) {
|
|
if (currentStepIndex >= routeSteps.length) return;
|
|
|
|
final step = routeSteps[currentStepIndex];
|
|
final endLocation = step['end_location'];
|
|
final endLatLng = LatLng(endLocation['lat'], endLocation['lng']);
|
|
|
|
final distance = calculateDistance(
|
|
currentPosition.latitude,
|
|
currentPosition.longitude,
|
|
endLatLng.latitude,
|
|
endLatLng.longitude,
|
|
);
|
|
|
|
if (distance < 50) {
|
|
// 50 متر قبل النقطة
|
|
currentStepIndex++;
|
|
if (currentStepIndex < routeSteps.length) {
|
|
currentInstruction = _parseInstruction(
|
|
routeSteps[currentStepIndex]['html_instructions']);
|
|
Get.isRegistered<TextToSpeechController>()
|
|
? Get.find<TextToSpeechController>().speakText(currentInstruction)
|
|
: Get.put(TextToSpeechController()).speakText(currentInstruction);
|
|
Log.print('Current Instruction: $currentInstruction');
|
|
update();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Calculates the distance in meters between two latitude/longitude points.
|
|
double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
|
|
const double earthRadius = 6371000; // meters
|
|
double dLat = _degreesToRadians(lat2 - lat1);
|
|
double dLon = _degreesToRadians(lon2 - lon1);
|
|
|
|
double a = (sin(dLat / 2) * sin(dLat / 2)) +
|
|
cos(_degreesToRadians(lat1)) *
|
|
cos(_degreesToRadians(lat2)) *
|
|
(sin(dLon / 2) * sin(dLon / 2));
|
|
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
|
|
double distance = earthRadius * c;
|
|
return distance;
|
|
}
|
|
|
|
double _degreesToRadians(double degrees) {
|
|
return degrees * (3.1415926535897932 / 180.0);
|
|
}
|
|
|
|
// String _parseInstruction(String htmlInstruction) {
|
|
// return htmlInstruction.replaceAll(RegExp(r'<[^>]*>'), '');
|
|
// }
|
|
|
|
void checkDestinationProximity() {
|
|
final distance = calculateDistance(
|
|
myLocation.latitude,
|
|
myLocation.longitude,
|
|
latLngPassengerDestination.latitude,
|
|
latLngPassengerDestination.longitude,
|
|
);
|
|
|
|
if (distance < 300) {
|
|
// 300 متر قبل الوجهة
|
|
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
|
|
"You are near the destination".tr,
|
|
"You are near the destination".tr,
|
|
tokenPassenger,
|
|
[
|
|
box.read(BoxName.driverID),
|
|
rideId,
|
|
box.read(BoxName.tokenDriver),
|
|
paymentAmount.toString()
|
|
],
|
|
'ding.wav',
|
|
);
|
|
// يمكن إضافة أي إجراء آخر هنا عند الاقتراب من الوجهة
|
|
}
|
|
}
|
|
|
|
List<LatLngBounds> stepBounds = [];
|
|
List<LatLng> stepEndPoints = [];
|
|
List<String> stepInstructions = [];
|
|
StreamSubscription<Position>? _posSub;
|
|
DateTime _lastCameraUpdateTs = DateTime.fromMillisecondsSinceEpoch(0);
|
|
|
|
getMapDestination(String origin, destination) async {
|
|
var url =
|
|
('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}');
|
|
|
|
var response = await CRUD().getGoogleApi(link: url, payload: {});
|
|
dataDestination = response['routes'][0]['legs'];
|
|
final points =
|
|
decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
|
|
|
|
polylineCoordinatesDestination.clear();
|
|
for (int i = 0; i < points.length; i++) {
|
|
double lat = points[i][0].toDouble();
|
|
double lng = points[i][1].toDouble();
|
|
polylineCoordinatesDestination.add(LatLng(lat, lng));
|
|
}
|
|
|
|
// الخطوات من أول leg
|
|
routeSteps = List<Map<String, dynamic>>.from(dataDestination[0]['steps']);
|
|
|
|
// حضّر بيانات كل step
|
|
_prepareStepData(routeSteps);
|
|
|
|
currentStepIndex = 0;
|
|
if (stepInstructions.isNotEmpty) {
|
|
currentInstruction = _parseInstruction(stepInstructions[0]);
|
|
Log.print('currentInstruction: $currentInstruction');
|
|
Get.isRegistered<TextToSpeechController>()
|
|
? Get.find<TextToSpeechController>().speakText(currentInstruction)
|
|
: Get.put(TextToSpeechController()).speakText(currentInstruction);
|
|
}
|
|
|
|
// ارسم الـ polyline الرئيسي (مثل ما لديك)
|
|
final summary = response["routes"][0]["summary"];
|
|
final polyline = Polyline(
|
|
polylineId: PolylineId(summary.isEmpty ? 'route' : summary),
|
|
points: polylineCoordinatesDestination,
|
|
width: 10,
|
|
color: AppColor.redColor,
|
|
);
|
|
polyLinesDestination.add(polyline);
|
|
|
|
// fit للخريطة على أول step
|
|
if (stepBounds.isNotEmpty) {
|
|
_fitToBounds(stepBounds[0]);
|
|
}
|
|
|
|
update();
|
|
|
|
// // ابدأ التتبع الحيّ لخطوات الملاحة
|
|
// await startListeningStepNavigation();
|
|
}
|
|
|
|
// ————————————————————————————————————————————————————————————————
|
|
// يُنادى عند كل تحديث للموقع
|
|
// void _onLocationTick(LatLng pos) async {
|
|
// if (routeSteps.isEmpty || currentStepIndex >= stepBounds.length) return;
|
|
|
|
// // إذا تجاوزت نهاية الـ step الحالية بمسافة صغيرة -> انتقل
|
|
// final double dToEnd = _distanceMeters(pos, stepEndPoints[currentStepIndex]);
|
|
// final bool nearEnd = dToEnd <= 25; // عتبة 25م (عدّلها حسب حاجتك)
|
|
|
|
// // إذا دخلت نطاق الـ step التالية -> انتقل
|
|
// bool insideNext = false;
|
|
// if (currentStepIndex < stepBounds.length - 1) {
|
|
// insideNext = _contains(stepBounds[currentStepIndex + 1], pos);
|
|
// }
|
|
|
|
// if (nearEnd || insideNext) {
|
|
// _advanceStep();
|
|
// return;
|
|
// }
|
|
|
|
// // تحديث الكاميرا بشكل خفيف أثناء الحركة (كل 2 ثانية مثلاً)
|
|
// final now = DateTime.now();
|
|
// if (now.difference(_lastCameraUpdateTs).inMilliseconds > 2000) {
|
|
// _lastCameraUpdateTs = now;
|
|
// // ركّز على bounds الحالية مع الحفاظ على تتبّع عام
|
|
// _fitToBounds(stepBounds[currentStepIndex], padding: 60);
|
|
// }
|
|
// }
|
|
|
|
LatLngBounds _boundsFromPoints(List<LatLng> pts) {
|
|
double? minLat, maxLat, minLng, maxLng;
|
|
for (final p in pts) {
|
|
minLat = (minLat == null) ? p.latitude : math.min(minLat, p.latitude);
|
|
maxLat = (maxLat == null) ? p.latitude : math.max(maxLat, p.latitude);
|
|
minLng = (minLng == null) ? p.longitude : math.min(minLng, p.longitude);
|
|
maxLng = (maxLng == null) ? p.longitude : math.max(maxLng, p.longitude);
|
|
}
|
|
return LatLngBounds(
|
|
southwest: LatLng(minLat ?? 0, minLng ?? 0),
|
|
northeast: LatLng(maxLat ?? 0, maxLng ?? 0),
|
|
);
|
|
}
|
|
|
|
bool _contains(LatLngBounds b, LatLng p) {
|
|
final south = math.min(b.southwest.latitude, b.northeast.latitude);
|
|
final north = math.max(b.southwest.latitude, b.northeast.latitude);
|
|
final west = math.min(b.southwest.longitude, b.northeast.longitude);
|
|
final east = math.max(b.southwest.longitude, b.northeast.longitude);
|
|
return (p.latitude >= south &&
|
|
p.latitude <= north &&
|
|
p.longitude >= west &&
|
|
p.longitude <= east);
|
|
}
|
|
|
|
double _distanceMeters(LatLng a, LatLng b) {
|
|
// هافرساين مبسطة
|
|
const R = 6371000.0; // m
|
|
final dLat = _deg2rad(b.latitude - a.latitude);
|
|
final dLng = _deg2rad(b.longitude - a.longitude);
|
|
final s1 = math.sin(dLat / 2);
|
|
final s2 = math.sin(dLng / 2);
|
|
final aa = s1 * s1 +
|
|
math.cos(_deg2rad(a.latitude)) *
|
|
math.cos(_deg2rad(b.latitude)) *
|
|
s2 *
|
|
s2;
|
|
final c = 2 * math.atan2(math.sqrt(aa), math.sqrt(1 - aa));
|
|
return R * c;
|
|
}
|
|
|
|
double _deg2rad(double d) => d * math.pi / 180.0;
|
|
|
|
// تحريك الكاميرا لباوند معيّن
|
|
// Future<void> _fitToBounds(LatLngBounds b, {double padding = 40}) async {
|
|
// if (mapController == null) return;
|
|
// try {
|
|
// // أحياناً يلزم انتظار فريم حتى تكون الخريطة مرسومة
|
|
// await Future.delayed(const Duration(milliseconds: 50));
|
|
// await mapController!.animateCamera(
|
|
// CameraUpdate.newLatLngBounds(b, padding),
|
|
// );
|
|
// } catch (_) {
|
|
// // fallback لو حصلت مشكلة الحجم
|
|
// final center = LatLng(
|
|
// (b.northeast.latitude + b.southwest.latitude) / 2,
|
|
// (b.northeast.longitude + b.southwest.longitude) / 2,
|
|
// );
|
|
// await mapController!.animateCamera(CameraUpdate.newLatLng(center));
|
|
// }
|
|
// }
|
|
|
|
// الانتقال للخطوة التالية وتحديث التعليمات والكاميرا
|
|
// void _advanceStep() {
|
|
// if (currentStepIndex >= stepBounds.length - 1) return;
|
|
|
|
// currentStepIndex++;
|
|
// currentInstruction = _parseInstruction(stepInstructions[currentStepIndex]);
|
|
|
|
// // نطق التعليمات
|
|
// if (Get.isRegistered<TextToSpeechController>()) {
|
|
// Get.find<TextToSpeechController>().speakText(currentInstruction);
|
|
// } else {
|
|
// Get.put(TextToSpeechController()).speakText(currentInstruction);
|
|
// }
|
|
|
|
// // تركيز الكاميرا على باوند الخطوة الجديدة
|
|
// _fitToBounds(stepBounds[currentStepIndex]);
|
|
// update();
|
|
// }
|
|
|
|
// void _prepareStepData(List<Map<String, dynamic>> steps) {
|
|
// stepBounds.clear();
|
|
// stepEndPoints.clear();
|
|
// stepInstructions.clear();
|
|
|
|
// for (final s in steps) {
|
|
// // 1) instruction
|
|
// final html = (s['html_instructions'] ?? '').toString();
|
|
// stepInstructions.add(html);
|
|
|
|
// // 2) end point
|
|
// final end = s['end_location'];
|
|
// final endLatLng = LatLng(
|
|
// (end['lat'] as num).toDouble(), (end['lng'] as num).toDouble());
|
|
// stepEndPoints.add(endLatLng);
|
|
|
|
// // 3) bounds من الـ polyline (إن لم يوجد bounds جاهز من الـ API)
|
|
// List<LatLng> pts = [];
|
|
// if (s['polyline'] != null && s['polyline']['points'] != null) {
|
|
// final decoded = decodePolyline(s['polyline']['points']);
|
|
// for (var p in decoded) {
|
|
// pts.add(LatLng((p[0] as num).toDouble(), (p[1] as num).toDouble()));
|
|
// }
|
|
// } else {
|
|
// // fallback: استخدم start/end فقط
|
|
// final start = s['start_location'];
|
|
// pts.add(LatLng((start['lat'] as num).toDouble(),
|
|
// (start['lng'] as num).toDouble()));
|
|
// pts.add(endLatLng);
|
|
// }
|
|
// stepBounds.add(_boundsFromPoints(pts));
|
|
// }
|
|
// }
|
|
// getMapDestination(String origin, destination) async {
|
|
// var url =
|
|
// ('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}');
|
|
|
|
// var response = await CRUD().getGoogleApi(link: url, payload: {});
|
|
// dataDestination = response['routes'][0]['legs'];
|
|
// final points =
|
|
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
|
|
// for (int i = 0; i < points.length; i++) {
|
|
// double lat = points[i][0].toDouble();
|
|
// double lng = points[i][1].toDouble();
|
|
// polylineCoordinatesDestination.add(LatLng(lat, lng));
|
|
// }
|
|
// // استخراج الخطوات
|
|
// routeSteps = List<Map<String, dynamic>>.from(dataDestination[0]['steps']);
|
|
// Log.print('routeSteps: ${routeSteps}');
|
|
// currentStepIndex = 0;
|
|
// if (routeSteps.isNotEmpty) {
|
|
// currentInstruction =
|
|
// _parseInstruction(routeSteps[0]['html_instructions']);
|
|
// Log.print('currentInstruction: ${currentInstruction}');
|
|
// Get.isRegistered<TextToSpeechController>()
|
|
// ? Get.find<TextToSpeechController>().speakText(currentInstruction)
|
|
// : Get.put(TextToSpeechController()).speakText(currentInstruction);
|
|
// }
|
|
// update();
|
|
|
|
// // دالة مساعدة لتنظيف التعليمات
|
|
|
|
// if (polyLinesDestination.isNotEmpty) {
|
|
// // clearPolyline();
|
|
// var polyline = Polyline(
|
|
// polylineId: PolylineId(response["routes"][0]["summary"]),
|
|
// points: polylineCoordinatesDestination,
|
|
// width: 10,
|
|
// color: AppColor.redColor,
|
|
// );
|
|
// polyLinesDestination.add(polyline);
|
|
// // rideConfirm = false;
|
|
// update();
|
|
// } else {
|
|
// var polyline = Polyline(
|
|
// polylineId: PolylineId(response["routes"][0]["summary"]),
|
|
// points: polylineCoordinatesDestination,
|
|
// width: 10,
|
|
// color: AppColor.redColor,
|
|
// );
|
|
// // final dataBounds = response["routes"][0]["bounds"];
|
|
|
|
// // updateCameraFromBoundsAfterGetMap(dataBounds);
|
|
// // polyLinesDestination.add(polyline);
|
|
// // rideConfirm = false;
|
|
// // Define the northeast and southwest coordinates
|
|
|
|
// update();
|
|
// }
|
|
// }
|
|
|
|
void updateCameraFromBoundsAfterGetMap(dynamic response) {
|
|
final bounds = response["routes"][0]["bounds"];
|
|
LatLng northeast =
|
|
LatLng(bounds['northeast']['lat'], bounds['northeast']['lng']);
|
|
LatLng southwest =
|
|
LatLng(bounds['southwest']['lat'], bounds['southwest']['lng']);
|
|
|
|
// Create the LatLngBounds object
|
|
LatLngBounds boundsData =
|
|
LatLngBounds(northeast: northeast, southwest: southwest);
|
|
|
|
// Fit the camera to the bounds
|
|
var cameraUpdate = CameraUpdate.newLatLngBounds(boundsData, 140);
|
|
mapController!.animateCamera(cameraUpdate);
|
|
}
|
|
|
|
void changePassengerInfoWindow() {
|
|
isPassengerInfoWindow = !isPassengerInfoWindow;
|
|
passengerInfoWindowHeight = isPassengerInfoWindow == true ? 200 : 0;
|
|
update();
|
|
}
|
|
|
|
double mpg = 0;
|
|
calculateConsumptionFuel() {
|
|
mpg = Get.find<HomeCaptainController>().fuelPrice /
|
|
12; //todo in register car add mpg in box
|
|
update();
|
|
}
|
|
|
|
argumentLoading() async {
|
|
passengerLocation = Get.arguments['passengerLocation'];
|
|
passengerDestination = Get.arguments['passengerDestination'];
|
|
duration = Get.arguments['Duration'];
|
|
totalCost = Get.arguments['totalCost'];
|
|
passengerId = Get.arguments['passengerId'];
|
|
driverId = Get.arguments['driverId'];
|
|
distance = Get.arguments['Distance'];
|
|
passengerName = Get.arguments['name'];
|
|
passengerEmail = Get.arguments['email'];
|
|
totalPricePassenger = Get.arguments['totalPassenger'];
|
|
passengerPhone = Get.arguments['phone'];
|
|
walletChecked = Get.arguments['WalletChecked'];
|
|
tokenPassenger = Get.arguments['tokenPassenger'];
|
|
direction = Get.arguments['direction'];
|
|
durationToPassenger = Get.arguments['DurationToPassenger'];
|
|
rideId = Get.arguments['rideId'];
|
|
durationOfRideValue = Get.arguments['durationOfRideValue'];
|
|
paymentAmount = Get.arguments['paymentAmount'];
|
|
paymentMethod = Get.arguments['paymentMethod'];
|
|
isHaveSteps = Get.arguments['isHaveSteps'];
|
|
step0 = Get.arguments['step0'];
|
|
step1 = Get.arguments['step1'];
|
|
step2 = Get.arguments['step2'];
|
|
step3 = Get.arguments['step3'];
|
|
step4 = Get.arguments['step4'];
|
|
passengerWalletBurc = Get.arguments['passengerWalletBurc'];
|
|
timeOfOrder = Get.arguments['timeOfOrder'];
|
|
carType = Get.arguments['carType'];
|
|
kazan = Get.arguments['kazan'];
|
|
startNameLocation = Get.arguments['startNameLocation'];
|
|
endNameLocation = Get.arguments['endNameLocation'];
|
|
|
|
// Parse to double
|
|
latlng(passengerLocation, passengerDestination);
|
|
|
|
String lat = Get.find<LocationController>().myLocation.latitude.toString();
|
|
String lng = Get.find<LocationController>().myLocation.longitude.toString();
|
|
String origin = '$lat,$lng';
|
|
// Set the origin and destination coordinates for the Google Maps directions request.
|
|
Future.delayed(const Duration(seconds: 1));
|
|
getRoute(
|
|
origin: Get.find<LocationController>().myLocation,
|
|
destination: latLngPassengerLocation,
|
|
routeColor: Colors.yellow // أو أي لون
|
|
);
|
|
// getRoute(
|
|
// origin: latLngPassengerLocation,
|
|
// destination: latLngPassengerDestination,
|
|
// routeColor: Colors.blue // أو أي لون
|
|
// );
|
|
// getMap(origin, passengerLocation);
|
|
// isHaveSteps == 'haveSteps'
|
|
// ? (
|
|
// await getMapDestination(step0, step1),
|
|
// await getMapDestination(step1, step2),
|
|
// step3 == '' ? await getMapDestination(step2, step3) : () {},
|
|
// step4 == '' ? await getMapDestination(step3, step4) : () {},
|
|
// )
|
|
// : await getMapDestination(passengerLocation, passengerDestination);
|
|
update();
|
|
}
|
|
|
|
latlng(String passengerLocation, passengerDestination) {
|
|
double latPassengerLocation =
|
|
double.parse(passengerLocation.toString().split(',')[0]);
|
|
double lngPassengerLocation =
|
|
double.parse(passengerLocation.toString().split(',')[1]);
|
|
double latPassengerDestination =
|
|
double.parse(passengerDestination.toString().split(',')[0]);
|
|
double lngPassengerDestination =
|
|
double.parse(passengerDestination.toString().split(',')[1]);
|
|
latLngPassengerLocation =
|
|
LatLng(latPassengerLocation, lngPassengerLocation);
|
|
latLngPassengerDestination =
|
|
LatLng(latPassengerDestination, lngPassengerDestination);
|
|
}
|
|
|
|
late Duration durationToAdd;
|
|
int hours = 0;
|
|
int minutes = 0;
|
|
late String carType;
|
|
late String kazan;
|
|
late String startNameLocation;
|
|
late String endNameLocation;
|
|
|
|
Future<void> runGoogleMapDirectly() async {
|
|
if (box.read(BoxName.googlaMapApp) == true) {
|
|
if (Platform.isAndroid) {
|
|
Bubble().startBubbleHead(sendAppToBackground: true);
|
|
}
|
|
await openGoogleMapFromDriverToPassenger();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void onInit() async {
|
|
mapAPIKEY = await storage.read(key: BoxName.mapAPIKEY);
|
|
// Get the passenger location from the arguments.
|
|
await argumentLoading();
|
|
Get.put(FirebaseMessagesController());
|
|
runGoogleMapDirectly();
|
|
addCustomCarIcon();
|
|
addCustomPassengerIcon();
|
|
addCustomStartIcon();
|
|
addCustomEndIcon();
|
|
// updateMarker();
|
|
// updateLocation();
|
|
startTimerToShowPassengerInfoWindowFromDriver();
|
|
durationToAdd = Duration(seconds: int.parse(duration));
|
|
hours = durationToAdd.inHours;
|
|
minutes = (durationToAdd.inMinutes % 60).round();
|
|
calculateConsumptionFuel();
|
|
updateLocation();
|
|
// cancelCheckRidefromPassenger();
|
|
// checkIsDriverNearPassenger();
|
|
super.onInit();
|
|
}
|
|
|
|
Timer? _navigationTimer;
|
|
Future<void> startListeningStepNavigation() async {
|
|
_posSub?.cancel();
|
|
_navigationTimer?.cancel();
|
|
Position? pos;
|
|
_posSub = Geolocator.getPositionStream(
|
|
locationSettings: LocationSettings(
|
|
accuracy: LocationAccuracy.high,
|
|
distanceFilter: 5,
|
|
// حدّث كل ~5 متر لتقليل الاهتزاز)
|
|
),
|
|
).listen((position) {
|
|
// خزّن آخر موقع معروف، لكن لا تعالجه فوراً
|
|
myLocation = LatLng(position.latitude, position.longitude);
|
|
heading = position.heading;
|
|
update();
|
|
});
|
|
_navigationTimer = Timer.periodic(
|
|
Duration(
|
|
seconds: box.read(BoxName.updateInterval) ?? 5,
|
|
), (timer) {
|
|
// تأكد أن لدينا موقع صالح قبل المعالجة
|
|
if (myLocation.latitude != 0) {
|
|
// استدعِ دالة المعالجة الثقيلة هنا
|
|
onLocationUpdated(pos!);
|
|
}
|
|
});
|
|
// _posSub = Geolocator.getPositionStream(
|
|
// locationSettings: const LocationSettings(
|
|
// accuracy: LocationAccuracy.high,
|
|
// distanceFilter: 5, // حدّث كل ~5 متر لتقليل الاهتزاز
|
|
// ),
|
|
// ).listen((pos) => _onLocationTick(LatLng(pos.latitude, pos.longitude)));
|
|
}
|
|
|
|
void stopListeningStepNavigation() {
|
|
_posSub?.cancel();
|
|
_posSub = null;
|
|
}
|
|
}
|