6173 lines
218 KiB
Dart
6173 lines
218 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'dart:math' show Random, atan2, cos, max, min, pi, pow, sin, sqrt;
|
|
import 'dart:math' as math;
|
|
import 'dart:ui';
|
|
import 'dart:convert';
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import 'package:Intaleq/constant/univeries_polygon.dart';
|
|
import 'package:Intaleq/controller/firebase/local_notification.dart';
|
|
import 'package:Intaleq/controller/functions/encrypt_decrypt.dart';
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter_confetti/flutter_confetti.dart';
|
|
import 'package:vector_math/vector_math.dart' show radians, degrees;
|
|
|
|
import 'package:Intaleq/controller/functions/tts.dart';
|
|
import 'package:Intaleq/views/home/map_page_passenger.dart';
|
|
import 'package:Intaleq/views/widgets/my_textField.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:intl/intl.dart';
|
|
import 'package:location/location.dart';
|
|
import 'package:Intaleq/constant/colors.dart';
|
|
import 'package:Intaleq/constant/style.dart';
|
|
import 'package:Intaleq/controller/home/points_for_rider_controller.dart';
|
|
import 'package:Intaleq/views/home/map_widget.dart/form_serch_multiy_point.dart';
|
|
import '../../constant/api_key.dart';
|
|
import '../../constant/box_name.dart';
|
|
import '../../constant/info.dart';
|
|
import '../../constant/links.dart';
|
|
import '../../constant/table_names.dart';
|
|
import '../../main.dart';
|
|
import '../../models/model/locations.dart';
|
|
import '../../models/model/painter_copoun.dart';
|
|
import '../../print.dart';
|
|
import '../../views/home/map_widget.dart/cancel_raide_page.dart';
|
|
import '../../views/home/map_widget.dart/car_details_widget_to_go.dart';
|
|
import '../../views/home/map_widget.dart/searching_captain_window.dart';
|
|
import '../../views/home/map_widget.dart/select_driver_mishwari.dart';
|
|
import '../../views/widgets/elevated_btn.dart';
|
|
import '../../views/widgets/error_snakbar.dart';
|
|
import '../../views/widgets/mydialoug.dart';
|
|
import '../firebase/firbase_messge.dart';
|
|
import '../firebase/notification_service.dart';
|
|
import '../functions/audio_record1.dart';
|
|
import '../functions/crud.dart';
|
|
import '../functions/launch.dart';
|
|
import '../functions/package_info.dart';
|
|
import '../functions/secure_storage.dart';
|
|
import '../payment/payment_controller.dart';
|
|
import 'decode_polyline_isolate.dart';
|
|
import 'deep_link_controller.dart';
|
|
import 'device_tier.dart';
|
|
import 'vip_waitting_page.dart';
|
|
|
|
class MapPassengerController extends GetxController {
|
|
bool isLoading = true;
|
|
TextEditingController placeDestinationController = TextEditingController();
|
|
TextEditingController increasFeeFromPassenger = TextEditingController();
|
|
TextEditingController placeStartController = TextEditingController();
|
|
TextEditingController wayPoint0Controller = TextEditingController();
|
|
TextEditingController wayPoint1Controller = TextEditingController();
|
|
TextEditingController wayPoint2Controller = TextEditingController();
|
|
TextEditingController wayPoint3Controller = TextEditingController();
|
|
TextEditingController wayPoint4Controller = TextEditingController();
|
|
TextEditingController sosPhonePassengerProfile = TextEditingController();
|
|
TextEditingController whatsAppLocationText = TextEditingController();
|
|
TextEditingController messageToDriver = TextEditingController();
|
|
final sosFormKey = GlobalKey<FormState>();
|
|
final promoFormKey = GlobalKey<FormState>();
|
|
final messagesFormKey = GlobalKey<FormState>();
|
|
final increaseFeeFormKey = GlobalKey<FormState>();
|
|
List data = [];
|
|
List<LatLng> bounds = [];
|
|
List placesStart = [];
|
|
List<String> driversToken = [];
|
|
LatLng previousLocationOfDrivers = const LatLng(0, 0);
|
|
double angleDegrees = 0;
|
|
LatLng currentLocationOfDrivers = const LatLng(0, 0);
|
|
List<TextEditingController> allTextEditingPlaces = [];
|
|
List placesDestination = [];
|
|
List wayPoint0 = [];
|
|
List wayPoint1 = [];
|
|
List wayPoint2 = [];
|
|
List wayPoint3 = [];
|
|
List wayPoint4 = [];
|
|
final firebaseMessagesController =
|
|
Get.isRegistered<FirebaseMessagesController>()
|
|
? Get.find<FirebaseMessagesController>()
|
|
: Get.put(FirebaseMessagesController());
|
|
List<List<dynamic>> placeListResponseAll = [];
|
|
|
|
List<Widget> placeListResponse = [
|
|
formSearchPlaces(0),
|
|
formSearchPlaces(1),
|
|
formSearchPlaces(2),
|
|
formSearchPlaces(3),
|
|
];
|
|
LatLngBounds? boundsdata;
|
|
List<Marker> markers = [];
|
|
List<Polyline> polyLines = [];
|
|
late LatLng passengerLocation = const LatLng(32, 34);
|
|
late LatLng newMyLocation = const LatLng(32.115295, 36.064773);
|
|
late LatLng newStartPointLocation = const LatLng(32.115295, 36.064773);
|
|
late LatLng newPointLocation0 = const LatLng(32.115295, 36.064773);
|
|
late LatLng newPointLocation1 = const LatLng(32.115295, 36.064773);
|
|
late LatLng newPointLocation2 = const LatLng(32.115295, 36.064773);
|
|
late LatLng newPointLocation3 = const LatLng(32.115295, 36.064773);
|
|
late LatLng newPointLocation4 = const LatLng(32.115295, 36.064773);
|
|
late LatLng myDestination;
|
|
List<LatLng> polylineCoordinates = [];
|
|
List<LatLng> polylineCoordinates0 = [];
|
|
List<LatLng> polylineCoordinates1 = [];
|
|
List<LatLng> polylineCoordinates2 = [];
|
|
List<LatLng> polylineCoordinates3 = [];
|
|
List<LatLng> polylineCoordinates4 = [];
|
|
List<List<LatLng>> polylineCoordinatesPointsAll = [];
|
|
List carsLocationByPassenger = [];
|
|
List<LatLng> driverCarsLocationToPassengerAfterApplied = [];
|
|
BitmapDescriptor markerIcon = BitmapDescriptor.defaultMarker;
|
|
BitmapDescriptor tripIcon = BitmapDescriptor.defaultMarker;
|
|
BitmapDescriptor startIcon = BitmapDescriptor.defaultMarker;
|
|
BitmapDescriptor endIcon = BitmapDescriptor.defaultMarker;
|
|
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
|
|
BitmapDescriptor motoIcon = BitmapDescriptor.defaultMarker;
|
|
BitmapDescriptor ladyIcon = BitmapDescriptor.defaultMarker;
|
|
double height = 150;
|
|
DateTime currentTime = DateTime.now();
|
|
final location = Location();
|
|
late LocationData currentLocation;
|
|
double heightMenu = 0;
|
|
double widthMenu = 0;
|
|
double heightPickerContainer = 90;
|
|
double heightPointsPageForRider = 0;
|
|
double mainBottomMenuMapHeight = Get.height * .2;
|
|
double wayPointSheetHeight = 0;
|
|
String stringRemainingTimeToPassenger = '';
|
|
String stringRemainingTimeDriverWaitPassenger5Minute = '';
|
|
bool isDriverInPassengerWay = false;
|
|
bool isDriverArrivePassenger = false;
|
|
bool startLocationFromMap = false;
|
|
bool isAnotherOreder = false;
|
|
bool isWhatsAppOrder = false;
|
|
bool passengerStartLocationFromMap = false;
|
|
bool workLocationFromMap = false;
|
|
bool homeLocationFromMap = false;
|
|
bool isPassengerRideLocationWidget = false;
|
|
bool startLocationFromMap0 = false;
|
|
bool startLocationFromMap1 = false;
|
|
bool startLocationFromMap2 = false;
|
|
bool startLocationFromMap3 = false;
|
|
bool startLocationFromMap4 = false;
|
|
List startLocationFromMapAll = [];
|
|
double latePrice = 0;
|
|
double fuelPrice = 0;
|
|
double heavyPrice = 0;
|
|
double naturePrice = 0;
|
|
bool heightMenuBool = false;
|
|
String statusRide = 'wait';
|
|
String statusRideVip = 'wait';
|
|
bool statusRideFromStart = false;
|
|
bool isPickerShown = false;
|
|
bool isPointsPageForRider = false;
|
|
bool isBottomSheetShown = false;
|
|
bool mapType = false;
|
|
bool mapTrafficON = false;
|
|
bool isCancelRidePageShown = false;
|
|
bool isCashConfirmPageShown = false;
|
|
bool isPaymentMethodPageShown = false;
|
|
bool isRideFinished = false;
|
|
bool rideConfirm = false;
|
|
bool isMarkersShown = false;
|
|
bool isMainBottomMenuMap = true;
|
|
Timer? markerReloadingTimer2 = Timer(Duration.zero, () {});
|
|
Timer? markerReloadingTimer1 = Timer(Duration.zero, () {});
|
|
int durationToPassenger = 0;
|
|
bool isWayPointSheet = false;
|
|
bool isWayPointStopsSheet = false;
|
|
bool isWayPointStopsSheetUtilGetMap = false;
|
|
double heightBottomSheetShown = 0;
|
|
double cashConfirmPageShown = 250;
|
|
late String driverId = '';
|
|
late String gender = '';
|
|
double widthMapTypeAndTraffic = 50;
|
|
double paymentPageShown = Get.height * .6;
|
|
late LatLng southwest;
|
|
late LatLng northeast;
|
|
List<CarLocationModel> carLocationsModels = <CarLocationModel>[];
|
|
var dataCarsLocationByPassenger;
|
|
var datadriverCarsLocationToPassengerAfterApplied;
|
|
CarLocation? nearestCar;
|
|
late Timer? markerReloadingTimer = Timer(Duration.zero, () {});
|
|
bool shouldFetch = true; // Flag to determine if fetch should be executed
|
|
int selectedPassengerCount = 1;
|
|
double progress = 0;
|
|
double progressTimerToPassengerFromDriverAfterApplied = 0;
|
|
double progressTimerDriverWaitPassenger5Minute = 0;
|
|
int durationTimer = 9;
|
|
int durationToRide = 0;
|
|
int remainingTime = 25;
|
|
int remainingTimeToPassengerFromDriverAfterApplied = 60;
|
|
int remainingTimeDriverWaitPassenger5Minute = 60;
|
|
int timeToPassengerFromDriverAfterApplied = 0;
|
|
Timer? timerToPassengerFromDriverAfterApplied;
|
|
bool rideTimerBegin = false;
|
|
double progressTimerRideBegin = 0;
|
|
int remainingTimeTimerRideBegin = 60;
|
|
String stringRemainingTimeRideBegin = '';
|
|
String hintTextStartPoint = 'Search for your Start point'.tr;
|
|
String hintTextwayPoint0 = 'Search for waypoint'.tr;
|
|
String hintTextwayPoint1 = 'Search for waypoint'.tr;
|
|
String hintTextwayPoint2 = 'Search for waypoint'.tr;
|
|
String hintTextwayPoint3 = 'Search for waypoint'.tr;
|
|
String hintTextwayPoint4 = 'Search for waypoint'.tr;
|
|
String currentLocationString = 'Current Location'.tr;
|
|
String currentLocationString0 = 'Current Location'.tr;
|
|
String currentLocationString1 = 'Add Location 1'.tr;
|
|
String currentLocationString2 = 'Add Location 2'.tr;
|
|
String currentLocationString3 = 'Add Location 3'.tr;
|
|
String currentLocationString4 = 'Add Location 4'.tr;
|
|
String placesCoordinate0 = ''.tr;
|
|
String placesCoordinate1 = ''.tr;
|
|
String placesCoordinate2 = ''.tr;
|
|
String placesCoordinate3 = ''.tr;
|
|
String placesCoordinate4 = ''.tr;
|
|
List<String> currentLocationStringAll = [];
|
|
List<String> hintTextwayPointStringAll = [];
|
|
var placesCoordinate = <String>[];
|
|
String hintTextDestinationPoint = 'Select your destination'.tr;
|
|
late String rideId = 'yet';
|
|
bool noCarString = false;
|
|
bool isCashSelectedBeforeConfirmRide = false;
|
|
bool isPassengerChosen = false;
|
|
bool isSearchingWindow = false;
|
|
bool currentLocationToFormPlaces = false;
|
|
bool currentLocationToFormPlaces0 = false;
|
|
bool currentLocationToFormPlaces1 = false;
|
|
bool currentLocationToFormPlaces2 = false;
|
|
bool currentLocationToFormPlaces3 = false;
|
|
bool currentLocationToFormPlaces4 = false;
|
|
List currentLocationToFormPlacesAll = [];
|
|
late String driverToken = '';
|
|
int carsOrder = 0;
|
|
int wayPointIndex = 0;
|
|
late double kazan = 8;
|
|
String? mapAPIKEY;
|
|
late double totalME = 0;
|
|
late double tax = 0;
|
|
late double totalPassenger = 0;
|
|
late double totalCostPassenger = 0;
|
|
late double totalPassengerComfort = 0;
|
|
late double totalPassengerComfortDiscount = 0;
|
|
late double totalPassengerElectricDiscount = 0;
|
|
late double totalPassengerLadyDiscount = 0;
|
|
late double totalPassengerSpeedDiscount = 0;
|
|
late double totalPassengerBalashDiscount = 0;
|
|
late double totalPassengerRaihGaiDiscount = 0;
|
|
late double totalPassengerScooter = 0;
|
|
late double totalPassengerVan = 0;
|
|
late double totalDriver = 0;
|
|
late double averageDuration = 0;
|
|
late double costDuration = 0;
|
|
late double costDistance = 0;
|
|
late double distance = 0;
|
|
late double duration = 0;
|
|
|
|
late Duration durationToAdd;
|
|
late DateTime newTime = DateTime.now();
|
|
int hours = 0;
|
|
int minutes = 0;
|
|
|
|
// --- إضافة جديدة: للوصول إلى وحدة التحكم بالروابط ---
|
|
final DeepLinkController _deepLinkController =
|
|
Get.isRegistered<DeepLinkController>()
|
|
? Get.find<DeepLinkController>()
|
|
: Get.put(DeepLinkController());
|
|
// ------------------------------------------------
|
|
|
|
void onChangedPassengerCount(int newValue) {
|
|
selectedPassengerCount = newValue;
|
|
update();
|
|
}
|
|
|
|
void onChangedPassengersChoose() {
|
|
isPassengerChosen = true;
|
|
update();
|
|
}
|
|
|
|
void getCurrentLocationFormString() async {
|
|
currentLocationToFormPlaces = true;
|
|
currentLocationString = 'Waiting for your location'.tr;
|
|
await getLocation();
|
|
currentLocationString = passengerLocation.toString();
|
|
newStartPointLocation = passengerLocation;
|
|
update();
|
|
}
|
|
|
|
List<String> coordinatesWithoutEmpty = [];
|
|
void getMapPointsForAllMethods() async {
|
|
clearPolyline();
|
|
isMarkersShown = false;
|
|
isWayPointStopsSheetUtilGetMap = false;
|
|
isWayPointSheet = false;
|
|
durationToRide = 0;
|
|
distanceOfDestination = 0;
|
|
wayPointSheetHeight = 0;
|
|
remainingTime = 25;
|
|
haveSteps = true;
|
|
|
|
// Filter out empty value
|
|
coordinatesWithoutEmpty =
|
|
placesCoordinate.where((coord) => coord.isNotEmpty).toList();
|
|
latestPosition = LatLng(
|
|
double.parse(coordinatesWithoutEmpty.last.split(',')[0]),
|
|
double.parse(coordinatesWithoutEmpty.last.split(',')[1]));
|
|
for (var i = 0; i < coordinatesWithoutEmpty.length; i++) {
|
|
if ((i + 1) < coordinatesWithoutEmpty.length) {
|
|
await getMapPoints(
|
|
coordinatesWithoutEmpty[i].toString(),
|
|
coordinatesWithoutEmpty[i + 1].toString(),
|
|
i,
|
|
);
|
|
if (i == 0) {
|
|
startNameAddress = data[0]['start_address'];
|
|
}
|
|
if (i == coordinatesWithoutEmpty.length) {
|
|
endNameAddress = data[0]['end_address'];
|
|
}
|
|
}
|
|
}
|
|
|
|
// isWayPointStopsSheet = false;
|
|
if (haveSteps) {
|
|
String latestWaypoint =
|
|
placesCoordinate.lastWhere((coord) => coord.isNotEmpty);
|
|
latestPosition = LatLng(
|
|
double.parse(latestWaypoint.split(',')[0]),
|
|
double.parse(latestWaypoint.split(',')[1]),
|
|
);
|
|
}
|
|
updateCameraForDistanceAfterGetMap();
|
|
changeWayPointStopsSheet();
|
|
bottomSheet();
|
|
showBottomSheet1();
|
|
|
|
update();
|
|
}
|
|
|
|
void convertHintTextStartNewPlaces(int index) {
|
|
if (placesStart.isEmpty) {
|
|
hintTextStartPoint = 'Search for your Start point'.tr;
|
|
update();
|
|
} else {
|
|
var res = placesStart[index];
|
|
|
|
hintTextStartPoint = res['displayName']?['text'] ??
|
|
res['formattedAddress'] ??
|
|
'Unknown Place';
|
|
|
|
double? lat = res['location']?['latitude'];
|
|
double? lng = res['location']?['longitude'];
|
|
|
|
if (lat != null && lng != null) {
|
|
newStartPointLocation = LatLng(lat, lng);
|
|
}
|
|
|
|
update();
|
|
}
|
|
}
|
|
|
|
void convertHintTextPlaces(int index, var res) {
|
|
if (placeListResponseAll[index].isEmpty) {
|
|
placeListResponseAll[index] = res;
|
|
hintTextwayPointStringAll[index] = 'Search for your Start point'.tr;
|
|
update();
|
|
} else {
|
|
hintTextwayPointStringAll[index] = res['name'];
|
|
currentLocationStringAll[index] = res['name'];
|
|
placesCoordinate[index] =
|
|
'${res['geometry']['location']['lat']},${res['geometry']['location']['lng']}';
|
|
placeListResponseAll[index] = [];
|
|
allTextEditingPlaces[index].clear();
|
|
// double lat = wayPoint0[index]['geometry']['location']['lat'];
|
|
// double lng = wayPoint0[index]['geometry']['location']['lng'];
|
|
// newPointLocation0 = LatLng(lat, lng);
|
|
update();
|
|
Get.back();
|
|
}
|
|
}
|
|
|
|
increaseFeeByPassengerAndReOrder() async {
|
|
if (increaseFeeFormKey.currentState!.validate()) {
|
|
if (double.parse(increasFeeFromPassenger.text) > totalPassenger) {
|
|
totalPassenger = double.parse(increasFeeFromPassenger.text);
|
|
Get.back();
|
|
if (rideId != 'yet') {
|
|
Log.print('rideId from increase: $rideId');
|
|
notifyAvailableDriversAgain();
|
|
await CRUD().post(link: AppLink.updateDriverOrder, payload: {
|
|
"order_id": rideId.toString(), // Convert to String
|
|
"status": 'waiting'
|
|
});
|
|
if (AppLink.endPoint != AppLink.IntaleqCairoServer) {
|
|
CRUD().post(
|
|
link: "${AppLink.endPoint}/ride/driver_order/update.php",
|
|
payload: {
|
|
"order_id": rideId.toString(), // Convert to String
|
|
"status": 'waiting'
|
|
});
|
|
}
|
|
await CRUD().post(link: AppLink.updateRides, payload: {
|
|
"id": rideId.toString(), // Convert to String
|
|
"status": 'waiting'
|
|
});
|
|
if (AppLink.endPoint != AppLink.IntaleqCairoServer) {
|
|
CRUD().post(
|
|
link: "${AppLink.endPoint}/ride/rides/update.php",
|
|
payload: {
|
|
"id": rideId.toString(), // Convert to String
|
|
"status": 'waiting'
|
|
});
|
|
}
|
|
CRUD().post(link: AppLink.updateWaitingTrip, payload: {
|
|
"id": rideId.toString(), // Convert to String
|
|
"status": 'wait'
|
|
});
|
|
if (AppLink.endPoint != AppLink.IntaleqCairoServer) {
|
|
CRUD().post(
|
|
link:
|
|
"${AppLink.endPoint}/ride/notificationCaptain/updateWaitingTrip.php",
|
|
payload: {
|
|
"id": rideId.toString(), // Convert to String
|
|
"status": 'wait'
|
|
});
|
|
}
|
|
tick = 0;
|
|
}
|
|
await getCarsLocationByPassengerAndReloadMarker(
|
|
box.read(BoxName.carType), 4000);
|
|
// confirmRideForAllDriverAvailable();
|
|
|
|
increaseForSameRideAndDelay();
|
|
}
|
|
}
|
|
}
|
|
|
|
void convertHintTextPlaces1(int index) {
|
|
if (wayPoint1.isEmpty) {
|
|
hintTextwayPoint1 = 'Search for your Start point'.tr;
|
|
update();
|
|
} else {
|
|
hintTextwayPoint1 = wayPoint1[index]['name'];
|
|
currentLocationString1 = wayPoint1[index]['name'];
|
|
double lat = wayPoint1[index]['geometry']['location']['lat'];
|
|
double lng = wayPoint1[index]['geometry']['location']['lng'];
|
|
newPointLocation1 = LatLng(lat, lng);
|
|
update();
|
|
}
|
|
}
|
|
|
|
void convertHintTextPlaces2(int index) {
|
|
if (wayPoint1.isEmpty) {
|
|
hintTextwayPoint2 = 'Search for your Start point'.tr;
|
|
update();
|
|
} else {
|
|
hintTextwayPoint2 = wayPoint2[index]['name'];
|
|
currentLocationString2 = wayPoint1[index]['name'];
|
|
double lat = wayPoint2[index]['geometry']['location']['lat'];
|
|
double lng = wayPoint2[index]['geometry']['location']['lng'];
|
|
newPointLocation2 = LatLng(lat, lng);
|
|
update();
|
|
}
|
|
}
|
|
|
|
void convertHintTextPlaces3(int index) {
|
|
if (wayPoint1.isEmpty) {
|
|
hintTextwayPoint3 = 'Search for your Start point'.tr;
|
|
update();
|
|
} else {
|
|
hintTextwayPoint3 = wayPoint3[index]['name'];
|
|
currentLocationString3 = wayPoint1[index]['name'];
|
|
double lat = wayPoint3[index]['geometry']['location']['lat'];
|
|
double lng = wayPoint3[index]['geometry']['location']['lng'];
|
|
newPointLocation3 = LatLng(lat, lng);
|
|
update();
|
|
}
|
|
}
|
|
|
|
void convertHintTextPlaces4(int index) {
|
|
if (wayPoint1.isEmpty) {
|
|
hintTextwayPoint4 = 'Search for your Start point'.tr;
|
|
update();
|
|
} else {
|
|
hintTextwayPoint4 = wayPoint4[index]['name'];
|
|
currentLocationString4 = wayPoint1[index]['name'];
|
|
double lat = wayPoint4[index]['geometry']['location']['lat'];
|
|
double lng = wayPoint4[index]['geometry']['location']['lng'];
|
|
newPointLocation4 = LatLng(lat, lng);
|
|
update();
|
|
}
|
|
}
|
|
|
|
void convertHintTextDestinationNewPlaces(int index) {
|
|
if (placesDestination.isEmpty) {
|
|
hintTextDestinationPoint = 'Search for your destination'.tr;
|
|
update();
|
|
} else {
|
|
var res = placesDestination[index];
|
|
|
|
// استخراج الاسم من displayName.text أو بديله
|
|
hintTextDestinationPoint = res['displayName']?['text'] ??
|
|
res['formattedAddress'] ??
|
|
'Unknown Place';
|
|
|
|
// استخراج الإحداثيات
|
|
double? lat = res['location']?['latitude'];
|
|
double? lng = res['location']?['longitude'];
|
|
|
|
if (lat != null && lng != null) {
|
|
newMyLocation = LatLng(lat, lng);
|
|
}
|
|
|
|
update();
|
|
}
|
|
}
|
|
|
|
void convertHintTextDestinationNewPlacesFromRecent(
|
|
List recentLocations, int index) {
|
|
hintTextDestinationPoint = recentLocations[index]['name'];
|
|
double lat = recentLocations[index]['latitude'];
|
|
double lng = recentLocations[index]['longitude'];
|
|
newMyLocation = LatLng(lat, lng);
|
|
|
|
update();
|
|
}
|
|
|
|
// final mainBottomMenuMap = GlobalKey<AnimatedContainer>();
|
|
void changeBottomSheetShown() {
|
|
isBottomSheetShown = !isBottomSheetShown;
|
|
heightBottomSheetShown = isBottomSheetShown == true ? 250 : 0;
|
|
update();
|
|
}
|
|
|
|
void changeCashConfirmPageShown() {
|
|
isCashConfirmPageShown = !isCashConfirmPageShown;
|
|
isCashSelectedBeforeConfirmRide = true;
|
|
cashConfirmPageShown = isCashConfirmPageShown == true ? 250 : 0;
|
|
// to get or sure picker point for origin //todo
|
|
// isPickerShown = true;
|
|
// clickPointPosition();
|
|
update();
|
|
}
|
|
|
|
void changePaymentMethodPageShown() {
|
|
isPaymentMethodPageShown = !isPaymentMethodPageShown;
|
|
paymentPageShown = isPaymentMethodPageShown == true ? Get.height * .6 : 0;
|
|
update();
|
|
}
|
|
|
|
void changeMapType() {
|
|
mapType = !mapType;
|
|
// heightButtomSheetShown = isButtomSheetShown == true ? 240 : 0;
|
|
update();
|
|
}
|
|
|
|
void changeMapTraffic() {
|
|
mapTrafficON = !mapTrafficON;
|
|
update();
|
|
}
|
|
|
|
void changeisAnotherOreder(bool val) {
|
|
isAnotherOreder = val;
|
|
update();
|
|
}
|
|
|
|
void changeIsWhatsAppOrder(bool val) {
|
|
isWhatsAppOrder = val;
|
|
update();
|
|
}
|
|
|
|
void sendSMS(String to) async {
|
|
// Get the driver's phone number.
|
|
String driverPhone =
|
|
(dataCarsLocationByPassenger['message'][carsOrder]['phone'].toString());
|
|
|
|
// Format the message.
|
|
String message =
|
|
'Hi! This is ${(box.read(BoxName.name).toString().split(' ')[0]).toString()}.\n I am using ${box.read(AppInformation.appName)} to ride with $passengerName as the driver. $passengerName \nis driving a $model\n with license plate $licensePlate.\n I am currently located at $passengerLocation.\n If you need to reach me, please contact the driver directly at\n\n $driverPhone.';
|
|
|
|
// Launch the URL to send the SMS.
|
|
launchCommunication('sms', to, message);
|
|
}
|
|
|
|
void sendWhatsapp(String to) async {
|
|
// Get the driver's phone number.
|
|
// String driverPhone = dataCarsLocationByPassenger['message'][carsOrder]['phone'].toString();
|
|
|
|
// Format the message.
|
|
String message =
|
|
'${'${'Hi! This is'.tr} ${(box.read(BoxName.name).toString().split(' ')[0]).toString()}.\n${' I am using'.tr}'} ${AppInformation.appName}${' to ride with'.tr} $passengerName${' as the driver.'.tr} $passengerName \n${'is driving a '.tr}$model\n${' with license plate '.tr}$licensePlate.\n${' I am currently located at '.tr} https://www.google.com/maps/place/${passengerLocation.latitude},${passengerLocation.longitude}.\n${' If you need to reach me, please contact the driver directly at'.tr}\n\n $driverPhone.';
|
|
|
|
// Launch the URL to send the WhatsApp message.
|
|
launchCommunication('whatsapp', to, message);
|
|
}
|
|
|
|
void changeCancelRidePageShow() {
|
|
showCancelRideBottomSheet();
|
|
isCancelRidePageShown = !isCancelRidePageShown;
|
|
// : cancelRide();
|
|
update();
|
|
}
|
|
|
|
void getDrawerMenu() {
|
|
heightMenuBool = !heightMenuBool;
|
|
widthMapTypeAndTraffic = heightMenuBool == true ? 0 : 50;
|
|
heightMenu = heightMenuBool == true ? 80 : 0;
|
|
widthMenu = heightMenuBool == true ? 110 : 0;
|
|
update();
|
|
}
|
|
|
|
calcualateDistsanceInMetet(LatLng prev, current) async {
|
|
double distance2 = Geolocator.distanceBetween(
|
|
prev.latitude,
|
|
prev.longitude,
|
|
current.latitude,
|
|
current.longitude,
|
|
);
|
|
return distance2;
|
|
}
|
|
|
|
// bool isTimerFromDriverToPassengerAfterAppliedRunning = true;
|
|
// int beginRideInterval = 4; // Interval in seconds for getBeginRideFromDriver
|
|
|
|
// void startTimerFromDriverToPassengerAfterApplied() async {
|
|
// int secondsElapsed = 0;
|
|
|
|
// while (secondsElapsed <= timeToPassengerFromDriverAfterApplied &&
|
|
// isTimerFromDriverToPassengerAfterAppliedRunning) {
|
|
// await Future.delayed(const Duration(seconds: 1));
|
|
// secondsElapsed++;
|
|
|
|
// progressTimerToPassengerFromDriverAfterApplied =
|
|
// secondsElapsed / timeToPassengerFromDriverAfterApplied;
|
|
// remainingTimeToPassengerFromDriverAfterApplied =
|
|
// timeToPassengerFromDriverAfterApplied - secondsElapsed;
|
|
|
|
// if (remainingTimeToPassengerFromDriverAfterApplied < 59) {
|
|
// if (rideTimerBegin == false) {
|
|
// rideTimerBegin = true;
|
|
// }
|
|
// }
|
|
|
|
// // Call getBeginRideFromDriver every 4 seconds
|
|
// if (secondsElapsed % beginRideInterval == 0) {
|
|
// getBeginRideFromDriver();
|
|
// uploadPassengerLocation();
|
|
// }
|
|
|
|
// int minutes =
|
|
// (remainingTimeToPassengerFromDriverAfterApplied / 60).floor();
|
|
// int seconds = remainingTimeToPassengerFromDriverAfterApplied % 60;
|
|
// stringRemainingTimeToPassenger =
|
|
// '$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
|
|
// update();
|
|
// }
|
|
// }
|
|
|
|
StreamController<int> _timerStreamController = StreamController<int>();
|
|
Stream<int> get timerStream => _timerStreamController.stream;
|
|
bool isTimerFromDriverToPassengerAfterAppliedRunning = true;
|
|
bool isTimerRunning = false; // Flag to track if the timer is running
|
|
int beginRideInterval = 4; // Interval in seconds for getBeginRideFromDriver
|
|
|
|
void startTimerFromDriverToPassengerAfterApplied() {
|
|
if (isTimerRunning) return; // Prevent duplicate streams
|
|
isTimerRunning = true;
|
|
|
|
int secondsElapsed = 0;
|
|
|
|
// Start the stream
|
|
Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
if (secondsElapsed > timeToPassengerFromDriverAfterApplied ||
|
|
!isTimerFromDriverToPassengerAfterAppliedRunning) {
|
|
timer.cancel();
|
|
isTimerRunning = false;
|
|
_timerStreamController.close(); // Close the stream when done
|
|
return;
|
|
}
|
|
|
|
secondsElapsed++;
|
|
_timerStreamController.add(secondsElapsed); // Emit elapsed time
|
|
|
|
// Calculate progress and remaining time
|
|
progressTimerToPassengerFromDriverAfterApplied =
|
|
secondsElapsed / timeToPassengerFromDriverAfterApplied;
|
|
remainingTimeToPassengerFromDriverAfterApplied =
|
|
timeToPassengerFromDriverAfterApplied - secondsElapsed;
|
|
|
|
// Update remaining time as string
|
|
int minutes =
|
|
(remainingTimeToPassengerFromDriverAfterApplied / 60).floor();
|
|
int seconds = remainingTimeToPassengerFromDriverAfterApplied % 60;
|
|
stringRemainingTimeToPassenger =
|
|
'$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
|
|
if (remainingTimeToPassengerFromDriverAfterApplied < 59 &&
|
|
!rideTimerBegin) {
|
|
rideTimerBegin = true;
|
|
}
|
|
|
|
// Call periodic functions
|
|
if (secondsElapsed % beginRideInterval == 0) {
|
|
getBeginRideFromDriver();
|
|
uploadPassengerLocation();
|
|
}
|
|
|
|
update(); // Notify listeners
|
|
});
|
|
}
|
|
|
|
// void startTimerFromDriverToPassengerAfterApplied() async {
|
|
// if (isTimerRunning) return; // Exit if timer is already running
|
|
// isTimerRunning = true; // Set the flag to true
|
|
|
|
// int secondsElapsed = 0;
|
|
// while (secondsElapsed <= timeToPassengerFromDriverAfterApplied &&
|
|
// isTimerFromDriverToPassengerAfterAppliedRunning) {
|
|
// await Future.delayed(const Duration(seconds: 1));
|
|
// secondsElapsed++;
|
|
|
|
// progressTimerToPassengerFromDriverAfterApplied =
|
|
// secondsElapsed / timeToPassengerFromDriverAfterApplied;
|
|
// remainingTimeToPassengerFromDriverAfterApplied =
|
|
// timeToPassengerFromDriverAfterApplied - secondsElapsed;
|
|
|
|
// if (remainingTimeToPassengerFromDriverAfterApplied < 59) {
|
|
// if (rideTimerBegin == false) {
|
|
// rideTimerBegin = true;
|
|
// }
|
|
// }
|
|
|
|
// // Call getBeginRideFromDriver every 4 seconds
|
|
// if (secondsElapsed % beginRideInterval == 0) {
|
|
// getBeginRideFromDriver();
|
|
// uploadPassengerLocation();
|
|
// }
|
|
|
|
// int minutes =
|
|
// (remainingTimeToPassengerFromDriverAfterApplied / 60).floor();
|
|
// int seconds = remainingTimeToPassengerFromDriverAfterApplied % 60;
|
|
// stringRemainingTimeToPassenger =
|
|
// '$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
|
|
// update();
|
|
// }
|
|
// isTimerRunning = false; // Reset the flag when timer completes
|
|
// }
|
|
// Remove the getBeginRideFromDriverForDuration function as it's no longer needed
|
|
|
|
// Function to stop the timer
|
|
void stopTimerFromDriverToPassengerAfterApplied() {
|
|
isTimerFromDriverToPassengerAfterAppliedRunning = false;
|
|
update();
|
|
}
|
|
|
|
void startTimerDriverWaitPassenger5Minute() async {
|
|
stopTimerFromDriverToPassengerAfterApplied();
|
|
isDriverArrivePassenger = true;
|
|
isDriverInPassengerWay = false;
|
|
timeToPassengerFromDriverAfterApplied = 0;
|
|
update();
|
|
for (int i = 0; i <= 300; i++) {
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
progressTimerDriverWaitPassenger5Minute = i / 300;
|
|
remainingTimeDriverWaitPassenger5Minute = 300 - i;
|
|
|
|
int minutes = (remainingTimeDriverWaitPassenger5Minute / 60).floor();
|
|
int seconds = remainingTimeDriverWaitPassenger5Minute % 60;
|
|
stringRemainingTimeDriverWaitPassenger5Minute =
|
|
'$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
|
|
update();
|
|
}
|
|
}
|
|
|
|
// Create a StreamController to manage the timer values
|
|
final timerController = StreamController<int>();
|
|
|
|
// Start the timer when the ride begins
|
|
void beginRideTimer() {
|
|
// Set up the timer to run every second
|
|
Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
// Update the timer value and notify listeners
|
|
timerController.add(timer.tick);
|
|
update();
|
|
});
|
|
}
|
|
|
|
// Stop the timer when the ride ends
|
|
void stopRideTimer() {
|
|
timerController.close();
|
|
update();
|
|
}
|
|
|
|
late String arrivalTime = '';
|
|
void rideIsBeginPassengerTimer() async {
|
|
// Calculate arrival time considering current time and duration
|
|
DateTime now = DateTime.now();
|
|
DateTime arrivalTime1 = now.add(Duration(seconds: durationToRide));
|
|
arrivalTime = DateFormat('hh:mm').format(arrivalTime1);
|
|
box.write(BoxName.arrivalTime, arrivalTime);
|
|
for (int i = 0; i <= durationToRide; i++) {
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
progressTimerRideBegin = i / durationToRide;
|
|
remainingTimeTimerRideBegin = durationToRide - i;
|
|
if (i == (durationToRide / 4).round() && (statusRide == 'Begin')) {
|
|
NotificationController().showNotification("Record Your Trip".tr,
|
|
"You can call or record audio during this trip.".tr, 'tone1');
|
|
}
|
|
bool sendSOS = false;
|
|
if (speed > 100 && sendSOS == false) {
|
|
NotificationController().showNotification(
|
|
"Warning: Speeding detected!".tr,
|
|
'You can call or record audio of this trip'.tr,
|
|
'tone1');
|
|
Get.defaultDialog(
|
|
barrierDismissible: false,
|
|
title: "Warning: Speeding detected!".tr,
|
|
titleStyle: AppStyle.title,
|
|
content: Text(
|
|
"We noticed the speed is exceeding 100 km/h. Please slow down for your safety. If you feel unsafe, you can share your trip details with a contact or call the police using the red SOS button."
|
|
.tr,
|
|
style: AppStyle.title,
|
|
),
|
|
confirm: MyElevatedButton(
|
|
title: "Share Trip Details".tr,
|
|
onPressed: () {
|
|
Get.back();
|
|
// Implement sharing trip details logic here
|
|
String message = "**Emergency SOS from Passenger:**\n";
|
|
|
|
// Get trip details from GetX or relevant provider
|
|
String origin = passengerLocation.toString();
|
|
String destination = myDestination.toString();
|
|
String driverName = passengerName;
|
|
String driverCarPlate = licensePlate;
|
|
|
|
// Add trip details to the message
|
|
message += "* ${'Origin'.tr}: $origin\n";
|
|
message += "* ${'Destination'.tr}: $destination\n";
|
|
message += "* ${'Driver Name'.tr}: $driverName\n";
|
|
message += "* ${'Driver Car Plate'.tr}: $driverCarPlate\n\n";
|
|
message += "* ${'Driver phone'.tr}:$driverPhone\n\n";
|
|
|
|
// Add any additional information you want to include (optional)
|
|
// - Example: current location (using GetX LocationController)
|
|
message +=
|
|
"${'Current Location'.tr}:https://www.google.com/maps/place/${passengerLocation.latitude},${passengerLocation.longitude} \n";
|
|
|
|
// Append a call to action
|
|
message += "Please help! Contact me as soon as possible.".tr;
|
|
|
|
// Launch WhatsApp communication with the constructed message
|
|
launchCommunication(
|
|
'whatsapp', box.read(BoxName.sosPhonePassenger), message);
|
|
sendSOS = true;
|
|
},
|
|
kolor: AppColor.redColor,
|
|
),
|
|
cancel: MyElevatedButton(
|
|
title: "Cancel".tr,
|
|
onPressed: () {
|
|
Get.back();
|
|
},
|
|
kolor: AppColor.greenColor,
|
|
),
|
|
);
|
|
}
|
|
int minutes = (remainingTimeTimerRideBegin / 60).floor();
|
|
int seconds = remainingTimeTimerRideBegin % 60;
|
|
stringRemainingTimeRideBegin =
|
|
'$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
|
|
update();
|
|
}
|
|
// rideTimerBegin = false;
|
|
// isRideFinished = true;
|
|
// update();
|
|
}
|
|
|
|
int progressTimerRideBeginVip = 0;
|
|
int elapsedTimeInSeconds = 0; // Timer starts from 0
|
|
String stringElapsedTimeRideBegin = '0:00';
|
|
String stringElapsedTimeRideBeginVip = '0:00';
|
|
bool rideInProgress = true; // To control when to stop the timer
|
|
|
|
void rideIsBeginPassengerTimerVIP() async {
|
|
rideInProgress = true; // Start the ride timer
|
|
bool sendSOS = false;
|
|
while (rideInProgress) {
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
|
|
// Increment elapsed time
|
|
elapsedTimeInSeconds++;
|
|
|
|
// Update the time display
|
|
int minutes = (elapsedTimeInSeconds / 60).floor();
|
|
int seconds = elapsedTimeInSeconds % 60;
|
|
stringElapsedTimeRideBeginVip =
|
|
'$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
|
|
// Check for speed and SOS conditions
|
|
if (speed > 100 && !sendSOS) {
|
|
Get.defaultDialog(
|
|
barrierDismissible: false,
|
|
title: "Warning: Speeding detected!".tr,
|
|
titleStyle: AppStyle.title,
|
|
content: Text(
|
|
"We noticed the speed is exceeding 100 km/h. Please slow down for your safety. If you feel unsafe, you can share your trip details with a contact or call the police using the red SOS button."
|
|
.tr,
|
|
style: AppStyle.title,
|
|
),
|
|
confirm: MyElevatedButton(
|
|
title: "Share Trip Details".tr,
|
|
onPressed: () {
|
|
Get.back();
|
|
// Implement sharing trip details logic here
|
|
String message = "**Emergency SOS from Passenger:**\n";
|
|
|
|
// Get trip details from GetX or relevant provider
|
|
String origin = passengerLocation.toString();
|
|
String destination = myDestination.toString();
|
|
String driverName = passengerName;
|
|
String driverCarPlate = licensePlate;
|
|
|
|
// Add trip details to the message
|
|
message += "* ${'Origin'.tr}: $origin\n";
|
|
message += "* ${'Destination'.tr}: $destination\n";
|
|
message += "* ${'Driver Name'.tr}: $driverName\n";
|
|
message += "* ${'Driver Car Plate'.tr}: $driverCarPlate\n\n";
|
|
message += "* ${'Driver Phone'.tr}: $driverPhone\n\n";
|
|
|
|
// Add current location
|
|
message +=
|
|
"${'Current Location'.tr}:https://www.google.com/maps/place/${passengerLocation.latitude},${passengerLocation.longitude} \n";
|
|
|
|
// Append a call to action
|
|
message += "Please help! Contact me as soon as possible.".tr;
|
|
|
|
// Launch WhatsApp communication
|
|
launchCommunication(
|
|
'whatsapp', box.read(BoxName.sosPhonePassenger), message);
|
|
sendSOS = true;
|
|
},
|
|
kolor: AppColor.redColor,
|
|
),
|
|
cancel: MyElevatedButton(
|
|
title: "Cancel".tr,
|
|
onPressed: () {
|
|
Get.back();
|
|
},
|
|
kolor: AppColor.greenColor,
|
|
),
|
|
);
|
|
}
|
|
|
|
// Update the UI
|
|
update();
|
|
}
|
|
}
|
|
|
|
void tripFinishedFromDriver() {
|
|
isRideFinished = true;
|
|
rideTimerBegin = false;
|
|
statusRideVip = 'Finished';
|
|
box.write(BoxName.arrivalTime, '');
|
|
remainingTimeTimerRideBegin = 0;
|
|
box.write(BoxName.passengerWalletTotal, '0');
|
|
update();
|
|
if (box.read(BoxName.parentTripSelected) == true) {
|
|
NotificationService.sendNotification(
|
|
target: box.read(BoxName.tokenParent),
|
|
title: "Finish Monitor".tr,
|
|
body: 'Finish Monitor'.tr,
|
|
isTopic: false, // Important: this is a token
|
|
tone: 'tone1',
|
|
);
|
|
box.write(BoxName.parentTripSelected, false);
|
|
box.remove(BoxName.tokenParent);
|
|
}
|
|
}
|
|
|
|
// bool isBeginRideFromDriver = false;
|
|
// void getBeginRideFromDriver() async {
|
|
// try {
|
|
// if (isBeginRideFromDriver) return; // Prevent duplicate streams
|
|
// isBeginRideFromDriver = true;
|
|
// var res = await CRUD()
|
|
// .get(link: AppLink.getRideStatusBegin, payload: {'ride_id': rideId});
|
|
// if (res != 'failure') {
|
|
// var decode = jsonDecode(res);
|
|
|
|
// // if (decode['data']['status'] != 'Apply') {
|
|
// if (decode['data']['status'] == 'Begin') {
|
|
// timeToPassengerFromDriverAfterApplied = 0;
|
|
// remainingTime = 0;
|
|
// remainingTimeToPassengerFromDriverAfterApplied = 0;
|
|
// remainingTimeDriverWaitPassenger5Minute = 0;
|
|
// rideTimerBegin = true;
|
|
// statusRide = 'Begin';
|
|
// isDriverInPassengerWay = false;
|
|
// isDriverArrivePassenger = false;
|
|
// update();
|
|
// // isCancelRidePageShown = true;
|
|
// rideIsBeginPassengerTimer();
|
|
// runWhenRideIsBegin();
|
|
// } else {}
|
|
// }
|
|
// } catch (e) {
|
|
// // Handle the error or perform any necessary actions
|
|
// }
|
|
// }
|
|
|
|
StreamController<String> _beginRideStreamController =
|
|
StreamController<String>.broadcast();
|
|
Stream<String> get beginRideStream => _beginRideStreamController.stream;
|
|
|
|
bool isBeginRideFromDriverRunning = false;
|
|
|
|
void getBeginRideFromDriver() {
|
|
if (isBeginRideFromDriverRunning) return; // Prevent duplicate streams
|
|
isBeginRideFromDriverRunning = true;
|
|
|
|
Timer.periodic(const Duration(seconds: 2), (timer) async {
|
|
try {
|
|
var res = await CRUD().get(
|
|
link: AppLink.getRideStatusBegin, payload: {'ride_id': rideId});
|
|
print(res);
|
|
print('1002');
|
|
if (res != 'failure') {
|
|
var decode = jsonDecode(res);
|
|
_beginRideStreamController
|
|
.add(decode['data']['status']); // Emit the status
|
|
|
|
if (decode['data']['status'] == 'Begin') {
|
|
// Stop the periodic check
|
|
timer.cancel();
|
|
isBeginRideFromDriverRunning = false;
|
|
|
|
timeToPassengerFromDriverAfterApplied = 0;
|
|
remainingTime = 0;
|
|
remainingTimeToPassengerFromDriverAfterApplied = 0;
|
|
remainingTimeDriverWaitPassenger5Minute = 0;
|
|
rideTimerBegin = true;
|
|
statusRide = 'Begin';
|
|
isDriverInPassengerWay = false;
|
|
isDriverArrivePassenger = false;
|
|
update();
|
|
|
|
// Trigger additional actions
|
|
rideIsBeginPassengerTimer();
|
|
runWhenRideIsBegin();
|
|
NotificationController().showNotification(
|
|
'Trip is begin'.tr,
|
|
'The trip has started! Feel free to contact emergency numbers, share your trip, or activate voice recording for the journey'
|
|
.tr,
|
|
'ding');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// Handle errors
|
|
_beginRideStreamController.addError(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Call this method to listen to the stream
|
|
void listenToBeginRideStream() {
|
|
beginRideStream.listen((status) {
|
|
print("Ride status: $status");
|
|
// Perform additional actions based on the status
|
|
}, onError: (error) {
|
|
print("Error in Begin Ride Stream: $error");
|
|
});
|
|
}
|
|
|
|
begiVIPTripFromPassenger() async {
|
|
timeToPassengerFromDriverAfterApplied = 0;
|
|
remainingTime = 0;
|
|
isBottomSheetShown = false;
|
|
remainingTimeToPassengerFromDriverAfterApplied = 0;
|
|
remainingTimeDriverWaitPassenger5Minute = 0;
|
|
rideTimerBegin = true;
|
|
statusRideVip = 'Begin';
|
|
isDriverInPassengerWay = false;
|
|
isDriverArrivePassenger = false;
|
|
update();
|
|
// isCancelRidePageShown = true;
|
|
rideIsBeginPassengerTimerVIP();
|
|
runWhenRideIsBegin();
|
|
}
|
|
|
|
Map rideStatusFromStartApp = {};
|
|
getRideStatusFromStartApp() async {
|
|
try {
|
|
var res = await CRUD().get(
|
|
link: AppLink.getRideStatusFromStartApp,
|
|
payload: {'passenger_id': box.read(BoxName.passengerID)});
|
|
print(res);
|
|
print('1070');
|
|
if (res == 'failure') {
|
|
print(
|
|
"No rides found for the given passenger ID within the last hour.");
|
|
}
|
|
rideStatusFromStartApp = jsonDecode(res);
|
|
if (rideStatusFromStartApp['data']['status'] == 'Begin') {
|
|
statusRide = 'Begin';
|
|
driverId = rideStatusFromStartApp['data']['driver_id'];
|
|
passengerName = rideStatusFromStartApp['data']['driverName'];
|
|
driverRate = rideStatusFromStartApp['data']['rateDriver'].toString();
|
|
statusRideFromStart = true;
|
|
// DateTime endTime =
|
|
// DateTime.parse(rideStatusFromStartApp['data']['endtime']);
|
|
// DateTime rideTimeStart =
|
|
// DateTime.parse(rideStatusFromStartApp['data']['rideTimeStart']);
|
|
//
|
|
// // Calculate the new end time by adding the duration to the rideTimeStart
|
|
// DateTime newEndTime = rideTimeStart.add(
|
|
// Duration(seconds: endTime.difference(rideTimeStart).inSeconds));
|
|
//
|
|
// // Save the new end time in a variable
|
|
// var newEndTimeVariable = newEndTime.toString();
|
|
update();
|
|
|
|
Map<String, dynamic> tripData =
|
|
box.read(BoxName.tripData) as Map<String, dynamic>;
|
|
String pointsString =
|
|
(tripData["routes"][0]["overview_polyline"]["points"]);
|
|
|
|
List<LatLng> decodedPoints =
|
|
await compute(decodePolylineIsolate, pointsString);
|
|
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
|
|
for (int i = 0; i < decodedPoints.length; i++) {
|
|
polylineCoordinates.add(decodedPoints[i]);
|
|
}
|
|
var polyline = Polyline(
|
|
polylineId: const PolylineId('begin trip'),
|
|
points: polylineCoordinates,
|
|
width: 10,
|
|
color: Colors.blue,
|
|
);
|
|
|
|
polyLines.add(polyline);
|
|
timeToPassengerFromDriverAfterApplied = 0;
|
|
remainingTime = 0;
|
|
remainingTimeToPassengerFromDriverAfterApplied = 0;
|
|
remainingTimeDriverWaitPassenger5Minute = 0;
|
|
rideTimerBegin = true;
|
|
isDriverInPassengerWay = false;
|
|
isDriverArrivePassenger = false;
|
|
// update();
|
|
// isCancelRidePageShown = true;
|
|
durationToAdd = tripData['routes'][0]['legs'][0]['duration']['value'];
|
|
rideIsBeginPassengerTimer();
|
|
runWhenRideIsBegin();
|
|
update();
|
|
}
|
|
} catch (e) {
|
|
// Handle the error or perform any necessary actions
|
|
}
|
|
}
|
|
|
|
void driverArrivePassenger() {
|
|
timeToPassengerFromDriverAfterApplied = 0;
|
|
remainingTime = 0;
|
|
// isCancelRidePageShown = true;
|
|
update();
|
|
rideIsBeginPassengerTimer();
|
|
// runWhenRideIsBegin();
|
|
}
|
|
|
|
void cancelTimerToPassengerFromDriverAfterApplied() {
|
|
timerToPassengerFromDriverAfterApplied?.cancel();
|
|
}
|
|
|
|
void clearPlacesDestination() {
|
|
placesDestination = [];
|
|
hintTextDestinationPoint = 'Search for your destination'.tr;
|
|
update();
|
|
}
|
|
|
|
void clearPlacesStart() {
|
|
placesStart = [];
|
|
hintTextStartPoint = 'Search for your Start point'.tr;
|
|
update();
|
|
}
|
|
|
|
void clearPlaces(int index) {
|
|
placeListResponseAll[index] = [];
|
|
hintTextwayPointStringAll[index] = 'Search for waypoint'.tr;
|
|
update();
|
|
}
|
|
|
|
void clearPlaces1() {
|
|
wayPoint1 = [];
|
|
hintTextwayPoint1 = 'Search for waypoint'.tr;
|
|
update();
|
|
}
|
|
|
|
void clearPlaces2() {
|
|
wayPoint2 = [];
|
|
hintTextwayPoint2 = 'Search for waypoint'.tr;
|
|
update();
|
|
}
|
|
|
|
void clearPlaces3() {
|
|
wayPoint3 = [];
|
|
hintTextwayPoint3 = 'Search for waypoint'.tr;
|
|
update();
|
|
}
|
|
|
|
void clearPlaces4() {
|
|
wayPoint4 = [];
|
|
hintTextwayPoint4 = 'Search for waypoint'.tr;
|
|
update();
|
|
}
|
|
|
|
int selectedReason = -1;
|
|
String? cancelNote;
|
|
void selectReason(int index, String note) {
|
|
selectedReason = index;
|
|
cancelNote = note;
|
|
update();
|
|
}
|
|
|
|
void getDialog(String title, String? midTitle, VoidCallback onPressed) {
|
|
final textToSpeechController = Get.find<TextToSpeechController>();
|
|
Get.defaultDialog(
|
|
title: title,
|
|
titleStyle: AppStyle.title,
|
|
middleTextStyle: AppStyle.title,
|
|
content: Column(
|
|
children: [
|
|
IconButton(
|
|
onPressed: () async {
|
|
await textToSpeechController.speakText(title ?? midTitle!);
|
|
},
|
|
icon: const Icon(Icons.headphones)),
|
|
Text(
|
|
midTitle!,
|
|
style: AppStyle.title,
|
|
)
|
|
],
|
|
),
|
|
confirm: MyElevatedButton(
|
|
title: 'Ok'.tr,
|
|
onPressed: onPressed,
|
|
kolor: AppColor.greenColor,
|
|
),
|
|
cancel: MyElevatedButton(
|
|
title: 'Cancel',
|
|
kolor: AppColor.redColor,
|
|
onPressed: () {
|
|
Get.back();
|
|
}));
|
|
}
|
|
|
|
Map<String, double>? extractCoordinatesFromLink(String link) {
|
|
try {
|
|
// Extract the URL part from the link by finding the first occurrence of "http"
|
|
int urlStartIndex = link.indexOf(RegExp(r'https?://'));
|
|
if (urlStartIndex == -1) {
|
|
throw const FormatException('No URL found in the provided link.');
|
|
}
|
|
|
|
// Extract the URL and clean it
|
|
link = link.substring(urlStartIndex).trim();
|
|
|
|
Uri uri = Uri.parse(link);
|
|
|
|
// Common coordinate query parameters
|
|
List<String> coordinateParams = ['q', 'cp', 'll'];
|
|
|
|
// Try to extract coordinates from query parameters
|
|
for (var param in coordinateParams) {
|
|
String? value = uri.queryParameters[param];
|
|
if (value != null && (value.contains(',') || value.contains('~'))) {
|
|
List<String> coordinates =
|
|
value.contains(',') ? value.split(',') : value.split('~');
|
|
if (coordinates.length == 2) {
|
|
double? latitude = double.tryParse(coordinates[0].trim());
|
|
double? longitude = double.tryParse(coordinates[1].trim());
|
|
if (latitude != null && longitude != null) {
|
|
return {
|
|
'latitude': latitude,
|
|
'longitude': longitude,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to extract coordinates from the path
|
|
List<String> pathSegments = uri.pathSegments;
|
|
for (var segment in pathSegments) {
|
|
if (segment.contains(',')) {
|
|
List<String> coordinates = segment.split(',');
|
|
if (coordinates.length == 2) {
|
|
double? latitude = double.tryParse(coordinates[0].trim());
|
|
double? longitude = double.tryParse(coordinates[1].trim());
|
|
if (latitude != null && longitude != null) {
|
|
return {
|
|
'latitude': latitude,
|
|
'longitude': longitude,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('Error parsing location link: $e');
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
double latitudeWhatsApp = 0;
|
|
double longitudeWhatsApp = 0;
|
|
void handleWhatsAppLink(String link) {
|
|
Map<String, double>? coordinates = extractCoordinatesFromLink(link);
|
|
|
|
if (coordinates != null) {
|
|
latitudeWhatsApp = coordinates['latitude']!;
|
|
longitudeWhatsApp = coordinates['longitude']!;
|
|
|
|
print(
|
|
'Extracted coordinates: Lat: $latitudeWhatsApp, Long: $longitudeWhatsApp');
|
|
// Use these coordinates in your app as needed
|
|
} else {
|
|
print('Failed to extract coordinates from the link');
|
|
}
|
|
}
|
|
|
|
void goToWhatappLocation() async {
|
|
if (sosFormKey.currentState!.validate()) {
|
|
changeIsWhatsAppOrder(true);
|
|
Get.back();
|
|
handleWhatsAppLink(whatsAppLocationText.text);
|
|
myDestination = LatLng(latitudeWhatsApp, longitudeWhatsApp);
|
|
await mapController?.animateCamera(CameraUpdate.newLatLng(
|
|
LatLng(passengerLocation.latitude, passengerLocation.longitude)));
|
|
changeMainBottomMenuMap();
|
|
passengerStartLocationFromMap = true;
|
|
isPickerShown = true;
|
|
update();
|
|
}
|
|
}
|
|
|
|
int currentTimeSearchingCaptainWindow = 0;
|
|
late String driverPhone = '';
|
|
late String driverRate = '';
|
|
late String passengerName = '';
|
|
late String carColor = '';
|
|
late String colorHex = '';
|
|
late String carYear = '';
|
|
late String model = '';
|
|
late String make = '';
|
|
late String licensePlate = '';
|
|
|
|
String driverOrderStatus = 'yet';
|
|
bool isDriversTokensSend = false;
|
|
|
|
Set<String> notifiedDrivers = {};
|
|
|
|
addRideToNotificationDriverAvailable() async {
|
|
await CRUD().post(link: AppLink.addWaitingRide, payload: {
|
|
'id': rideId.toString(),
|
|
'start_location':
|
|
'${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
|
|
'end_location':
|
|
'${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}',
|
|
"date": DateTime.now().toString(),
|
|
"time": DateTime.now().toString(),
|
|
"price": totalPassenger.toStringAsFixed(2),
|
|
'passenger_id': box.read(BoxName.passengerID).toString(),
|
|
'status': 'waiting',
|
|
'carType': box.read(BoxName.carType),
|
|
'passengerRate': passengerRate.toStringAsFixed(2),
|
|
'price_for_passenger': totalME.toStringAsFixed(2),
|
|
'distance': distance.toStringAsFixed(1),
|
|
'duration': duration.toStringAsFixed(1),
|
|
});
|
|
}
|
|
|
|
// Future<void> confirmRideForAllDriverAvailable1() async {
|
|
// // Try to fetch car locations up to 4 times with a 2-second delay
|
|
// bool driversFound = false;
|
|
// for (int attempt = 0; attempt < 8; attempt++) {
|
|
// await getCarsLocationByPassengerAndReloadMarker(
|
|
// box.read(BoxName.carType), attempt > 5 ? 4500 : 3000);
|
|
|
|
// // Check if dataCarsLocationByPassenger is valid and contains drivers
|
|
// if (dataCarsLocationByPassenger != 'failure' &&
|
|
// dataCarsLocationByPassenger != null &&
|
|
// dataCarsLocationByPassenger.containsKey('data') &&
|
|
// dataCarsLocationByPassenger['message'] != null) {
|
|
// driversFound = true;
|
|
// break; // Exit loop if drivers are found
|
|
// }
|
|
|
|
// // Wait 2 seconds before next attempt
|
|
// await Future.delayed(const Duration(seconds: 2));
|
|
// }
|
|
|
|
// // If no drivers were found after 4 attempts, show a dialog
|
|
// if (!driversFound) {
|
|
// Get.dialog(
|
|
// BackdropFilter(
|
|
// filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
|
// child: CupertinoAlertDialog(
|
|
// title: Text(
|
|
// "No Car or Driver Found in your area.".tr,
|
|
// style: AppStyle.title.copyWith(
|
|
// fontSize: 20,
|
|
// fontWeight: FontWeight.bold,
|
|
// ),
|
|
// ),
|
|
// content: Text(
|
|
// "No Car or Driver Found in your area.".tr,
|
|
// style: AppStyle.title.copyWith(fontSize: 16),
|
|
// ),
|
|
// actions: [
|
|
// CupertinoDialogAction(
|
|
// onPressed: () {
|
|
// Get.back();
|
|
// Get.offAll(() => const MapPagePassenger());
|
|
// },
|
|
// child: Text('OK'.tr,
|
|
// style: const TextStyle(color: AppColor.greenColor)),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
// barrierDismissible: false,
|
|
// );
|
|
|
|
// return;
|
|
// }
|
|
|
|
// // Proceed with the rest of the function if drivers are found
|
|
// PaymentController paymentController = Get.find<PaymentController>();
|
|
// rideConfirm = true;
|
|
// shouldFetch = true;
|
|
// isBottomSheetShown = false;
|
|
// timeToPassengerFromDriverAfterApplied = 60;
|
|
|
|
// // Add ride to database
|
|
// await CRUD()
|
|
// .post(link: "${AppLink.IntaleqCairoServer}/ride/rides/add.php", payload: {
|
|
// "start_location":
|
|
// '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
|
|
// "end_location":
|
|
// '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}',
|
|
// "date": DateTime.now().toString(),
|
|
// "time": DateTime.now().toString(),
|
|
// "endtime": durationToAdd.toString(),
|
|
// "price": totalPassenger.toStringAsFixed(2),
|
|
// "passenger_id": box.read(BoxName.passengerID).toString(),
|
|
// "driver_id": dataCarsLocationByPassenger['message'][carsOrder]['driver_id']
|
|
// .toString(),
|
|
// "status": "waiting",
|
|
// 'carType': box.read(BoxName.carType),
|
|
// "price_for_driver": totalPassenger.toString(),
|
|
// "price_for_passenger": totalME.toString(),
|
|
// "distance": distance.toString(),
|
|
// "paymentMethod": paymentController.isWalletChecked.toString(),
|
|
// }).then((value) {
|
|
// if (value is String) {
|
|
// final parsedValue = jsonDecode(value);
|
|
// rideId = parsedValue['message'];
|
|
// } else if (value is Map) {
|
|
// rideId = value['message'];
|
|
// } else {
|
|
// Log.print('Unexpected response type: ${value.runtimeType}');
|
|
// }
|
|
|
|
// // Timer to notify drivers every 2 seconds for 5 iterations
|
|
// int iteration = 0;
|
|
// Timer.periodic(const Duration(seconds: 2), (timer) async {
|
|
// if (iteration >= 5) {
|
|
// timer.cancel();
|
|
// return;
|
|
// }
|
|
// iteration++;
|
|
|
|
// // Reload driver locations and notify available drivers
|
|
// await getCarsLocationByPassengerAndReloadMarker(
|
|
// box.read(BoxName.carType), 3000);
|
|
// if (dataCarsLocationByPassenger != null &&
|
|
// dataCarsLocationByPassenger.containsKey('data') &&
|
|
// dataCarsLocationByPassenger['message'] != null) {
|
|
// for (var driverData in dataCarsLocationByPassenger['message']) {
|
|
// String driverId = driverData['driver_id'].toString();
|
|
// if (!notifiedDrivers.contains(driverId)) {
|
|
// notifiedDrivers.add(driverId);
|
|
// List<String> body = [
|
|
// '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
|
|
// '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}',
|
|
// totalPassenger.toStringAsFixed(2),
|
|
// totalDriver.toStringAsFixed(2),
|
|
// durationToRide.toString(),
|
|
// distance.toStringAsFixed(2),
|
|
// driverId.toString(),
|
|
// box.read(BoxName.passengerID).toString(),
|
|
// box.read(BoxName.name).toString(),
|
|
// box.read(BoxName.tokenFCM).toString(),
|
|
// box.read(BoxName.phone).toString(),
|
|
// durationByPassenger.toString(),
|
|
// distanceByPassenger.toString(),
|
|
// paymentController.isWalletChecked.toString(),
|
|
// driverData['token'].toString(),
|
|
// durationToPassenger.toString(),
|
|
// rideId.toString(),
|
|
// rideTimerBegin.toString(),
|
|
// driverId.toString(),
|
|
// durationToRide.toString(),
|
|
// Get.find<WayPointController>().wayPoints.length > 1
|
|
// ? 'haveSteps'
|
|
// : 'startEnd',
|
|
// placesCoordinate[0],
|
|
// placesCoordinate[1],
|
|
// placesCoordinate[2],
|
|
// placesCoordinate[3],
|
|
// placesCoordinate[4],
|
|
// costForDriver.toStringAsFixed(2),
|
|
// (double.parse(box.read(BoxName.passengerWalletTotal)) < 0
|
|
// ? double.parse(box.read(BoxName.passengerWalletTotal))
|
|
// .toStringAsFixed(2)
|
|
// : '0'),
|
|
// box.read(BoxName.email).toString(),
|
|
// data[0]['start_address'],
|
|
// data[0]['end_address'],
|
|
// box.read(BoxName.carType),
|
|
// kazan.toStringAsFixed(0),
|
|
// passengerRate.toStringAsFixed(2),
|
|
// ];
|
|
// Log.print('body: ${body}');
|
|
// firebaseMessagesController.sendNotificationToDriverMAP(
|
|
// 'OrderSpeed',
|
|
// rideId,
|
|
// driverData['token'].toString(),
|
|
// body,
|
|
// 'order',
|
|
// );
|
|
// }
|
|
// }
|
|
// }
|
|
// });
|
|
// });
|
|
|
|
// // If an additional endpoint is available, post data there as well
|
|
// if (AppLink.endPoint != AppLink.IntaleqCairoServer) {
|
|
// CRUD().post(link: '${AppLink.endPoint}/ride/rides/add.php', payload: {
|
|
// "start_location":
|
|
// '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
|
|
// "end_location":
|
|
// '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}',
|
|
// "date": DateTime.now().toString(),
|
|
// "time": DateTime.now().toString(),
|
|
// "endtime": durationToAdd.toString(),
|
|
// "price": totalPassenger.toStringAsFixed(2),
|
|
// "passenger_id": box.read(BoxName.passengerID).toString(),
|
|
// "driver_id": dataCarsLocationByPassenger['message'][carsOrder]['driver_id']
|
|
// .toString(),
|
|
// "status": "waiting",
|
|
// 'carType': box.read(BoxName.carType),
|
|
// "price_for_driver": totalPassenger.toString(),
|
|
// "price_for_passenger": totalME.toString(),
|
|
// "distance": distance.toString(),
|
|
// "paymentMethod": paymentController.isWalletChecked.toString(),
|
|
// });
|
|
// }
|
|
// delayAndFetchRideStatusForAllDriverAvailable(rideId);
|
|
// update();
|
|
// }
|
|
|
|
increaseForSameRideAndDelay() async {
|
|
reSearchAfterCanceledFromDriver();
|
|
// bool driversFound = false;
|
|
// for (int attempt = 0; attempt < 8; attempt++) {
|
|
// await getCarsLocationByPassengerAndReloadMarker(
|
|
// box.read(BoxName.carType), 4500);
|
|
|
|
// // Check if dataCarsLocationByPassenger is valid and contains drivers
|
|
// if (dataCarsLocationByPassenger != 'failure' &&
|
|
// dataCarsLocationByPassenger != null &&
|
|
// dataCarsLocationByPassenger.containsKey('message') &&
|
|
// dataCarsLocationByPassenger['message'] != null) {
|
|
// driversFound = true;
|
|
// break; // Exit loop if drivers are found
|
|
// }
|
|
|
|
// // Wait 2 seconds before next attempt
|
|
// await Future.delayed(const Duration(seconds: 2));
|
|
// }
|
|
|
|
// // If no drivers were found after 4 attempts, show a dialog
|
|
// if (!driversFound) {
|
|
// Get.dialog(
|
|
// BackdropFilter(
|
|
// filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
|
// child: CupertinoAlertDialog(
|
|
// title: Text(
|
|
// "No Car or Driver Found in your area.".tr,
|
|
// style: AppStyle.title.copyWith(
|
|
// fontSize: 20,
|
|
// fontWeight: FontWeight.bold,
|
|
// ),
|
|
// ),
|
|
// content: Text(
|
|
// "No Car or Driver Found in your area.".tr,
|
|
// style: AppStyle.title.copyWith(fontSize: 16),
|
|
// ),
|
|
// actions: [
|
|
// CupertinoDialogAction(
|
|
// onPressed: () {
|
|
// Get.back();
|
|
// Get.offAll(() => const MapPagePassenger());
|
|
// },
|
|
// child: Text('OK'.tr,
|
|
// style: const TextStyle(color: AppColor.greenColor)),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
// barrierDismissible: false,
|
|
// );
|
|
|
|
// return;
|
|
// }
|
|
// PaymentController paymentController = Get.find<PaymentController>();
|
|
// rideConfirm = true;
|
|
// shouldFetch = true;
|
|
// isBottomSheetShown = false;
|
|
// timeToPassengerFromDriverAfterApplied = 60;
|
|
// // confirmRideForAllDriverAvailable();
|
|
// for (var i = 0; i < dataCarsLocationByPassenger['message'].length; i++) {
|
|
// List<String> body = [
|
|
// '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
|
|
// '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}',
|
|
// totalPassenger.toStringAsFixed(2),
|
|
// totalDriver.toStringAsFixed(2),
|
|
// durationToRide.toString(),
|
|
// distance.toStringAsFixed(2),
|
|
// dataCarsLocationByPassenger['message'][i]['driver_id'].toString(),
|
|
// box.read(BoxName.passengerID).toString(),
|
|
// box.read(BoxName.name).toString(),
|
|
// box.read(BoxName.tokenFCM).toString(),
|
|
// box.read(BoxName.phone).toString(),
|
|
// durationByPassenger.toString(),
|
|
// distanceByPassenger.toString(),
|
|
// paymentController.isWalletChecked.toString(),
|
|
// dataCarsLocationByPassenger['message'][i]['token'].toString(),
|
|
// durationToPassenger.toString(),
|
|
// rideId.toString(),
|
|
// rideTimerBegin.toString(),
|
|
// dataCarsLocationByPassenger['message'][i]['driver_id'].toString(),
|
|
// durationToRide.toString(),
|
|
// Get.find<WayPointController>().wayPoints.length > 1
|
|
// ? 'haveSteps'
|
|
// : 'startEnd',
|
|
// placesCoordinate[0],
|
|
// placesCoordinate[1],
|
|
// placesCoordinate[2],
|
|
// placesCoordinate[3],
|
|
// placesCoordinate[4],
|
|
// costForDriver.toStringAsFixed(2),
|
|
// double.parse(box.read(BoxName.passengerWalletTotal)) < 0
|
|
// ? double.parse(box.read(BoxName.passengerWalletTotal))
|
|
// .toStringAsFixed(2)
|
|
// : '0',
|
|
// box.read(BoxName.email).toString(),
|
|
// data[0]['start_address'],
|
|
// data[0]['end_address'],
|
|
// box.read(BoxName.carType),
|
|
// kazan.toStringAsFixed(0),
|
|
// passengerRate.toStringAsFixed(2),
|
|
// ];
|
|
// // Log.print('body: ${body}');
|
|
|
|
// firebaseMessagesController.sendNotificationToDriverMAP(
|
|
// 'OrderSpeed',
|
|
// rideId.toString(),
|
|
// dataCarsLocationByPassenger['message'][i]['token'].toString(),
|
|
// body,
|
|
// 'order');
|
|
// }
|
|
}
|
|
|
|
int tick = 0; // Move tick outside the function to maintain its state
|
|
|
|
// void delayAndFetchRideStatus(String rideId, carType) {
|
|
// Timer.periodic(const Duration(seconds: 1), (timer) async {
|
|
// if (shouldFetch) {
|
|
// if (remainingTimeToPassengerFromDriverAfterApplied > 0) {
|
|
// String res = await getRideStatus(rideId);
|
|
|
|
// Log.print('tick: $tick');
|
|
// String rideStatusDelayed = res.toString();
|
|
// if ((rideStatusDelayed == 'waiting' ||
|
|
// rideStatusDelayed == 'Refused') &&
|
|
// tick >= 15) {
|
|
// timer.cancel(); // Stop the current timer
|
|
// showAndResearchForCaptain();
|
|
// //TODO add to wait
|
|
// await getCarsLocationByPassengerAndReloadMarker(carType, 3000);
|
|
// // await getNearestDriverByPassengerLocationAPIGOOGLE();
|
|
// // getCarForFirstConfirm(carType);
|
|
// confirmRideForAllDriverAvailable();
|
|
// // delayAndFetchRideStatusForAllDriverAvailable(rideId);
|
|
// } else if (rideStatusDelayed == 'Apply' || statusRide == 'Apply') {
|
|
// Log.print('rideStatusDelayed == Apply: $rideStatusDelayed');
|
|
// // todo play sound
|
|
// Get.find<AudioRecorderController>()
|
|
// .playSoundFromAssets('assets/start');
|
|
// timer.cancel(); // Stop the current timer
|
|
// await getUpdatedRideForDriverApply(rideId);
|
|
// shouldFetch = false; // Stop further fetches
|
|
// statusRide = 'Apply';
|
|
// rideConfirm = false;
|
|
// isSearchingWindow = false;
|
|
// update();
|
|
// startTimerFromDriverToPassengerAfterApplied();
|
|
// if (box.read(BoxName.carType) == 'Speed' ||
|
|
// box.read(BoxName.carType) == 'Awfar Car') {
|
|
// NotificationController().showNotification(
|
|
// 'The captain is responsible for the route.'.tr,
|
|
// 'This price is fixed even if the route changes for the driver.'
|
|
// .tr,
|
|
// 'ding');
|
|
// } else if (box.read(BoxName.carType) == 'Comfort' ||
|
|
// box.read(BoxName.carType) == 'Lady') {
|
|
// NotificationController().showNotification('Attention'.tr,
|
|
// 'The price may increase if the route changes.'.tr, 'ding');
|
|
// }
|
|
// } else if (rideStatusDelayed == 'Refused') {
|
|
// statusRide = 'Refused';
|
|
// if (isDriversTokensSend == false) {
|
|
// confirmRideForAllDriverAvailable();
|
|
// isDriversTokensSend = true;
|
|
// } // Start 15-second timer
|
|
// }
|
|
// //else if (isDriversTokensSend == false) {
|
|
// // No need to recall delayAndFetchRideStatus as Timer.periodic is already running
|
|
// update();
|
|
// // }
|
|
// if (tick < 19) {
|
|
// tick++;
|
|
// } else {
|
|
// timer.cancel();
|
|
// // Stop the timer if remainingTimeToPassengerFromDriverAfterApplied <= 0
|
|
// }
|
|
// } else {
|
|
// timer.cancel();
|
|
// // Stop the timer if remainingTimeToPassengerFromDriverAfterApplied <= 0
|
|
// }
|
|
// } else {
|
|
// timer.cancel(); // Stop the timer if shouldFetch is false
|
|
// }
|
|
// });
|
|
// }
|
|
|
|
showAndResearchForCaptain() {
|
|
Get.snackbar(
|
|
"No Captain Accepted Your Order".tr,
|
|
"We are looking for a captain but the price may increase to let a captain accept"
|
|
.tr,
|
|
duration: const Duration(seconds: 5),
|
|
backgroundColor: AppColor.yellowColor,
|
|
);
|
|
isSearchingWindow == true;
|
|
update();
|
|
}
|
|
|
|
String driversStatusForSearchWindow = '';
|
|
|
|
Future<void> confirmRideForAllDriverAvailable() async {
|
|
bool driversFound = false;
|
|
const maxAttempts = 8;
|
|
const attemptDelay = Duration(seconds: 3);
|
|
|
|
for (int attempt = 0; attempt < maxAttempts; attempt++) {
|
|
final reloadDuration = attempt > 5 ? 4500 : 3000;
|
|
await getCarsLocationByPassengerAndReloadMarker(
|
|
box.read(BoxName.carType), reloadDuration);
|
|
// await getNearestDriverByPassengerLocation();
|
|
currentTimeSearchingCaptainWindow = 0;
|
|
driversStatusForSearchWindow = 'We are search for nearst driver'.tr;
|
|
if (isDriversDataValid()) {
|
|
driversFound = true;
|
|
break;
|
|
}
|
|
|
|
await Future.delayed(attemptDelay);
|
|
}
|
|
|
|
if (!driversFound) {
|
|
showNoDriversDialog();
|
|
return;
|
|
}
|
|
driversStatusForSearchWindow = 'Your order is being prepared'.tr;
|
|
Log.print('driversStatusForSearchWindow: $driversStatusForSearchWindow');
|
|
update();
|
|
await postRideDetailsToServer();
|
|
driversStatusForSearchWindow = 'Your order sent to drivers'.tr;
|
|
|
|
await notifyAvailableDrivers();
|
|
|
|
driversStatusForSearchWindow = 'The drivers are reviewing your request'.tr;
|
|
Log.print('driversStatusForSearchWindow: $driversStatusForSearchWindow');
|
|
update();
|
|
delayAndFetchRideStatusForAllDriverAvailable(rideId);
|
|
// update();
|
|
}
|
|
|
|
Future<void> updateConfirmRideForAllDriverAvailable() async {
|
|
bool driversFound = false;
|
|
const maxAttempts = 8;
|
|
const attemptDelay = Duration(seconds: 3);
|
|
|
|
for (int attempt = 0; attempt < maxAttempts; attempt++) {
|
|
final reloadDuration = attempt > 5 ? 4500 : 3000;
|
|
await getCarsLocationByPassengerAndReloadMarker(
|
|
box.read(BoxName.carType), reloadDuration);
|
|
// await getNearestDriverByPassengerLocation();
|
|
|
|
if (isDriversDataValid()) {
|
|
driversFound = true;
|
|
break;
|
|
}
|
|
|
|
await Future.delayed(attemptDelay);
|
|
}
|
|
|
|
if (!driversFound) {
|
|
showNoDriversDialog();
|
|
return;
|
|
}
|
|
|
|
// await postRideDetailsToServer();
|
|
delayAndFetchRideStatusForAllDriverAvailable(rideId);
|
|
// update();
|
|
await notifyAvailableDrivers();
|
|
}
|
|
|
|
bool isDriversDataValid() {
|
|
return dataCarsLocationByPassenger != 'failure' &&
|
|
dataCarsLocationByPassenger != null &&
|
|
dataCarsLocationByPassenger.containsKey('message') &&
|
|
dataCarsLocationByPassenger['message'] != null;
|
|
}
|
|
|
|
void showNoDriversDialog() {
|
|
Get.dialog(
|
|
BackdropFilter(
|
|
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
|
child: CupertinoAlertDialog(
|
|
title: Text("No Car or Driver Found in your area.".tr,
|
|
style: AppStyle.title
|
|
.copyWith(fontSize: 20, fontWeight: FontWeight.bold)),
|
|
content: Text("No Car or Driver Found in your area.".tr,
|
|
style: AppStyle.title.copyWith(fontSize: 16)),
|
|
actions: [
|
|
CupertinoDialogAction(
|
|
onPressed: () {
|
|
Get.back();
|
|
Get.offAll(() => const MapPagePassenger());
|
|
},
|
|
child: Text('OK'.tr,
|
|
style: const TextStyle(color: AppColor.greenColor)),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
barrierDismissible: false,
|
|
);
|
|
}
|
|
|
|
Future<void> postRideDetailsToServer() async {
|
|
final paymentController = Get.find<PaymentController>();
|
|
final payload = constructRidePayload(paymentController);
|
|
|
|
try {
|
|
final response = await CRUD().post(
|
|
link: "${AppLink.IntaleqCairoServer}/ride/rides/add.php",
|
|
payload: payload);
|
|
if (response is String) {
|
|
final parsedValue = jsonDecode(response);
|
|
rideId = parsedValue['message'];
|
|
} else if (response is Map) {
|
|
rideId = response['message'];
|
|
} else {
|
|
Log.print('Unexpected response type: ${response.runtimeType}');
|
|
}
|
|
} catch (e) {
|
|
Log.print('Error posting ride details: $e');
|
|
}
|
|
}
|
|
|
|
Map<String, dynamic> constructRidePayload(
|
|
PaymentController paymentController) {
|
|
final startLocation =
|
|
'${data[0]['start_location']['lat']},${data[0]['start_location']['lng']}';
|
|
final endLocation =
|
|
'${data[0]['end_location']['lat']},${data[0]['end_location']['lng']}';
|
|
|
|
return {
|
|
"start_location": startLocation,
|
|
"end_location": endLocation,
|
|
"date": DateTime.now().toString(),
|
|
"time": DateTime.now().toString(),
|
|
"endtime": durationToAdd.toString(),
|
|
"price": totalPassenger.toStringAsFixed(2),
|
|
"passenger_id": box.read(BoxName.passengerID).toString(),
|
|
"driver_id": dataCarsLocationByPassenger['message'][carsOrder]
|
|
['driver_id']
|
|
.toString(),
|
|
"status": "waiting",
|
|
'carType': box.read(BoxName.carType),
|
|
"price_for_driver": totalPassenger.toString(),
|
|
"price_for_passenger": totalME.toString(),
|
|
"distance": distance.toString(),
|
|
"paymentMethod": paymentController.isWalletChecked.toString(),
|
|
};
|
|
}
|
|
|
|
Future<void> notifyAvailableDrivers() async {
|
|
int iteration = 0;
|
|
const maxIterations = 5;
|
|
const iterationDelay = Duration(seconds: 2);
|
|
|
|
while (iteration < maxIterations) {
|
|
await Future.delayed(iterationDelay);
|
|
await getCarsLocationByPassengerAndReloadMarker(
|
|
box.read(BoxName.carType), 3000);
|
|
|
|
if (dataCarsLocationByPassenger != null &&
|
|
dataCarsLocationByPassenger.containsKey('message') &&
|
|
dataCarsLocationByPassenger['message'] != null) {
|
|
for (var driverData in dataCarsLocationByPassenger['message']) {
|
|
String driverId = driverData['driver_id'].toString();
|
|
if (!notifiedDrivers.contains(driverId)) {
|
|
notifiedDrivers.add(driverId);
|
|
double driverLat = double.parse(driverData['latitude']);
|
|
double driverLng = double.parse(driverData['longitude']);
|
|
double distanceToDriverInMeters = Geolocator.distanceBetween(
|
|
passengerLocation.latitude,
|
|
passengerLocation.longitude,
|
|
driverLat,
|
|
driverLng,
|
|
);
|
|
|
|
double distanceToDriverInKm = distanceToDriverInMeters *
|
|
1.25 / //to approximate to stright distance
|
|
1000;
|
|
double durationToDriverInHours =
|
|
distanceToDriverInKm / 25; // 25 km/h as default speed
|
|
double durationToDriverInSeconds = durationToDriverInHours * 3600;
|
|
durationToPassenger = durationToDriverInSeconds.toInt();
|
|
distanceByPassenger =
|
|
(distanceToDriverInMeters * 1.25).toStringAsFixed(0);
|
|
Future.delayed(const Duration(microseconds: 10));
|
|
final body = constructNotificationBody(driverData);
|
|
Log.print('body:ww $body');
|
|
// firebaseMessagesController.sendNotificationToDriverMAP(
|
|
// 'Order', // without tr since background not valid
|
|
// endNameAddress,
|
|
// (driverData['token'].toString()),
|
|
// body,
|
|
// 'order');
|
|
NotificationService.sendNotification(
|
|
target: (driverData['token'].toString()),
|
|
title: 'Order',
|
|
body: endNameAddress,
|
|
isTopic: false, // Important: this is a token
|
|
tone: 'tone1',
|
|
driverList: body);
|
|
}
|
|
}
|
|
}
|
|
iteration++;
|
|
}
|
|
}
|
|
|
|
Future<void> notifyAvailableDriversAgain() async {
|
|
Log.print('[DEBUG] Starting notifyAvailableDriversAgain()');
|
|
await getCarsLocationByPassengerAndReloadMarker(
|
|
box.read(BoxName.carType), 3000);
|
|
Log.print(
|
|
'[DEBUG] Found drivers to notify: ${dataCarsLocationByPassenger['message']?.length}');
|
|
if (dataCarsLocationByPassenger != null &&
|
|
dataCarsLocationByPassenger.containsKey('message') &&
|
|
dataCarsLocationByPassenger['message'] != null) {
|
|
for (var driverData in dataCarsLocationByPassenger['message']) {
|
|
String driverId = driverData['driver_id'].toString();
|
|
if (!notifiedDrivers.contains(driverId)) {
|
|
notifiedDrivers.add(driverId);
|
|
double driverLat = double.parse(driverData['latitude']);
|
|
double driverLng = double.parse(driverData['longitude']);
|
|
double distanceToDriverInMeters = Geolocator.distanceBetween(
|
|
passengerLocation.latitude,
|
|
passengerLocation.longitude,
|
|
driverLat,
|
|
driverLng,
|
|
);
|
|
|
|
double distanceToDriverInKm = distanceToDriverInMeters *
|
|
1.25 / //to approximate to stright distance
|
|
1000;
|
|
double durationToDriverInHours =
|
|
distanceToDriverInKm / 25; // 25 km/h as default speed
|
|
double durationToDriverInSeconds = durationToDriverInHours * 3600;
|
|
durationToPassenger = durationToDriverInSeconds.toInt();
|
|
distanceByPassenger =
|
|
(distanceToDriverInMeters * 1.25).toStringAsFixed(0);
|
|
Future.delayed(const Duration(microseconds: 10));
|
|
final body = constructNotificationBody(driverData);
|
|
// Log.print('body:ww ${body}');
|
|
Log.print(
|
|
'[DEBUG] Sending to driver: ${driverData['driver_id']}, token: ${driverData['token']}');
|
|
// firebaseMessagesController.sendNotificationToDriverMAP(
|
|
// 'Order', // without tr since background not valid
|
|
// endNameAddress,
|
|
// (driverData['token'].toString()),
|
|
// body,
|
|
// 'order');
|
|
NotificationService.sendNotification(
|
|
target: (driverData['token'].toString()),
|
|
title: 'Order',
|
|
body: endNameAddress,
|
|
isTopic: false, // Important: this is a token
|
|
tone: 'tone1',
|
|
driverList: body);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
List<String> constructNotificationBody(var driverData) {
|
|
final paymentController = Get.find<PaymentController>();
|
|
return [
|
|
'${data[0]['start_location']['lat']},${data[0]['start_location']['lng']}',
|
|
'${data[0]['end_location']['lat']},${data[0]['end_location']['lng']}',
|
|
totalPassenger.toStringAsFixed(2),
|
|
totalDriver.toStringAsFixed(2),
|
|
durationToRide.toString(),
|
|
distance.toStringAsFixed(2),
|
|
driverData['driver_id'].toString(),
|
|
box.read(BoxName.passengerID).toString(),
|
|
(box.read(BoxName.name).toString().split(' ')[0]).toString(),
|
|
(box.read(BoxName.tokenFCM).toString()),
|
|
(box.read(BoxName.phone).toString()),
|
|
durationToPassenger.toStringAsFixed(0) ?? '120',
|
|
distanceByPassenger.toString() ?? '2000',
|
|
paymentController.isWalletChecked.toString(),
|
|
(driverData['token'].toString()),
|
|
durationToPassenger.toString(),
|
|
rideId.toString(),
|
|
rideTimerBegin.toString(),
|
|
driverData['driver_id'].toString(),
|
|
durationToRide.toString(),
|
|
Get.find<WayPointController>().wayPoints.length > 1
|
|
? 'haveSteps'
|
|
: 'startEnd',
|
|
placesCoordinate[0],
|
|
placesCoordinate[1],
|
|
placesCoordinate[2],
|
|
placesCoordinate[3],
|
|
placesCoordinate[4],
|
|
costForDriver.toStringAsFixed(2),
|
|
(double.parse(box.read(BoxName.passengerWalletTotal)) < 0
|
|
? double.parse(box.read(BoxName.passengerWalletTotal))
|
|
.toStringAsFixed(2)
|
|
: '0'),
|
|
box.read(BoxName.email).toString(),
|
|
data[0]['start_address'],
|
|
data[0]['end_address'],
|
|
box.read(BoxName.carType),
|
|
kazan.toStringAsFixed(0),
|
|
passengerRate.toStringAsFixed(2),
|
|
];
|
|
}
|
|
|
|
StreamController<String> _rideStatusStreamController =
|
|
StreamController<String>.broadcast();
|
|
Stream<String> get rideStatusStream => _rideStatusStreamController.stream;
|
|
|
|
int maxAttempts = 28;
|
|
|
|
Future<void> delayAndFetchRideStatusForAllDriverAvailable(
|
|
String rideId) async {
|
|
int attemptCounter = 0;
|
|
bool isApplied = false;
|
|
tick = 0;
|
|
await addRideToNotificationDriverAvailable();
|
|
Timer.periodic(const Duration(seconds: 1), (timer) async {
|
|
if (attemptCounter >= maxAttempts || isApplied == true) {
|
|
timer.cancel();
|
|
_rideStatusStreamController.close(); // Close the stream when done
|
|
return;
|
|
}
|
|
|
|
attemptCounter++;
|
|
tick++;
|
|
|
|
try {
|
|
var res = await getRideStatus(rideId);
|
|
String rideStatusDelayed = res.toString();
|
|
Log.print('rideStatusDelayed: $rideStatusDelayed');
|
|
|
|
_rideStatusStreamController
|
|
.add(rideStatusDelayed); // Emit the ride status
|
|
// addRideToNotificationDriverString();
|
|
if (rideStatusDelayed == 'Cancel') {
|
|
timer.cancel();
|
|
NotificationController().showNotification(
|
|
"Order Cancelled".tr, "you canceled order".tr, 'ding');
|
|
_rideStatusStreamController
|
|
.close(); // Close stream after cancellation
|
|
//
|
|
//
|
|
} else if (rideStatusDelayed == 'Apply' ||
|
|
rideStatusDelayed == 'Applied') {
|
|
isApplied = true;
|
|
// timer.cancel();
|
|
rideAppliedFromDriver(isApplied);
|
|
|
|
timer.cancel();
|
|
// Close stream after applying
|
|
} else if (attemptCounter >= maxAttempts ||
|
|
rideStatusDelayed == 'waiting') {
|
|
// timer.cancel(); //todo
|
|
// addRideToNotificationDriverString();
|
|
// Show dialog to increase fee...
|
|
|
|
// buildTimerForIncrease();
|
|
Get.defaultDialog(
|
|
title: 'Are you want to wait drivers to accept your order'.tr,
|
|
middleText: '',
|
|
onConfirm: () {
|
|
Log.print('[DEBUG] User chose to wait again');
|
|
Get.back();
|
|
notifyAvailableDriversAgain();
|
|
delayAndFetchRideStatusForAllDriverAvailable(rideId);
|
|
// addRideToNotificationDriverAvailable();
|
|
},
|
|
onCancel: () {
|
|
timer.cancel();
|
|
Get.back();
|
|
showCancelRideBottomSheet();
|
|
},
|
|
);
|
|
// MyDialog().getDialog(
|
|
// 'Are you want to wait drivers to accept your order'.tr, '', () {
|
|
// Get.back();
|
|
// addRideToNotificationDriverAvailable();
|
|
// });
|
|
update();
|
|
_rideStatusStreamController
|
|
.close(); // Close stream after max attempts
|
|
}
|
|
} catch (e) {
|
|
_rideStatusStreamController.addError(e); // Handle errors in the stream
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> rideAppliedFromDriver(bool isApplied) async {
|
|
await getUpdatedRideForDriverApply(rideId);
|
|
NotificationController().showNotification(
|
|
'Accepted Ride',
|
|
'$driverName ${'accepted your order at price'.tr} ${totalPassenger.toStringAsFixed(1)} ${'with type'.tr} ${box.read(BoxName.carType)}',
|
|
'ding');
|
|
if (box.read(BoxName.carType) == 'Speed' ||
|
|
box.read(BoxName.carType) == 'Awfar Car') {
|
|
NotificationController().showNotification(
|
|
'The captain is responsible for the route.'.tr,
|
|
'This price is fixed even if the route changes for the driver.'.tr,
|
|
'ding');
|
|
} else if (box.read(BoxName.carType) == 'Comfort' ||
|
|
box.read(BoxName.carType) == 'Lady') {
|
|
NotificationController().showNotification('Attention'.tr,
|
|
'The price may increase if the route changes.'.tr, 'ding');
|
|
}
|
|
isApplied = true;
|
|
statusRide = 'Apply';
|
|
rideConfirm = false;
|
|
isSearchingWindow = false;
|
|
update();
|
|
startTimer();
|
|
// todo stop this because this method in startTimer()
|
|
// startTimerFromDriverToPassengerAfterApplied();
|
|
|
|
// timer.cancel();
|
|
_rideStatusStreamController.close();
|
|
}
|
|
|
|
// Listening to the Stream
|
|
void listenToRideStatusStream() {
|
|
rideStatusStream.listen((rideStatus) {
|
|
print("Ride Status: $rideStatus");
|
|
// Handle updates based on the ride status
|
|
}, onError: (error) {
|
|
print("Error in Ride Status Stream: $error");
|
|
// Handle stream errors
|
|
}, onDone: () {
|
|
print("Ride status stream closed.");
|
|
});
|
|
}
|
|
|
|
reSearchAfterCanceledFromDriver() async {
|
|
shouldFetch = true; // Stop further fetches
|
|
statusRide = 'wait';
|
|
rideConfirm = true;
|
|
isSearchingWindow = true;
|
|
update();
|
|
updateConfirmRideForAllDriverAvailable();
|
|
}
|
|
|
|
void start15SecondTimer(String rideId) {
|
|
Timer(const Duration(seconds: 15), () {
|
|
delayAndFetchRideStatusForAllDriverAvailable(rideId);
|
|
});
|
|
}
|
|
|
|
void startTimer() async {
|
|
for (int i = 0; i <= durationTimer; i++) {
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
progress = i / durationTimer;
|
|
remainingTime = durationTimer - i;
|
|
if (remainingTime == 0) {
|
|
rideConfirm = false;
|
|
|
|
timeToPassengerFromDriverAfterApplied += durationToPassenger;
|
|
|
|
// timeToPassengerFromDriverAfterApplied.toString());
|
|
startTimerFromDriverToPassengerAfterApplied();
|
|
update();
|
|
}
|
|
update();
|
|
}
|
|
timerEnded();
|
|
}
|
|
|
|
void timerEnded() async {
|
|
runEvery30SecondsUntilConditionMet();
|
|
isCancelRidePageShown = false;
|
|
print('isCancelRidePageShown: $isCancelRidePageShown');
|
|
update();
|
|
}
|
|
|
|
Future<String> getRideStatus(String rideId) async {
|
|
final response = await CRUD().get(
|
|
link: "${AppLink.endPoint}/ride/rides/getRideStatus.php",
|
|
payload: {'id': rideId});
|
|
print(response);
|
|
print('2176');
|
|
return jsonDecode(response)['data'];
|
|
}
|
|
|
|
late String driverCarModel,
|
|
driverCarMake,
|
|
driverLicensePlate,
|
|
driverName = '';
|
|
getUpdatedRideForDriverApply(String rideId) async {
|
|
// if (isDriversTokensSend) {
|
|
final res = await CRUD().get(
|
|
link: "${AppLink.endPoint}/ride/rides/getRideOrderID.php",
|
|
payload: {'id': rideId});
|
|
if (res != 'failure') {
|
|
var response = jsonDecode(res);
|
|
Log.print('getUpdatedRideForDriverApply: $response');
|
|
driverId = response['data']['driver_id'];
|
|
driverPhone = (response['data']['phone']);
|
|
driverCarMake = response['data']['make'];
|
|
model = response['data']['model'];
|
|
colorHex = response['data']['color_hex'];
|
|
carColor = response['data']['color'];
|
|
make = response['data']['make'];
|
|
licensePlate = (response['data']['car_plate']);
|
|
passengerName = (response['data']['passengerName']);
|
|
driverName = (response['data']['driverName'].toString());
|
|
driverToken = (response['data']['token']);
|
|
// Log.print('driverToken updated: $driverToken');
|
|
carYear = response['data']['year'];
|
|
driverRate = response['data']['ratingDriver'].toString();
|
|
}
|
|
// driversToken.remove(driverToken);
|
|
// for (var i = 1; i < driversToken.length; i++) {
|
|
// firebaseMessagesController.sendNotificationToDriverMAP(
|
|
// 'Order Accepted',
|
|
// '$driverName${'Accepted your order'.tr}',
|
|
// driverToken.toString(),
|
|
// [],
|
|
// 'start',
|
|
// );
|
|
NotificationService.sendNotification(
|
|
target: driverToken.toString(),
|
|
title: 'Order Accepted',
|
|
body: '$driverName${'Accepted your order'.tr}',
|
|
isTopic: false, // Important: this is a token
|
|
tone: 'tone1',
|
|
// driverList: body,
|
|
);
|
|
// }
|
|
// }
|
|
}
|
|
|
|
late LatLng currentDriverLocation;
|
|
late double headingList;
|
|
|
|
// Future getCarsLocationByPassengerAndReloadMarker() async {
|
|
// if (statusRide == 'wait') {
|
|
// carsLocationByPassenger = [];
|
|
// LatLngBounds bounds = calculateBounds(
|
|
// passengerLocation.latitude, passengerLocation.longitude, 7000);
|
|
// var res;
|
|
// if (box.read(BoxName.carType) == 'Lady') {
|
|
// res = await CRUD()
|
|
// .get(link: AppLink.getFemalDriverLocationByPassenger, payload: {
|
|
// 'southwestLat': bounds.southwest.latitude.toString(),
|
|
// 'southwestLon': bounds.southwest.longitude.toString(),
|
|
// 'northeastLat': bounds.northeast.latitude.toString(),
|
|
// 'northeastLon': bounds.northeast.longitude.toString(),
|
|
// });
|
|
// } else if (box.read(BoxName.carType) == 'Speed') {
|
|
// res = await CRUD().get(
|
|
// link: AppLink.getCarsLocationByPassengerSpeed,
|
|
// payload: {
|
|
// 'southwestLat': bounds.southwest.latitude.toString(),
|
|
// 'southwestLon': bounds.southwest.longitude.toString(),
|
|
// 'northeastLat': bounds.northeast.latitude.toString(),
|
|
// 'northeastLon': bounds.northeast.longitude.toString(),
|
|
// },
|
|
// );
|
|
// } else if (box.read(BoxName.carType) == 'Delivery') {
|
|
// res = await CRUD().get(
|
|
// link: AppLink.getCarsLocationByPassengerDelivery,
|
|
// payload: {
|
|
// 'southwestLat': bounds.southwest.latitude.toString(),
|
|
// 'southwestLon': bounds.southwest.longitude.toString(),
|
|
// 'northeastLat': bounds.northeast.latitude.toString(),
|
|
// 'northeastLon': bounds.northeast.longitude.toString(),
|
|
// },
|
|
// );
|
|
// } else {
|
|
// res = await CRUD()
|
|
// .get(link: AppLink.getCarsLocationByPassenger, payload: {
|
|
// 'southwestLat': bounds.southwest.latitude.toString(),
|
|
// 'southwestLon': bounds.southwest.longitude.toString(),
|
|
// 'northeastLat': bounds.northeast.latitude.toString(),
|
|
// 'northeastLon': bounds.northeast.longitude.toString(),
|
|
// });
|
|
// }
|
|
// if (res == 'failure') {
|
|
// noCarString = true;
|
|
// dataCarsLocationByPassenger = res;
|
|
// update();
|
|
// } else {
|
|
// // Get.snackbar('no car', 'message');
|
|
// noCarString = false;
|
|
// dataCarsLocationByPassenger = jsonDecode(res);
|
|
// // if (dataCarsLocationByPassenger.length > carsOrder) {
|
|
// driverId = dataCarsLocationByPassenger['message'][carsOrder]
|
|
// ['driver_id']
|
|
// .toString();
|
|
// gender = dataCarsLocationByPassenger['message'][carsOrder]['gender']
|
|
// .toString();
|
|
// // }
|
|
|
|
// carsLocationByPassenger.clear(); // Clear existing markers
|
|
|
|
// // late LatLng lastDriverLocation; // Initialize a variable for last location
|
|
|
|
// for (var i = 0;
|
|
// i < dataCarsLocationByPassenger['message'].length;
|
|
// i++) {
|
|
// var json = dataCarsLocationByPassenger['message'][i];
|
|
// // CarLocationModel model = CarLocationModel.fromJson(json);
|
|
// if (carLocationsModels.length < i + 1) {
|
|
// // carLocationsModels.add(model);
|
|
// markers.add(
|
|
// Marker(
|
|
// markerId: MarkerId(json['latitude']),
|
|
// position: LatLng(
|
|
// double.parse(json['latitude']),
|
|
// double.parse(json['longitude']),
|
|
// ),
|
|
// rotation: double.parse(json['heading']),
|
|
// icon: json['model'].toString().contains('دراجة')
|
|
// ? motoIcon
|
|
// : json['gender'] == 'Male'.tr
|
|
// ? carIcon
|
|
// : ladyIcon,
|
|
// ),
|
|
// );
|
|
// driversToken.add(json['token']);
|
|
// // driversToken = json['token'];
|
|
// } else {
|
|
// // carLocationsModels[i] = model;
|
|
// markers[i] = Marker(
|
|
// markerId: MarkerId(json['latitude']),
|
|
// position: LatLng(
|
|
// double.parse(json['latitude']),
|
|
// double.parse(json['longitude']),
|
|
// ),
|
|
// rotation: double.parse(json['heading']),
|
|
// icon: json['model'].contains('دراجة')
|
|
// ? motoIcon
|
|
// : json['gender'] == 'Male'.tr
|
|
// ? carIcon
|
|
// : ladyIcon,
|
|
// );
|
|
// // driversToken = json['token'];
|
|
// driversToken.add(json['token']);
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// update();
|
|
// }
|
|
// }
|
|
|
|
Map<String, Timer> _animationTimers = {};
|
|
final int updateIntervalMs = 100; // Update every 100ms
|
|
final double minMovementThreshold =
|
|
10; // Minimum movement in meters to trigger update
|
|
Future getCarForFirstConfirm(String carType) async {
|
|
bool foundCars = false;
|
|
int attempt = 0;
|
|
|
|
// Set up the periodic timer
|
|
Timer? timer = Timer.periodic(const Duration(seconds: 4), (Timer t) async {
|
|
// Attempt to get car location
|
|
foundCars = await getCarsLocationByPassengerAndReloadMarker(
|
|
carType, attempt * 2000);
|
|
Log.print('foundCars: $foundCars');
|
|
|
|
if (foundCars) {
|
|
// If cars are found, cancel the timer and exit the search
|
|
t.cancel();
|
|
} else if (attempt >= 4) {
|
|
// After 4 attempts, stop the search
|
|
t.cancel();
|
|
|
|
// No cars found after 4 attempts
|
|
// MyDialog().getDialog(
|
|
// "No Car or Driver Found in your area.".tr,
|
|
// "No Car or Driver Found in your area.".tr,
|
|
// () {
|
|
// Get.back();
|
|
// },
|
|
// );
|
|
if (!foundCars) {
|
|
noCarString = true;
|
|
dataCarsLocationByPassenger = 'failure';
|
|
}
|
|
|
|
update();
|
|
}
|
|
|
|
attempt++; // Increment attempt
|
|
});
|
|
}
|
|
|
|
void startCarLocationSearch(String carType) {
|
|
int searchInterval = 5; // Interval in seconds
|
|
Log.print('searchInterval: $searchInterval');
|
|
int boundIncreaseStep = 2500; // Initial bounds in meters
|
|
Log.print('boundIncreaseStep: $boundIncreaseStep');
|
|
int maxAttempts = 3; // Maximum attempts to increase bounds
|
|
int maxBoundIncreaseStep = 6000; // Maximum bounds increase step
|
|
int attempt = 0; // Current attempt
|
|
Log.print('initial attempt: $attempt');
|
|
|
|
Timer.periodic(Duration(seconds: searchInterval), (Timer timer) async {
|
|
Log.print('Current attempt: $attempt'); // Log current attempt
|
|
bool foundCars = false;
|
|
if (attempt >= maxAttempts) {
|
|
timer.cancel();
|
|
if (foundCars == false) {
|
|
noCarString = true;
|
|
// dataCarsLocationByPassenger = 'failure';
|
|
update();
|
|
}
|
|
|
|
// return;
|
|
} else if (reloadStartApp == true) {
|
|
Log.print('reloadStartApp: $reloadStartApp');
|
|
foundCars = await getCarsLocationByPassengerAndReloadMarker(
|
|
carType, boundIncreaseStep);
|
|
Log.print('foundCars: $foundCars');
|
|
|
|
if (foundCars) {
|
|
timer.cancel();
|
|
} else {
|
|
attempt++;
|
|
if (reloadCount >= 3 || tick > 18 || reloadCount > 15) {
|
|
timer.cancel();
|
|
}
|
|
Log.print(
|
|
'Incrementing attempt to: $attempt'); // Log incremented attempt
|
|
|
|
if (boundIncreaseStep < maxBoundIncreaseStep) {
|
|
boundIncreaseStep += 1500; // Increase bounds
|
|
if (boundIncreaseStep > maxBoundIncreaseStep) {
|
|
boundIncreaseStep =
|
|
maxBoundIncreaseStep; // Ensure it does not exceed the maximum
|
|
}
|
|
Log.print(
|
|
'New boundIncreaseStep: $boundIncreaseStep'); // Log new bounds
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
String getLocationArea(double latitude, double longitude) {
|
|
final locations = box.read(BoxName.locationName) ?? [];
|
|
for (final location in locations) {
|
|
final locationData = location as Map<String, dynamic>;
|
|
|
|
// Debugging: Print location data
|
|
// print('Location Data: $locationData');
|
|
|
|
// Convert string values to double
|
|
final minLatitude =
|
|
double.tryParse(locationData['min_latitude'].toString()) ?? 0.0;
|
|
final maxLatitude =
|
|
double.tryParse(locationData['max_latitude'].toString()) ?? 0.0;
|
|
final minLongitude =
|
|
double.tryParse(locationData['min_longitude'].toString()) ?? 0.0;
|
|
final maxLongitude =
|
|
double.tryParse(locationData['max_longitude'].toString()) ?? 0.0;
|
|
|
|
// Debugging: Print converted values
|
|
print(
|
|
'Converted Values: minLatitude=$minLatitude, maxLatitude=$maxLatitude, minLongitude=$minLongitude, maxLongitude=$maxLongitude');
|
|
|
|
if (latitude >= minLatitude &&
|
|
latitude <= maxLatitude &&
|
|
longitude >= minLongitude &&
|
|
longitude <= maxLongitude) {
|
|
box.write(BoxName.serverChosen, (locationData['server_link']));
|
|
// Log.print(
|
|
// 'locationData----server_link: ${(locationData['server_link'])}');
|
|
return locationData['name'];
|
|
}
|
|
}
|
|
|
|
// Default case
|
|
box.write(BoxName.serverChosen, AppLink.IntaleqCairoServer);
|
|
return 'Cairo';
|
|
}
|
|
|
|
// if (latitude >= 29.918901 &&
|
|
// latitude <= 30.198857 &&
|
|
// longitude >= 31.215009 &&
|
|
// longitude <= 31.532186) {
|
|
// box.write(BoxName.serverChosen, AppLink.IntaleqCairoServer);
|
|
|
|
// return 'Cairo';
|
|
// } else if (latitude >= 29.904975 &&
|
|
// latitude <= 30.143372 &&
|
|
// longitude >= 30.787030 &&
|
|
// longitude <= 31.215009) {
|
|
// box.write(BoxName.serverChosen, AppLink.IntaleqGizaServer);
|
|
// return 'Giza';
|
|
// } else if (latitude >= 30.396286 &&
|
|
// latitude <= 31.654458 &&
|
|
// longitude >= 29.041139 &&
|
|
// longitude <= 32.626259) {
|
|
// box.write(BoxName.serverChosen, AppLink.IntaleqAlexandriaServer);
|
|
// return 'Alexandria';
|
|
// } else {
|
|
// box.write(BoxName.serverChosen, AppLink.IntaleqCairoServer);
|
|
// return 'Cairo';
|
|
// }
|
|
// }
|
|
|
|
Future<bool> getCarsLocationByPassengerAndReloadMarker(
|
|
String carType, int boundIncreaseStep) async {
|
|
// if (statusRide == 'wait') {
|
|
carsLocationByPassenger = [];
|
|
LatLngBounds bounds = calculateBounds(passengerLocation.latitude,
|
|
passengerLocation.longitude, boundIncreaseStep.toDouble());
|
|
var res;
|
|
// await getLocation();
|
|
|
|
switch (carType) {
|
|
case 'Lady':
|
|
res = await CRUD()
|
|
.get(link: AppLink.getFemalDriverLocationByPassenger, payload: {
|
|
'southwestLat': bounds.southwest.latitude.toString(),
|
|
'southwestLon': bounds.southwest.longitude.toString(),
|
|
'northeastLat': bounds.northeast.latitude.toString(),
|
|
'northeastLon': bounds.northeast.longitude.toString(),
|
|
});
|
|
break;
|
|
case 'Comfort':
|
|
res = await CRUD()
|
|
.get(link: AppLink.getCarsLocationByPassengerComfort, payload: {
|
|
'southwestLat': bounds.southwest.latitude.toString(),
|
|
'southwestLon': bounds.southwest.longitude.toString(),
|
|
'northeastLat': bounds.northeast.latitude.toString(),
|
|
'northeastLon': bounds.northeast.longitude.toString(),
|
|
});
|
|
break;
|
|
case 'Speed':
|
|
res = await CRUD()
|
|
.get(link: AppLink.getCarsLocationByPassengerSpeed, payload: {
|
|
'southwestLat': bounds.southwest.latitude.toString(),
|
|
'southwestLon': bounds.southwest.longitude.toString(),
|
|
'northeastLat': bounds.northeast.latitude.toString(),
|
|
'northeastLon': bounds.northeast.longitude.toString(),
|
|
});
|
|
break;
|
|
case 'Scooter':
|
|
res = await CRUD()
|
|
.get(link: AppLink.getCarsLocationByPassengerDelivery, payload: {
|
|
'southwestLat': bounds.southwest.latitude.toString(),
|
|
'southwestLon': bounds.southwest.longitude.toString(),
|
|
'northeastLat': bounds.northeast.latitude.toString(),
|
|
'northeastLon': bounds.northeast.longitude.toString(),
|
|
});
|
|
break;
|
|
case 'Awfar Car':
|
|
res = await CRUD()
|
|
.get(link: AppLink.getCarsLocationByPassengerBalash, payload: {
|
|
'southwestLat': bounds.southwest.latitude.toString(),
|
|
'southwestLon': bounds.southwest.longitude.toString(),
|
|
'northeastLat': bounds.northeast.latitude.toString(),
|
|
'northeastLon': bounds.northeast.longitude.toString(),
|
|
});
|
|
break;
|
|
case 'Electric':
|
|
res = await CRUD()
|
|
.get(link: AppLink.getCarsLocationByPassengerElectric, payload: {
|
|
'southwestLat': bounds.southwest.latitude.toString(),
|
|
'southwestLon': bounds.southwest.longitude.toString(),
|
|
'northeastLat': bounds.northeast.latitude.toString(),
|
|
'northeastLon': bounds.northeast.longitude.toString(),
|
|
});
|
|
break;
|
|
case 'Pink Bike':
|
|
res = await CRUD()
|
|
.get(link: AppLink.getCarsLocationByPassengerPinkBike, payload: {
|
|
'southwestLat': bounds.southwest.latitude.toString(),
|
|
'southwestLon': bounds.southwest.longitude.toString(),
|
|
'northeastLat': bounds.northeast.latitude.toString(),
|
|
'northeastLon': bounds.northeast.longitude.toString(),
|
|
});
|
|
break;
|
|
default:
|
|
res = await CRUD()
|
|
.get(link: AppLink.getCarsLocationByPassenger, payload: {
|
|
'southwestLat': bounds.southwest.latitude.toString(),
|
|
'southwestLon': bounds.southwest.longitude.toString(),
|
|
'northeastLat': bounds.northeast.latitude.toString(),
|
|
'northeastLon': bounds.northeast.longitude.toString(),
|
|
});
|
|
}
|
|
|
|
if (res == 'failure') {
|
|
noCarString = true;
|
|
// dataCarsLocationByPassenger = 'failure';
|
|
update();
|
|
return false;
|
|
} else {
|
|
noCarString = false;
|
|
dataCarsLocationByPassenger = jsonDecode(res);
|
|
// Log.print(
|
|
// 'dataCarsLocationByPassenger:getCarsLocationByPassengerAndReloadMarker $dataCarsLocationByPassenger');
|
|
|
|
// Check if 'message' is present and not null
|
|
if (dataCarsLocationByPassenger != null &&
|
|
dataCarsLocationByPassenger.isNotEmpty) {
|
|
// Check if carsOrder is within bounds
|
|
// if (carsOrder < dataCarsLocationByPassenger['message'].length) {
|
|
// driverId = dataCarsLocationByPassenger['message'][carsOrder]
|
|
// ['driver_id']
|
|
// .toString();
|
|
// gender = dataCarsLocationByPassenger['message'][carsOrder]['gender']
|
|
// .toString();
|
|
// driverToken = dataCarsLocationByPassenger['message'][carsOrder]
|
|
// ['token']
|
|
// .toString();
|
|
// } else {
|
|
print('carsOrder is in of bounds for message array');
|
|
// return false;
|
|
// }
|
|
} else {
|
|
// Get.defaultDialog(title: 'No cars available ');
|
|
print('No cars available or message is null');
|
|
return false;
|
|
}
|
|
|
|
carsLocationByPassenger.clear(); // Clear existing markers
|
|
|
|
for (var i = 0; i < dataCarsLocationByPassenger['message'].length; i++) {
|
|
var json = dataCarsLocationByPassenger['message'][i];
|
|
_updateOrCreateMarker(
|
|
MarkerId(json['latitude']).toString(),
|
|
LatLng(
|
|
double.parse(json['latitude']), double.parse(json['longitude'])),
|
|
double.parse(json['heading']),
|
|
_getIconForCar(json),
|
|
);
|
|
|
|
driversToken.add((json['token']));
|
|
}
|
|
|
|
// Add fake car markers
|
|
_addFakeCarMarkers(passengerLocation, 1);
|
|
|
|
update();
|
|
return true;
|
|
}
|
|
// }
|
|
// return false;
|
|
}
|
|
|
|
final List<Map<String, dynamic>> fakeCarData = [];
|
|
|
|
void _addFakeCarMarkers(LatLng center, int count) {
|
|
if (fakeCarData.isEmpty) {
|
|
Random random = Random();
|
|
double radiusInKm = 2.5; // 3 km diameter, so 1.5 km radius
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
// Generate a random angle and distance within the circle
|
|
double angle = random.nextDouble() * 2 * pi;
|
|
double distance = sqrt(random.nextDouble()) * radiusInKm;
|
|
|
|
// Convert distance to latitude and longitude offsets
|
|
double latOffset = (distance / 111.32); // 1 degree lat ≈ 111.32 km
|
|
double lonOffset =
|
|
(distance / (111.32 * cos(radians(center.latitude))));
|
|
|
|
// Calculate new position
|
|
double lat = center.latitude + (latOffset * cos(angle));
|
|
double lon = center.longitude + (lonOffset * sin(angle));
|
|
|
|
double heading = random.nextDouble() * 360;
|
|
|
|
fakeCarData.add({
|
|
'id': 'fake_$i',
|
|
'latitude': lat,
|
|
'longitude': lon,
|
|
'heading': heading,
|
|
'gender': 'Male', // Randomize gender
|
|
});
|
|
}
|
|
}
|
|
|
|
for (var carData in fakeCarData) {
|
|
_updateOrCreateMarker(
|
|
MarkerId(carData['id']).toString(),
|
|
LatLng(carData['latitude'], carData['longitude']),
|
|
carData['heading'],
|
|
_getIconForCar(carData),
|
|
);
|
|
}
|
|
}
|
|
|
|
BitmapDescriptor _getIconForCar(Map<String, dynamic> carData) {
|
|
if (carData['model'].toString().contains('دراجة')) {
|
|
return motoIcon;
|
|
} else if (carData['gender'] == 'Female') {
|
|
return ladyIcon;
|
|
} else {
|
|
return carIcon;
|
|
}
|
|
}
|
|
|
|
void _updateOrCreateMarker(String markerId, LatLng newPosition,
|
|
double newHeading, BitmapDescriptor icon) {
|
|
Marker? existingMarker = markers.cast<Marker?>().firstWhere(
|
|
(m) => m?.markerId == MarkerId(markerId),
|
|
orElse: () => null,
|
|
);
|
|
|
|
if (existingMarker == null) {
|
|
markers.add(Marker(
|
|
markerId: MarkerId(markerId),
|
|
position: newPosition,
|
|
rotation: newHeading,
|
|
icon: icon,
|
|
));
|
|
} else {
|
|
double distance =
|
|
_calculateDistance(existingMarker.position, newPosition);
|
|
if (distance >= minMovementThreshold) {
|
|
_smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _smoothlyUpdateMarker(Marker oldMarker, LatLng newPosition,
|
|
double newHeading, BitmapDescriptor icon) {
|
|
String markerId = oldMarker.markerId.value;
|
|
LatLng startPosition = oldMarker.position;
|
|
double startHeading = oldMarker.rotation ?? 0;
|
|
|
|
_animationTimers[markerId]?.cancel();
|
|
_animationTimers[markerId] =
|
|
Timer.periodic(Duration(milliseconds: updateIntervalMs), (timer) {
|
|
double progress =
|
|
timer.tick / (500 / updateIntervalMs); // 500ms total duration
|
|
if (progress >= 1.0) {
|
|
timer.cancel();
|
|
_animationTimers.remove(markerId);
|
|
progress = 1.0;
|
|
}
|
|
|
|
LatLng intermediatePosition = LatLng(
|
|
startPosition.latitude +
|
|
(newPosition.latitude - startPosition.latitude) * progress,
|
|
startPosition.longitude +
|
|
(newPosition.longitude - startPosition.longitude) * progress);
|
|
double intermediateHeading =
|
|
startHeading + (newHeading - startHeading) * progress;
|
|
|
|
markers.removeWhere((m) => m.markerId == oldMarker.markerId);
|
|
markers.add(Marker(
|
|
markerId: oldMarker.markerId,
|
|
position: intermediatePosition,
|
|
rotation: intermediateHeading,
|
|
icon: icon,
|
|
));
|
|
|
|
update();
|
|
});
|
|
}
|
|
|
|
double _calculateDistance(LatLng start, LatLng end) {
|
|
// Implement distance calculation (e.g., Haversine formula)
|
|
// For simplicity, this is a placeholder. Replace with actual implementation.
|
|
return 1000 *
|
|
sqrt(pow(start.latitude - end.latitude, 2) +
|
|
pow(start.longitude - end.longitude, 2));
|
|
}
|
|
|
|
String formatSyrianPhoneNumber(String phoneNumber) {
|
|
// Trim any whitespace from the input.
|
|
String trimmedPhone = phoneNumber.trim();
|
|
|
|
// If the number starts with '09', remove the leading '0' and prepend '963'.
|
|
if (trimmedPhone.startsWith('09')) {
|
|
return '963${trimmedPhone.substring(1)}';
|
|
}
|
|
// If the number already starts with '963', return it as is to avoid duplication.
|
|
if (trimmedPhone.startsWith('963')) {
|
|
return trimmedPhone;
|
|
}
|
|
// For any other case (e.g., number starts with '9' without a '0'),
|
|
// prepend '963' to ensure the correct format.
|
|
return '963$trimmedPhone';
|
|
}
|
|
|
|
Future getTokenForParent() async {
|
|
if (box.read(BoxName.sosPhonePassenger) == null) {
|
|
Get.defaultDialog(
|
|
title: 'Add SOS Phone'.tr,
|
|
titleStyle: AppStyle.title,
|
|
content: Form(
|
|
key: sosFormKey,
|
|
child: MyTextForm(
|
|
controller: sosPhonePassengerProfile,
|
|
label: 'insert sos phone'.tr,
|
|
hint: 'e.g. 0912345678'.tr,
|
|
type: TextInputType.phone,
|
|
),
|
|
),
|
|
confirm: MyElevatedButton(
|
|
title: 'Add SOS Phone'.tr,
|
|
onPressed: () async {
|
|
if (sosFormKey.currentState!.validate()) {
|
|
Get.back();
|
|
var numberPhone =
|
|
formatSyrianPhoneNumber(sosPhonePassengerProfile.text);
|
|
await CRUD().post(
|
|
link: AppLink.updateprofile,
|
|
payload: {
|
|
'id': box.read(BoxName.passengerID),
|
|
'sosPhone': numberPhone,
|
|
},
|
|
);
|
|
box.write(BoxName.sosPhonePassenger, numberPhone);
|
|
}
|
|
}));
|
|
}
|
|
var numberPhone = formatSyrianPhoneNumber(sosPhonePassengerProfile.text);
|
|
var res = await CRUD().getTokenParent(
|
|
link: AppLink.getTokenParent, payload: {'phone': numberPhone});
|
|
|
|
// Check if `res` is already a map
|
|
if (res is Map<String, dynamic>) {
|
|
var res1 = res;
|
|
handleResponse(res1);
|
|
} else {
|
|
// If it's a string, decode it
|
|
var res1 = jsonDecode(res);
|
|
handleResponse(res1);
|
|
}
|
|
}
|
|
|
|
// Function to check if the point is inside the polygon
|
|
bool isPointInPolygon(LatLng point, List<LatLng> polygon) {
|
|
int intersections = 0;
|
|
for (int i = 0; i < polygon.length; i++) {
|
|
LatLng vertex1 = polygon[i];
|
|
LatLng vertex2 =
|
|
polygon[(i + 1) % polygon.length]; // Loop back to the start
|
|
|
|
if (_rayIntersectsSegment(point, vertex1, vertex2)) {
|
|
intersections++;
|
|
}
|
|
}
|
|
|
|
// If the number of intersections is odd, the point is inside
|
|
return intersections % 2 != 0;
|
|
}
|
|
|
|
// Helper function to check if a ray from the point intersects with a polygon segment
|
|
bool _rayIntersectsSegment(LatLng point, LatLng vertex1, LatLng vertex2) {
|
|
double px = point.longitude;
|
|
double py = point.latitude;
|
|
|
|
double v1x = vertex1.longitude;
|
|
double v1y = vertex1.latitude;
|
|
double v2x = vertex2.longitude;
|
|
double v2y = vertex2.latitude;
|
|
|
|
// Check if the point is outside the vertical bounds of the segment
|
|
if ((py < v1y && py < v2y) || (py > v1y && py > v2y)) {
|
|
return false;
|
|
}
|
|
|
|
// Calculate the intersection of the ray and the segment
|
|
double intersectX = v1x + (py - v1y) * (v2x - v1x) / (v2y - v1y);
|
|
|
|
// Check if the intersection is to the right of the point
|
|
return intersectX > px;
|
|
}
|
|
|
|
bool isInUniversity = false;
|
|
// Function to check if the passenger is in any university polygon
|
|
// Function to check if the passenger is in any university polygon and return the university name
|
|
String checkPassengerLocation(LatLng passengerLocation,
|
|
List<List<LatLng>> universityPolygons, List<String> universityNames) {
|
|
for (int i = 0; i < universityPolygons.length; i++) {
|
|
if (isPointInPolygon(passengerLocation, universityPolygons[i])) {
|
|
isInUniversity = true;
|
|
return "Passenger is in ${universityNames[i]}";
|
|
}
|
|
}
|
|
return "Passenger is not in any university";
|
|
}
|
|
|
|
String passengerLocationStringUnvirsity = 'unKnown';
|
|
void getPassengerLocationUniversity() {
|
|
// Check if the passenger is inside any of the university polygons and get the university name
|
|
passengerLocationStringUnvirsity = checkPassengerLocation(
|
|
passengerLocation,
|
|
UniversitiesPolygons.universityPolygons,
|
|
UniversitiesPolygons.universityNames,
|
|
);
|
|
if (passengerLocationStringUnvirsity != 'unKnown') {
|
|
// Get.snackbar('you are in $passengerLocationStringUnvirsity', "");
|
|
}
|
|
print(passengerLocationStringUnvirsity);
|
|
}
|
|
|
|
var polygons = <Polygon>{}.obs;
|
|
|
|
// Initialize polygons from UniversitiesPolygons
|
|
void _initializePolygons() {
|
|
List<List<LatLng>> universityPolygons =
|
|
UniversitiesPolygons.universityPolygons;
|
|
List<String> universityNames = UniversitiesPolygons.universityNames;
|
|
|
|
for (int i = 0; i < universityPolygons.length; i++) {
|
|
Polygon polygon = Polygon(
|
|
polygonId: PolygonId(universityNames[i]),
|
|
points: universityPolygons[i],
|
|
strokeColor: Colors.blueAccent,
|
|
fillColor: Colors.blueAccent.withOpacity(0.2),
|
|
strokeWidth: 2,
|
|
);
|
|
polygons.add(polygon); // Add polygon to observable set
|
|
}
|
|
}
|
|
|
|
void handleResponse(Map<String, dynamic> res1) {
|
|
if (res1['message'] == "No passenger found for the given phone number") {
|
|
Get.defaultDialog(
|
|
title: "No user found for the given phone number".tr,
|
|
titleStyle: AppStyle.title,
|
|
content: Column(
|
|
children: [
|
|
Text(
|
|
"No passenger found for the given phone number".tr,
|
|
style: AppStyle.title,
|
|
),
|
|
Text(
|
|
"Send Intaleq app to him".tr,
|
|
style: AppStyle.title.copyWith(color: AppColor.greenColor),
|
|
)
|
|
],
|
|
),
|
|
confirm: MyElevatedButton(
|
|
title: 'Ok'.tr,
|
|
onPressed: () {
|
|
Get.back();
|
|
var phone = box.read(BoxName.countryCode) == 'Egypt'
|
|
? '+2${box.read(BoxName.sosPhonePassenger)}'
|
|
: '+962${box.read(BoxName.sosPhonePassenger)}';
|
|
var message = '''Dear ,
|
|
|
|
🚀 I have just started an exciting trip and I would like to share the details of my journey and my current location with you in real-time! Please download the Intaleq app. It will allow you to view my trip details and my latest location.
|
|
|
|
👉 Download link:
|
|
Android [https://play.google.com/store/apps/details?id=com.mobileapp.store.ride]
|
|
iOS [https://getapp.cc/app/6458734951]
|
|
|
|
I look forward to keeping you close during my adventure!
|
|
|
|
Intaleq ,'''
|
|
.tr;
|
|
launchCommunication('whatsapp', phone, message);
|
|
}),
|
|
cancel: MyElevatedButton(
|
|
title: 'No'.tr,
|
|
onPressed: () {
|
|
Get.back();
|
|
}));
|
|
} else if (res1['status'] == 'success') {
|
|
var tokenParent = (res1['data'][0]['token']);
|
|
Get.snackbar("The invitation was sent successfully".tr, '',
|
|
backgroundColor: AppColor.greenColor);
|
|
// firebaseMessagesController.sendNotificationToPassengerToken(
|
|
// "Trip Monitoring".tr,
|
|
// "Trip Monitoring".tr,
|
|
// tokenParent,
|
|
// [rideId, driverId],
|
|
// 'order1',
|
|
// );
|
|
NotificationService.sendNotification(
|
|
target: tokenParent.toString(),
|
|
title: "Trip Monitoring".tr,
|
|
body: "Trip Monitoring".tr,
|
|
isTopic: false, // Important: this is a token
|
|
tone: 'tone1',
|
|
driverList: [rideId, driverId],
|
|
);
|
|
box.write(BoxName.parentTripSelected, true);
|
|
box.write(BoxName.tokenParent, tokenParent);
|
|
}
|
|
}
|
|
|
|
LatLng driverLocationToPassenger = const LatLng(32, 35);
|
|
Future getDriverCarsLocationToPassengerAfterApplied() async {
|
|
driverCarsLocationToPassengerAfterApplied = [];
|
|
|
|
var res = await CRUD().get(
|
|
link: AppLink.getDriverCarsLocationToPassengerAfterApplied,
|
|
payload: {'driver_id': driverId});
|
|
|
|
datadriverCarsLocationToPassengerAfterApplied = jsonDecode(res);
|
|
driverLocationToPassenger = LatLng(
|
|
double.parse(datadriverCarsLocationToPassengerAfterApplied['message'][0]
|
|
['latitude']),
|
|
double.parse(datadriverCarsLocationToPassengerAfterApplied['message'][0]
|
|
['longitude']));
|
|
driverCarsLocationToPassengerAfterApplied.add(LatLng(
|
|
double.parse(datadriverCarsLocationToPassengerAfterApplied['message'][0]
|
|
['latitude']),
|
|
double.parse(datadriverCarsLocationToPassengerAfterApplied['message'][0]
|
|
['longitude'])));
|
|
CarLocationModel model = CarLocationModel.fromJson(
|
|
datadriverCarsLocationToPassengerAfterApplied['message'][0]);
|
|
carLocationsModels.add(model);
|
|
update();
|
|
}
|
|
|
|
Future runEvery30SecondsUntilConditionMet() async {
|
|
// Calculate the duration of the trip in minutes.
|
|
double tripDurationInMinutes = durationToPassenger / 5;
|
|
int loopCount = tripDurationInMinutes.ceil();
|
|
// If the trip duration is less than or equal to 50 minutes, then break the loop.
|
|
for (var i = 0; i < loopCount; i++) {
|
|
// Wait for 50 seconds.
|
|
await Future.delayed(const Duration(seconds: 5));
|
|
if (rideTimerBegin == true || statusRide == 'Apply') {
|
|
await getDriverCarsLocationToPassengerAfterApplied();
|
|
reloadMarkerDriverCarsLocationToPassengerAfterApplied();
|
|
}
|
|
}
|
|
}
|
|
|
|
Future runWhenRideIsBegin() async {
|
|
// Calculate the duration of the trip in minutes.
|
|
double tripDurationInMinutes = durationToRide / 6;
|
|
int loopCount = tripDurationInMinutes.ceil();
|
|
// If the trip duration is less than or equal to 50 minutes, then break the loop.
|
|
clearMarkersExceptStartEnd();
|
|
for (var i = 0; i < loopCount; i++) {
|
|
// Wait for 50 seconds.
|
|
await Future.delayed(const Duration(seconds: 4));
|
|
// if (rideTimerBegin == true && statusRide == 'Apply') {
|
|
await getDriverCarsLocationToPassengerAfterApplied();
|
|
// }
|
|
reloadMarkerDriverCarsLocationToPassengerAfterApplied();
|
|
}
|
|
}
|
|
|
|
Timer? _timer;
|
|
// final int updateIntervalMs = 100; // Update every 100ms
|
|
// final double minMovementThreshold =
|
|
// 1.0; // Minimum movement in meters to trigger update
|
|
|
|
void clearMarkersExceptStartEnd() {
|
|
Set<Marker> markersToRemove = markers
|
|
.where((marker) =>
|
|
marker.markerId != const MarkerId("start") &&
|
|
marker.markerId != const MarkerId("end"))
|
|
.toSet();
|
|
|
|
for (Marker marker in markersToRemove) {
|
|
markers.remove(marker);
|
|
}
|
|
update();
|
|
}
|
|
|
|
void reloadMarkerDriverCarsLocationToPassengerAfterApplied() {
|
|
// clearMarkersExceptStartEnd();
|
|
|
|
LatLng driverPosition = LatLng(
|
|
double.parse(datadriverCarsLocationToPassengerAfterApplied['message'][0]
|
|
['latitude']),
|
|
double.parse(datadriverCarsLocationToPassengerAfterApplied['message'][0]
|
|
['longitude']));
|
|
|
|
double heading = double.parse(
|
|
datadriverCarsLocationToPassengerAfterApplied['message'][0]['heading']
|
|
.toString());
|
|
|
|
BitmapDescriptor icon =
|
|
datadriverCarsLocationToPassengerAfterApplied['message'][0]
|
|
['model']
|
|
.toString()
|
|
.contains('دراجة') ||
|
|
datadriverCarsLocationToPassengerAfterApplied['message'][0]
|
|
['make']
|
|
.toString()
|
|
.contains('دراجة')
|
|
? motoIcon
|
|
: datadriverCarsLocationToPassengerAfterApplied['message'][0]
|
|
['gender'] ==
|
|
'Female'
|
|
? ladyIcon
|
|
: carIcon;
|
|
|
|
_updateMarkerPosition(driverPosition, heading, icon);
|
|
}
|
|
|
|
void _updateMarkerPosition(
|
|
LatLng newPosition, double newHeading, BitmapDescriptor icon) {
|
|
const String markerId = 'driverToPassengers';
|
|
Marker? existingMarker = markers.cast<Marker?>().firstWhere(
|
|
(m) => m?.markerId == const MarkerId(markerId),
|
|
orElse: () => null,
|
|
);
|
|
|
|
if (existingMarker == null) {
|
|
// If the marker doesn't exist, create it at the new position
|
|
markers.add(Marker(
|
|
markerId: const MarkerId(markerId),
|
|
position: newPosition,
|
|
rotation: newHeading,
|
|
icon: icon,
|
|
));
|
|
update();
|
|
} else {
|
|
// If the marker exists, check if the movement is significant enough to update
|
|
double distance =
|
|
_calculateDistance(existingMarker.position, newPosition);
|
|
if (distance >= minMovementThreshold) {
|
|
_smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon);
|
|
}
|
|
}
|
|
|
|
mapController?.animateCamera(CameraUpdate.newLatLng(newPosition));
|
|
}
|
|
|
|
// @override
|
|
// void onClose() {
|
|
// _timer?.cancel();
|
|
// _animationTimers.forEach((_, timer) => timer.cancel());
|
|
// _animationTimers.clear();
|
|
// super.onClose();
|
|
// }
|
|
@override
|
|
void onClose() {
|
|
print(
|
|
"--- MapPassengerController: Closing and cleaning up all resources. ---");
|
|
|
|
// 1. إلغاء المؤقتات الفردية
|
|
// Using ?.cancel() is safe even if the timer is null
|
|
markerReloadingTimer!.cancel();
|
|
markerReloadingTimer1!.cancel();
|
|
markerReloadingTimer2!.cancel();
|
|
timerToPassengerFromDriverAfterApplied?.cancel();
|
|
_timer?.cancel();
|
|
|
|
// 2. إلغاء جميع المؤقتات في الخريطة (للتحريكات السلسة)
|
|
_animationTimers.forEach((key, timer) {
|
|
timer.cancel();
|
|
});
|
|
_animationTimers.clear();
|
|
|
|
// 3. إغلاق متحكمات البث (StreamControllers) لمنع تسريب الذاكرة
|
|
// Using .close() is the correct way to shut down a stream
|
|
if (!_timerStreamController.isClosed) {
|
|
_timerStreamController.close();
|
|
}
|
|
if (!_beginRideStreamController.isClosed) {
|
|
_beginRideStreamController.close();
|
|
}
|
|
if (!_rideStatusStreamController.isClosed) {
|
|
_rideStatusStreamController.close();
|
|
}
|
|
if (!timerController.isClosed) {
|
|
timerController.close();
|
|
}
|
|
|
|
// 4. التخلص من متحكم الخريطة (ممارسة جيدة)
|
|
mapController?.dispose();
|
|
|
|
print("--- Cleanup complete. ---");
|
|
super.onClose();
|
|
}
|
|
|
|
restCounter() {
|
|
clearPlacesDestination();
|
|
clearPolyline();
|
|
data = [];
|
|
rideConfirm = false;
|
|
shouldFetch = false;
|
|
timeToPassengerFromDriverAfterApplied = 0;
|
|
update();
|
|
}
|
|
|
|
//driver behaviour
|
|
double calculateBearing(double lat1, double lon1, double lat2, double lon2) {
|
|
double deltaLon = lon2 - lon1;
|
|
double y = sin(deltaLon) * cos(lat2);
|
|
double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(deltaLon);
|
|
double bearing = atan2(y, x);
|
|
return (bearing * 180 / pi + 360) % 360; // تحويل إلى درجات
|
|
}
|
|
|
|
void analyzeBehavior(Position currentPosition, List<LatLng> routePoints) {
|
|
double actualBearing = currentPosition.heading; // الاتجاه الفعلي من GPS
|
|
double expectedBearing = calculateBearing(
|
|
routePoints[0].latitude,
|
|
routePoints[0].longitude,
|
|
routePoints[1].latitude,
|
|
routePoints[1].longitude,
|
|
);
|
|
|
|
double bearingDifference = (expectedBearing - actualBearing).abs();
|
|
if (bearingDifference > 30) {
|
|
print("⚠️ السائق انحرف عن المسار!");
|
|
}
|
|
}
|
|
|
|
void detectStops(Position currentPosition) {
|
|
if (currentPosition.speed < 0.5) {
|
|
print("🚦 السائق توقف في موقع غير متوقع!");
|
|
}
|
|
}
|
|
|
|
Future<void> cancelRideAfterRejectFromAll() async {
|
|
clearPlacesDestination();
|
|
clearPolyline();
|
|
data = [];
|
|
await CRUD().post(link: AppLink.updateRides, payload: {
|
|
"id": rideId.toString(), // Convert to String
|
|
"status": 'notApplyFromAnyDriver'
|
|
});
|
|
if (AppLink.endPoint != AppLink.IntaleqCairoServer) {
|
|
CRUD().post(link: "${AppLink.endPoint}/ride/rides/update.php", payload: {
|
|
"id": rideId.toString(), // Convert to String
|
|
"status": 'notApplyFromAnyDriver'
|
|
});
|
|
}
|
|
rideConfirm = false;
|
|
statusRide == 'Cancel';
|
|
isSearchingWindow = false;
|
|
shouldFetch = false;
|
|
isPassengerChosen = false;
|
|
isCashConfirmPageShown = false;
|
|
// totalStepDurations = 0;
|
|
isCashSelectedBeforeConfirmRide = false;
|
|
timeToPassengerFromDriverAfterApplied = 0;
|
|
changeCancelRidePageShow();
|
|
remainingTime = 0;
|
|
|
|
update();
|
|
}
|
|
|
|
Future cancelRide() async {
|
|
clearPlacesDestination();
|
|
clearPolyline();
|
|
// clearPolylineAll();
|
|
data = [];
|
|
changeCancelRidePageShow();
|
|
if (rideId != 'yet') {
|
|
Log.print('cancelRide: 1');
|
|
// await firebaseMessagesController.sendNotificationToDriverMAP(
|
|
// 'Cancel Trip',
|
|
// 'Trip Cancelled'.tr,
|
|
// driverToken.toString(),
|
|
// [],
|
|
// 'cancel',
|
|
// );
|
|
await NotificationService.sendNotification(
|
|
target: driverToken.toString(),
|
|
title: 'Cancel Trip'.tr,
|
|
body: 'Cancel Trip'.tr,
|
|
isTopic: false, // Important: this is a token
|
|
tone: 'tone1',
|
|
driverList: [],
|
|
);
|
|
await Future.wait([
|
|
CRUD().post(link: AppLink.updateRides, payload: {
|
|
"id": rideId.toString(), // Convert to String
|
|
"status": 'Cancel'
|
|
}),
|
|
CRUD().post(link: AppLink.updateDriverOrder, payload: {
|
|
"order_id": rideId.toString(), // Convert to String
|
|
"status": 'Cancel'
|
|
}),
|
|
CRUD().post(link: AppLink.updateWaitingTrip, payload: {
|
|
"id": rideId.toString(), // Convert to String
|
|
"status": 'Cancel'
|
|
}),
|
|
]);
|
|
}
|
|
// Future.delayed(const Duration(seconds: 1));
|
|
Get.offAll(() => const MapPagePassenger());
|
|
}
|
|
|
|
void changePickerShown() {
|
|
isPickerShown = !isPickerShown;
|
|
heightPickerContainer = isPickerShown == true ? 150 : 90;
|
|
update();
|
|
}
|
|
|
|
void changeHeightPointsPageForRider() {
|
|
isPointsPageForRider = !isPointsPageForRider;
|
|
heightPointsPageForRider = isPointsPageForRider == true ? Get.height : 0;
|
|
update();
|
|
}
|
|
|
|
getCoordinateFromMapWayPoints(int index) {
|
|
placesCoordinate[index] = newStartPointLocation.toString();
|
|
update();
|
|
}
|
|
// --- ابدأ الإضافة هنا ---
|
|
|
|
// 1. قائمة لتخزين نقاط التوقف
|
|
List<Map<String, dynamic>> waypoints = [];
|
|
|
|
// 2. دالة لإضافة نقطة توقف جديدة
|
|
void addWaypoint(Map<String, dynamic> placeDetails) {
|
|
// يمكنك إضافة منطق للتحقق من عدد نقاط التوقف المسموح بها هنا
|
|
waypoints.add(placeDetails);
|
|
update(); // لتحديث الواجهة
|
|
// TODO: أضف هنا استدعاء دالة إعادة رسم المسار مع نقاط التوقف الجديدة
|
|
// getDirectionMapWithWaypoints();
|
|
}
|
|
|
|
// 3. دالة لحذف نقطة توقف
|
|
void removeWaypoint(int index) {
|
|
if (index >= 0 && index < waypoints.length) {
|
|
waypoints.removeAt(index);
|
|
update(); // لتحديث الواجهة
|
|
// TODO: أضف هنا استدعاء دالة إعادة رسم المسار بعد حذف النقطة
|
|
// getDirectionMapWithWaypoints();
|
|
}
|
|
}
|
|
|
|
// --- انتهى ---
|
|
|
|
void changeMainBottomMenuMap() {
|
|
if (isWayPointStopsSheetUtilGetMap == true) {
|
|
changeWayPointSheet();
|
|
} else {
|
|
isMainBottomMenuMap = !isMainBottomMenuMap;
|
|
mainBottomMenuMapHeight =
|
|
isMainBottomMenuMap == true ? Get.height * .22 : Get.height * .6;
|
|
isWayPointSheet = false;
|
|
if (heightMenuBool == true) {
|
|
getDrawerMenu();
|
|
}
|
|
initilizeGetStorage();
|
|
update();
|
|
}
|
|
}
|
|
|
|
void downPoints() {
|
|
if (Get.find<WayPointController>().wayPoints.length < 2) {
|
|
isWayPointStopsSheetUtilGetMap = false;
|
|
isWayPointSheet = false;
|
|
wayPointSheetHeight = isWayPointStopsSheet ? Get.height * .45 : 0;
|
|
// changeWayPointStopsSheet();
|
|
update();
|
|
}
|
|
// changeWayPointStopsSheet();
|
|
// isWayPointSheet = false;
|
|
update();
|
|
}
|
|
|
|
void changeWayPointSheet() {
|
|
isWayPointSheet = !isWayPointSheet;
|
|
wayPointSheetHeight = isWayPointSheet == false ? 0 : Get.height * .45;
|
|
// if (heightMenuBool == true) {
|
|
// getDrawerMenu();
|
|
// }
|
|
update();
|
|
}
|
|
|
|
void changeWayPointStopsSheet() {
|
|
// int waypointsLength = Get.find<WayPointController>().wayPoints.length;
|
|
|
|
if (wayPointIndex > -1) {
|
|
isWayPointStopsSheet = true;
|
|
isWayPointStopsSheetUtilGetMap = true;
|
|
}
|
|
isWayPointStopsSheet = !isWayPointStopsSheet;
|
|
wayPointSheetHeight = isWayPointStopsSheet ? Get.height * .45 : 0;
|
|
// if (heightMenuBool == true) {
|
|
// getDrawerMenu();
|
|
// }
|
|
update();
|
|
}
|
|
|
|
changeHeightPlaces() {
|
|
if (placesDestination.isEmpty) {
|
|
height = 0;
|
|
update();
|
|
}
|
|
height = 150;
|
|
update();
|
|
}
|
|
|
|
changeHeightStartPlaces() {
|
|
if (placesStart.isEmpty) {
|
|
height = 0;
|
|
update();
|
|
}
|
|
height = 150;
|
|
update();
|
|
}
|
|
|
|
changeHeightPlacesAll(int index) {
|
|
if (placeListResponseAll[index].isEmpty) {
|
|
height = 0;
|
|
update();
|
|
}
|
|
height = 150;
|
|
update();
|
|
}
|
|
|
|
changeHeightPlaces1() {
|
|
if (wayPoint1.isEmpty) {
|
|
height = 0;
|
|
update();
|
|
}
|
|
height = 150;
|
|
update();
|
|
}
|
|
|
|
changeHeightPlaces2() {
|
|
if (wayPoint2.isEmpty) {
|
|
height = 0;
|
|
update();
|
|
}
|
|
height = 150;
|
|
update();
|
|
}
|
|
|
|
changeHeightPlaces3() {
|
|
if (wayPoint3.isEmpty) {
|
|
height = 0;
|
|
update();
|
|
}
|
|
height = 150;
|
|
update();
|
|
}
|
|
|
|
changeHeightPlaces4() {
|
|
if (wayPoint4.isEmpty) {
|
|
height = 0;
|
|
update();
|
|
}
|
|
height = 150;
|
|
update();
|
|
}
|
|
|
|
hidePlaces() {
|
|
height = 0;
|
|
|
|
update();
|
|
}
|
|
|
|
/// تحويل نصف قطر بالكيلومتر إلى دلتا درجات عرض
|
|
|
|
// double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
|
|
// const R = 6371.0; // km
|
|
// final dLat = (lat2 - lat1) * math.pi / 180.0;
|
|
// final dLon = (lon2 - lon1) * math.pi / 180.0;
|
|
// final a = math.sin(dLat / 2) * math.sin(dLat / 2) +
|
|
// math.cos(lat1 * math.pi / 180.0) *
|
|
// math.cos(lat2 * math.pi / 180.0) *
|
|
// math.sin(dLon / 2) *
|
|
// math.sin(dLon / 2);
|
|
// final c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a));
|
|
// return R * c;
|
|
// }
|
|
|
|
/// تحويل نصف قطر بالكيلومتر إلى دلتا درجات عرض
|
|
// double _kmToLatDelta(double km) => km / 111.0;
|
|
|
|
// /// تحويل نصف قطر بالكيلومتر إلى دلتا درجات طول (تعتمد على خط العرض)
|
|
// double _kmToLngDelta(double km, double atLat) =>
|
|
// km / (111.320 * math.cos(atLat * math.pi / 180.0)).abs().clamp(1e-6, 1e9);
|
|
|
|
/// حساب درجة التطابق النصي (كل كلمة تبدأ بها الاسم = 2 نقاط، يحتويها = 1 نقطة)
|
|
// double _relevanceScore(String name, String query) {
|
|
// final n = name.toLowerCase();
|
|
// final parts =
|
|
// query.toLowerCase().split(RegExp(r'\s+')).where((p) => p.length >= 2);
|
|
// double s = 0.0;
|
|
// for (final p in parts) {
|
|
// if (n.startsWith(p)) {
|
|
// s += 2.0;
|
|
// } else if (n.contains(p)) {
|
|
// s += 1.0;
|
|
// }
|
|
// }
|
|
// return s;
|
|
// }
|
|
// الدالة الرئيسية لجلب الأماكن من السيرفر وترتيبها
|
|
// انسخ هذه الدوال والصقها داخل كلاس الكنترولر الخاص بك
|
|
|
|
// -----------------------------------------------------------------
|
|
// --== الدالة الرئيسية للبحث ==--
|
|
// -----------------------------------------------------------------
|
|
/// الدالة الرئيسية لجلب الأماكن من السيرفر وترتيبها
|
|
// انسخ هذه الدوال والصقها داخل كلاس الكنترولر الخاص بك
|
|
|
|
// -----------------------------------------------------------------
|
|
// --== الدالة الرئيسية للبحث ==--
|
|
// -----------------------------------------------------------------
|
|
/// الدالة الرئيسية لجلب الأماكن من السيرفر وترتيبها
|
|
Future<void> getPlaces() async {
|
|
// افترض وجود `placeDestinationController` و `passengerLocation` و `CRUD()` معرفة في الكنترولر
|
|
final q = placeDestinationController.text.trim();
|
|
if (q.isEmpty || q.length < 3) {
|
|
// يفضل عدم البحث قبل 3 أحرف
|
|
placesDestination = [];
|
|
update(); // افترض أنك تستخدم GetX أو أي State Management آخر
|
|
return;
|
|
}
|
|
|
|
final lat = passengerLocation.latitude;
|
|
final lng = passengerLocation.longitude;
|
|
|
|
// نصف قطر البحث بالكيلومتر
|
|
const radiusKm = 200.0;
|
|
|
|
// حساب النطاق الجغرافي (Bounding Box) لإرساله للسيرفر
|
|
final latDelta = _kmToLatDelta(radiusKm);
|
|
final lngDelta = _kmToLngDelta(radiusKm, lat);
|
|
|
|
final latMin = lat - latDelta;
|
|
final latMax = lat + latDelta;
|
|
final lngMin = lng - lngDelta;
|
|
final lngMax = lng + lngDelta;
|
|
|
|
try {
|
|
// استدعاء الـ API (تأكد من أن AppLink.getPlacesSyria يشير للسكريبت الجديد)
|
|
final response = await CRUD().post(
|
|
link: AppLink.getPlacesSyria,
|
|
payload: {
|
|
'query': q,
|
|
'lat_min': latMin.toString(),
|
|
'lat_max': latMax.toString(),
|
|
'lng_min': lngMin.toString(),
|
|
'lng_max': lngMax.toString(),
|
|
},
|
|
);
|
|
|
|
// --- [تم الإصلاح هنا] ---
|
|
// معالجة الاستجابة من السيرفر بشكل يوافق {"status":"success", "message":[...]}
|
|
List list;
|
|
if (response is Map) {
|
|
if (response['status'] == 'success' && response['message'] is List) {
|
|
list = List.from(response['message'] as List);
|
|
} else if (response['status'] == 'failure') {
|
|
print('Server Error: ${response['message']}');
|
|
return;
|
|
} else {
|
|
print('Unexpected Map shape from server');
|
|
return;
|
|
}
|
|
} else if (response is List) {
|
|
// للتعامل مع الحالات التي قد يرجع فيها السيرفر قائمة مباشرة
|
|
list = List.from(response);
|
|
} else {
|
|
print('Unexpected response shape from server');
|
|
return;
|
|
}
|
|
|
|
// --- هنا يبدأ عمل فلاتر: الترتيب النهائي الدقيق ---
|
|
|
|
// دالة مساعدة لاختيار أفضل اسم متاح
|
|
String _bestName(Map p) {
|
|
return (p['name_ar'] ?? p['name'] ?? p['name_en'] ?? '').toString();
|
|
}
|
|
|
|
// حساب المسافة والصلة والنقاط النهائية لكل نتيجة
|
|
for (final p in list) {
|
|
final plat = double.tryParse(p['latitude']?.toString() ?? '0.0') ?? 0.0;
|
|
final plng =
|
|
double.tryParse(p['longitude']?.toString() ?? '0.0') ?? 0.0;
|
|
|
|
final distance = _haversineKm(lat, lng, plat, plng);
|
|
final relevance = _relevanceScore(_bestName(p), q);
|
|
|
|
// معادلة الترتيب: (الأولوية للمسافة الأقرب) * (ثم الصلة الأعلى)
|
|
final score = (1.0 / (1.0 + distance)) * (1.0 + relevance);
|
|
|
|
p['distanceKm'] = distance;
|
|
p['relevance'] = relevance;
|
|
p['score'] = score;
|
|
}
|
|
|
|
// ترتيب القائمة النهائية حسب النقاط (الأعلى أولاً)
|
|
list.sort((a, b) {
|
|
final sa = (a['score'] ?? 0.0) as double;
|
|
final sb = (b['score'] ?? 0.0) as double;
|
|
return sb.compareTo(sa);
|
|
});
|
|
|
|
placesDestination = list;
|
|
print('Updated places: $placesDestination');
|
|
update();
|
|
} catch (e) {
|
|
print('Exception in getPlaces: $e');
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------
|
|
// --== دوال مساعدة ==--
|
|
// -----------------------------------------------------------------
|
|
|
|
/// تحسب المسافة بين نقطتين بالكيلومتر (معادلة هافرساين)
|
|
double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
|
|
const R = 6371.0; // نصف قطر الأرض بالكيلومتر
|
|
final dLat = (lat2 - lat1) * (pi / 180.0);
|
|
final dLon = (lon2 - lon1) * (pi / 180.0);
|
|
final rLat1 = lat1 * (pi / 180.0);
|
|
final rLat2 = lat2 * (pi / 180.0);
|
|
|
|
final a = sin(dLat / 2) * sin(dLat / 2) +
|
|
cos(rLat1) * cos(rLat2) * sin(dLon / 2) * sin(dLon / 2);
|
|
final c = 2 * atan2(sqrt(a), sqrt(1 - a));
|
|
return R * c;
|
|
}
|
|
|
|
/// تحسب درجة تطابق بسيطة بين اسم المكان وكلمة البحث
|
|
double _relevanceScore(String placeName, String query) {
|
|
if (placeName.isEmpty || query.isEmpty) return 0.0;
|
|
final pLower = placeName.toLowerCase();
|
|
final qLower = query.toLowerCase();
|
|
if (pLower.startsWith(qLower)) return 1.0; // تطابق كامل في البداية
|
|
if (pLower.contains(qLower)) return 0.5; // تحتوي على الكلمة
|
|
return 0.0;
|
|
}
|
|
|
|
/// تحويل كيلومتر إلى فرق درجات لخط العرض
|
|
double _kmToLatDelta(double km) {
|
|
const kmInDegree = 111.32;
|
|
return km / kmInDegree;
|
|
}
|
|
|
|
/// تحويل كيلومتر إلى فرق درجات لخط الطول (يعتمد على خط العرض الحالي)
|
|
double _kmToLngDelta(double km, double latitude) {
|
|
const kmInDegree = 111.32;
|
|
return km / (kmInDegree * cos(latitude * (pi / 180.0)));
|
|
}
|
|
|
|
// var languageCode;
|
|
|
|
// // تحديد اللغة حسب الإدخال
|
|
// if (RegExp(r'[a-zA-Z]').hasMatch(placeDestinationController.text)) {
|
|
// languageCode = 'en';
|
|
// } else {
|
|
// languageCode = 'ar';
|
|
// }
|
|
|
|
// final bool isTextEmpty = placeDestinationController.text.trim().isEmpty;
|
|
// var key = Platform.isAndroid ? AK.mapAPIKEY : AK.mapAPIKEYIOS;
|
|
// final Uri url = Uri.parse(
|
|
// isTextEmpty
|
|
// ? 'https://places.googleapis.com/v1/places:searchNearby?key=$key'
|
|
// : 'https://places.googleapis.com/v1/places:searchText?key=$key',
|
|
// );
|
|
// Log.print('url: $url');
|
|
// // بناء الجسم حسب نوع الطلب
|
|
// final body = isTextEmpty
|
|
// ? jsonEncode({
|
|
// "languageCode": languageCode,
|
|
// "locationRestriction": {
|
|
// "circle": {
|
|
// "center": {
|
|
// "latitude": passengerLocation.latitude,
|
|
// "longitude": passengerLocation.longitude
|
|
// },
|
|
// "radius": 40000 // 40 كم
|
|
// }
|
|
// },
|
|
// "maxResultCount": 10
|
|
// })
|
|
// : jsonEncode({
|
|
// "textQuery": placeDestinationController.text,
|
|
// "languageCode": languageCode,
|
|
// "maxResultCount": 10,
|
|
// "locationBias": {
|
|
// "circle": {
|
|
// "center": {
|
|
// "latitude": passengerLocation.latitude,
|
|
// "longitude": passengerLocation.longitude
|
|
// },
|
|
// "radius": 40000
|
|
// }
|
|
// }
|
|
// });
|
|
|
|
// final headers = {
|
|
// 'Content-Type': 'application/json',
|
|
// 'X-Goog-Api-Key': AK.mapAPIKEY,
|
|
// 'X-Goog-FieldMask':
|
|
// 'places.displayName,places.formattedAddress,places.location'
|
|
// };
|
|
|
|
// try {
|
|
// final response = await http.post(url, headers: headers, body: body);
|
|
// print('response: ${response.statusCode} - ${response.body}');
|
|
|
|
// if (response.statusCode == 200) {
|
|
// final data = jsonDecode(response.body);
|
|
// placesDestination = data['places'] ?? [];
|
|
// update();
|
|
// } else {
|
|
// print('Error: ${response.statusCode} - ${response.reasonPhrase}');
|
|
// }
|
|
// } catch (e) {
|
|
// print('Exception: $e');
|
|
// }
|
|
// }
|
|
|
|
getAIKey(String key) async {
|
|
var res =
|
|
await CRUD().get(link: AppLink.getapiKey, payload: {"keyName": key});
|
|
if (res != 'failure') {
|
|
var d = jsonDecode(res)['message'];
|
|
return d[key].toString();
|
|
} else {}
|
|
}
|
|
|
|
// Future<void> getPlaces() async {
|
|
// var languageCode;
|
|
|
|
// // Check if `placeDestinationController.text` contains English characters
|
|
// if (RegExp(r'[a-zA-Z]').hasMatch(placeDestinationController.text)) {
|
|
// languageCode = 'en';
|
|
// } else {
|
|
// languageCode = 'ar';
|
|
// }
|
|
|
|
// // Construct the URL
|
|
// var url = Uri.parse(
|
|
// '${AppLink.searcMaps}?q=${Uri.encodeQueryComponent(placeDestinationController.text)}&limit=10&in=circle:${passengerLocation.latitude},${passengerLocation.longitude};r=50000&lang=$languageCode&apiKey=$k',
|
|
// );
|
|
|
|
// // Log the URL for debugging
|
|
// print(url);
|
|
// // box.remove(BoxName.placesDestination);
|
|
// try {
|
|
// // Make the API request
|
|
// var response = await CRUD().getHereMap(
|
|
// link: url.toString(),
|
|
// );
|
|
|
|
// // Log the response for debugging
|
|
// // Log.print('response: ${response}');
|
|
|
|
// // Check if the response is valid
|
|
// if (response != null && response['items'] != null) {
|
|
// placesDestination = response['items'];
|
|
// // Log.print('placesDestination: ${placesDestination}');
|
|
|
|
// placesDestination = response['items'];
|
|
// // box.write(BoxName.placesDestination, placesDestination);
|
|
// for (var i = 0; i < placesDestination.length; i++) {
|
|
// var res = placesDestination[i];
|
|
|
|
// // Extract fields with null safety
|
|
// var title = res['title']?.toString() ?? 'Unknown Place';
|
|
// var position = res['position'];
|
|
// var address = res['address']?['label'] ?? 'Unknown Address';
|
|
// if (position == null) {
|
|
// Log.print('Position is null for place: $title');
|
|
// continue; // Skip this place and continue with the next one
|
|
// }
|
|
|
|
// String latitude = position['lat']?.toString() ?? '0.0';
|
|
// String longitude = position['lng']?.toString() ?? '0.0';
|
|
|
|
// try {
|
|
// await savePlaceToServer(latitude, longitude, title, address);
|
|
// // Log.print('Place saved successfully: $title');
|
|
// } catch (e) {
|
|
// // Log.print('Failed to save place: $e');
|
|
// }
|
|
// } // todo save key in env then get key and use it
|
|
// } else {
|
|
// placesDestination = [];
|
|
// }
|
|
// } catch (e) {
|
|
// // Handle any errors that occur during the API request
|
|
// Log.print('Error fetching places: $e');
|
|
// placesDestination = [];
|
|
// }
|
|
|
|
// // Notify listeners that the state has changed
|
|
// update();
|
|
// }
|
|
|
|
Future<void> getPlacesStart() async {
|
|
// افترض وجود `placeDestinationController` و `passengerLocation` و `CRUD()` معرفة في الكنترولر
|
|
final q = placeStartController.text.trim();
|
|
if (q.isEmpty || q.length < 3) {
|
|
// يفضل عدم البحث قبل 3 أحرف
|
|
placesStart = [];
|
|
update(); // افترض أنك تستخدم GetX أو أي State Management آخر
|
|
return;
|
|
}
|
|
|
|
final lat = passengerLocation.latitude;
|
|
final lng = passengerLocation.longitude;
|
|
|
|
// نصف قطر البحث بالكيلومتر
|
|
const radiusKm = 200.0;
|
|
|
|
// حساب النطاق الجغرافي (Bounding Box) لإرساله للسيرفر
|
|
final latDelta = _kmToLatDelta(radiusKm);
|
|
final lngDelta = _kmToLngDelta(radiusKm, lat);
|
|
|
|
final latMin = lat - latDelta;
|
|
final latMax = lat + latDelta;
|
|
final lngMin = lng - lngDelta;
|
|
final lngMax = lng + lngDelta;
|
|
|
|
try {
|
|
// استدعاء الـ API (تأكد من أن AppLink.getPlacesSyria يشير للسكريبت الجديد)
|
|
final response = await CRUD().post(
|
|
link: AppLink.getPlacesSyria,
|
|
payload: {
|
|
'query': q,
|
|
'lat_min': latMin.toString(),
|
|
'lat_max': latMax.toString(),
|
|
'lng_min': lngMin.toString(),
|
|
'lng_max': lngMax.toString(),
|
|
},
|
|
);
|
|
|
|
// --- [تم الإصلاح هنا] ---
|
|
// معالجة الاستجابة من السيرفر بشكل يوافق {"status":"success", "message":[...]}
|
|
List list;
|
|
if (response is Map) {
|
|
if (response['status'] == 'success' && response['message'] is List) {
|
|
list = List.from(response['message'] as List);
|
|
} else if (response['status'] == 'failure') {
|
|
print('Server Error: ${response['message']}');
|
|
return;
|
|
} else {
|
|
print('Unexpected Map shape from server');
|
|
return;
|
|
}
|
|
} else if (response is List) {
|
|
// للتعامل مع الحالات التي قد يرجع فيها السيرفر قائمة مباشرة
|
|
list = List.from(response);
|
|
} else {
|
|
print('Unexpected response shape from server');
|
|
return;
|
|
}
|
|
|
|
// --- هنا يبدأ عمل فلاتر: الترتيب النهائي الدقيق ---
|
|
|
|
// دالة مساعدة لاختيار أفضل اسم متاح
|
|
String _bestName(Map p) {
|
|
return (p['name_ar'] ?? p['name'] ?? p['name_en'] ?? '').toString();
|
|
}
|
|
|
|
// حساب المسافة والصلة والنقاط النهائية لكل نتيجة
|
|
for (final p in list) {
|
|
final plat = double.tryParse(p['latitude']?.toString() ?? '0.0') ?? 0.0;
|
|
final plng =
|
|
double.tryParse(p['longitude']?.toString() ?? '0.0') ?? 0.0;
|
|
|
|
final distance = _haversineKm(lat, lng, plat, plng);
|
|
final relevance = _relevanceScore(_bestName(p), q);
|
|
|
|
// معادلة الترتيب: (الأولوية للمسافة الأقرب) * (ثم الصلة الأعلى)
|
|
final score = (1.0 / (1.0 + distance)) * (1.0 + relevance);
|
|
|
|
p['distanceKm'] = distance;
|
|
p['relevance'] = relevance;
|
|
p['score'] = score;
|
|
}
|
|
|
|
// ترتيب القائمة النهائية حسب النقاط (الأعلى أولاً)
|
|
list.sort((a, b) {
|
|
final sa = (a['score'] ?? 0.0) as double;
|
|
final sb = (b['score'] ?? 0.0) as double;
|
|
return sb.compareTo(sa);
|
|
});
|
|
|
|
placesStart = list;
|
|
print('Updated places: $placesDestination');
|
|
update();
|
|
} catch (e) {
|
|
print('Exception in getPlaces: $e');
|
|
}
|
|
}
|
|
|
|
Future getPlacesListsWayPoint(int index) async {
|
|
var languageCode = wayPoint0Controller.text;
|
|
|
|
// Regular expression to check for English alphabet characters
|
|
final englishRegex = RegExp(r'[a-zA-Z]');
|
|
|
|
// Check if text contains English characters
|
|
if (englishRegex.hasMatch(languageCode)) {
|
|
languageCode = 'en';
|
|
} else {
|
|
languageCode = 'ar';
|
|
}
|
|
|
|
var url =
|
|
'${AppLink.googleMapsLink}place/nearbysearch/json?keyword=${wayPoint0Controller.text}&location=${passengerLocation.latitude},${passengerLocation.longitude}&radius=250000&language=$languageCode&key=${AK.mapAPIKEY.toString()}';
|
|
|
|
try {
|
|
var response = await CRUD().getGoogleApi(link: url, payload: {});
|
|
|
|
if (response != null && response['results'] != null) {
|
|
wayPoint0 = response['results'];
|
|
placeListResponseAll[index] = response['results'];
|
|
update();
|
|
} else {
|
|
print('Error: Invalid response from Google Places API');
|
|
}
|
|
} catch (e) {
|
|
print('Error fetching places: $e');
|
|
}
|
|
}
|
|
|
|
// داخل MapPassengerController
|
|
bool lowPerf = false;
|
|
Timer? _camThrottle;
|
|
DateTime _lastUiUpdate = DateTime.fromMillisecondsSinceEpoch(0);
|
|
|
|
// نادِها في onInit مثلاً
|
|
Future<void> detectPerfMode() async {
|
|
try {
|
|
if (GetPlatform.isAndroid) {
|
|
final info = await DeviceInfoPlugin().androidInfo;
|
|
final sdk = info.version.sdkInt ?? 0;
|
|
final ram = info.availableRamSize ?? 0;
|
|
lowPerf = (sdk < 28) || (ram > 0 && ram < 3 * 1024 * 1024 * 1024);
|
|
} else {
|
|
lowPerf = false;
|
|
}
|
|
} catch (_) {
|
|
lowPerf = false;
|
|
}
|
|
update();
|
|
}
|
|
|
|
// تحديث الكاميرا بثروتل
|
|
void onCameraMoveThrottled(CameraPosition pos) {
|
|
if (_camThrottle?.isActive ?? false) return;
|
|
_camThrottle = Timer(const Duration(milliseconds: 160), () {
|
|
// ضع فقط المنطق الضروري هنا لتقليل الحمل
|
|
int waypointsLength = Get.find<WayPointController>().wayPoints.length;
|
|
int index = wayPointIndex;
|
|
if (waypointsLength > 0) {
|
|
placesCoordinate[index] =
|
|
'${pos.target.latitude},${pos.target.longitude}';
|
|
}
|
|
newMyLocation = pos.target;
|
|
});
|
|
}
|
|
|
|
// تهيئة polylines خفيفة (استدعها بعد جلب المسار)
|
|
Set<Polyline> polyLinesLight = {};
|
|
List<LatLng> simplifyPolyline(List<LatLng> pts, double epsilonMeters) {
|
|
if (pts.length <= 2) return pts;
|
|
double _perpDist(LatLng p, LatLng a, LatLng b) {
|
|
// مسافة عمودية تقريبية بالأمتار
|
|
double _toRad(double d) => d * math.pi / 180.0;
|
|
// تحويل بسيط للمتر (تقريبي)
|
|
final x1 = a.longitude, y1 = a.latitude;
|
|
final x2 = b.longitude, y2 = b.latitude;
|
|
final x0 = p.longitude, y0 = p.latitude;
|
|
final num = ((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1).abs();
|
|
final den = math.sqrt(math.pow(y2 - y1, 2) + math.pow(x2 - x1, 2));
|
|
// تحويل درجات -> أمتار تقريبي (1 درجة ~ 111km)
|
|
final degDist = den == 0 ? 0 : num / den;
|
|
return degDist * 111000; // متر
|
|
}
|
|
|
|
List<LatLng> dp(int start, int end) {
|
|
double maxDist = 0;
|
|
int index = start;
|
|
for (int i = start + 1; i < end; i++) {
|
|
final d = _perpDist(pts[i], pts[start], pts[end]);
|
|
if (d > maxDist) {
|
|
maxDist = d;
|
|
index = i;
|
|
}
|
|
}
|
|
if (maxDist > epsilonMeters) {
|
|
final r1 = dp(start, index);
|
|
final r2 = dp(index, end);
|
|
return [...r1.sublist(0, r1.length - 1), ...r2];
|
|
} else {
|
|
return [pts[start], pts[end]];
|
|
}
|
|
}
|
|
|
|
return dp(0, pts.length - 1);
|
|
}
|
|
|
|
void buildLightPolylines(List<LatLng> originalPoints) {
|
|
final simplified = simplifyPolyline(originalPoints, lowPerf ? 12 : 3);
|
|
polyLinesLight = {
|
|
Polyline(
|
|
polylineId: const PolylineId('route_light'),
|
|
points: simplified,
|
|
width: lowPerf ? 4 : 6,
|
|
geodesic: true,
|
|
color: AppColor.primaryColor,
|
|
endCap: Cap.roundCap,
|
|
startCap: Cap.roundCap,
|
|
jointType: JointType.round,
|
|
),
|
|
};
|
|
update();
|
|
}
|
|
|
|
Future<void> savePlaceToServer(
|
|
String latitude, String longitude, String name, String rate) async {
|
|
var data = {
|
|
'latitude': latitude,
|
|
'longitude': longitude,
|
|
'name': name,
|
|
'rate': rate,
|
|
};
|
|
|
|
try {
|
|
CRUD().post(
|
|
link: AppLink.savePlacesServer,
|
|
payload: data,
|
|
);
|
|
} catch (e) {
|
|
print('Error: $e');
|
|
}
|
|
}
|
|
// Future getPlacesListsWayPoint(int index) async {
|
|
// var url =
|
|
// '${AppLink.googleMapsLink}place/nearbysearch/json?keyword=${wayPoint0Controller.text}&location=${passengerLocation.latitude},${passengerLocation.longitude}&radius=80000&language=${}&key=${AK.mapAPIKEY.toString()}';
|
|
|
|
// var response = await CRUD().getGoogleApi(link: url, payload: {});
|
|
|
|
// wayPoint0 = response['results'];
|
|
// placeListResponseAll[index] = response['results'];
|
|
// update();
|
|
// }
|
|
|
|
LatLng fromString(String location) {
|
|
List<String> parts = location.split(',');
|
|
double lat = double.parse(parts[0]);
|
|
double lng = double.parse(parts[1]);
|
|
return LatLng(lat, lng);
|
|
}
|
|
|
|
void clearPolyline() {
|
|
polyLines = [];
|
|
polylineCoordinates.clear();
|
|
// polylineCoordinates = [];
|
|
polylineCoordinatesPointsAll[0].clear();
|
|
polylineCoordinatesPointsAll[1].clear();
|
|
polylineCoordinatesPointsAll[2].clear();
|
|
polylineCoordinatesPointsAll[3].clear();
|
|
polylineCoordinatesPointsAll[4].clear();
|
|
isMarkersShown = false;
|
|
update();
|
|
}
|
|
|
|
void addCustomPicker() {
|
|
ImageConfiguration config = ImageConfiguration(
|
|
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio
|
|
// scale: 1.0,
|
|
);
|
|
BitmapDescriptor.asset(
|
|
config,
|
|
'assets/images/picker.png',
|
|
// mipmaps: false,
|
|
).then((value) {
|
|
markerIcon = 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',
|
|
// mipmaps: false,
|
|
).then((value) {
|
|
startIcon = value;
|
|
update();
|
|
});
|
|
}
|
|
|
|
void addCustomEndIcon() {
|
|
ImageConfiguration config = ImageConfiguration(
|
|
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
|
|
BitmapDescriptor.asset(
|
|
config,
|
|
'assets/images/b.png',
|
|
// mipmaps: false,
|
|
).then((value) {
|
|
endIcon = value;
|
|
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 addCustomMotoIcon() {
|
|
ImageConfiguration config = ImageConfiguration(
|
|
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
|
|
BitmapDescriptor.asset(
|
|
config,
|
|
'assets/images/moto1.png',
|
|
// mipmaps: false,
|
|
).then((value) {
|
|
motoIcon = value;
|
|
update();
|
|
});
|
|
}
|
|
|
|
void addCustomLadyIcon() {
|
|
ImageConfiguration config = ImageConfiguration(
|
|
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
|
|
BitmapDescriptor.asset(
|
|
config,
|
|
'assets/images/lady1.png',
|
|
// mipmaps: false,
|
|
).then((value) {
|
|
ladyIcon = value;
|
|
update();
|
|
});
|
|
}
|
|
|
|
void addCustomStepIcon() {
|
|
ImageConfiguration config = ImageConfiguration(
|
|
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
|
|
BitmapDescriptor.asset(
|
|
config,
|
|
'assets/images/brand.png',
|
|
// mipmaps: false,
|
|
).then((value) {
|
|
tripIcon = value;
|
|
update();
|
|
});
|
|
}
|
|
|
|
dialoge() {
|
|
Get.defaultDialog(
|
|
title: 'Location '.tr,
|
|
content: Container(
|
|
child: Column(
|
|
children: [
|
|
Text(
|
|
'We use location to get accurate and nearest driver for you'.tr,
|
|
style: AppStyle.title,
|
|
),
|
|
TextButton(
|
|
onPressed: () async {
|
|
// await Permission.location.request();
|
|
Get.back();
|
|
},
|
|
child: Text(
|
|
'Grant'.tr,
|
|
style: AppStyle.title,
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
double speed = 0;
|
|
Future<void> getLocation() async {
|
|
isLoading = true;
|
|
update();
|
|
bool serviceEnabled;
|
|
PermissionStatus permissionGranted;
|
|
// dialoge();
|
|
// Check if location services are enabled
|
|
serviceEnabled = await location.serviceEnabled();
|
|
if (!serviceEnabled) {
|
|
serviceEnabled = await location.requestService();
|
|
if (!serviceEnabled) {
|
|
// Location services are still not enabled, handle the error
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check if the app has permission to access location
|
|
permissionGranted = await location.hasPermission();
|
|
if (permissionGranted == PermissionStatus.denied) {
|
|
permissionGranted = await location.requestPermission();
|
|
if (permissionGranted != PermissionStatus.granted) {
|
|
// Location permission is still not granted, handle the error
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Configure location accuracy
|
|
// LocationAccuracy desiredAccuracy = LocationAccuracy.high;
|
|
|
|
// Get the current location
|
|
LocationData _locationData = await location.getLocation();
|
|
passengerLocation =
|
|
(_locationData.latitude != null && _locationData.longitude != null
|
|
? LatLng(_locationData.latitude!, _locationData.longitude!)
|
|
: null)!;
|
|
getLocationArea(passengerLocation.latitude, passengerLocation.longitude);
|
|
Log.print('AppLink.endPoint: ${AppLink.endPoint}');
|
|
// Log.print('BoxName.serverChosen: ${box.read(BoxName.serverChosen)}');
|
|
|
|
newStartPointLocation = passengerLocation;
|
|
Log.print('passengerLocation: $passengerLocation');
|
|
speed = _locationData.speed!;
|
|
// //print location details
|
|
isLoading = false;
|
|
update();
|
|
}
|
|
|
|
LatLngBounds calculateBounds(double lat, double lng, double radiusInMeters) {
|
|
const double earthRadius = 6378137.0; // Earth's radius in meters
|
|
|
|
double latDelta = (radiusInMeters / earthRadius) * (180 / pi);
|
|
double lngDelta =
|
|
(radiusInMeters / (earthRadius * cos(pi * lat / 180))) * (180 / pi);
|
|
|
|
double minLat = lat - latDelta;
|
|
double maxLat = lat + latDelta;
|
|
|
|
double minLng = lng - lngDelta;
|
|
double maxLng = lng + lngDelta;
|
|
|
|
// Ensure the latitude is between -90 and 90
|
|
minLat = max(-90.0, minLat);
|
|
maxLat = min(90.0, maxLat);
|
|
|
|
// Ensure the longitude is between -180 and 180
|
|
minLng = (minLng + 180) % 360 - 180;
|
|
maxLng = (maxLng + 180) % 360 - 180;
|
|
|
|
// Ensure the bounds are in the correct order
|
|
if (minLng > maxLng) {
|
|
double temp = minLng;
|
|
minLng = maxLng;
|
|
maxLng = temp;
|
|
}
|
|
|
|
return LatLngBounds(
|
|
southwest: LatLng(minLat, minLng),
|
|
northeast: LatLng(maxLat, maxLng),
|
|
);
|
|
}
|
|
|
|
GoogleMapController? mapController;
|
|
void onMapCreated(GoogleMapController controller) {
|
|
// myLocation = Get.find<LocationController>().location as LatLng;
|
|
// myLocation = myLocation;
|
|
mapController = controller;
|
|
controller.getVisibleRegion();
|
|
controller.animateCamera(
|
|
CameraUpdate.newLatLng(passengerLocation),
|
|
);
|
|
// Future.delayed(const Duration(milliseconds: 500), () {
|
|
// markers.forEach((marker) {
|
|
// controller.showMarkerInfoWindow(marker.markerId);
|
|
// });
|
|
// });
|
|
update();
|
|
}
|
|
|
|
// void startMarkerReloading() {
|
|
// int count = 0;
|
|
// markerReloadingTimer = Timer.periodic(const Duration(seconds: 30), (timer) {
|
|
// reloadMarkers();
|
|
//
|
|
// count++;
|
|
// if (count == 10) {
|
|
// timer.cancel();
|
|
// }
|
|
// });
|
|
// }
|
|
bool reloadStartApp = false;
|
|
int reloadCount = 0;
|
|
startMarkerReloading() async {
|
|
if (reloadStartApp == false) {
|
|
Timer.periodic(const Duration(seconds: 3), (timer) async {
|
|
reloadCount++;
|
|
Log.print('reloadCount: $reloadCount');
|
|
|
|
if (rideConfirm == false) {
|
|
clearMarkersExceptStartEnd();
|
|
// _smoothlyUpdateMarker();
|
|
// startCarLocationSearch(box.read(BoxName.carType));
|
|
await getCarsLocationByPassengerAndReloadMarker(
|
|
box.read(BoxName.carType), 5000);
|
|
await getNearestDriverByPassengerLocation();
|
|
// Log.print('reloadMarkers: from startMarkerReloading');
|
|
} else {
|
|
// runWhenRideIsBegin();
|
|
}
|
|
|
|
if (reloadCount >= 6) {
|
|
reloadStartApp = true;
|
|
timer.cancel(); // Stop the timer after 5 reloads
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
String durationByPassenger = '';
|
|
late DateTime newTime1 = DateTime.now();
|
|
late DateTime timeFromDriverToPassenger = DateTime.now();
|
|
String distanceByPassenger = '';
|
|
late Duration durationFromDriverToPassenger;
|
|
double nearestDistance = double.infinity;
|
|
|
|
// Future<CarLocation?> getNearestDriverByPassengerLocation() async {
|
|
// if (polyLines.isEmpty || data.isEmpty) {
|
|
// return null; // Early return if data is empty
|
|
// }
|
|
|
|
// if (!rideConfirm) {
|
|
// if (dataCarsLocationByPassenger != 'failure') {
|
|
// if (dataCarsLocationByPassenger != null) {
|
|
// if (dataCarsLocationByPassenger['message'].length > 0) {
|
|
// double nearestDistance = double
|
|
// .infinity; // Initialize nearest distance to a large number
|
|
// CarLocation? nearestCar;
|
|
|
|
// for (var i = 0;
|
|
// i < dataCarsLocationByPassenger['message'].length;
|
|
// i++) {
|
|
// var carLocation = dataCarsLocationByPassenger['message'][i];
|
|
|
|
// // Calculate the distance between passenger's location and current driver's location
|
|
// final distance = Geolocator.distanceBetween(
|
|
// passengerLocation.latitude,
|
|
// passengerLocation.longitude,
|
|
// double.parse(carLocation['latitude']),
|
|
// double.parse(carLocation['longitude']),
|
|
// );
|
|
|
|
// // Calculate duration assuming an average speed of 25 km/h (adjust as needed)
|
|
// int durationToPassenger =
|
|
// (distance * 25 * (1000 / 3600)).round(); // 25 km/h in m/s
|
|
|
|
// // Update the UI with the distance and duration for each car
|
|
// update();
|
|
|
|
// // If this distance is smaller than the nearest distance found so far, update nearestCar
|
|
// if (distance < nearestDistance) {
|
|
// nearestDistance = distance;
|
|
|
|
// nearestCar = CarLocation(
|
|
// distance: distance,
|
|
// duration: durationToPassenger.toDouble(),
|
|
// id: carLocation['driver_id'],
|
|
// latitude: double.parse(carLocation['latitude']),
|
|
// longitude: double.parse(carLocation['longitude']),
|
|
// );
|
|
|
|
// // Update the UI with the nearest driver
|
|
// update();
|
|
// }
|
|
// }
|
|
|
|
// // Return the nearest car found
|
|
// return nearestCar;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// // Return null if no drivers are found or if ride is confirmed
|
|
// return null;
|
|
// }
|
|
Future<CarLocation?> getNearestDriverByPassengerLocation() async {
|
|
if (!rideConfirm) {
|
|
if (dataCarsLocationByPassenger != 'failure' &&
|
|
dataCarsLocationByPassenger != null &&
|
|
dataCarsLocationByPassenger['message'] != null &&
|
|
dataCarsLocationByPassenger['message'].length > 0) {
|
|
double nearestDistance = double.infinity; // Initialize nearest distance
|
|
CarLocation? nearestCar;
|
|
|
|
for (var i = 0;
|
|
i < dataCarsLocationByPassenger['message'].length;
|
|
i++) {
|
|
var carLocation = dataCarsLocationByPassenger['message'][i];
|
|
// Log.print('carLocation: $carLocation');
|
|
|
|
try {
|
|
// Calculate distance between passenger's location and current driver's location
|
|
final distance = Geolocator.distanceBetween(
|
|
passengerLocation.latitude,
|
|
passengerLocation.longitude,
|
|
double.parse(carLocation['latitude']),
|
|
double.parse(carLocation['longitude']),
|
|
);
|
|
|
|
// Calculate duration assuming an average speed of 25 km/h (adjust as needed)
|
|
int durationToPassenger = (distance / 1000 / 25 * 3600).round();
|
|
// Log.print('distance: $distance');
|
|
// Log.print('durationToPassenger: $durationToPassenger');
|
|
// Log.print('passengerLocation: $passengerLocation');
|
|
// Log.print('carLocation: $carLocation');
|
|
// Log.print('distance: $distance meters');
|
|
// Log.print('durationToPassenger: $durationToPassenger seconds');
|
|
// Update the UI with the distance and duration for each car
|
|
update();
|
|
|
|
// If this distance is smaller than the nearest distance found so far, update nearestCar
|
|
if (distance < nearestDistance) {
|
|
nearestDistance = distance;
|
|
|
|
nearestCar = CarLocation(
|
|
distance: distance,
|
|
duration: durationToPassenger.toDouble(),
|
|
id: carLocation['driver_id'],
|
|
latitude: double.parse(carLocation['latitude']),
|
|
longitude: double.parse(carLocation['longitude']),
|
|
);
|
|
// Log.print('nearestCar: $nearestCar');
|
|
// Update the UI with the nearest driver
|
|
update();
|
|
}
|
|
} catch (e) {
|
|
Log.print('Error calculating distance/duration: $e');
|
|
}
|
|
}
|
|
|
|
// Return the nearest car found
|
|
return nearestCar;
|
|
}
|
|
}
|
|
|
|
// Return null if no drivers are found or if ride is confirmed
|
|
return null;
|
|
}
|
|
|
|
getNearestDriverByPassengerLocationAPIGOOGLE() async {
|
|
if (polyLines.isEmpty || data.isEmpty) {
|
|
return null; // Early return if data is empty
|
|
}
|
|
if (!rideConfirm) {
|
|
double nearestDistance = double.infinity;
|
|
if (dataCarsLocationByPassenger != 'failure') {
|
|
if (dataCarsLocationByPassenger['message'].length > 0) {
|
|
for (var i = 0;
|
|
i < dataCarsLocationByPassenger['message'].length;
|
|
i++) {
|
|
var carLocation = dataCarsLocationByPassenger['message'][i];
|
|
|
|
// }
|
|
// isloading = true;
|
|
update();
|
|
// Make API request to get exact distance and duration
|
|
String apiUrl =
|
|
'${AppLink.googleMapsLink}distancematrix/json?destinations=${carLocation['latitude']},${carLocation['longitude']}&origins=${passengerLocation.latitude},${passengerLocation.longitude}&units=metric&key=${AK.mapAPIKEY}';
|
|
var response = await CRUD().getGoogleApi(link: apiUrl, payload: {});
|
|
if (response != null && response['status'] == "OK") {
|
|
var data = response;
|
|
// Extract distance and duration from the response and handle accordingly
|
|
int distance1 =
|
|
data['rows'][0]['elements'][0]['distance']['value'];
|
|
distanceByPassenger =
|
|
data['rows'][0]['elements'][0]['distance']['text'];
|
|
durationToPassenger =
|
|
data['rows'][0]['elements'][0]['duration']['value'];
|
|
|
|
durationFromDriverToPassenger =
|
|
Duration(seconds: durationToPassenger.toInt());
|
|
newTime1 = currentTime.add(durationFromDriverToPassenger);
|
|
timeFromDriverToPassenger =
|
|
newTime1.add(Duration(minutes: 2.toInt()));
|
|
durationByPassenger =
|
|
data['rows'][0]['elements'][0]['duration']['text'];
|
|
update();
|
|
if (distance1 < nearestDistance) {
|
|
nearestDistance = distance1.toDouble();
|
|
|
|
nearestCar = CarLocation(
|
|
distance: distance1.toDouble(),
|
|
duration: durationToPassenger.toDouble(),
|
|
id: carLocation['driver_id'],
|
|
latitude: double.parse(carLocation['latitude']),
|
|
longitude: double.parse(carLocation['longitude']),
|
|
);
|
|
// isloading = false;
|
|
update();
|
|
}
|
|
}
|
|
|
|
// Handle the distance and duration as needed
|
|
else {
|
|
// 'Failed to retrieve distance and duration: ${response['status']}');
|
|
Log.print('${response['status']}: ${response['status']}}');
|
|
// Handle the failure case
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
calculateDistanceBetweenPassengerAndDriverBeforeCancelRide() async {
|
|
await getDriverCarsLocationToPassengerAfterApplied();
|
|
double distance = Geolocator.distanceBetween(
|
|
passengerLocation.latitude,
|
|
passengerLocation.longitude,
|
|
driverCarsLocationToPassengerAfterApplied.last.latitude,
|
|
driverCarsLocationToPassengerAfterApplied.last.longitude,
|
|
);
|
|
if (distance > 500) {
|
|
isCancelRidePageShown = true;
|
|
update();
|
|
} else {
|
|
Get.defaultDialog(
|
|
barrierDismissible: false,
|
|
title: 'The Driver Will be in your location soon .'.tr,
|
|
middleText: 'The distance less than 500 meter.'.tr,
|
|
confirm: Column(
|
|
children: [
|
|
MyElevatedButton(
|
|
kolor: AppColor.greenColor,
|
|
title: 'Ok'.tr,
|
|
onPressed: () {
|
|
Get.back();
|
|
},
|
|
),
|
|
MyElevatedButton(
|
|
kolor: AppColor.redColor,
|
|
title: 'No, I want to cancel this trip'.tr,
|
|
onPressed: () {
|
|
Get.back();
|
|
MyDialog().getDialog(
|
|
'Attention'.tr,
|
|
'You will be charged for the cost of the driver coming to your location.'
|
|
.tr,
|
|
() async {
|
|
Get.back();
|
|
Get.find<PaymentController>()
|
|
.payToDriverForCancelAfterAppliedAndHeNearYou(rideId);
|
|
// isCancelRidePageShown = true;
|
|
// update();
|
|
},
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
// cancel: MyElevatedButton(
|
|
// title: 'No.Iwant Cancel Trip.'.tr, onPressed: () {}));
|
|
}
|
|
}
|
|
|
|
List<double> headingAngles = [];
|
|
double calculateAngleBetweenLocations(LatLng start, LatLng end) {
|
|
double startLat = start.latitude * math.pi / 180;
|
|
double startLon = start.longitude * math.pi / 180;
|
|
double endLat = end.latitude * math.pi / 180;
|
|
double endLon = end.longitude * math.pi / 180;
|
|
|
|
double dLon = endLon - startLon;
|
|
|
|
double y = math.sin(dLon) * cos(endLat);
|
|
double x = cos(startLat) * math.sin(endLat) -
|
|
math.sin(startLat) * cos(endLat) * cos(dLon);
|
|
|
|
double angle = math.atan2(y, x);
|
|
double angleDegrees = angle * 180 / math.pi;
|
|
|
|
return angleDegrees;
|
|
}
|
|
|
|
late LatLngBounds boundsData;
|
|
late String startNameAddress = '';
|
|
late String endNameAddress = '';
|
|
List<Map<String, dynamic>> stopPoints = [];
|
|
void removeStop(Map<String, dynamic> stop) {
|
|
stopPoints.remove(stop);
|
|
update(); // Trigger a rebuild of the UI
|
|
}
|
|
|
|
getDirectionMap(String origin, destination,
|
|
[List<String> waypoints = const []]) async {
|
|
isLoading = true;
|
|
update();
|
|
remainingTime = 25; //to make cancel every call
|
|
// startCarLocationSearch(box.read(BoxName.carType));
|
|
await getCarsLocationByPassengerAndReloadMarker(
|
|
box.read(BoxName.carType), 5000);
|
|
// await getCarsLocationByPassengerAndReloadMarker();
|
|
var coordDestination = destination.split(',');
|
|
double latPassengerDestination = double.parse(coordDestination[0]);
|
|
double lngPassengerDestination = double.parse(coordDestination[1]);
|
|
myDestination = LatLng(latPassengerDestination, lngPassengerDestination);
|
|
if (origin.isEmpty) {
|
|
origin =
|
|
'${passengerLocation.latitude.toString().split(',')[0]},${passengerLocation.longitude.toString().split(',')[1]}'; //todo
|
|
}
|
|
isLoading = false;
|
|
update();
|
|
var url =
|
|
('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang) ?? 'ar'}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}');
|
|
if (waypoints.isNotEmpty) {
|
|
String formattedWaypoints = waypoints.join('|');
|
|
url += '&waypoints=$formattedWaypoints';
|
|
}
|
|
var response = await CRUD().getGoogleApi(link: url, payload: {});
|
|
|
|
data = response['routes'][0]['legs'];
|
|
box.remove(BoxName.tripData);
|
|
box.write(BoxName.tripData, response);
|
|
|
|
startNameAddress = shortenAddress(data[0]['start_address']);
|
|
// print('data[0][start_address]: ${data[0]['start_address']}');
|
|
endNameAddress = shortenAddress(data[0]['end_address']);
|
|
isLoading = false;
|
|
newStartPointLocation = LatLng(
|
|
data[0]["start_location"]['lat'], data[0]["start_location"]['lng']);
|
|
|
|
durationToRide = data[0]['duration']['value'];
|
|
final String pointsString =
|
|
response['routes'][0]["overview_polyline"]["points"];
|
|
List<LatLng> decodedPoints =
|
|
await compute(decodePolylineIsolate, pointsString);
|
|
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
|
|
for (int i = 0; i < decodedPoints.length; i++) {
|
|
// double lat = decodedPoints[i][0].toDouble();
|
|
// double lng = decodedPoints[i][1].toDouble();
|
|
polylineCoordinates.add(decodedPoints[i]);
|
|
}
|
|
// Define the northeast and southwest coordinates
|
|
|
|
// 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
|
|
LatLngBounds boundsData =
|
|
LatLngBounds(northeast: northeast, southwest: southwest);
|
|
|
|
// Fit the camera to the bounds
|
|
var cameraUpdate = CameraUpdate.newLatLngBounds(boundsData, 130);
|
|
mapController!.animateCamera(cameraUpdate);
|
|
|
|
// getDistanceFromText(data[0]['distance']['text']);
|
|
double distanceOfTrip = (data[0]['distance']['value']) / 1000;
|
|
distance = distanceOfTrip;
|
|
durationToAdd = Duration(seconds: durationToRide);
|
|
hours = durationToAdd.inHours;
|
|
minutes = (durationToAdd.inMinutes % 60).round();
|
|
// updateCameraForDistanceAfterGetMap();
|
|
markers.clear();
|
|
update();
|
|
markers.add(
|
|
Marker(
|
|
markerId: const MarkerId('start'),
|
|
position: newStartPointLocation,
|
|
icon: startIcon,
|
|
infoWindow: InfoWindow(
|
|
title: startNameAddress,
|
|
snippet: '',
|
|
),
|
|
),
|
|
);
|
|
|
|
// Add end marker
|
|
markers.add(
|
|
Marker(
|
|
markerId: const MarkerId('end'),
|
|
position: LatLng(
|
|
data[0]["end_location"]['lat'], data[0]["end_location"]['lng']),
|
|
icon: endIcon,
|
|
infoWindow: InfoWindow(
|
|
title: endNameAddress,
|
|
snippet:
|
|
'$distance ${'KM'.tr} ⌛ ${hours > 0 ? '${'Your Ride Duration is '.tr}$hours ${'H and'.tr} $minutes ${'m'.tr}' : '${'Your Ride Duration is '.tr} $minutes ${'m'.tr}'}'),
|
|
),
|
|
);
|
|
// // Show info windows automatically
|
|
// Future.delayed(const Duration(milliseconds: 500), () {
|
|
// mapController?.showMarkerInfoWindow(const MarkerId('start'));
|
|
// });
|
|
// Future.delayed(const Duration(milliseconds: 500), () {
|
|
// mapController?.showMarkerInfoWindow(const MarkerId('end'));
|
|
// });
|
|
// update();
|
|
|
|
if (polyLines.isNotEmpty) {
|
|
clearPolyline();
|
|
} else {
|
|
// الآن نقرأ القيمة ونحدد عدد النقاط بناءً عليها
|
|
bool lowEndMode = box.read(BoxName.lowEndMode) ??
|
|
false; // الأفضل أن يكون الافتراضي هو الجودة العالية
|
|
|
|
// نمرر عدد النقاط المناسب هنا
|
|
if (Platform.isIOS) {
|
|
animatePolylineLayered(
|
|
polylineCoordinates,
|
|
maxPoints:
|
|
lowEndMode ? 30 : 150, // 30 نقطة لوضع الأداء، 150 للوضع العادي
|
|
);
|
|
} else {
|
|
polyLines.add(Polyline(
|
|
polylineId: const PolylineId('route'),
|
|
points: polylineCoordinates,
|
|
width: 6,
|
|
color: AppColor.primaryColor,
|
|
endCap: Cap.roundCap,
|
|
startCap: Cap.roundCap,
|
|
jointType: JointType.round,
|
|
));
|
|
}
|
|
|
|
rideConfirm = false;
|
|
isMarkersShown = true;
|
|
|
|
update();
|
|
}
|
|
}
|
|
|
|
// 1) تقليل النقاط إلى حد أقصى 30 نقطة (مع بداية ونهاية محفوظة وتوزيع متساوٍ)
|
|
List<LatLng> _downsampleEven(List<LatLng> coords, {int maxPoints = 30}) {
|
|
if (coords.isEmpty) return const [];
|
|
if (coords.length <= maxPoints) return List<LatLng>.from(coords);
|
|
|
|
final int n = coords.length;
|
|
final int keep = maxPoints.clamp(2, n);
|
|
final List<int> idx = [];
|
|
for (int i = 0; i < keep; i++) {
|
|
final double pos = i * (n - 1) / (keep - 1);
|
|
idx.add(pos.round());
|
|
}
|
|
final seen = <int>{};
|
|
final List<LatLng> out = [];
|
|
for (final i in idx) {
|
|
if (seen.add(i)) out.add(coords[i]);
|
|
}
|
|
if (out.first != coords.first) out.insert(0, coords.first);
|
|
if (out.last != coords.last) out.add(coords.last);
|
|
while (out.length > maxPoints) {
|
|
out.removeAt(out.length ~/ 2);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// 2) رسم متدرّج بطبقات متراكبة (بدون حذف)، برونزي ↔ أخضر، مع zIndex وعرض مختلف
|
|
Future<void> animatePolylineLayered(List<LatLng> coordinates,
|
|
{int layersCount = 8, int stepDelayMs = 10, int maxPoints = 160}) async {
|
|
// امسح أي طبقات قديمة فقط الخاصة بالطريق
|
|
polyLines.removeWhere((p) => p.polylineId.value.startsWith('route_layer_'));
|
|
update();
|
|
|
|
final List<LatLng> coords =
|
|
_downsampleEven(coordinates, maxPoints: maxPoints);
|
|
if (coords.length < 2) return;
|
|
|
|
// ألوان مع شفافية خفيفة للتمييز
|
|
Color bronze([int alpha = 220]) => AppColor.gold;
|
|
Color green([int alpha = 220]) => AppColor.primaryColor;
|
|
|
|
Color _layerColor(int layer) => (layer % 2 == 0) ? bronze() : green();
|
|
|
|
// عرض الخط: البرونزي أعرض، الأخضر أنحف
|
|
int _layerWidth(int layer) => (layer % 2 == 0) ? 6 : 4;
|
|
|
|
// لكل طبقة: أنشئ Polyline بهوية فريدة وزي إندكس أعلى من السابقة
|
|
for (int layer = 0; layer < layersCount; layer++) {
|
|
final id = PolylineId('route_layer_$layer');
|
|
polyLines.add(Polyline(
|
|
polylineId: id,
|
|
points: const [],
|
|
width: _layerWidth(layer),
|
|
color: _layerColor(layer),
|
|
zIndex: layer, // مهم لإظهار جميع الطبقات
|
|
endCap: Cap.roundCap,
|
|
startCap: Cap.roundCap,
|
|
geodesic: true,
|
|
visible: true,
|
|
));
|
|
}
|
|
update();
|
|
|
|
// نبني كل طبقة تدريجيًا فوق التي قبلها — بدون مسح الطبقات السابقة
|
|
for (int layer = 0; layer < layersCount; layer++) {
|
|
final id = PolylineId('route_layer_$layer');
|
|
final List<LatLng> growing = [];
|
|
|
|
for (int i = 0; i < coords.length; i++) {
|
|
growing.add(coords[i]);
|
|
|
|
// حدّث فقط هذه الطبقة
|
|
polyLines.removeWhere((p) => p.polylineId == id);
|
|
polyLines.add(Polyline(
|
|
polylineId: id,
|
|
points: List<LatLng>.from(growing),
|
|
width: _layerWidth(layer),
|
|
color: _layerColor(layer),
|
|
zIndex: layer,
|
|
endCap: Cap.roundCap,
|
|
startCap: Cap.roundCap,
|
|
geodesic: true,
|
|
visible: true,
|
|
));
|
|
|
|
update();
|
|
await Future.delayed(Duration(milliseconds: stepDelayMs));
|
|
}
|
|
|
|
// مهلة خفيفة بين الطبقات عشان يبان التبديل
|
|
await Future.delayed(const Duration(milliseconds: 120));
|
|
}
|
|
}
|
|
|
|
String shortenAddress(String fullAddress) {
|
|
// Split the address into parts
|
|
List<String> parts = fullAddress.split('،');
|
|
|
|
// Remove any leading or trailing whitespace from each part
|
|
parts = parts.map((part) => part.trim()).toList();
|
|
|
|
// Remove any empty parts
|
|
parts = parts.where((part) => part.isNotEmpty).toList();
|
|
|
|
// Initialize the short address
|
|
String shortAddress = '';
|
|
|
|
if (parts.isNotEmpty) {
|
|
// Add the first part (usually the most specific location)
|
|
shortAddress += parts[0];
|
|
}
|
|
|
|
if (parts.length > 2) {
|
|
// Add the district or area name (usually the third part in Arabic format)
|
|
shortAddress += '، ${parts[2]}';
|
|
} else if (parts.length > 1) {
|
|
// Add the second part for English or shorter addresses
|
|
shortAddress += '، ${parts[1]}';
|
|
}
|
|
|
|
// Add the country (usually the last part)
|
|
if (parts.length > 1) {
|
|
shortAddress += '، ${parts.last}';
|
|
}
|
|
|
|
// Remove any part that's just numbers (like postal codes)
|
|
shortAddress = shortAddress
|
|
.split('،')
|
|
.where((part) => !RegExp(r'^[0-9 ]+$').hasMatch(part.trim()))
|
|
.join('،');
|
|
|
|
// Check if the address is in English
|
|
bool isEnglish =
|
|
RegExp(r'^[a-zA-Z0-9 ]+$').hasMatch(shortAddress.replaceAll('،', ''));
|
|
|
|
if (isEnglish) {
|
|
// Further processing for English addresses
|
|
List<String> englishParts = shortAddress.split('،');
|
|
if (englishParts.length > 2) {
|
|
shortAddress =
|
|
'${englishParts[0]}، ${englishParts[1]}، ${englishParts.last}';
|
|
} else if (englishParts.length > 1) {
|
|
shortAddress = '${englishParts[0]}، ${englishParts.last}';
|
|
}
|
|
}
|
|
|
|
return shortAddress;
|
|
}
|
|
|
|
double distanceOfDestination = 0;
|
|
bool haveSteps = false;
|
|
late LatLng latestPosition;
|
|
|
|
getMapPoints(String originSteps, String destinationSteps, int index) async {
|
|
isWayPointStopsSheetUtilGetMap = false;
|
|
// haveSteps = true;
|
|
// startCarLocationSearch(box.read(BoxName.carType));
|
|
await getCarsLocationByPassengerAndReloadMarker(
|
|
box.read(BoxName.carType), 7000);
|
|
// await getCarsLocationByPassengerAndReloadMarker();
|
|
// isLoading = true;
|
|
update();
|
|
var url =
|
|
('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destinationSteps&origin=$originSteps&key=${AK.mapAPIKEY}');
|
|
var response = await CRUD().getGoogleApi(link: url, payload: {});
|
|
|
|
data = response['routes'][0]['legs'];
|
|
// isLoading = false;
|
|
|
|
int durationToRide0 = data[0]['duration']['value'];
|
|
durationToRide = durationToRide + durationToRide0;
|
|
distance = distanceOfDestination + (data[0]['distance']['value']) / 1000;
|
|
|
|
update();
|
|
// final points =
|
|
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
|
|
final String pointsString =
|
|
response['routes'][0]["overview_polyline"]["points"];
|
|
|
|
List<LatLng> decodedPoints =
|
|
await compute(decodePolylineIsolate, pointsString);
|
|
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
|
|
for (int i = 0; i < decodedPoints.length; i++) {
|
|
polylineCoordinates.add(decodedPoints[i]);
|
|
}
|
|
// Define the northeast and southwest coordinates
|
|
|
|
if (polyLines.isNotEmpty) {
|
|
// clearPolyline();
|
|
} else {
|
|
var polyline = Polyline(
|
|
polylineId: PolylineId(response["routes"][0]["summary"]),
|
|
points: polylineCoordinatesPointsAll[index],
|
|
width: 10,
|
|
color: Colors.blue,
|
|
);
|
|
|
|
polyLines.add(polyline);
|
|
rideConfirm = false;
|
|
// isMarkersShown = true;
|
|
update();
|
|
}
|
|
}
|
|
|
|
void updateCameraForDistanceAfterGetMap() {
|
|
LatLng coord1 = LatLng(
|
|
double.parse(coordinatesWithoutEmpty.first.split(',')[0]),
|
|
double.parse(coordinatesWithoutEmpty.first.split(',')[1]));
|
|
|
|
LatLng coord2 = LatLng(
|
|
double.parse(coordinatesWithoutEmpty.last.split(',')[0]),
|
|
double.parse(coordinatesWithoutEmpty.last.split(',')[1]));
|
|
|
|
LatLng northeast;
|
|
LatLng southwest;
|
|
|
|
if (coord1.latitude > coord2.latitude) {
|
|
northeast = coord1;
|
|
southwest = coord2;
|
|
} else {
|
|
northeast = coord2;
|
|
southwest = coord1;
|
|
}
|
|
|
|
// Create the LatLngBounds object
|
|
LatLngBounds bounds =
|
|
LatLngBounds(northeast: northeast, southwest: southwest);
|
|
|
|
// Fit the camera to the bounds
|
|
var cameraUpdate = CameraUpdate.newLatLngBounds(bounds, 180);
|
|
mapController!.animateCamera(cameraUpdate);
|
|
update();
|
|
}
|
|
|
|
int selectedIndex = -1; // Initialize with no selection
|
|
void selectCarFromList(int index) {
|
|
selectedIndex = index; // Update selected index
|
|
carTypes.forEach(
|
|
(element) => element.isSelected = false); // Reset selection flags
|
|
carTypes[index].isSelected = true;
|
|
update();
|
|
}
|
|
|
|
showBottomSheet1() async {
|
|
await bottomSheet();
|
|
isBottomSheetShown = true;
|
|
heightBottomSheetShown = 250;
|
|
|
|
update();
|
|
}
|
|
|
|
final promo = TextEditingController();
|
|
bool promoTaken = false;
|
|
applyPromoCodeToPassenger(BuildContext context) async {
|
|
if (promoTaken == true) {
|
|
MyDialog().getDialog(
|
|
'Promo Already Used'.tr,
|
|
'You have already used this promo code.'.tr,
|
|
() => Get.back(),
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!promoFormKey.currentState!.validate()) return;
|
|
|
|
// عتبات تفعيل البرومو بالليرة السورية
|
|
const double minPromoLowSYP = 16000; // Speed / Balash
|
|
const double minPromoHighSYP = 20000; // Comfort / Electric / Lady
|
|
|
|
try {
|
|
final value = await CRUD().get(
|
|
link: AppLink.getPassengersPromo,
|
|
payload: {'promo_code': promo.text},
|
|
);
|
|
|
|
if (value == 'failure') {
|
|
MyDialog().getDialog(
|
|
'Promo Ended'.tr,
|
|
'The promotion period has ended.'.tr,
|
|
() => Get.back(),
|
|
);
|
|
return;
|
|
}
|
|
|
|
// تحقق أولي: هل عندنا أي فئة حاليًا فوق العتبات لتفعيل البرومو؟
|
|
final bool eligibleNow = (totalPassengerSpeed >= minPromoLowSYP) ||
|
|
(totalPassengerBalash >= minPromoLowSYP) ||
|
|
(totalPassengerComfort >= minPromoHighSYP) ||
|
|
(totalPassengerElectric >= minPromoHighSYP) ||
|
|
(totalPassengerLady >= minPromoHighSYP);
|
|
|
|
if (!eligibleNow) {
|
|
Get.snackbar(
|
|
'Lowest Price Achieved'.tr, 'Cannot apply further discounts.'.tr,
|
|
backgroundColor: AppColor.yellowColor);
|
|
return;
|
|
}
|
|
|
|
final decode = jsonDecode(value);
|
|
if (decode["status"] != "success") {
|
|
MyDialog().getDialog(
|
|
'Promo Ended'.tr,
|
|
'The promotion period has ended.'.tr,
|
|
() => Get.back(),
|
|
);
|
|
return;
|
|
}
|
|
|
|
Get.snackbar('Promo Code Accepted'.tr, '',
|
|
backgroundColor: AppColor.greenColor);
|
|
|
|
final firstElement = decode["message"][0];
|
|
final int discountPercentage =
|
|
int.tryParse(firstElement['amount'].toString()) ?? 0;
|
|
|
|
// قيمة المحفظة (قد تكون سالبة)
|
|
final double walletVal = double.tryParse(
|
|
box.read(BoxName.passengerWalletTotal)?.toString() ?? '0') ??
|
|
0.0;
|
|
|
|
// دالة مساعدة: تطبق الخصم لكل فئة حسب عتبتها
|
|
double _applyDiscountPerTier({
|
|
required double fare,
|
|
required double minThreshold,
|
|
required bool isWalletNegative,
|
|
}) {
|
|
if (fare < minThreshold) return fare; // غير مؤهل
|
|
|
|
// منطقك السابق:
|
|
// - إذا المحفظة سالبة: fare + (-wallet) - (fare * pct)
|
|
// - إذا المحفظة ليست سالبة: fare - (fare * pct)
|
|
final double discount = fare * (discountPercentage / 100.0);
|
|
|
|
if (isWalletNegative) {
|
|
final double neg = (-1) * walletVal; // walletVal < 0
|
|
return (fare + neg - discount).clamp(0, double.infinity);
|
|
} else {
|
|
return (fare - discount).clamp(0, double.infinity);
|
|
}
|
|
}
|
|
|
|
final bool isWalletNegative = walletVal < 0;
|
|
|
|
// Comfort
|
|
totalPassengerComfort = _applyDiscountPerTier(
|
|
fare: totalPassengerComfort,
|
|
minThreshold: minPromoHighSYP,
|
|
isWalletNegative: isWalletNegative,
|
|
);
|
|
|
|
// Electric
|
|
totalPassengerElectric = _applyDiscountPerTier(
|
|
fare: totalPassengerElectric,
|
|
minThreshold: minPromoHighSYP,
|
|
isWalletNegative: isWalletNegative,
|
|
);
|
|
|
|
// Lady
|
|
totalPassengerLady = _applyDiscountPerTier(
|
|
fare: totalPassengerLady,
|
|
minThreshold: minPromoHighSYP,
|
|
isWalletNegative: isWalletNegative,
|
|
);
|
|
|
|
// Speed
|
|
totalPassengerSpeed = _applyDiscountPerTier(
|
|
fare: totalPassengerSpeed,
|
|
minThreshold: minPromoLowSYP,
|
|
isWalletNegative: isWalletNegative,
|
|
);
|
|
|
|
// Balash
|
|
totalPassengerBalash = _applyDiscountPerTier(
|
|
fare: totalPassengerBalash,
|
|
minThreshold: minPromoLowSYP,
|
|
isWalletNegative: isWalletNegative,
|
|
);
|
|
|
|
// (اختياري) لو عندك فئات أخرى تريد تطبيق البرومو عليها، أضفها بنفس النمط.
|
|
|
|
// تعديل دخل السائق حسب نسبة البرومو (كما كان في كودك)
|
|
totalDriver = totalDriver - (totalDriver * discountPercentage / 100.0);
|
|
|
|
promoTaken = true;
|
|
update();
|
|
|
|
// مؤثرات نجاح
|
|
Confetti.launch(
|
|
context,
|
|
options: const ConfettiOptions(particleCount: 100, spread: 70, y: 0.6),
|
|
);
|
|
|
|
Get.back();
|
|
await Future.delayed(const Duration(milliseconds: 120));
|
|
} catch (e) {
|
|
Get.snackbar('Error'.tr, e.toString(),
|
|
backgroundColor: AppColor.redColor);
|
|
}
|
|
}
|
|
|
|
double getDistanceFromText(String distanceText) {
|
|
// Remove any non-digit characters from the distance text
|
|
String distanceValue = distanceText.replaceAll(RegExp(r'[^0-9.]+'), '');
|
|
|
|
// Parse the extracted numerical value as a double
|
|
double distance = double.parse(distanceValue);
|
|
|
|
return distance;
|
|
}
|
|
|
|
double costForDriver = 0;
|
|
double totalPassengerSpeed = 0;
|
|
double totalPassengerBalash = 0;
|
|
double totalPassengerElectric = 0;
|
|
double totalPassengerLady = 0;
|
|
double totalPassengerRayehGai = 0;
|
|
double totalPassengerRayehGaiComfort = 0;
|
|
double totalPassengerRayehGaiBalash = 0;
|
|
Future bottomSheet() async {
|
|
if (data.isEmpty) return;
|
|
|
|
// === إعدادات عامة ===
|
|
const double minFareSYP = 16000; // حد أدنى
|
|
const double minBillableKm = 0.3; // حد أدنى للمسافة المفوترة
|
|
const double ladyFlatAddon = 2000; // إضافة ثابتة لـ Lady
|
|
const double airportAddonSYP = 20000; // إضافة المطار
|
|
|
|
// كهرباء
|
|
const double electricPerKmUplift = 400; // زيادة/كم
|
|
const double electricFlatAddon = 1000; // زيادة ثابتة
|
|
|
|
// Long Speed
|
|
const double longSpeedThresholdKm = 40.0;
|
|
const double longSpeedPerKm = 2600.0; // Speed عند >40كم
|
|
|
|
// قواعد الرحلات البعيدة للدقائق (تعمل لكل الأوقات)
|
|
const double mediumDistThresholdKm = 25.0; // >25كم
|
|
const double longDistThresholdKm = 35.0; // >35كم
|
|
const double longTripPerMin = 600.0;
|
|
const int minuteCapMedium = 60; // سقف دقائق عند >25كم
|
|
const int minuteCapLong = 60; // سقف دقائق عند >35كم
|
|
const int freeMinutesLong = 10; // عفو 10 دقائق عند >35كم
|
|
|
|
// تخفيضات المسافات الكبيرة للفئات غير Speed
|
|
const double extraReduction100 = 0.07; // +7% فوق تخفيض >40كم للرحلات >100كم
|
|
const double maxReductionCap = 0.35; // سقف 35% كحد أقصى
|
|
|
|
// ====== زمن الرحلة ======
|
|
durationToAdd = Duration(seconds: durationToRide);
|
|
hours = durationToAdd.inHours;
|
|
minutes = (durationToAdd.inMinutes % 60).round();
|
|
final DateTime currentTime = DateTime.now();
|
|
newTime = currentTime.add(durationToAdd);
|
|
averageDuration = (durationToRide / 60) / distance;
|
|
final int totalMinutes = (durationToRide / 60).floor();
|
|
|
|
// ====== أدوات مساعدة ======
|
|
bool _isAirport(String s) {
|
|
final t = s.toLowerCase();
|
|
return t.contains('airport') ||
|
|
s.contains('مطار') ||
|
|
s.contains('المطار');
|
|
}
|
|
|
|
bool _isClub(String s) {
|
|
final t = s.toLowerCase();
|
|
return t.contains('club') ||
|
|
t.contains('nightclub') ||
|
|
t.contains('night club') ||
|
|
s.contains('ديسكو') ||
|
|
s.contains('ملهى ليلي');
|
|
}
|
|
|
|
// أسعار الدقيقة من السيرفر
|
|
final double naturePerMin = naturePrice; // طبيعي
|
|
final double latePerMin = latePrice; // ليل
|
|
final double heavyPerMin = heavyPrice; // ذروة
|
|
|
|
// سعر الدقيقة حسب الوقت (أساس قبل قواعد المسافة)
|
|
double _perMinuteByTime(DateTime now, bool clubCtx) {
|
|
final h = now.hour;
|
|
if (h >= 21 || h < 1) return latePerMin; // ليل
|
|
if (h >= 1 && h < 5) return clubCtx ? (latePerMin * 2) : latePerMin;
|
|
if (h >= 14 && h <= 17) return heavyPerMin; // ذروة
|
|
return naturePerMin; // طبيعي
|
|
}
|
|
|
|
// حد أدنى
|
|
double _applyMinFare(double fare) =>
|
|
(fare < minFareSYP) ? minFareSYP : fare;
|
|
|
|
// عمولة الراكب (kazan من السيرفر)
|
|
double _withCommission(double base) =>
|
|
(base * (1 + kazan / 100)).ceilToDouble();
|
|
|
|
// ====== سياق ======
|
|
final bool airportCtx =
|
|
_isAirport(startNameAddress) || _isAirport(endNameAddress);
|
|
final bool clubCtx = _isClub(startNameAddress) || _isClub(endNameAddress);
|
|
|
|
// ====== مسافة مفوترة ======
|
|
final double billableDistance =
|
|
(distance < minBillableKm) ? minBillableKm : distance;
|
|
|
|
// ====== Speed (قصير/طويل) ======
|
|
final bool isLongSpeed = billableDistance > longSpeedThresholdKm;
|
|
final double perKmSpeedBaseFromServer =
|
|
speedPrice; // مثال: 2900 يأتي من السيرفر
|
|
final double perKmSpeed =
|
|
isLongSpeed ? longSpeedPerKm : perKmSpeedBaseFromServer;
|
|
|
|
// ====== تخفيضات الفئات الأخرى حسب بُعد الرحلة ======
|
|
// نسبة التخفيض عند >40كم محسوبة مقارنة بسعر Speed الأساسي القادم من السيرفر
|
|
double reductionPct40 = 0.0;
|
|
if (perKmSpeedBaseFromServer > 0) {
|
|
reductionPct40 = (1.0 - (longSpeedPerKm / perKmSpeedBaseFromServer))
|
|
.clamp(0.0, maxReductionCap);
|
|
}
|
|
// نسبة التخفيض عند >100كم
|
|
final double reductionPct100 =
|
|
(reductionPct40 + extraReduction100).clamp(0.0, maxReductionCap);
|
|
|
|
// نحدد أي تخفيض سنستخدمه
|
|
double distanceReduction = 0.0; // لا تخفيض افتراضيًا
|
|
if (billableDistance > 100.0) {
|
|
distanceReduction = reductionPct100;
|
|
} else if (billableDistance > 40.0) {
|
|
distanceReduction = reductionPct40;
|
|
}
|
|
|
|
// ====== منطق الدقيقة يعمل لكل الأوقات ويتكيّف مع المسافة ======
|
|
double effectivePerMin = _perMinuteByTime(currentTime, clubCtx);
|
|
int billableMinutes = totalMinutes;
|
|
|
|
if (billableDistance > longDistThresholdKm) {
|
|
// >35كم: 600/د + سقف 60 + عفو 10
|
|
effectivePerMin = longTripPerMin;
|
|
final int capped =
|
|
(billableMinutes > minuteCapLong) ? minuteCapLong : billableMinutes;
|
|
billableMinutes = capped - freeMinutesLong;
|
|
if (billableMinutes < 0) billableMinutes = 0;
|
|
} else if (billableDistance > mediumDistThresholdKm) {
|
|
// >25كم و≤35كم: 600/د + سقف 60 (بدون عفو)
|
|
effectivePerMin = longTripPerMin;
|
|
billableMinutes = (billableMinutes > minuteCapMedium)
|
|
? minuteCapMedium
|
|
: billableMinutes;
|
|
}
|
|
// ≤25كم: نبقي السعر حسب الوقت بدون سقف/عفو
|
|
|
|
// ====== أسعار/كم قبل التخفيض ======
|
|
final double perKmComfortRaw = comfortPrice;
|
|
final double perKmDelivery = deliveryPrice;
|
|
final double perKmVanRaw =
|
|
(familyPrice > 0 ? familyPrice : (speedPrice + 1300));
|
|
// Electric مبني على Comfort
|
|
final double perKmElectricRaw = perKmComfortRaw + electricPerKmUplift;
|
|
|
|
// ====== تطبيق التخفيضات على الفئات (نفس نسبة Speed للبعيد) ======
|
|
// ملاحظة: Balash يتبع Speed دائمًا (Speed-500) فلا حاجة لتخفيض إضافي له.
|
|
double perKmComfort = perKmComfortRaw * (1.0 - distanceReduction);
|
|
double perKmElectric = perKmElectricRaw * (1.0 - distanceReduction);
|
|
double perKmVan = perKmVanRaw * (1.0 - distanceReduction);
|
|
|
|
// نضمن عدم الهبوط تحت الصفر
|
|
perKmComfort = perKmComfort.clamp(0, double.infinity);
|
|
perKmElectric = perKmElectric.clamp(0, double.infinity);
|
|
perKmVan = perKmVan.clamp(0, double.infinity);
|
|
|
|
// Awfar/Balash: مرتبط بسرعة Speed بعد التفعيل
|
|
final double perKmBalash = (perKmSpeed - 500).clamp(0, double.infinity);
|
|
|
|
// ====== دوال الاحتساب ======
|
|
double _oneWayFare({
|
|
required double perKm,
|
|
required bool isLady,
|
|
double flatAddon = 0,
|
|
}) {
|
|
double fare = billableDistance * perKm;
|
|
fare +=
|
|
billableMinutes * effectivePerMin; // دقائق بعد السقف/العفو إن وُجد
|
|
fare += flatAddon;
|
|
if (isLady) fare += ladyFlatAddon;
|
|
if (airportCtx) fare += airportAddonSYP;
|
|
return _applyMinFare(fare);
|
|
}
|
|
|
|
double _roundTripFare({required double perKm}) {
|
|
// خصم 40% لمسافة إياب واحدة + زمن مضاعف (بنفس قواعد الدقيقة المعدّلة)
|
|
double distPart =
|
|
(billableDistance * 2 * perKm) - ((billableDistance * perKm) * 0.4);
|
|
double timePart = (billableMinutes * 2) * effectivePerMin;
|
|
double fare = distPart + timePart;
|
|
if (airportCtx) fare += airportAddonSYP;
|
|
return _applyMinFare(fare);
|
|
}
|
|
|
|
// ====== حساب كل الفئات (Base قبل العمولة) ======
|
|
final double costSpeed = _oneWayFare(perKm: perKmSpeed, isLady: false);
|
|
final double costBalash = _oneWayFare(perKm: perKmBalash, isLady: false);
|
|
final double costComfort = _oneWayFare(perKm: perKmComfort, isLady: false);
|
|
final double costElectric = _oneWayFare(
|
|
perKm: perKmElectric, isLady: false, flatAddon: electricFlatAddon);
|
|
final double costDelivery =
|
|
_oneWayFare(perKm: perKmDelivery, isLady: false);
|
|
final double costLady = _oneWayFare(
|
|
perKm: perKmComfort,
|
|
isLady: true); // Lady تعتمد Comfort بعد التخفيض + إضافة ثابتة
|
|
final double costVan = _oneWayFare(perKm: perKmVan, isLady: false);
|
|
final double costRayehGai = _roundTripFare(perKm: perKmSpeed);
|
|
final double costRayehGaiComfort = _roundTripFare(perKm: perKmComfort);
|
|
final double costRayehGaiBalash = _roundTripFare(perKm: perKmBalash);
|
|
|
|
// ====== أسعار الراكب بعد العمولة (kazan من السيرفر) ======
|
|
totalPassengerSpeed = _withCommission(costSpeed);
|
|
totalPassengerBalash = _withCommission(costBalash);
|
|
totalPassengerComfort = _withCommission(costComfort);
|
|
totalPassengerElectric = _withCommission(costElectric);
|
|
totalPassengerLady = _withCommission(costLady);
|
|
totalPassengerScooter = _withCommission(costDelivery);
|
|
totalPassengerVan = _withCommission(costVan);
|
|
totalPassengerRayehGai = _withCommission(costRayehGai);
|
|
totalPassengerRayehGaiComfort = _withCommission(costRayehGaiComfort);
|
|
totalPassengerRayehGaiBalash = _withCommission(costRayehGaiBalash);
|
|
|
|
// افتراضي للعرض
|
|
totalPassenger = totalPassengerSpeed;
|
|
totalCostPassenger = totalPassenger;
|
|
|
|
// ====== دعم رصيد محفظة سلبي ======
|
|
try {
|
|
final walletStr = box.read(BoxName.passengerWalletTotal).toString();
|
|
final walletVal = double.tryParse(walletStr) ?? 0.0;
|
|
if (walletVal < 0) {
|
|
final neg = (-1) * walletVal;
|
|
totalPassenger += neg;
|
|
totalPassengerComfort += neg;
|
|
totalPassengerElectric += neg;
|
|
totalPassengerLady += neg;
|
|
totalPassengerBalash += neg;
|
|
totalPassengerScooter += neg;
|
|
totalPassengerRayehGai += neg;
|
|
totalPassengerRayehGaiComfort += neg;
|
|
totalPassengerRayehGaiBalash += neg;
|
|
totalPassengerVan += neg;
|
|
}
|
|
} catch (_) {}
|
|
|
|
update();
|
|
changeBottomSheetShown();
|
|
}
|
|
|
|
// addToken() async {
|
|
// String fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
|
|
|
// await CRUD()
|
|
// .post(link: "${AppLink.server}/ride/firebase/add.php", payload: {
|
|
// 'token': (box.read(BoxName.tokenFCM.toString())),
|
|
// 'passengerID': box.read(BoxName.passengerID).toString(),
|
|
// "fingerPrint": fingerPrint
|
|
// });
|
|
// CRUD().postWallet(
|
|
// link: "${AppLink.seferPaymentServer}/ride/firebase/add.php",
|
|
// payload: {
|
|
// 'token': (box.read(BoxName.tokenFCM.toString())),
|
|
// 'passengerID': box.read(BoxName.passengerID).toString(),
|
|
// "fingerPrint": fingerPrint
|
|
// });
|
|
// }
|
|
|
|
List<LatLng> polylineCoordinate = [];
|
|
String? cardNumber;
|
|
void readyWayPoints() {
|
|
hintTextwayPointStringAll = [
|
|
hintTextwayPoint0,
|
|
hintTextwayPoint1,
|
|
hintTextwayPoint2,
|
|
hintTextwayPoint3,
|
|
hintTextwayPoint4,
|
|
];
|
|
polylineCoordinatesPointsAll = [
|
|
polylineCoordinates0,
|
|
polylineCoordinates1,
|
|
polylineCoordinates2,
|
|
polylineCoordinates3,
|
|
polylineCoordinates4,
|
|
];
|
|
allTextEditingPlaces = [
|
|
wayPoint0Controller,
|
|
wayPoint1Controller,
|
|
wayPoint2Controller,
|
|
wayPoint3Controller,
|
|
wayPoint4Controller,
|
|
];
|
|
currentLocationToFormPlacesAll = [
|
|
currentLocationToFormPlaces0,
|
|
currentLocationToFormPlaces1,
|
|
currentLocationToFormPlaces2,
|
|
currentLocationToFormPlaces3,
|
|
currentLocationToFormPlaces4,
|
|
];
|
|
placeListResponseAll = [
|
|
wayPoint0,
|
|
wayPoint1,
|
|
wayPoint2,
|
|
wayPoint3,
|
|
wayPoint4
|
|
];
|
|
startLocationFromMapAll = [
|
|
startLocationFromMap0,
|
|
startLocationFromMap1,
|
|
startLocationFromMap2,
|
|
startLocationFromMap3,
|
|
startLocationFromMap4,
|
|
];
|
|
currentLocationStringAll = [
|
|
currentLocationString0,
|
|
currentLocationString1,
|
|
currentLocationString2,
|
|
currentLocationString3,
|
|
currentLocationString4,
|
|
];
|
|
placesCoordinate = [
|
|
placesCoordinate0,
|
|
placesCoordinate1,
|
|
placesCoordinate2,
|
|
placesCoordinate3,
|
|
placesCoordinate4,
|
|
];
|
|
update();
|
|
}
|
|
|
|
List driversForMishwari = [];
|
|
|
|
Future selectDriverAndCarForMishwariTrip() async {
|
|
// Calculate the bounds for 20km
|
|
double latitudeOffset = 0.1795; // 20km range in latitude
|
|
double longitudeOffset = 0.2074; // 20km range in longitude
|
|
|
|
// Calculate bounding box based on passenger's location
|
|
double southwestLat = passengerLocation.latitude - latitudeOffset;
|
|
double northeastLat = passengerLocation.latitude + latitudeOffset;
|
|
double southwestLon = passengerLocation.longitude - longitudeOffset;
|
|
double northeastLon = passengerLocation.longitude + longitudeOffset;
|
|
|
|
// Create the payload with calculated bounds
|
|
var payload = {
|
|
'southwestLat': southwestLat.toString(),
|
|
'northeastLat': northeastLat.toString(),
|
|
'southwestLon': southwestLon.toString(),
|
|
'northeastLon': northeastLon.toString(),
|
|
};
|
|
|
|
try {
|
|
// Fetch data from the API
|
|
var res = await CRUD().get(
|
|
link: AppLink.selectDriverAndCarForMishwariTrip, payload: payload);
|
|
|
|
if (res != 'failure') {
|
|
// Check if response is valid JSON
|
|
try {
|
|
var d = jsonDecode(res);
|
|
driversForMishwari = d['message'];
|
|
Log.print('driversForMishwari: $driversForMishwari');
|
|
update();
|
|
} catch (e) {
|
|
// Handle invalid JSON format
|
|
print("Error decoding JSON: $e");
|
|
return 'Server returned invalid data. Please try again later.';
|
|
}
|
|
} else {
|
|
return 'No driver available now, try again later. Thanks for using our app.'
|
|
.tr;
|
|
}
|
|
} catch (e) {
|
|
// Handle network or other exceptions
|
|
print("Error fetching data: $e");
|
|
return 'There was an issue connecting to the server. Please try again later.'
|
|
.tr;
|
|
}
|
|
}
|
|
|
|
final Rx<DateTime> selectedDateTime = DateTime.now().obs;
|
|
|
|
void updateDateTime(DateTime newDateTime) {
|
|
selectedDateTime.value = newDateTime;
|
|
}
|
|
|
|
Future mishwariOption() async {
|
|
isLoading = true;
|
|
update();
|
|
// add dialoug for select driver and car
|
|
await selectDriverAndCarForMishwariTrip();
|
|
Future.delayed(Duration.zero);
|
|
isLoading = false;
|
|
update();
|
|
Get.to(() => CupertinoDriverListWidget());
|
|
|
|
// changeCashConfirmPageShown();
|
|
}
|
|
|
|
var driverIdVip = '';
|
|
Future<void> saveTripData(
|
|
Map<String, dynamic> driver, DateTime tripDateTime) async {
|
|
try {
|
|
// Prepare trip data
|
|
Map<String, dynamic> tripData = {
|
|
'id': driver['driver_id'].toString(), // Ensure the id is a string
|
|
'phone': driver['phone'],
|
|
'gender': driver['gender'],
|
|
'name': driver['NAME'],
|
|
'name_english': driver['name_english'],
|
|
'address': driver['address'],
|
|
'religion': driver['religion'] ?? 'UnKnown',
|
|
'age': driver['age'].toString(), // Convert age to String
|
|
'education': driver['education'] ?? 'UnKnown', //startlocationname
|
|
'license_type': driver['license_type'] ?? 'UnKnown',
|
|
'national_number': driver['national_number'] ?? 'UnKnown',
|
|
'car_plate': driver['car_plate'],
|
|
'make': driver['make'],
|
|
'model': driver['model'],
|
|
'year': driver['year'].toString(), // Convert year to String
|
|
'color': driver['color'],
|
|
'color_hex': driver['color_hex'],
|
|
'displacement': driver['displacement'],
|
|
'fuel': driver['fuel'],
|
|
'token': driver['token'],
|
|
'rating': driver['rating'].toString(), // Convert rating to String
|
|
'countRide':
|
|
driver['ride_count'].toString(), // Convert countRide to String
|
|
'passengerId': box.read(BoxName.passengerID),
|
|
'timeSelected': tripDateTime.toIso8601String(),
|
|
'status': 'pending',
|
|
'startNameAddress': startNameAddress.toString(),
|
|
'locationCoordinate':
|
|
'${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
|
|
};
|
|
Log.print('tripData: $tripData');
|
|
|
|
// Send data to server
|
|
var response =
|
|
await CRUD().post(link: AppLink.addMishwari, payload: tripData);
|
|
// Log.print('response: $response');
|
|
|
|
if (response != 'failure') {
|
|
// Trip saved successfully
|
|
// Get.snackbar('Success'.tr, 'Trip booked successfully'.tr);
|
|
var id = response['message']['id'].toString();
|
|
await CRUD().post(
|
|
link: '${AppLink.IntaleqCairoServer}/ride/rides/add.php',
|
|
payload: {
|
|
"start_location":
|
|
'${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
|
|
"end_location":
|
|
'${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
|
|
"date": DateTime.now().toString(),
|
|
"time": DateTime.now().toString(),
|
|
"endtime":
|
|
DateTime.now().add(const Duration(hours: 2)).toString(),
|
|
"price": '50',
|
|
"passenger_id": box.read(BoxName.passengerID).toString(),
|
|
"driver_id": driver['driver_id'].toString(),
|
|
"status": "waiting",
|
|
'carType': 'vip',
|
|
"price_for_driver": '50',
|
|
"price_for_passenger": '50',
|
|
"distance": '20',
|
|
"paymentMethod": 'cash',
|
|
}).then((value) {
|
|
if (value is String) {
|
|
final parsedValue = jsonDecode(value);
|
|
rideId = parsedValue['message'];
|
|
} else if (value is Map) {
|
|
rideId = value['message'];
|
|
} else {
|
|
Log.print('Unexpected response type: ${value.runtimeType}');
|
|
}
|
|
});
|
|
if (AppLink.endPoint != AppLink.IntaleqCairoServer) {
|
|
await CRUD().post(
|
|
link: "${AppLink.endPoint}/ride/mishwari/add.php",
|
|
payload: tripData);
|
|
CRUD().post(link: '${AppLink.endPoint}/ride/rides/add.php', payload: {
|
|
"start_location":
|
|
'${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
|
|
"end_location":
|
|
'${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
|
|
"date": DateTime.now().toString(),
|
|
"time": DateTime.now().toString(),
|
|
"endtime": DateTime.now().add(const Duration(hours: 2)).toString(),
|
|
"price": '50',
|
|
"passenger_id": box.read(BoxName.passengerID).toString(),
|
|
"driver_id": driver['driver_id'].toString(),
|
|
"status": "waiting",
|
|
'carType': 'vip',
|
|
"price_for_driver": '50',
|
|
"price_for_passenger": '50',
|
|
"distance": '20',
|
|
"paymentMethod": 'cash',
|
|
});
|
|
}
|
|
driverIdVip = driver['driver_id'].toString();
|
|
driverId = driver['driver_id'].toString();
|
|
|
|
DateTime timeSelected = DateTime.parse(tripDateTime.toIso8601String());
|
|
Get.find<NotificationController>().scheduleNotificationsForTimeSelected(
|
|
"Your trip is scheduled".tr,
|
|
"Don't forget your ride!".tr,
|
|
"tone1",
|
|
timeSelected);
|
|
// Optionally, set up local notification or send a push notification
|
|
|
|
// await firebaseMessagesController.sendNotificationToDriverMAP(
|
|
// 'OrderVIP',
|
|
// rideId.toString(),
|
|
// (driver['token'].toString()),
|
|
// [
|
|
// id,
|
|
// rideId,
|
|
// driver['id'],
|
|
// passengerLocation.latitude.toString(),
|
|
// startNameAddress.toString(),
|
|
// passengerLocation.longitude.toString(),
|
|
// (box.read(BoxName.name).toString().split(' ')[0]).toString(),
|
|
// box.read(BoxName.passengerID).toString(),
|
|
// box.read(BoxName.phone).toString(),
|
|
// box.read(BoxName.email).toString(),
|
|
// box.read(BoxName.passengerPhotoUrl).toString(),
|
|
// box.read(BoxName.tokenFCM).toString(),
|
|
// (driver['token'].toString()),
|
|
// ],
|
|
// 'order');
|
|
await NotificationService.sendNotification(
|
|
target: driver['token'].toString(),
|
|
title: 'OrderVIP',
|
|
body: '$rideId - VIP Trip',
|
|
isTopic: false, // Important: this is a token
|
|
tone: 'tone1',
|
|
driverList: [
|
|
id,
|
|
rideId,
|
|
driver['id'],
|
|
passengerLocation.latitude.toString(),
|
|
startNameAddress.toString(),
|
|
passengerLocation.longitude.toString(),
|
|
(box.read(BoxName.name).toString().split(' ')[0]).toString(),
|
|
box.read(BoxName.passengerID).toString(),
|
|
box.read(BoxName.phone).toString(),
|
|
box.read(BoxName.email).toString(),
|
|
box.read(BoxName.passengerPhotoUrl).toString(),
|
|
box.read(BoxName.tokenFCM).toString(),
|
|
(driver['token'].toString()),
|
|
],
|
|
);
|
|
if (response['message'] == "Trip updated successfully") {
|
|
mySnackbarSuccess("Trip updated successfully".tr);
|
|
Log.print(
|
|
'previous_driver_token: ${response['previous_driver_token']}');
|
|
|
|
// firebaseMessagesController.sendNotificationToDriverMAP(
|
|
// 'Order VIP Canceld'.tr,
|
|
// 'Passenger cancel order'.tr,
|
|
// response['previous_driver_token'].toString(),
|
|
// [],
|
|
// 'cancel',
|
|
// );
|
|
await NotificationService.sendNotification(
|
|
target: response['previous_driver_token'].toString(),
|
|
title: 'Order VIP Canceld'.tr,
|
|
body: 'Passenger cancel order'.tr,
|
|
isTopic: false, // Important: this is a token
|
|
tone: 'cancel',
|
|
driverList: [],
|
|
);
|
|
}
|
|
// data = [];
|
|
isBottomSheetShown = false;
|
|
update();
|
|
Get.to(() => VipWaittingPage());
|
|
} else {
|
|
throw Exception('Failed to save trip');
|
|
}
|
|
} catch (e) {
|
|
// Show error message with more details for debugging
|
|
Get.snackbar('Error'.tr, 'Failed to book trip: $e'.tr,
|
|
backgroundColor: AppColor.redColor);
|
|
Log.print('Error: $e');
|
|
}
|
|
}
|
|
|
|
cancelVip(String token, tripId) async {
|
|
// firebaseMessagesController.sendNotificationToDriverMAP(
|
|
// 'Order VIP Canceld'.tr,
|
|
// 'Passenger cancel order'.tr,
|
|
// token,
|
|
// [],
|
|
// 'cancel',
|
|
// );
|
|
var res = await CRUD()
|
|
.post(link: AppLink.cancelMishwari, payload: {'id': tripId});
|
|
if (res != 'failur') {
|
|
Get.back();
|
|
mySnackbarSuccess('You canceled VIP trip'.tr);
|
|
}
|
|
}
|
|
|
|
sendToDriverAgain(String token) {
|
|
// firebaseMessagesController.sendNotificationToDriverMAP(
|
|
// 'Order VIP Canceld'.tr,
|
|
// 'Passenger cancel order'.tr,
|
|
// token,
|
|
// [],
|
|
// 'cancel',
|
|
// );
|
|
NotificationService.sendNotification(
|
|
target: token.toString(),
|
|
title: 'Order VIP Canceld'.tr,
|
|
body: 'Passenger cancel order'.tr,
|
|
isTopic: false, // Important: this is a token
|
|
tone: 'cancel',
|
|
driverList: [],
|
|
);
|
|
}
|
|
|
|
initilizeGetStorage() async {
|
|
if (box.read(BoxName.addWork) == null) {
|
|
box.write(BoxName.addWork, 'addWork');
|
|
}
|
|
if (box.read(BoxName.addHome) == null) {
|
|
box.write(BoxName.addHome, 'addHome');
|
|
}
|
|
}
|
|
|
|
late List recentPlaces = [];
|
|
getFavioratePlaces0() async {
|
|
recentPlaces = await sql.getCustomQuery(
|
|
'SELECT DISTINCT latitude, longitude, name, rate FROM ${TableName.recentLocations}');
|
|
}
|
|
|
|
getFavioratePlaces() async {
|
|
recentPlaces = await sql.getCustomQuery(
|
|
'SELECT * FROM ${TableName.recentLocations} ORDER BY createdAt DESC');
|
|
// Log.print('recentPlaces: ${recentPlaces}');
|
|
}
|
|
|
|
double passengerRate = 5;
|
|
double comfortPrice = 8;
|
|
double speedPrice = 4;
|
|
double mashwariPrice = 4;
|
|
double familyPrice = 4;
|
|
double deliveryPrice = 1.2;
|
|
double minFareSYP = 16000; // حد أدنى للأجرة (سوريا)
|
|
double minBillableKm = 1.0; // حد أدنى للمسافة المفوترة
|
|
double commissionPct = 15; // عمولة التطبيق % (راكب)
|
|
|
|
getKazanPercent() async {
|
|
var res = await CRUD().get(
|
|
link: AppLink.getKazanPercent,
|
|
payload: {'country': box.read(BoxName.countryCode).toString()},
|
|
);
|
|
if (res != 'failure') {
|
|
var json = jsonDecode(res);
|
|
kazan = double.parse(json['message'][0]['kazan']);
|
|
naturePrice = double.parse(json['message'][0]['naturePrice']);
|
|
heavyPrice = double.parse(json['message'][0]['heavyPrice']);
|
|
latePrice = double.parse(json['message'][0]['latePrice']);
|
|
comfortPrice = double.parse(json['message'][0]['comfortPrice']);
|
|
speedPrice = double.parse(json['message'][0]['speedPrice']);
|
|
deliveryPrice = double.parse(json['message'][0]['deliveryPrice']);
|
|
mashwariPrice = double.parse(json['message'][0]['freePrice']);
|
|
familyPrice = double.parse(json['message'][0]['familyPrice']);
|
|
fuelPrice = double.parse(json['message'][0]['fuelPrice']);
|
|
}
|
|
}
|
|
|
|
getPassengerRate() async {
|
|
var res = await CRUD().get(
|
|
link: AppLink.getPassengerRate,
|
|
payload: {'passenger_id': box.read(BoxName.passengerID)});
|
|
if (res != 'failure') {
|
|
var message = jsonDecode(res)['message'];
|
|
if (message['rating'] == null) {
|
|
passengerRate = 5.0; // Default rating
|
|
} else {
|
|
// Safely parse the rating to double
|
|
var rating = message['rating'];
|
|
if (rating is String) {
|
|
passengerRate =
|
|
double.tryParse(rating) ?? 5.0; // Default if parsing fails
|
|
} else if (rating is num) {
|
|
passengerRate =
|
|
rating.toDouble(); // Already a number, convert to double
|
|
} else {
|
|
passengerRate = 5.0; // Default for unexpected data types
|
|
}
|
|
}
|
|
} else {
|
|
passengerRate = 5.0; // Default rating for failure
|
|
}
|
|
}
|
|
|
|
addFingerPrint() async {
|
|
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
|
await CRUD().postWallet(link: AppLink.addFingerPrint, payload: {
|
|
'token': (box.read(BoxName.tokenFCM.toString())),
|
|
'passengerID': box.read(BoxName.passengerID).toString(),
|
|
"fingerPrint": fingerPrint
|
|
});
|
|
}
|
|
|
|
firstTimeRunToGetCoupon() async {
|
|
// Check if it's the first time and the app is installed and gift token is available
|
|
if (box.read(BoxName.isFirstTime).toString() == '0' &&
|
|
box.read(BoxName.isInstall).toString() == '1' &&
|
|
box.read(BoxName.isGiftToken).toString() == '0') {
|
|
var promo, discount, validity;
|
|
var resPromo = await CRUD().get(link: AppLink.getPromoFirst, payload: {
|
|
"passengerID": box.read(BoxName.passengerID).toString(),
|
|
});
|
|
if (resPromo != 'failure') {
|
|
var d1 = jsonDecode(resPromo);
|
|
promo = d1['message']['promo_code'];
|
|
discount = d1['message']['amount'];
|
|
validity = d1['message']['validity_end_date'];
|
|
}
|
|
box.write(BoxName.isFirstTime, '1');
|
|
|
|
// Show a full-screen modal styled as an ad
|
|
Get.dialog(
|
|
AlertDialog(
|
|
contentPadding:
|
|
EdgeInsets.zero, // Removes the padding around the content
|
|
content: SizedBox(
|
|
width: 300, // Match the width of PromoBanner
|
|
// height: 250, // Match the height of PromoBanner
|
|
child: PromoBanner(
|
|
promoCode: promo,
|
|
discountPercentage: discount,
|
|
validity: validity,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// --- دالة جديدة للاستماع ومعالجة الرابط ---
|
|
void _listenForDeepLink() {
|
|
// استمع إلى أي تغيير في الإحداثيات القادمة من الرابط
|
|
ever(_deepLinkController.deepLinkLatLng, (LatLng? latLng) {
|
|
if (latLng != null) {
|
|
print('MapPassengerController detected deep link LatLng: $latLng');
|
|
// عندما يتم استلام إحداثيات جديدة، عينها كوجهة
|
|
myDestination = latLng;
|
|
|
|
// قم بتشغيل المنطق الخاص بك لعرض المسار
|
|
// (تأكد من أن `passengerLocation` لديها قيمة أولاً)
|
|
if (passengerLocation != null) {
|
|
getDirectionMap(
|
|
'${passengerLocation.latitude},${passengerLocation.longitude}',
|
|
'${myDestination.latitude},${myDestination.longitude}');
|
|
} else {
|
|
// يمكنك إظهار رسالة للمستخدم لتمكين الموقع أولاً
|
|
print(
|
|
'Cannot process deep link route yet, passenger location is null.');
|
|
}
|
|
|
|
// إعادة تعيين القيمة إلى null لمنع التشغيل مرة أخرى عند إعادة بناء الواجهة
|
|
_deepLinkController.deepLinkLatLng.value = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
void onInit() async {
|
|
super.onInit();
|
|
// // --- إضافة جديدة: تهيئة وحدة التحكم في الروابط العميقة ---
|
|
Get.put(DeepLinkController(), permanent: true);
|
|
// // ----------------------------------------------------
|
|
// مرحلة 0: الضروري جداً لعرض الخريطة سريعاً
|
|
// mapAPIKEY = await storage.read(key: BoxName.mapAPIKEY);
|
|
await initilizeGetStorage(); // إعداد سريع
|
|
await _initMinimalIcons(); // start/end فقط
|
|
// await addToken(); // لو لازم للمصادقة
|
|
_listenForDeepLink();
|
|
await getLocation(); // لتحديد الكاميرا
|
|
box.write(BoxName.carType, 'yet');
|
|
box.write(BoxName.tipPercentage, '0');
|
|
await detectAndCacheDeviceTier();
|
|
|
|
// لا تُنشئ Controllers الثقيلة الآن:
|
|
Get.lazyPut<TextToSpeechController>(() => TextToSpeechController(),
|
|
fenix: true);
|
|
Get.lazyPut<FirebaseMessagesController>(() => FirebaseMessagesController(),
|
|
fenix: true);
|
|
Get.lazyPut<AudioRecorderController>(() => AudioRecorderController(),
|
|
fenix: true);
|
|
|
|
// ابدأ الخريطة الآن (الشاشة ظهرت للمستخدم)
|
|
// لاحقاً: استخدم SchedulerBinding.instance.addPostFrameCallback إذا احتجت.
|
|
|
|
// مرحلة 1: مهام ضرورية للتسعير لكن غير حرجة لظهور UI
|
|
unawaited(_stagePricingAndState());
|
|
|
|
// مرحلة 2: تحسينات/كماليات بالخلفية
|
|
unawaited(_stageNiceToHave());
|
|
|
|
// ابدأ إعادة تحميل الماركر لكن بثروتل داخلي
|
|
startMarkerReloading(); // تأكد أنه مَخنوق التحديث (throttled)
|
|
}
|
|
|
|
// === Helpers ===
|
|
|
|
Future<void> _initMinimalIcons() async {
|
|
addCustomStartIcon();
|
|
addCustomEndIcon();
|
|
// أجّل باقي الأيقونات:
|
|
// addCustomCarIcon(), addCustomLadyIcon(), addCustomMotoIcon(), addCustomStepIcon()
|
|
}
|
|
|
|
Future<void> _stagePricingAndState() async {
|
|
try {
|
|
await getKazanPercent(); // أسعار السيرفر
|
|
} catch (_) {}
|
|
try {
|
|
await getRideStatusFromStartApp();
|
|
} catch (_) {}
|
|
// لو عندك ضبط “وضع خفيف” حسب الجهاز:
|
|
_applyLowEndModeIfNeeded();
|
|
}
|
|
|
|
Future<void> _stageNiceToHave() async {
|
|
// نفّذ بالتوازي
|
|
await Future.wait([
|
|
Future(() async {
|
|
try {
|
|
getFavioratePlaces();
|
|
} catch (_) {}
|
|
}),
|
|
Future(() async {
|
|
try {
|
|
readyWayPoints();
|
|
} catch (_) {}
|
|
}),
|
|
Future(() async {
|
|
try {
|
|
addCustomPicker();
|
|
} catch (_) {}
|
|
}),
|
|
Future(() async {
|
|
try {
|
|
addCustomCarIcon();
|
|
} catch (_) {}
|
|
}),
|
|
Future(() async {
|
|
try {
|
|
addCustomLadyIcon();
|
|
} catch (_) {}
|
|
}),
|
|
Future(() async {
|
|
try {
|
|
addCustomMotoIcon();
|
|
} catch (_) {}
|
|
}),
|
|
Future(() async {
|
|
try {
|
|
addCustomStepIcon();
|
|
} catch (_) {}
|
|
}),
|
|
Future(() async {
|
|
try {
|
|
getPassengerRate();
|
|
} catch (_) {}
|
|
}),
|
|
Future(() async {
|
|
try {
|
|
firstTimeRunToGetCoupon();
|
|
} catch (_) {}
|
|
}),
|
|
]);
|
|
try {
|
|
cardNumber = await SecureStorage().readData(BoxName.cardNumber);
|
|
} catch (_) {}
|
|
}
|
|
|
|
void _applyLowEndModeIfNeeded() {
|
|
// مثال بسيط: يمكنك حفظ فلاج بنظامك (من السيرفر/الإعدادات/الكاش) لتفعيل وضع خفيف
|
|
// لاحقاً فعّل: map.trafficEnabled=false, buildingsEnabled=false, تبسيط polylines...
|
|
// controller.lowEndMode = true;
|
|
}
|
|
// @override
|
|
// void onInit() async {
|
|
// mapAPIKEY = await storage.read(key: BoxName.mapAPIKEY);
|
|
// // k = await getAIKey('HERE_API');
|
|
// getFavioratePlaces();
|
|
// readyWayPoints();
|
|
// addCustomPicker();
|
|
// addCustomCarIcon();
|
|
// addCustomLadyIcon();
|
|
// addCustomMotoIcon();
|
|
// addCustomStepIcon();
|
|
// addCustomStartIcon();
|
|
// addCustomEndIcon();
|
|
// addToken();
|
|
// await getLocation();
|
|
// addFingerPrint();
|
|
// // getPassengerLocationUniversity();
|
|
// // _initializePolygons();
|
|
// // await addToken();
|
|
// getKazanPercent();
|
|
// getPassengerRate();
|
|
// getRideStatusFromStartApp();
|
|
// reloadStartApp = false;
|
|
// startMarkerReloading();
|
|
// Get.put(TextToSpeechController());
|
|
// Get.put(FirebaseMessagesController());
|
|
// box.write(BoxName.carType, 'yet');
|
|
// box.write(BoxName.tipPercentage, '0');
|
|
// Get.put(AudioRecorderController());
|
|
// // await getNearestDriverByPassengerLocation();
|
|
// firstTimeRunToGetCoupon();
|
|
// initilizeGetStorage();
|
|
// cardNumber = await SecureStorage().readData(BoxName.cardNumber);
|
|
|
|
// super.onInit();
|
|
// }
|
|
|
|
uploadPassengerLocation() async {
|
|
await CRUD().post(link: AppLink.addpassengerLocation, payload: {
|
|
"passengerId": box.read(BoxName.passengerID),
|
|
"lat": passengerLocation.latitude.toString(),
|
|
"lng": passengerLocation.longitude.toString(),
|
|
"rideId": rideId.toString()
|
|
});
|
|
}
|
|
}
|
|
|
|
class CarLocation {
|
|
final String id;
|
|
final double latitude;
|
|
final double longitude;
|
|
final double distance;
|
|
final double duration;
|
|
|
|
CarLocation({
|
|
required this.id,
|
|
required this.latitude,
|
|
required this.longitude,
|
|
this.distance = 10000,
|
|
this.duration = 10000,
|
|
});
|
|
}
|