7753 lines
299 KiB
Dart
7753 lines
299 KiB
Dart
// import 'package:siro_rider/constant/currency.dart';
|
|
import 'dart:async';
|
|
// import 'package:siro_rider/services/offline_map_service.dart';
|
|
// import 'package:siro_rider/services/emergency_signal_service.dart';
|
|
// import 'package:siro_rider/views/widgets/mycircular.dart';
|
|
// 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:typed_data';
|
|
// import 'package:image/image.dart' as img;
|
|
// import 'package:siro_rider/services/ride_live_notification.dart';
|
|
// import 'package:crypto/crypto.dart';
|
|
// import 'package:siro_rider/views/Rate/rate_captain.dart';
|
|
// import 'package:siro_rider/views/Rate/rating_driver_bottom.dart';
|
|
// import 'package:device_info_plus/device_info_plus.dart';
|
|
// import 'package:flutter/foundation.dart';
|
|
// import 'package:flutter/services.dart';
|
|
// import 'package:http/http.dart' as http;
|
|
|
|
// import 'package:siro_rider/constant/univeries_polygon.dart';
|
|
// import 'package:siro_rider/controller/firebase/local_notification.dart';
|
|
// import 'package:flutter/cupertino.dart';
|
|
// import 'package:flutter_confetti/flutter_confetti.dart' hide Circle;
|
|
// import 'package:socket_io_client/socket_io_client.dart' as IO;
|
|
// import 'package:vector_math/vector_math.dart' show radians;
|
|
|
|
// import 'package:siro_rider/controller/functions/tts.dart';
|
|
// import 'package:siro_rider/views/home/map_page_passenger.dart';
|
|
// import 'package:siro_rider/views/widgets/my_textField.dart';
|
|
// import 'package:flutter/material.dart';
|
|
// import 'package:geolocator/geolocator.dart';
|
|
// import 'package:get/get.dart';
|
|
// import 'package:intaleq_maps/intaleq_maps.dart';
|
|
// // import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
|
|
// import 'package:intl/intl.dart';
|
|
// import 'package:location/location.dart';
|
|
// import 'package:siro_rider/constant/country_polygons.dart';
|
|
// import 'package:siro_rider/constant/links.dart';
|
|
// import 'package:siro_rider/constant/style.dart';
|
|
// import 'package:siro_rider/controller/home/points_for_rider_controller.dart';
|
|
// import 'package:siro_rider/views/home/map_widget.dart/form_serch_multiy_point.dart';
|
|
// import '../../constant/api_key.dart';
|
|
// import '../../constant/box_name.dart';
|
|
// import '../../constant/colors.dart';
|
|
// import '../../constant/info.dart';
|
|
// import '../../constant/table_names.dart';
|
|
// import '../../env/env.dart';
|
|
// import '../../main.dart';
|
|
// import '../../models/model/locations.dart';
|
|
// import '../../models/model/painter_copoun.dart';
|
|
// import '../../print.dart';
|
|
// import '../../services/pip_service.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/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_performance.dart';
|
|
// import 'ios_live_activity_service.dart';
|
|
// import 'vip_waitting_page.dart';
|
|
|
|
// enum RideState {
|
|
// noRide, // لا يوجد رحلة جارية، عرض واجهة البحث
|
|
// cancelled, // تم إلغاء الرحلة
|
|
// preCheckReview, // يوجد رحلة منتهية، تحقق من التقييم
|
|
// searching, // جاري البحث عن كابتن
|
|
// driverApplied, // تم قبول الطلب
|
|
// driverArrived, // وصل السائق
|
|
// inProgress, // الرحلة بدأت بالفعل
|
|
// finished, // انتهت الرحلة (سيتم تحويلها إلى preCheckReview)
|
|
// }
|
|
|
|
// 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),
|
|
// ];
|
|
|
|
// SiroMapController? mapController;
|
|
// bool isStyleLoaded = false;
|
|
|
|
// Set<Marker> markers = {};
|
|
// Set<Polyline> polyLines = {};
|
|
// Set<Polygon> polygons = {};
|
|
// Set<Circle> circles = {};
|
|
// double speed = 0;
|
|
// PermissionStatus? permissionGranted;
|
|
|
|
// LatLngBounds? lastComputedBounds;
|
|
// 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 = [];
|
|
// String markerIcon = "marker_icon";
|
|
// String tripIcon = "trip_icon";
|
|
// String startIcon = "start_icon";
|
|
// String endIcon = "end_icon";
|
|
// String carIcon = "car_icon";
|
|
// String motoIcon = "moto_icon";
|
|
// String ladyIcon = "lady_icon";
|
|
// 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 reloadStartApp = 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;
|
|
|
|
// 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;
|
|
|
|
// 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 = [];
|
|
|
|
// // ── Multi-Waypoint (max 2 stops) ──────────────────────────────────────────
|
|
// List<LatLng?> menuWaypoints = [null, null];
|
|
// List<String> menuWaypointNames = ['', ''];
|
|
// int activeMenuWaypointCount = 0;
|
|
// bool isPickingWaypoint = false;
|
|
// int pickingWaypointIndex = -1;
|
|
|
|
// 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;
|
|
// bool _isDriverAppliedLogicExecuted = false; // فلاج لمنع التنفيذ المتكرر
|
|
// bool _isDriverArrivedLogicExecuted = false;
|
|
// bool _isRideBeginLogicExecuted = false;
|
|
// DateTime? _searchStartTime; // لتتبع مدة البحث
|
|
// DateTime? _lastDriversNotifyTime; // لتتبع آخر مرة تم إرسال إشعار للسائقين
|
|
// final int _masterTimerIntervalSeconds = 5; // فاصل زمني ثابت للمؤقت الرئيسي
|
|
// final int _searchTimeoutSeconds = 60; // مهلة البحث قبل عرض خيار زيادة السعر
|
|
// final int _notifyDriversIntervalSeconds =
|
|
// 25; // إرسال إشعار للسائقين كل 25 ثانية
|
|
// // متغير لمنع أي عمليات تحديث أثناء التقييم
|
|
// bool _isRatingScreenOpen = false;
|
|
// // --- إضافة جديدة: متغيرات لإدارة البحث المتوسع ---
|
|
// int _currentSearchPhase = 0; // لتتبع المرحلة الحالية للبحث
|
|
// bool _isFetchingDriverLocation = false; // متغير لمنع تكرار الطلب
|
|
|
|
// // === استبدل initSocket بالكامل ===
|
|
// late IO.Socket socket;
|
|
// bool isSocketConnected = false;
|
|
// int _reconnectAttempts = 0;
|
|
// final int _maxReconnectAttempts = 5;
|
|
// Timer? _reconnectTimer;
|
|
// var currentRideId;
|
|
// // لتخزين نقاط مسار السائق الحالية للمقارنة
|
|
// List<LatLng> _currentDriverRoutePoints = [];
|
|
|
|
// // متغير لتتبع مصدر القبول — Socket أم غيره
|
|
// String _rideAcceptedViaSource = "Unknown";
|
|
// // عدّاد تحديثات الموقع المستلمة من السوكيت (لقياس الصحة)
|
|
// int _socketLocationUpdatesCount = 0;
|
|
|
|
// final Map<RideState, int> _pollingIntervals = {
|
|
// RideState.noRide: 6,
|
|
// RideState.searching: 8,
|
|
// RideState.driverApplied: 10,
|
|
// RideState.driverArrived: 15,
|
|
// RideState.inProgress: 15,
|
|
// RideState.cancelled: 3600,
|
|
// RideState.finished: 3600,
|
|
// RideState.preCheckReview: 3600,
|
|
// };
|
|
// // لمنع التكرار (عشان ما يعمل 100 طلب في نفس اللحظة)
|
|
// bool _isRecalculatingRoute = false;
|
|
// // متغير لمراقبة صحة السوكيت
|
|
// DateTime? _lastSocketLocationTime;
|
|
// // مسافة السماحية (مثلاً 150 متر) قبل اعتبار السائق "خارج المسار"
|
|
// final double _deviationThresholdMeters = 150.0;
|
|
// // ... (باقي الـ Imports)
|
|
|
|
// // متغيرات التحكم
|
|
// Timer? _locationPollingTimer; // تايمر مخصص للموقع فقط
|
|
|
|
// // ==============================================================================
|
|
// // 1. الدالة الرئيسية لتأسيس الاتصال (تستدعى عند بدء البحث startSearchingForDriver)
|
|
// // ==============================================================================
|
|
// Timer? _heartbeatTimer;
|
|
// void initConnectionWithSocket() {
|
|
// if (isSocketConnected && socket != null) return;
|
|
|
|
// String passengerId = box.read(BoxName.passengerID).toString();
|
|
// Log.print("🔌 Initializing Socket for Passenger: $passengerId");
|
|
|
|
// socket = IO.io(
|
|
// AppLink.serverSocket,
|
|
// IO.OptionBuilder()
|
|
// .setTransports(['websocket'])
|
|
// .disableAutoConnect()
|
|
// .setQuery({'id': passengerId})
|
|
// // ✅ [FIX] إعادة اتصال شبه-لانهائية (999 محاولة) بدلاً من 20
|
|
// .setReconnectionAttempts(20)
|
|
// // ✅ [FIX] تأخير أقل (1.5 ثانية) مع حد أقصى (8 ثواني) للتسريع
|
|
// .setReconnectionDelay(1500)
|
|
// .setReconnectionDelayMax(8000)
|
|
// .enableReconnection()
|
|
// .setExtraHeaders({'Connection': 'Upgrade'})
|
|
// .build(),
|
|
// );
|
|
|
|
// socket.connect();
|
|
|
|
// // ✅ معالج الاتصال الأول
|
|
// socket.onConnect((_) {
|
|
// Log.print("✅ Socket Connected Successfully");
|
|
// isSocketConnected = true;
|
|
// _reconnectAttempts = 0;
|
|
// _startHeartbeat();
|
|
|
|
// // ✅ [FIX] الاشتراك مجدداً في أحداث الرحلة عند كل اتصال
|
|
// if (rideId != null && rideId != 'yet' && driverId.isNotEmpty) {
|
|
// socket.emit('subscribe_driver_location', {
|
|
// 'ride_id': rideId,
|
|
// 'driver_id': driverId,
|
|
// });
|
|
// Log.print("📡 Re-subscribed to driver location after connect");
|
|
// }
|
|
|
|
// update();
|
|
// });
|
|
|
|
// // ⚠️ معالج الانقطاع
|
|
// socket.onDisconnect((_) {
|
|
// Log.print("⚠️ Socket Disconnected — Auto-Reconnect will handle it");
|
|
// isSocketConnected = false;
|
|
|
|
// // تفعيل Polling أسرع كـ Fallback مؤقت (سيتم إيقافه عند عودة الاتصال)
|
|
// if (_isActiveRideState()) {
|
|
// Log.print("🔄 Enabling Fast Polling Fallback (4s) until reconnect...");
|
|
// _startMasterTimerWithInterval(4);
|
|
// }
|
|
// update();
|
|
// });
|
|
|
|
// // 🔁 [FIX] معالج إعادة الاتصال الناجحة
|
|
// socket.onReconnect((_) {
|
|
// Log.print("🔁 Socket Reconnected Successfully!");
|
|
// isSocketConnected = true;
|
|
// _reconnectAttempts = 0;
|
|
|
|
// // استئناف النبضة فوراً
|
|
// _startHeartbeat();
|
|
|
|
// // إعادة الاشتراك في أحداث الرحلة
|
|
// if (rideId != null && rideId != 'yet' && driverId.isNotEmpty) {
|
|
// socket.emit('subscribe_driver_location', {
|
|
// 'ride_id': rideId,
|
|
// 'driver_id': driverId,
|
|
// });
|
|
// Log.print("📡 Re-subscribed to driver location after reconnect");
|
|
// }
|
|
|
|
// // ✅ [FIX] إيقاف الـ Fast Polling لأن السوكيت عاد
|
|
// if (_isActiveRideState()) {
|
|
// Log.print("✅ Socket back online — stopping Fast Polling Fallback");
|
|
// _masterTimer?.cancel();
|
|
// _masterTimer = null;
|
|
// }
|
|
|
|
// update();
|
|
// });
|
|
|
|
// // 🔄 [FIX] معالج محاولات إعادة الاتصال (للتشخيص)
|
|
// socket.onReconnectAttempt((attemptNumber) {
|
|
// Log.print("🔄 Socket Reconnect Attempt #$attemptNumber...");
|
|
// });
|
|
|
|
// // ❌ معالج الأخطاء
|
|
// socket.onError((error) {
|
|
// Log.print("❌ Socket Error: $error");
|
|
// isSocketConnected = false;
|
|
// });
|
|
|
|
// // 📩 معالج تحديثات الحالة
|
|
// socket.on('ride_status_change', (data) {
|
|
// Log.print("📩 Socket Event: ride_status_change -> $data");
|
|
// _handleRideStatusChangeWithSocket(data);
|
|
// });
|
|
|
|
// // 📍 معالج موقع السائق
|
|
// socket.on('driver_location_update', (data) {
|
|
// handleDriverLocationUpdate(data);
|
|
// });
|
|
// }
|
|
|
|
// void _startHeartbeat() {
|
|
// _heartbeatTimer?.cancel();
|
|
// _heartbeatTimer = Timer.periodic(const Duration(seconds: 25), (timer) {
|
|
// if (isSocketConnected && socket.connected) {
|
|
// socket.emit('heartbeat',
|
|
// {'passenger_id': box.read(BoxName.passengerID).toString()});
|
|
// }
|
|
// });
|
|
// }
|
|
|
|
// // دالة مساعدة
|
|
// bool _isActiveRideState() {
|
|
// return currentRideState.value == RideState.searching ||
|
|
// currentRideState.value == RideState.driverApplied ||
|
|
// currentRideState.value == RideState.driverArrived ||
|
|
// currentRideState.value == RideState.inProgress;
|
|
// }
|
|
|
|
// /// فحص سريع: هل السوكيت يعمل ويرسل بيانات؟
|
|
// bool _isSocketHealthy() {
|
|
// if (!isSocketConnected) return false;
|
|
// if (_lastSocketLocationTime == null) return false;
|
|
// final diff = DateTime.now().difference(_lastSocketLocationTime!).inSeconds;
|
|
// return diff < 20; // إذا آخر تحديث قبل أقل من 20 ثانية
|
|
// }
|
|
|
|
// /// 🧠 خوارزمية ذكية: حساب أقصر مسافة بين موقع السائق والـ Polyline (بدون API)
|
|
// double _calculateDistanceToPolyline(LatLng point, List<LatLng> polyline) {
|
|
// if (polyline.isEmpty) return 999.0;
|
|
// double minDistance = double.infinity;
|
|
|
|
// for (int i = 0; i < polyline.length - 1; i++) {
|
|
// double d = _distToSegment(point, polyline[i], polyline[i + 1]);
|
|
// if (d < minDistance) minDistance = d;
|
|
// }
|
|
// return minDistance;
|
|
// }
|
|
|
|
// double _distToSegment(LatLng p, LatLng v, LatLng w) {
|
|
// double l2 = _dist2(v, w);
|
|
// if (l2 == 0) return _distanceBetween(p, v);
|
|
// double t = ((p.latitude - v.latitude) * (w.latitude - v.latitude) +
|
|
// (p.longitude - v.longitude) * (w.longitude - v.longitude)) /
|
|
// l2;
|
|
// t = max(0, min(1, t));
|
|
// return _distanceBetween(
|
|
// p,
|
|
// LatLng(v.latitude + t * (w.latitude - v.latitude),
|
|
// v.longitude + t * (w.longitude - v.longitude)));
|
|
// }
|
|
|
|
// double _dist2(LatLng v, LatLng w) {
|
|
// return pow(v.latitude - w.latitude, 2).toDouble() +
|
|
// pow(v.longitude - w.longitude, 2).toDouble();
|
|
// }
|
|
|
|
// double _distanceBetween(LatLng p1, LatLng p2) {
|
|
// return Geolocator.distanceBetween(
|
|
// p1.latitude, p1.longitude, p2.latitude, p2.longitude);
|
|
// }
|
|
|
|
// // ==============================================================================
|
|
// // 2. العقل المدبر: توجيه الحالات
|
|
// // ==============================================================================
|
|
// void _handleRideStatusChangeWithSocket(dynamic data) {
|
|
// if (data == null || data['status'] == null) return;
|
|
|
|
// String newStatus = data['status'].toString().toLowerCase();
|
|
// Log.print("🔔 Socket Status Update: $newStatus");
|
|
// // استخراج بيانات السائق إذا توفرت (تأتي من acceptRide.php)
|
|
// Map<String, dynamic>? driverInfo;
|
|
// if (data['driver_info'] != null && data['driver_info'] is Map) {
|
|
// driverInfo = Map<String, dynamic>.from(data['driver_info']);
|
|
// }
|
|
// switch (newStatus) {
|
|
// case 'accepted': // أو apply/applied حسب تسمية السيرفر
|
|
// _onDriverAcceptedWithSocket(data, driverData: driverInfo);
|
|
// break;
|
|
|
|
// case 'arrived':
|
|
// _onDriverArrivedWithSocket();
|
|
// break;
|
|
|
|
// case 'started': // أو begin
|
|
// _onRideStartedWithSocket();
|
|
// break;
|
|
|
|
// case 'finished': // أو ended
|
|
// _onRideFinishedWithSocket(data);
|
|
// break;
|
|
|
|
// case 'cancelled':
|
|
// _onRideCancelledWithSocket(data);
|
|
// break;
|
|
|
|
// case 'no_drivers_found':
|
|
// showNoDriverDialog();
|
|
// break;
|
|
// }
|
|
// }
|
|
|
|
// // ==============================================================================
|
|
// // 3. دوال المعالجة التفصيلية (Actions)
|
|
// // ==============================================================================
|
|
// void showNoDriverDialog() {
|
|
// Get.defaultDialog(
|
|
// title: "No Drivers Found".tr,
|
|
// middleText:
|
|
// "Sorry, there are no cars available of this type right now.".tr,
|
|
// textConfirm: "Refresh Map".tr,
|
|
// textCancel: "Cancel".tr,
|
|
// confirmTextColor: Colors.white,
|
|
// onConfirm: () {
|
|
// Get.back(); // إغلاق الديالوج
|
|
// restCounter();
|
|
// stopAllTimers();
|
|
// Get.offAll(() => MapPagePassenger()); // إعادة تحميل صفحة الخريطة
|
|
// },
|
|
// );
|
|
// }
|
|
|
|
// // أ) عند قبول السائق للرحلة
|
|
// // أ) عند قبول السائق للرحلة (معدلة)
|
|
// // دالة الاستقبال من السوكيت (تصبح مجرد محول)
|
|
// void _onDriverAcceptedWithSocket(dynamic data,
|
|
// {Map<String, dynamic>? driverData}) {
|
|
// // استخراج البيانات وتمريرها للدالة الموحدة
|
|
// Map<String, dynamic>? info = driverData;
|
|
|
|
// // دعم الهيكلية الجديدة
|
|
// if (info == null && data['driver_info'] != null) {
|
|
// info = Map<String, dynamic>.from(data['driver_info']);
|
|
// }
|
|
// // دعم الهيكلية القديمة (إن وجدت)
|
|
// else if (info == null && data['driverList'] != null) {
|
|
// // تحويل driverList إلى map إذا لزم الأمر
|
|
// }
|
|
|
|
// processRideAcceptance(driverData: info, source: "Socket");
|
|
// }
|
|
|
|
// void _fillDriverDataLocally(Map<String, dynamic> data) {
|
|
// try {
|
|
// // تعبئة المتغيرات بناءً على أسماء الحقول في acceptRide.php
|
|
// driverId = data['driver_id']?.toString() ?? '';
|
|
// driverPhone = data['phone']?.toString() ?? '';
|
|
|
|
// String fName = data['first_name']?.toString() ?? '';
|
|
// String lName = data['last_name']?.toString() ?? '';
|
|
// passengerName = lName.isNotEmpty
|
|
// ? "$fName $lName"
|
|
// : fName; // (هنا المتغير اسمه passengerName لكنه يحمل اسم السائق في الكود لديك)
|
|
// driverName = passengerName;
|
|
|
|
// make = data['make']?.toString() ?? '';
|
|
// model = data['model']?.toString() ?? '';
|
|
// carColor = data['color']?.toString() ?? '';
|
|
// colorHex = data['color_hex']?.toString() ?? '';
|
|
// licensePlate = data['car_plate']?.toString() ?? '';
|
|
// carYear = data['year']?.toString() ?? '';
|
|
|
|
// driverRate = data['ratingDriver']?.toString() ?? '5.0';
|
|
// driverToken = data['token']?.toString() ?? '';
|
|
|
|
// // إذا كان هناك أي بيانات أخرى تحتاجها الواجهة
|
|
// update();
|
|
// } catch (e) {
|
|
// Log.print("Error parsing socket driver data: $e");
|
|
// }
|
|
// }
|
|
|
|
// // دالة موحدة: تجلب المسار + الوقت + المسافة + ترسم الخط + تضبط الكاميرا
|
|
// Future<void> calculateDriverToPassengerRoute(
|
|
// LatLng driverPos, LatLng passengerPos) async {
|
|
// // 1. تجهيز الرابط (نفس API الـ Direction)
|
|
// // نستخدم overview=full للحصول على الرسمة، و steps=false لتخفيف البيانات
|
|
// final Map<String, String> queryParams = {
|
|
// 'fromLat': driverPos.latitude.toString(),
|
|
// 'fromLng': driverPos.longitude.toString(),
|
|
// 'toLat': passengerPos.latitude.toString(),
|
|
// 'toLng': passengerPos.longitude.toString(),
|
|
// };
|
|
// final uri =
|
|
// Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: queryParams);
|
|
|
|
// Log.print('📍 Calculating Driver Route: $uri');
|
|
|
|
// try {
|
|
// final response = await http.get(uri, headers: {
|
|
// 'x-api-key': Env.mapSaasKey,
|
|
// }).timeout(const Duration(seconds: 20));
|
|
|
|
// if (response.statusCode == 200) {
|
|
// final responseData = json.decode(response.body);
|
|
|
|
// // Support both old format (routes[0]) and new SaaS format (top-level)
|
|
// var routeData = responseData['routes'] != null
|
|
// ? responseData['routes'][0]
|
|
// : responseData;
|
|
|
|
// // 2. تحديث المتغيرات (المسافة والوقت)
|
|
// double durationSecondsRaw = (routeData['duration'] as num).toDouble();
|
|
// int finalDurationSeconds =
|
|
// (durationSecondsRaw * kDurationScalar).toInt();
|
|
// double distanceMeters = (routeData['distance'] as num).toDouble();
|
|
|
|
// timeToPassengerFromDriverAfterApplied = finalDurationSeconds;
|
|
// remainingTimeToPassengerFromDriverAfterApplied = finalDurationSeconds;
|
|
// distanceByPassenger = distanceMeters.toStringAsFixed(0);
|
|
|
|
// // تحديث نصوص الواجهة
|
|
// int minutes = (finalDurationSeconds / 60).floor();
|
|
// int seconds = finalDurationSeconds % 60;
|
|
// stringRemainingTimeToPassenger =
|
|
// '$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
|
|
// Log.print(
|
|
// '✅ Driver Route Info: $minutes min, ${distanceMeters.toInt()} m');
|
|
|
|
// // 3. معالجة الرسم (Polyline)
|
|
// // SaaS uses 'points', OSRM uses 'geometry'
|
|
// String pointsString =
|
|
// routeData['points'] ?? routeData['geometry'] ?? "";
|
|
// if (pointsString.isNotEmpty) {
|
|
// List<LatLng> decodedPoints =
|
|
// await compute(decodePolylineIsolate, pointsString);
|
|
// // حفظ نسخة للمقارنة
|
|
// _currentDriverRoutePoints = decodedPoints;
|
|
// // إزالة خط مسار السائق القديم فقط
|
|
// polyLines = polyLines
|
|
// .where((p) => p.polylineId.value != 'driver_route')
|
|
// .toSet();
|
|
|
|
// // إضافة الخط الجديد (بستايل مميز للسائق)
|
|
// polyLines = {
|
|
// ...polyLines,
|
|
// Polyline(
|
|
// polylineId: const PolylineId('driver_route'),
|
|
// points: decodedPoints,
|
|
// color:
|
|
// const Color(0xFF333333), // لون مختلف عن مسار الرحلة الأساسي
|
|
// width: 5,
|
|
// )
|
|
// };
|
|
// }
|
|
|
|
// // 4. ضبط الكاميرا لتشمل السائق والراكب
|
|
// _fitCameraToPoints(driverPos, passengerPos);
|
|
|
|
// update(); // تحديث واحد للكل
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print('❌ Error calculating driver route: $e');
|
|
// }
|
|
// }
|
|
|
|
// Future<void> _checkAndRecalculateIfDeviated(LatLng driverPos) async {
|
|
// // 1. شروط الخروج السريع
|
|
// if (_isRecalculatingRoute || _currentDriverRoutePoints.isEmpty) return;
|
|
|
|
// // 2. حساب المسافة لأقرب نقطة في المسار (خوارزمية سريعة)
|
|
// // نستخدم مكتبة Geolocator أو حساب رياضي بسيط
|
|
// double minDistance = 100000.0;
|
|
|
|
// // لتقليل الحمل، لا نفحص كل النقاط، نفحص عينة (كل 5 نقاط مثلاً) أو الكل إذا المسار قصير
|
|
// for (var point in _currentDriverRoutePoints) {
|
|
// double dist = Geolocator.distanceBetween(driverPos.latitude,
|
|
// driverPos.longitude, point.latitude, point.longitude);
|
|
// if (dist < minDistance) minDistance = dist;
|
|
// }
|
|
|
|
// // 3. اتخاذ القرار
|
|
// if (minDistance > _deviationThresholdMeters) {
|
|
// Log.print("⚠️ Driver deviated ($minDistance m). Recalculating route...");
|
|
|
|
// _isRecalculatingRoute = true;
|
|
|
|
// // إعادة حساب المسار من موقع السائق الجديد
|
|
// await calculateDriverToPassengerRoute(driverPos, passengerLocation);
|
|
|
|
// _isRecalculatingRoute = false;
|
|
// }
|
|
// }
|
|
|
|
// // ب) عند وصول السائق
|
|
// void _onDriverArrivedWithSocket() {
|
|
// Log.print("🚖 Driver Arrived (Socket)");
|
|
|
|
// processDriverArrival("Socket");
|
|
// }
|
|
|
|
// // ج) عند بدء الرحلة
|
|
// void _onRideStartedWithSocket() {
|
|
// Log.print("🚀 Ride Started (Socket)");
|
|
// processRideBegin(source: "Socket");
|
|
// }
|
|
|
|
// // ربط السوكيت
|
|
// // د) عند انتهاء الرحلة (Socket Listener)
|
|
// void _onRideFinishedWithSocket(dynamic data) {
|
|
// Log.print("🏁 Ride Finished (Socket)");
|
|
|
|
// // نحاول استخراج DriverList من البيلود القادم من PHP
|
|
// // في finish_ride_updates.php أسميناه 'DriverList'
|
|
// var rawList = data['DriverList'];
|
|
|
|
// List<dynamic> listToSend = [];
|
|
|
|
// if (rawList != null) {
|
|
// if (rawList is List) {
|
|
// listToSend = rawList;
|
|
// } else if (rawList is String) {
|
|
// // احتياطاً لو وصل كنص
|
|
// try {
|
|
// listToSend = jsonDecode(rawList);
|
|
// } catch (e) {
|
|
// Log.print("Error: $e");
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// // إذا كانت القائمة فارغة، نحاول بناءها من البيانات المتفرقة (Fallback)
|
|
// if (listToSend.isEmpty && data['price'] != null) {
|
|
// listToSend = [
|
|
// driverId, // 0
|
|
// rideId, // 1
|
|
// driverToken, // 2
|
|
// data['price'].toString() // 3
|
|
// ];
|
|
// }
|
|
|
|
// // استدعاء المعالج الموحد
|
|
// processRideFinished(listToSend, source: "Socket");
|
|
// }
|
|
|
|
// // هـ) عند الإلغاء
|
|
// void _onRideCancelledWithSocket(dynamic data) {
|
|
// processRideCancelledByDriver(data, source: "Socket");
|
|
// }
|
|
|
|
// // ==============================================================================
|
|
// // 4. إدارة تتبع الموقع (Polling) - مفصولة عن السوكيت
|
|
// // ==============================================================================
|
|
// // متغير لمنع التكرار (Race Condition Guard)
|
|
// bool _isCancelProcessed = false;
|
|
|
|
// /// **معالجة إلغاء الرحلة الموحدة (Gatekeeper)**
|
|
// ///
|
|
// /// تستدعى من [Socket] أو [FCM] عند قيام السائق بإلغاء الرحلة.
|
|
// /// تضمن عدم تضارب الإشعارات وتوحد تجربة المستخدم.
|
|
// Future<void> processRideCancelledByDriver(dynamic data,
|
|
// {String source = "Unknown"}) async {
|
|
// if (_isCancelProcessed) return;
|
|
|
|
// _isCancelProcessed = true;
|
|
// stopAllTimers();
|
|
// if (Get.isDialogOpen == true) Get.back();
|
|
// await RideLiveNotification.cancel();
|
|
// IosLiveActivityService.endRideActivity(); // ✅ أضف هذا السطر
|
|
// PipService.disablePip(); // ✅ إيقاف PiP عند انتهاء الرحلة
|
|
// if (Get.isDialogOpen == true) Get.back();
|
|
// await RideLiveNotification.cancel();
|
|
// Get.defaultDialog(
|
|
// title: "Sorry 😔".tr, // استخدام المفتاح الإنجليزي
|
|
// titleStyle:
|
|
// const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
|
|
// barrierDismissible: false,
|
|
// content: Column(
|
|
// children: [
|
|
// const Icon(Icons.cancel_presentation,
|
|
// size: 50, color: Colors.redAccent),
|
|
// const SizedBox(height: 10),
|
|
// Text(
|
|
// "The driver cancelled the trip for an emergency reason.\nDo you want to search for another driver immediately?"
|
|
// .tr,
|
|
// textAlign: TextAlign.center,
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// actions: [
|
|
// TextButton(
|
|
// onPressed: () {
|
|
// Get.back();
|
|
// handleNoDriverFound();
|
|
// },
|
|
// child: Text("Cancel Trip".tr,
|
|
// style: const TextStyle(color: Colors.grey)),
|
|
// ),
|
|
// ElevatedButton.icon(
|
|
// style:
|
|
// ElevatedButton.styleFrom(backgroundColor: AppColor.primaryColor),
|
|
// icon: const Icon(Icons.refresh, color: Colors.white),
|
|
// label: Text("Search for another driver".tr,
|
|
// style: const TextStyle(color: Colors.white)),
|
|
// onPressed: () {
|
|
// Get.back();
|
|
// retrySearchForDrivers();
|
|
// },
|
|
// ),
|
|
// ],
|
|
// );
|
|
// }
|
|
|
|
// Future<void> handleNoDriverFound() async {
|
|
// stopAllTimers();
|
|
// await RideLiveNotification.cancel();
|
|
// IosLiveActivityService.endRideActivity(); // ✅ أضف هذا السطر
|
|
// PipService.disablePip(); // ✅ إيقاف PiP
|
|
// _isCancelProcessed = false;
|
|
// currentRideState.value = RideState.noRide;
|
|
// resetAllMapStates();
|
|
// Get.offAll(() => const MapPagePassenger());
|
|
|
|
// Get.defaultDialog(
|
|
// title: "We apologize 😔".tr,
|
|
// middleText: "No drivers found at the moment.\nPlease try again later.".tr,
|
|
// confirm: ElevatedButton(
|
|
// onPressed: () => Navigator.pop(Get.context!),
|
|
// child: Text("Ok".tr),
|
|
// ),
|
|
// );
|
|
// }
|
|
|
|
// /// **إعادة البحث عن سائقين (Retry Search)**
|
|
// ///
|
|
// /// تقوم باستدعاء السكربت لإعادة تفعيل الرحلة وبدء عداد البحث من جديد.
|
|
// void retrySearchForDrivers() async {
|
|
// _isCancelProcessed = false;
|
|
// isSearchingWindow = true;
|
|
// currentRideState.value = RideState.searching;
|
|
// driversStatusForSearchWindow = 'Searching for nearby drivers...'.tr;
|
|
// update();
|
|
|
|
// try {
|
|
// Log.print("🔄 Retrying search for ride ID: $rideId");
|
|
|
|
// // تجهيز البيانات المخزنة للإرسال
|
|
// var payload = {
|
|
// "ride_id": rideId.toString(),
|
|
// "passenger_id": box.read(BoxName.passengerID).toString(),
|
|
// "passenger_name": box.read(BoxName.name).toString(),
|
|
// "passenger_phone": box.read(BoxName.phone).toString(),
|
|
// "passenger_email": box.read(BoxName.email).toString(),
|
|
// "passenger_token": box.read(BoxName.tokenFCM).toString(),
|
|
// "passenger_wallet": box.read(BoxName.passengerWalletTotal).toString(),
|
|
// "passenger_rating": "5.0", // أو قراءة التقييم الحقيقي إن وجد
|
|
|
|
// // قراءة البيانات من المتغيرات المحفوظة في الكنترولر أو الـ Box
|
|
// "start_lat": startLocation.latitude.toString(),
|
|
// "start_lng": startLocation.longitude.toString(),
|
|
// "end_lat": endLocation.latitude.toString(),
|
|
// "end_lng": endLocation.longitude.toString(),
|
|
// "start_name": startNameAddress,
|
|
// "end_name": endNameAddress,
|
|
// "distance": distance.toString(),
|
|
// "distance_text": distanceByPassenger ?? "",
|
|
// "duration_text": durationToPassenger.toString(),
|
|
// "price": totalPassenger.toString(),
|
|
// "price_for_driver": costForDriver.toString(),
|
|
// "car_type": box.read(BoxName.carType).toString(),
|
|
// "is_wallet": Get.find<PaymentController>().isWalletChecked.toString(),
|
|
|
|
// // الخطوات (اختياري)
|
|
// "has_steps": Get.find<WayPointController>().wayPoints.length > 1
|
|
// ? "true"
|
|
// : "false",
|
|
// // يمكنك إضافة الخطوات إذا كانت لديك في مصفوفة
|
|
// };
|
|
|
|
// var response = await CRUD().post(
|
|
// link: "${AppLink.rideServerSide}/rides/retry_search_drivers.php",
|
|
// payload: payload,
|
|
// );
|
|
|
|
// if (response['status'] == 'success') {
|
|
// Log.print("✅ Search reset successfully.");
|
|
// startSearchingTimer();
|
|
// } else {
|
|
// mySnackbarWarning("Failed to search, please try again later".tr);
|
|
// handleNoDriverFound();
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print("Error retrying search: $e");
|
|
// mySnackbarWarning("Please check your internet connection".tr);
|
|
// handleNoDriverFound();
|
|
// }
|
|
// }
|
|
|
|
// Timer? _searchTimer;
|
|
|
|
// /// **بدء مؤقت البحث (Search Timer)**
|
|
// ///
|
|
// /// يبدأ عداداً (مثلاً 90 ثانية). إذا لم يتم قبول الرحلة خلال هذه المدة،
|
|
// /// يتم إنهاء البحث واستدعاء [handleNoDriverFound].
|
|
// Future<void> startSearchingTimer() async {
|
|
// _searchTimer?.cancel();
|
|
// int seconds = 0;
|
|
|
|
// Log.print("⏳ Search Timer Started (90s)...");
|
|
// await RideLiveNotification.showSearching(driversStatusForSearchWindow);
|
|
|
|
// _searchTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
// seconds++;
|
|
|
|
// // إذا تغيرت الحالة (مثلاً سائق قبل الرحلة)، نوقف العداد
|
|
// if (currentRideState.value != RideState.searching) {
|
|
// timer.cancel();
|
|
// return;
|
|
// }
|
|
|
|
// // انتهاء الوقت (90 ثانية)
|
|
// if (seconds >= 90) {
|
|
// timer.cancel();
|
|
// handleNoDriverFound(); // ⏳ انتهى الوقت ولم نجد سائقاً
|
|
// }
|
|
// });
|
|
// }
|
|
|
|
// // متغير لمنع التكرار (Race Condition Guard)
|
|
// bool _isArrivalProcessed = false;
|
|
|
|
// /// **معالجة وصول السائق الموحدة (Unified Driver Arrival Handler)**
|
|
// ///
|
|
// /// تقوم هذه الدالة بإدارة حدث وصول السائق إلى موقع الراكب، وتعمل كـ "حارس بوابة" (Gatekeeper)
|
|
// /// لتوحيد المصادر سواء كانت من **WebSocket** أو **Firebase Notification**.
|
|
// ///
|
|
// /// **المهام التي تقوم بها:**
|
|
// /// 1. **منع التضارب (Race Condition Guard):** تتأكد أن الحدث لم يتم تنفيذه مسبقاً لتجنب تكرار الإشعارات أو إعادة ضبط العدادات.
|
|
// /// 2. **تحديث الحالة:** تغير حالة الرحلة فوراً إلى [RideState.driverArrived].
|
|
// /// 3. **تفعيل الواجهة:** تظهر ديالوج "السائق وصل" وتبدأ عداد الانتظار المجاني (5 دقائق).
|
|
// ///
|
|
// /// * [source] : نص يوضح مصدر الاستدعاء (مثل "Socket" أو "FCM") لأغراض التتبع (Logging).
|
|
// Future<void> processDriverArrival(String source) async {
|
|
// // 1. الحارس: إذا تم التنفيذ سابقاً، توقف
|
|
// if (currentRideState.value == RideState.driverArrived ||
|
|
// _isArrivalProcessed) {
|
|
// Log.print("✋ Ignored Arrival from $source. Already processed.");
|
|
// return;
|
|
// }
|
|
|
|
// _isArrivalProcessed = true;
|
|
// Log.print("🚖 Driver Arrived via $source! Processing...");
|
|
|
|
// // 2. تحديث الحالة
|
|
// currentRideState.value = RideState.driverArrived;
|
|
// statusRide = 'Arrived';
|
|
// await RideLiveNotification.showDriverArrived(driverName ?? '');
|
|
|
|
// // 3. تشغيل واجهة الوصول والعداد
|
|
// driverArrivePassengerDialoge();
|
|
// startTimerDriverWaitPassenger5Minute();
|
|
|
|
// // 4. إزالة مسار السائق واستعادة مسار الراكب للوجهة
|
|
// if (polylineCoordinates.isNotEmpty) {
|
|
// _playRouteAnimation(polylineCoordinates, lastComputedBounds);
|
|
// }
|
|
|
|
// update();
|
|
// }
|
|
|
|
// // متغير لمنع التكرار
|
|
// bool _isFinishProcessed = false;
|
|
|
|
// /// **معالجة إنهاء الرحلة الموحدة (Unified Ride Finish Handler)**
|
|
// ///
|
|
// /// تستدعى عند استلام حدث النهاية من السوكيت أو FCM.
|
|
// /// تقوم بإغلاق السوكيت، إيقاف التتبع، والانتقال لشاشة التقييم والدفع.
|
|
// ///
|
|
// /// * [driverList]: قائمة البيانات [driverId, rideId, token, price].
|
|
// Future<void> processRideFinished(List<dynamic> driverList,
|
|
// {String source = "Unknown"}) async {
|
|
// // 1. الحارس: منع التكرار
|
|
// if (currentRideState.value == RideState.finished || _isFinishProcessed) {
|
|
// Log.print("✋ Ignored Finish Request from $source. Already Finished.");
|
|
// return;
|
|
// }
|
|
|
|
// _isFinishProcessed = true;
|
|
// Log.print(
|
|
// "🏁 Ride Finished via $source. Price: ${driverList.length > 3 ? driverList[3] : 'N/A'}");
|
|
|
|
// // 2. تحديث الحالة
|
|
// currentRideState.value = RideState.finished;
|
|
|
|
// // 3. تنظيف الموارد
|
|
// disposeRideSocket(); // إغلاق السوكيت
|
|
// _stopDriverLocationPolling(); // إيقاف تتبع الموقع
|
|
// if (Get.isRegistered<AudioRecorderController>()) {
|
|
// Get.find<AudioRecorderController>().stopRecording();
|
|
// }
|
|
|
|
// // إغلاق أي ديالوج سابق
|
|
// if (Get.isDialogOpen == true) Get.back();
|
|
|
|
// // 4. إشعار "لا تنسى أغراضك"
|
|
// NotificationController().showNotification(
|
|
// 'Alert'.tr,
|
|
// "Please make sure not to leave any personal belongings in the car.".tr,
|
|
// 'tone1',
|
|
// );
|
|
// IosLiveActivityService.endRideActivity();
|
|
// PipService.disablePip(); // ✅ إيقاف PiP
|
|
// await RideLiveNotification.cancel();
|
|
// // 5. استخراج البيانات والانتقال
|
|
// if (driverList.length >= 4) {
|
|
// String price = driverList[3].toString();
|
|
|
|
// // الانتقال لصفحة التقييم
|
|
// Get.offAll(() => RateDriverFromPassenger(), arguments: {
|
|
// 'driverId': driverList[0].toString(),
|
|
// 'rideId': driverList[1].toString(),
|
|
// 'price': price
|
|
// });
|
|
// }
|
|
// }
|
|
|
|
// void _startDriverLocationPollingWithTimer() {
|
|
// Log.print("📍 Starting Driver Location Polling (6s interval)");
|
|
|
|
// _locationPollingTimer?.cancel();
|
|
|
|
// // استدعاء فوري لأول مرة
|
|
// // getDriverCarsLocationToPassengerAfterApplied();
|
|
|
|
// _locationPollingTimer = Timer.periodic(Duration(seconds: 6), (timer) {
|
|
// // شرط التوقف: إذا انتهت الرحلة أو ألغيت
|
|
// if (currentRideState.value == RideState.finished ||
|
|
// currentRideState.value == RideState.cancelled ||
|
|
// currentRideState.value == RideState.noRide) {
|
|
// timer.cancel();
|
|
// return;
|
|
// }
|
|
|
|
// // جلب الموقع وتحديث الماركر
|
|
// getDriverCarsLocationToPassengerAfterApplied();
|
|
// });
|
|
// }
|
|
|
|
// void _stopDriverLocationPolling() {
|
|
// Log.print("🛑 Stopping Location Polling");
|
|
// _locationPollingTimer?.cancel();
|
|
// _locationPollingTimer = null;
|
|
// }
|
|
|
|
// // ==============================================================================
|
|
// // 5. التنظيف والإغلاق
|
|
// // ==============================================================================
|
|
// void disposeRideSocket() {
|
|
// if (socket != null) {
|
|
// socket!.disconnect();
|
|
// socket!.dispose();
|
|
// // socket = null;
|
|
// isSocketConnected = false;
|
|
// Log.print("🔌 Socket Disposed");
|
|
// }
|
|
// }
|
|
|
|
// /// ==============================================================================
|
|
// /// 6. معالجة تحديث موقع السائق من السوكيت
|
|
// /// ==============================================================================
|
|
// void handleDriverLocationUpdate(dynamic data) {
|
|
// if (!isSocketConnected || data == null) return;
|
|
// // 🔥 1. تسجيل وقت استلام البيانات (تغذية الـ Watchdog)
|
|
// _lastSocketLocationTime = DateTime.now();
|
|
// _socketLocationUpdatesCount++;
|
|
|
|
// // 🧠 إذا وصلتنا 3 تحديثات ناجحة من السوكيت، أوقف البولينج (إن كان يعمل)
|
|
// if (_socketLocationUpdatesCount >= 3 && _locationPollingTimer != null) {
|
|
// Log.print("✅ Socket delivering locations reliably. Stopping polling.");
|
|
// _stopDriverLocationPolling();
|
|
// }
|
|
// try {
|
|
// double lat = double.tryParse(data['latitude']?.toString() ?? '0') ?? 0;
|
|
// double lng = double.tryParse(data['longitude']?.toString() ?? '0') ?? 0;
|
|
// double heading = double.tryParse(data['heading']?.toString() ?? '0') ?? 0;
|
|
|
|
// if (lat == 0 || lng == 0) return;
|
|
|
|
// LatLng newPos = LatLng(lat, lng);
|
|
|
|
// // تحديث القائمة (نفس المنطق القديم)
|
|
// if (driverCarsLocationToPassengerAfterApplied.isEmpty) {
|
|
// driverCarsLocationToPassengerAfterApplied.add(newPos);
|
|
// } else {
|
|
// driverCarsLocationToPassengerAfterApplied[0] = newPos;
|
|
// }
|
|
|
|
// currentLocationOfDrivers = newPos;
|
|
// // ------------------------------------------------------------------
|
|
// // 🔥 2. Smart Rerouting Logic ( deviation > 50m )
|
|
// // ------------------------------------------------------------------
|
|
// if (_currentDriverRoutePoints.isNotEmpty) {
|
|
// double deviation =
|
|
// _calculateDistanceToPolyline(newPos, _currentDriverRoutePoints);
|
|
// if (deviation > 50.0) {
|
|
// Log.print(
|
|
// "⚠️ Driver deviated by ${deviation.toStringAsFixed(1)}m. Smart Rerouting triggered.");
|
|
// // إعادة رسم المسار محلياً (لا يتم استدعاؤه إلا عند الانحراف الحقيقي)
|
|
// calculateDriverToPassengerRoute(newPos, passengerLocation);
|
|
// }
|
|
// }
|
|
|
|
// // 🔥 تحديث الكاميرا: ضمان بقاء السيارة في منتصف الخريطة
|
|
// // ------------------------------------------------------------------
|
|
// // ملاحظة: تأكد من أن mapController قد تم تهيئته (initialized)
|
|
// if (mapController != null) {
|
|
// // نستخدم animateCamera لحركة ناعمة بدلاً من moveCamera القاسية
|
|
// mapController!.animateCamera(CameraUpdate.newLatLng(newPos));
|
|
// }
|
|
// // ------------------------------------------------------------------
|
|
|
|
// // تحديث الوقت المتبقي (إذا أرسله السيرفر)
|
|
// if (data['eta_seconds'] != null) {
|
|
// int etaSeconds = int.tryParse(data['eta_seconds'].toString()) ?? 0;
|
|
// if (etaSeconds > 0) {
|
|
// remainingTimeToPassengerFromDriverAfterApplied = etaSeconds;
|
|
// int minutes = (etaSeconds / 60).floor();
|
|
// int seconds = etaSeconds % 60;
|
|
// stringRemainingTimeToPassenger =
|
|
// '$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
// }
|
|
// }
|
|
|
|
// // تحديث الماركر
|
|
// _updateDriverMarker(newPos, heading);
|
|
// } catch (e) {
|
|
// Log.print('Error in handleDriverLocationUpdate: $e');
|
|
// }
|
|
// }
|
|
|
|
// // دالة مساعدة لتحديث ماركر السائق
|
|
// void _updateDriverMarker(LatLng position, double heading) {
|
|
// const String markerId = 'driver_location';
|
|
// final mId = MarkerId(markerId);
|
|
|
|
// final existingMarker = markers.cast<Marker?>().firstWhere(
|
|
// (m) => m?.markerId == mId,
|
|
// orElse: () => null,
|
|
// );
|
|
|
|
// if (existingMarker != null) {
|
|
// _smoothlyUpdateMarker(existingMarker, position, heading, carIcon);
|
|
// } else {
|
|
// markers = {
|
|
// ...markers,
|
|
// Marker(
|
|
// markerId: mId,
|
|
// position: position,
|
|
// icon: InlqBitmap.fromStyleImage(carIcon),
|
|
// rotation: heading,
|
|
// anchor: const Offset(0.5, 0.5),
|
|
// ),
|
|
// };
|
|
// update();
|
|
// }
|
|
// }
|
|
|
|
// // === إضافة متغير للتحكم ===
|
|
// bool _isUsingFallback = false;
|
|
|
|
// void _startPollingFallback() {
|
|
// if (_isUsingFallback) return;
|
|
|
|
// Log.print('🔄 Starting Polling Fallback Mode');
|
|
// _isUsingFallback = true;
|
|
|
|
// // استخدام _handleRideState الموجود (كما كان)
|
|
// _startMasterTimer();
|
|
// }
|
|
|
|
// void handleActiveRideOnStartup(dynamic data) {
|
|
// try {
|
|
// if (data == null || data['has_active_ride'] != true) {
|
|
// Log.print('[Startup] No active ride');
|
|
// currentRideState.value = RideState.noRide;
|
|
// _startMasterTimer();
|
|
// return;
|
|
// }
|
|
|
|
// Log.print('[Startup] ✅ Active ride found!');
|
|
|
|
// var rideData = data['ride'];
|
|
// rideId = rideData['ride_id'].toString();
|
|
// currentRideId = rideId;
|
|
// driverId = rideData['driver_id']?.toString() ?? '';
|
|
|
|
// String status = rideData['status']?.toString().toLowerCase() ?? '';
|
|
|
|
// // تحديد الحالة
|
|
// if (status == 'waiting' || status == 'searching') {
|
|
// currentRideState.value = RideState.searching;
|
|
// isSearchingWindow = true;
|
|
// } else if (status == 'apply' || status == 'applied') {
|
|
// currentRideState.value = RideState.driverApplied;
|
|
// statusRide = 'Apply';
|
|
|
|
// // الاشتراك في موقع السائق
|
|
// socket.emit('subscribe_driver_location', {
|
|
// 'ride_id': rideId,
|
|
// 'driver_id': driverId,
|
|
// });
|
|
|
|
// // استعادة بيانات السائق
|
|
// if (rideData['driver_info'] != null) {
|
|
// var dInfo = rideData['driver_info'];
|
|
// passengerName = dInfo['first_name']?.toString() ?? '';
|
|
// driverPhone = dInfo['phone']?.toString() ?? '';
|
|
// model = dInfo['model']?.toString() ?? '';
|
|
// licensePlate = dInfo['license_plate']?.toString() ?? '';
|
|
// }
|
|
// } else if (status == 'arrived') {
|
|
// currentRideState.value = RideState.driverArrived;
|
|
// statusRide = 'Arrived';
|
|
// isDriverArrivePassenger = true;
|
|
// } else if (status == 'begin' || status == 'started') {
|
|
// currentRideState.value = RideState.inProgress;
|
|
// statusRide = 'Begin';
|
|
// rideTimerBegin = true;
|
|
|
|
// // استعادة Polyline
|
|
// if (rideData['polyline'] != null) {
|
|
// _restorePolyline(rideData['polyline']);
|
|
// }
|
|
|
|
// rideIsBeginPassengerTimer();
|
|
// }
|
|
|
|
// update();
|
|
// _startMasterTimer(); // للـ Safety Checks
|
|
// } catch (e) {
|
|
// Log.print('[Startup] Error: $e');
|
|
// currentRideState.value = RideState.noRide;
|
|
// _startMasterTimer();
|
|
// }
|
|
// }
|
|
|
|
// Future<void> _restorePolyline(String polylineString) async {
|
|
// try {
|
|
// List<LatLng> points =
|
|
// await compute(decodePolylineIsolate, polylineString);
|
|
|
|
// polylineCoordinates.clear();
|
|
// polylineCoordinates.addAll(points);
|
|
|
|
// polyLines.clear();
|
|
// polyLines = {
|
|
// ...polyLines,
|
|
// Polyline(
|
|
// polylineId: const PolylineId('route_direct'),
|
|
// points: polylineCoordinates,
|
|
// color: const Color(0xFF2196F3),
|
|
// width: 6,
|
|
// )
|
|
// };
|
|
|
|
// update();
|
|
// } catch (e) {
|
|
// Log.print('Error restoring polyline: $e');
|
|
// }
|
|
// }
|
|
|
|
// // متغير لمنع التكرار (Race Condition Guard)
|
|
// bool _isAcceptanceProcessed = false;
|
|
|
|
// // ==============================================================================
|
|
// // الدالة الموحدة لمعالجة القبول (من السوكيت أو FCM)
|
|
// // ==============================================================================
|
|
// Future<void> processRideAcceptance(
|
|
// {Map<String, dynamic>? driverData, required String source}) async {
|
|
// // 1. الحماية: إذا تم المعالجة مسبقاً، تجاهل
|
|
// // نستثني حالة واحدة: إذا كنا في وضع البحث (Searching) نقبل الأمر
|
|
// // إذا كنا في driverApplied، نتجاهل الأمر
|
|
// if (currentRideState.value != RideState.searching &&
|
|
// _isAcceptanceProcessed) {
|
|
// Log.print("✋ Ignored Acceptance from $source. Already processed.");
|
|
// return;
|
|
// }
|
|
|
|
// _rideAcceptedViaSource = source;
|
|
// _socketLocationUpdatesCount = 0;
|
|
|
|
// _isAcceptanceProcessed = true; // قفل الباب
|
|
// Log.print("🚀 Winner: $source triggered acceptance! Processing...");
|
|
|
|
// // 2. إيقاف جميع التايمرات القديمة فوراً
|
|
// _masterTimer?.cancel();
|
|
// // stopSearchingTimer(); // إذا كان لديك تايمر للبحث
|
|
|
|
// // 3. تحديث الحالة في الواجهة
|
|
// currentRideState.value = RideState.driverApplied;
|
|
// statusRide = 'Apply';
|
|
// isSearchingWindow = false;
|
|
|
|
// // 4. معالجة البيانات (تعبئة المتغيرات)
|
|
// if (driverData != null && driverData.isNotEmpty) {
|
|
// Log.print("📥 Populating Data from $source payload...");
|
|
// _fillDriverDataLocally(driverData);
|
|
// } else {
|
|
// Log.print("⚠️ No Data in Payload. Fallback to API.");
|
|
// await getUpdatedRideForDriverApply(rideId);
|
|
// }
|
|
// // أضف هذا بعد السطر الذي تستدعي فيه RideTrackingNative
|
|
// await IosLiveActivityService.startRideActivity(
|
|
// rideId: rideId,
|
|
// driverName: driverName ?? 'السائق',
|
|
// carDetails: '$make • $carColor',
|
|
// etaText: stringRemainingTimeToPassenger,
|
|
// progress: 0.0,
|
|
// );
|
|
// // إشعارات (الأسعار، الأمان...)
|
|
// _showRideStartNotifications();
|
|
// final etaText = stringRemainingTimeToPassenger; // مثال: "8 دقائق"
|
|
// final carInfo = '$make • $model • $licensePlate';
|
|
|
|
// await RideLiveNotification.showDriverOnWay(
|
|
// driverName: driverName,
|
|
// etaText: etaText,
|
|
// carInfo: carInfo,
|
|
// );
|
|
|
|
// update(); // تحديث الواجهة لإظهار بيانات السائق
|
|
|
|
// // 5. 🔥 العمليات الجغرافية (المسار والوقت) 🔥
|
|
|
|
// // أ) جلب موقع السائق الأولي
|
|
// await getDriverCarsLocationToPassengerAfterApplied();
|
|
// _startSocketWatchdog();
|
|
// // ب) رسم المسار وحساب الوقت
|
|
// if (driverCarsLocationToPassengerAfterApplied.isNotEmpty) {
|
|
// LatLng driverPos = driverCarsLocationToPassengerAfterApplied.last;
|
|
|
|
// // نستخدم الدالة الموحدة الجديدة للحساب والرسم
|
|
// await calculateDriverToPassengerRoute(driverPos, passengerLocation);
|
|
|
|
// // ج) تشغيل التايمر المحلي (للعد التنازلي فقط)
|
|
// startTimerFromDriverToPassengerAfterApplied();
|
|
// }
|
|
// final int timeToPassengerSeconds =
|
|
// timeToPassengerFromDriverAfterApplied; // مثلاً من السيرفر
|
|
// final double distanceDriverToPassengerMeters =
|
|
// double.tryParse(distanceByPassenger.toString()) ?? 0.0;
|
|
|
|
// // [PiP] تفعيل PiP عند بدء الرحلة (سيدخل وضع النافذة العائمة عند خروج المستخدم)
|
|
// PipService.enablePip();
|
|
|
|
// // 6. 🔥 القرار الذكي: هل نبدأ البولينج أم نعتمد على السوكيت؟ 🔥
|
|
// if (source == "Socket" && isSocketConnected) {
|
|
// // ✅ السوكيت هو من أخبرنا بالقبول — نثق به ولا نشغل البولينج
|
|
// Log.print(
|
|
// "🧠 Smart Mode: Socket accepted ride. Skipping polling, relying on WebSocket.");
|
|
// // الـ watchdog وحده يكفي كشبكة أمان
|
|
// } else {
|
|
// // ⚠️ القبول من FCM أو Polling — نشغل البولينج كالمعتاد
|
|
// Log.print("🔄 Fallback Mode: $source accepted ride. Starting polling.");
|
|
// _startDriverLocationPollingWithTimer();
|
|
// }
|
|
// }
|
|
|
|
// Timer? _watchdogTimer;
|
|
|
|
// void _startSocketWatchdog() {
|
|
// _watchdogTimer?.cancel();
|
|
// Log.print("👀 Starting Socket Watchdog (Hybrid Mode)...");
|
|
|
|
// _watchdogTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
|
|
// if (currentRideState.value != RideState.driverApplied &&
|
|
// currentRideState.value != RideState.driverArrived &&
|
|
// currentRideState.value != RideState.inProgress) {
|
|
// timer.cancel();
|
|
// return;
|
|
// }
|
|
|
|
// final lastTime = _lastSocketLocationTime ??
|
|
// DateTime.now().subtract(const Duration(minutes: 1));
|
|
// final difference = DateTime.now().difference(lastTime).inSeconds;
|
|
|
|
// if (difference < 15 && isSocketConnected) {
|
|
// // ✅ السوكيت صحي — تأكد أن البولينج متوقف إذا كنا في وضع السوكيت
|
|
// if (_locationPollingTimer != null &&
|
|
// _rideAcceptedViaSource == "Socket") {
|
|
// Log.print("✅ Socket recovered. Stopping polling fallback.");
|
|
// _stopDriverLocationPolling();
|
|
// }
|
|
// } else if (difference >= 15 && difference < 30) {
|
|
// // ⚠️ تأخر متوسط — جلب واحد فقط
|
|
// Log.print("⚠️ Socket silent for ${difference}s. Single API Poll...");
|
|
// await getDriverCarsLocationToPassengerAfterApplied();
|
|
// } else if (difference >= 30) {
|
|
// // 🔴 السوكيت ميت — تفعيل البولينج الكامل كـ fallback
|
|
// if (_locationPollingTimer == null) {
|
|
// Log.print(
|
|
// "🔴 Socket dead for ${difference}s. Activating polling fallback!");
|
|
// _startDriverLocationPollingWithTimer();
|
|
// } else {
|
|
// // البولينج يعمل بالفعل، فقط أكمل
|
|
// await getDriverCarsLocationToPassengerAfterApplied();
|
|
// }
|
|
// }
|
|
// });
|
|
// }
|
|
|
|
// // قائمة بأنصاف الأقطار (بالأمتار) لكل مرحلة
|
|
// final List<int> _searchRadii = [
|
|
// 2400,
|
|
// 3000,
|
|
// 3100
|
|
// ]; // 0 ثانية، 30 ثانية، 60 ثانية
|
|
// // المدة الزمنية لكل مرحلة بحث (بالثواني)
|
|
// final int _searchPhaseDurationSeconds = 30;
|
|
// // المهلة الإجمالية للبحث قبل عرض خيار زيادة السعر
|
|
// final int _totalSearchTimeoutSeconds = 90; // 90 ثانية
|
|
// // --- noRide throttling ---
|
|
// int _noRideSearchCount = 0;
|
|
// final int _noRideMaxTries = 3; // نفذ البحث 6 مرات فقط
|
|
// final int _noRideIntervalSec = 5; // بين كل محاولة وأخرى 5 ثواني
|
|
// DateTime? _noRideNextAllowed; // متى نسمح بالمحاولة التالية
|
|
// bool _noRideSearchCapped = false; // وصلنا للحد وتوقفنا
|
|
// // ============== new design to manage ride state ==============
|
|
// // === 1. حالة الرحلة والمؤقت الرئيسي (Single Source of Truth) ===
|
|
// Rx<RideState> currentRideState = RideState.noRide.obs;
|
|
// Timer? _masterTimer;
|
|
// final int _pollingIntervalSeconds = 13; // فاصل زمني موحد للاستعلام
|
|
|
|
// void _startMasterTimer() {
|
|
// // نضمن أن مؤقت واحد فقط يعمل في أي وقت
|
|
// _masterTimer?.cancel();
|
|
// _masterTimer =
|
|
// Timer.periodic(Duration(seconds: _pollingIntervalSeconds), (_) {
|
|
// _handleRideState(currentRideState.value);
|
|
// });
|
|
// }
|
|
|
|
// void stopAllTimers() {
|
|
// Log.print('🛑 FORCE STOP: Stopping ALL Timers and Streams 🛑');
|
|
|
|
// // 1. إيقاف الماكينة الرئيسية
|
|
// _masterTimer?.cancel();
|
|
// _masterTimer = null;
|
|
|
|
// // 2. إيقاف مؤقتات تتبع السائق
|
|
// timerToPassengerFromDriverAfterApplied?.cancel();
|
|
// _timer?.cancel();
|
|
// _uiCountdownTimer?.cancel();
|
|
|
|
// // 3. إيقاف مؤقتات الخريطة
|
|
|
|
// _animationTimers.forEach((key, timer) => timer.cancel());
|
|
// _animationTimers.clear();
|
|
|
|
// // 4. إغلاق الستريمز
|
|
// if (!_rideStatusStreamController.isClosed)
|
|
// _rideStatusStreamController.close();
|
|
// if (!_beginRideStreamController.isClosed)
|
|
// _beginRideStreamController.close();
|
|
// if (!timerController.isClosed) timerController.close();
|
|
|
|
// // 5. تصفير العدادات لمنع إعادة الدخول
|
|
// isTimerRunning = false;
|
|
// isBeginRideFromDriverRunning = false;
|
|
// _isFetchingDriverLocation = false;
|
|
|
|
// update();
|
|
// }
|
|
|
|
// final int _maxNoRideSearch = 3; // عدد المرات القصوى
|
|
// final int _noRideDelaySeconds = 6; // الفاصل الزمني بين كل بحث
|
|
// //
|
|
// //
|
|
// // !!! يرجى استبدال الدالة القديمة بالكامل بهذه الدالة الجديدة !!!
|
|
// //
|
|
// //
|
|
// bool _isStateProcessing = false;
|
|
|
|
// Future<void> _handleRideState(RideState state) async {
|
|
// if (_isRatingScreenOpen) {
|
|
// Log.print('⛔ Rating Screen is Open. Skipping Logic.');
|
|
// stopAllTimers(); // تأكيد إضافي للإيقاف
|
|
// return;
|
|
// }
|
|
// Log.print('Handling state: $state');
|
|
|
|
// // int effectivePollingInterval = _pollingIntervalSeconds;
|
|
|
|
// // الحصول على الفاصل الزمني من الخريطة
|
|
// int effectivePollingInterval =
|
|
// _pollingIntervals[state] ?? _pollingIntervalSeconds;
|
|
|
|
// switch (state) {
|
|
// case RideState.noRide:
|
|
// final now = DateTime.now();
|
|
// if (_noRideSearchCount >= _noRideMaxTries) {
|
|
// if (!_noRideSearchCapped) {
|
|
// _noRideSearchCapped = true;
|
|
// Log.print('[noRide] search capped at $_noRideMaxTries attempts');
|
|
// }
|
|
// break;
|
|
// }
|
|
// if (_noRideNextAllowed != null && now.isBefore(_noRideNextAllowed!)) {
|
|
// break;
|
|
// }
|
|
// _noRideSearchCount++;
|
|
// Log.print('_noRideSearchCount: $_noRideSearchCount');
|
|
// _noRideNextAllowed = now.add(Duration(seconds: _noRideIntervalSec));
|
|
// String currentCarType = box.read(BoxName.carType) ?? 'yet';
|
|
// getCarsLocationByPassengerAndReloadMarker();
|
|
// getNearestDriverByPassengerLocation();
|
|
// break;
|
|
|
|
// case RideState.cancelled:
|
|
// Log.print('[handleRideState] Ride cancelled. Stopping polling.');
|
|
// stopAllTimers();
|
|
// // effectivePollingInterval = 3600;
|
|
// break;
|
|
|
|
// case RideState.preCheckReview:
|
|
// stopAllTimers();
|
|
// _checkLastRideForReview();
|
|
// break;
|
|
|
|
// case RideState.searching:
|
|
// // Guard: Don't poll if ride is not registered yet
|
|
// if (rideId == 'yet' || rideId.isEmpty) break;
|
|
|
|
// // 1. التحقق من حالة الطلب (هل قبله أحد؟) عبر البولينج كشبكة أمان
|
|
// try {
|
|
// String statusFromServer = await getRideStatus(rideId);
|
|
// if (statusFromServer == 'Apply' || statusFromServer == 'Applied') {
|
|
// await processRideAcceptance(source: "Polling");
|
|
// break;
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print('Error polling getRideStatus: $e');
|
|
// }
|
|
|
|
// final now = DateTime.now();
|
|
// final int elapsedSeconds = now.difference(_searchStartTime!).inSeconds;
|
|
|
|
// // انتهاء وقت البحث الكلي
|
|
// if (elapsedSeconds > _totalSearchTimeoutSeconds) {
|
|
// stopAllTimers();
|
|
// currentRideState.value = RideState.noRide;
|
|
// isSearchingWindow = false;
|
|
// update();
|
|
// _showIncreaseFeeDialog();
|
|
// break;
|
|
// }
|
|
|
|
// // 2. إدارة مراحل البحث (توسيع النطاق)
|
|
// // السيناريو الجديد: لا نقوم بالقصف العشوائي، نرسل بناء على المرحلة أو مرور وقت كافٍ لدخول سائقين جدد
|
|
|
|
// int targetPhase =
|
|
// (elapsedSeconds / _searchPhaseDurationSeconds).floor();
|
|
// if (targetPhase >= _searchRadii.length) {
|
|
// targetPhase = _searchRadii.length - 1;
|
|
// }
|
|
|
|
// // هل تغيرت المرحلة (توسع النطاق)؟ أو هل مر 10 ثواني منذ آخر محاولة إرسال؟
|
|
// // هذا يمنع إرسال الإشعار في كل دورة (كل 5 ثواني) ويقلل الازعاج
|
|
// bool isNewPhase = targetPhase > _currentSearchPhase;
|
|
// bool timeToScanForNewDrivers =
|
|
// (elapsedSeconds % 15 == 0); // كل 15 ثانية نفحص الدخول الجديد
|
|
|
|
// if (isNewPhase || timeToScanForNewDrivers || elapsedSeconds < 5) {
|
|
// _currentSearchPhase = targetPhase;
|
|
// int currentRadius = _searchRadii[_currentSearchPhase];
|
|
|
|
// Log.print(
|
|
// '[Search Logic] Scanning for drivers. Phase: $_currentSearchPhase, Radius: $currentRadius');
|
|
// // استدعاء دالة الإشعار الذكية
|
|
// // _findAndNotifyNearestDrivers(currentRadius);
|
|
// }
|
|
|
|
// // تحديث نصوص الواجهة
|
|
// if (elapsedSeconds < 5) {
|
|
// driversStatusForSearchWindow = 'Your order is being prepared'.tr;
|
|
// } else if (elapsedSeconds < 15) {
|
|
// driversStatusForSearchWindow = 'Your order sent to drivers'.tr;
|
|
// } else {
|
|
// driversStatusForSearchWindow =
|
|
// 'The drivers are reviewing your request'.tr;
|
|
// }
|
|
// update();
|
|
// break;
|
|
|
|
// case RideState.driverApplied:
|
|
// // effectivePollingInterval = 10;
|
|
// if (!_isDriverAppliedLogicExecuted) {
|
|
// Log.print('[handleRideState] Execution driverApplied logic.');
|
|
// rideAppliedFromDriver(true);
|
|
// _isDriverAppliedLogicExecuted = true;
|
|
// }
|
|
|
|
// if (!isSocketConnected) {
|
|
// try {
|
|
// String statusFromServer = await getRideStatus(rideId);
|
|
// if (statusFromServer == 'Arrived') {
|
|
// currentRideState.value = RideState.driverArrived;
|
|
// break;
|
|
// } else if (statusFromServer == 'Begin' ||
|
|
// statusFromServer == 'inProgress') {
|
|
// processRideBegin();
|
|
// break;
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print('Error polling for Arrived/Begin status: $e');
|
|
// }
|
|
// }
|
|
// // 🧠 جلب الموقع فقط إذا السوكيت غير صحي
|
|
// if (!_isSocketHealthy()) {
|
|
// getDriverCarsLocationToPassengerAfterApplied();
|
|
// }
|
|
// break;
|
|
|
|
// case RideState.driverArrived:
|
|
// if (!_isDriverArrivedLogicExecuted) {
|
|
// _isDriverArrivedLogicExecuted = true;
|
|
// startTimerDriverWaitPassenger5Minute();
|
|
// driverArrivePassengerDialoge();
|
|
// }
|
|
// // effectivePollingInterval = 8;
|
|
// break;
|
|
|
|
// case RideState.inProgress:
|
|
// // effectivePollingInterval = 6;
|
|
|
|
// if (!isSocketConnected) {
|
|
// try {
|
|
// String statusFromServer = await getRideStatus(rideId);
|
|
|
|
// // !!! هنا التغيير الجذري !!!
|
|
// if (statusFromServer == 'Finished' ||
|
|
// statusFromServer == 'finished') {
|
|
// Log.print(
|
|
// '🏁 DETECTED FINISHED: Killing processes and forcing Review.');
|
|
|
|
// // 1. قتل العمليات فوراً
|
|
// stopAllTimers();
|
|
|
|
// // 2. تغيير الحالة الداخلية لمنع أي كود آخر من العمل
|
|
// currentRideState.value = RideState.preCheckReview;
|
|
|
|
// // 3. تنظيف الواجهة
|
|
// tripFinishedFromDriver();
|
|
|
|
// // 4. استدعاء شاشة التقييم فوراً
|
|
// _checkLastRideForReview();
|
|
|
|
// return; // خروج نهائي من الدالة لمنع أي كود بالأسفل من التنفيذ
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print('Error polling status: $e');
|
|
// }
|
|
// }
|
|
|
|
// // بقية كود التتبع العادي (لن يتم الوصول إليه إذا انتهت الرحلة)
|
|
// if (!_isRideBeginLogicExecuted) {
|
|
// _isRideBeginLogicExecuted = true;
|
|
// _executeBeginRideLogic();
|
|
// }
|
|
// // 🧠 جلب الموقع فقط إذا السوكيت غير صحي
|
|
// if (!_isSocketHealthy()) {
|
|
// getDriverCarsLocationToPassengerAfterApplied();
|
|
// }
|
|
// break;
|
|
// case RideState.finished:
|
|
// tripFinishedFromDriver();
|
|
// stopAllTimers();
|
|
// effectivePollingInterval = 3600;
|
|
// break;
|
|
// }
|
|
// // تحديث الماكينة الرئيسية إذا تغير الفاصل الزمني
|
|
// _startMasterTimerWithInterval(effectivePollingInterval);
|
|
// }
|
|
|
|
// int _masterIntervalSeconds = -1;
|
|
|
|
// void _startMasterTimerWithInterval(int seconds) {
|
|
// // نفس الانترفَل؟ لا تعمل شيء
|
|
// if (_masterTimer != null && _masterIntervalSeconds == seconds) return;
|
|
|
|
// _masterIntervalSeconds = seconds;
|
|
// _masterTimer?.cancel();
|
|
|
|
// _masterTimer = Timer.periodic(Duration(seconds: seconds), (_) {
|
|
// _handleRideState(currentRideState.value);
|
|
// });
|
|
// }
|
|
|
|
// Future<void> _checkInitialRideStatus() async {
|
|
// // 1. جلب الحالة من السيرفر (باستخدام getRideStatusFromStartApp)
|
|
// await getRideStatusFromStartApp();
|
|
// String _status = rideStatusFromStartApp['data']['status'];
|
|
// // Log.print('rideStatusFromStartApp: ${rideStatusFromStartApp}');
|
|
// // Log.print('_status: ${_status}');
|
|
|
|
// if (_status == 'waiting' || _status == 'Apply' || _status == 'Begin') {
|
|
// // رحلة جارية
|
|
// rideId = rideStatusFromStartApp['data']['rideId'].toString();
|
|
// currentRideState.value = _status == 'waiting'
|
|
// ? RideState.searching
|
|
// : _status == 'Apply'
|
|
// ? RideState.driverApplied
|
|
// : _status == 'Begin'
|
|
// ? RideState.inProgress
|
|
// : _status == 'Cancel'
|
|
// ? RideState.cancelled
|
|
// : RideState.noRide;
|
|
// } else if (_status == 'Finished') {
|
|
// // رحلة منتهية/ملغاة
|
|
// if (rideStatusFromStartApp['data']['needsReview'] == 1) {
|
|
// currentRideState.value = RideState.preCheckReview;
|
|
// } else {
|
|
// currentRideState.value = RideState.noRide;
|
|
// }
|
|
// } else {
|
|
// currentRideState.value = RideState.noRide;
|
|
// }
|
|
|
|
// // بدء المعالجة الفورية
|
|
// _handleRideState(currentRideState.value);
|
|
// }
|
|
|
|
// Future<void> _checkLastRideForReview() async {
|
|
// Log.print('⭐ FORCE OPEN RATING PAGE (Get.to mode)');
|
|
|
|
// // جلب البيانات
|
|
// await getRideStatusFromStartApp();
|
|
|
|
// if (rideStatusFromStartApp['data'] == null) {
|
|
// currentRideState.value = RideState.noRide;
|
|
// _startMasterTimer();
|
|
// return;
|
|
// }
|
|
|
|
// String needsReview =
|
|
// rideStatusFromStartApp['data']['needsReview'].toString();
|
|
|
|
// if (needsReview == '1') {
|
|
// _isRatingScreenOpen = true;
|
|
// // 1. تجهيز البيانات (Arguments)
|
|
// var args = {
|
|
// 'driverId': rideStatusFromStartApp['data']['driver_id'].toString(),
|
|
// 'rideId': rideStatusFromStartApp['data']['rideId'].toString(),
|
|
// 'driverName': rideStatusFromStartApp['data']['driverName'],
|
|
// 'price': rideStatusFromStartApp['data']['price'],
|
|
// };
|
|
|
|
// // 2. استخدام Get.to مع await (هذا هو الحل الجذري)
|
|
// // الكود سيتوقف هنا ولن يكمل التنفيذ حتى يتم إغلاق صفحة التقييم
|
|
// await Get.to(
|
|
// () => RatingDriverBottomSheet(),
|
|
// arguments: args, // تمرير البيانات بالطريقة التي تريدها
|
|
// preventDuplicates: true, // لمنع فتح الصفحة مرتين
|
|
// popGesture: false, // لمنع السحب للرجوع (في iOS)
|
|
// );
|
|
|
|
// // 3. هذا الكود لن يتنفذ إلا بعد أن يضغط المستخدم "تم" في التقييم ويغلق الصفحة
|
|
// Log.print('✅ Rating Page Closed. Resetting App.');
|
|
// _isRatingScreenOpen = false;
|
|
// restCounter();
|
|
// currentRideState.value = RideState.noRide;
|
|
// _startMasterTimer(); // إعادة تشغيل البحث الآن فقط
|
|
// } else {
|
|
// currentRideState.value = RideState.noRide;
|
|
// _startMasterTimer();
|
|
// }
|
|
// }
|
|
|
|
// void startSearchingForDriver() async {
|
|
// // ✅ منع الضغط المزدوج
|
|
// if (currentRideState.value == RideState.searching) {
|
|
// return;
|
|
// }
|
|
// // 1. تحديث الحالة الأولية
|
|
// isSearchingWindow = true;
|
|
// currentRideState.value = RideState.searching;
|
|
// driversStatusForSearchWindow = 'Searching for nearby drivers...'.tr;
|
|
// update();
|
|
|
|
// // 2. إرسال الطلب للسيرفر (add_ride.php)
|
|
// bool rideCreated = await postRideDetailsToServer();
|
|
|
|
// if (!rideCreated) {
|
|
// // فشل الإنشاء
|
|
// isSearchingWindow = false;
|
|
// currentRideState.value = RideState.noRide;
|
|
// mySnackbarWarning("Could not create ride. Please try again.".tr);
|
|
// update();
|
|
// return;
|
|
// }
|
|
|
|
// // 3. نجاح الإنشاء: إضافة لجدول الانتظار المحلي (اختياري حسب منطقك)
|
|
// _addRideToWaitingTable();
|
|
|
|
// // 4. 🔥 الاتصال بالسوكيت فوراً وانتظار الرد الحقيقي 🔥
|
|
// // نغلق أي اتصال سابق ونبدأ اتصالاً جديداً مخصصاً لهذه الرحلة
|
|
// initConnectionWithSocket();
|
|
|
|
// // تشغيل الماكينة الرئيسية للمراقبة (كحماية إضافية)
|
|
// // _startMasterTimer();
|
|
// }
|
|
|
|
// /// دالة لإظهار النافذة المنبثقة لزيادة السعر
|
|
// void _showIncreaseFeeDialog() {
|
|
// Get.dialog(
|
|
// CupertinoAlertDialog(
|
|
// title: Text("No drivers accepted your request yet".tr),
|
|
// content: Text(
|
|
// "Increasing the fare might attract more drivers. Would you like to increase the price?"
|
|
// .tr),
|
|
// actions: [
|
|
// CupertinoDialogAction(
|
|
// child: Text("Cancel Ride".tr,
|
|
// style: TextStyle(color: AppColor.redColor)),
|
|
// onPressed: () {
|
|
// Get.back();
|
|
// changeCancelRidePageShow();
|
|
// // cancelRide(); // دالة إلغاء الرحلة
|
|
// },
|
|
// ),
|
|
// CupertinoDialogAction(
|
|
// child: Text("Increase Fare".tr,
|
|
// style: TextStyle(color: AppColor.greenColor)),
|
|
// onPressed: () {
|
|
// Get.back();
|
|
// // هنا يمكنك عرض نافذة أخرى لإدخال السعر الجديد
|
|
// // وبعدها استدعاء الدالة التالية
|
|
// // كمثال، سنزيد السعر بنسبة 10%
|
|
// double newPrice = totalPassenger * 1.10;
|
|
// increasePriceAndRestartSearch(newPrice);
|
|
// },
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// barrierDismissible: false,
|
|
// );
|
|
// }
|
|
|
|
// /// دالة لتحديث السعر وإعادة بدء البحث
|
|
// Future<void> increasePriceAndRestartSearch(double newPrice) async {
|
|
// totalPassenger = newPrice;
|
|
// update();
|
|
|
|
// // await CRUD().post(link: AppLink.updateRides, payload: {
|
|
// // "id": rideId,
|
|
// // "price": newPrice.toStringAsFixed(2),
|
|
// // });
|
|
// CRUD().post(link: "${AppLink.server}/ride/rides/update.php", payload: {
|
|
// "id": rideId,
|
|
// "price": newPrice.toStringAsFixed(2),
|
|
// });
|
|
|
|
// // تصفير القائمة لأن السعر تغير، ويجب إبلاغ الجميع (حتى من وصله الإشعار سابقاً)
|
|
// Log.print(
|
|
// '[increasePrice] Price changed. Clearing notified list to resend.');
|
|
// notifiedDrivers.clear();
|
|
|
|
// _searchStartTime = DateTime.now();
|
|
// _currentSearchPhase = 0;
|
|
|
|
// isSearchingWindow = true;
|
|
// update();
|
|
|
|
// _startMasterTimer();
|
|
// }
|
|
|
|
// /// (دالة جديدة موحدة)
|
|
// /// هذه هي الدالة الوحيدة المسؤولة عن بدء عملية قبول الرحلة.
|
|
// /// يتم استدعاؤها إما من إشعار Firebase أو من البحث الدوري (Polling).
|
|
// /// هي تحتوي على "حارس البوابة" لمنع تضارب السباق.
|
|
// /// (دالة موحدة وصارمة)
|
|
// /// تستدعى من FCM أو من Polling عند اكتشاف قبول السائق
|
|
// /// تمنع تضارب السباق وتضمن تنفيذ المنطق مرة واحدة فقط
|
|
// /// متغير لمنع التكرار (Race Condition Guard)
|
|
|
|
// Future<dynamic> driverArrivePassengerDialoge() {
|
|
// return Get.defaultDialog(
|
|
// barrierDismissible: false,
|
|
// title: 'Hi ,I Arrive your site'.tr,
|
|
// titleStyle: AppStyle.title,
|
|
// middleText: 'Please go to Car Driver'.tr,
|
|
// middleTextStyle: AppStyle.title,
|
|
// confirm: MyElevatedButton(
|
|
// title: 'Ok I will go now.'.tr,
|
|
// onPressed: () {
|
|
// NotificationService.sendNotification(
|
|
// target: driverToken.toString(),
|
|
// title: 'Hi ,I will go now'.tr,
|
|
// body: 'I will go now'.tr,
|
|
// isTopic: false, // Important: this is a token
|
|
// tone: 'ding',
|
|
// driverList: [],
|
|
// category: 'Hi ,I will go now',
|
|
// );
|
|
|
|
// Get.back();
|
|
// remainingTime = 0;
|
|
// update();
|
|
// }));
|
|
// }
|
|
|
|
// /// (دالة خاصة جديدة)
|
|
// /// تحتوي على كل المنطق الفعلي لبدء الرحلة.
|
|
// ///
|
|
// Timer? _waitPassengerTimer;
|
|
// static const int _waitPassengerTotalSeconds = 300;
|
|
// int _waitPassengerElapsedSeconds = 0;
|
|
|
|
// /// **إيقاف عداد انتظار الراكب (Stop Wait Timer)**
|
|
// ///
|
|
// /// تقوم هذه الدالة بإلغاء التايمر النشط فوراً لتحرير الموارد ومنع تسريب الذاكرة.
|
|
// ///
|
|
// /// * [resetUI]: (اختياري) عند وضعه `true`، يتم تصفير العدادات وتحديث الواجهة لإخفاء التوقيت القديم.
|
|
// void _stopWaitPassengerTimer({bool resetUI = false}) {
|
|
// // 1. الإلغاء الآمن للتايمر (Safe Cancellation)
|
|
// _waitPassengerTimer?.cancel();
|
|
// _waitPassengerTimer = null;
|
|
|
|
// // 2. تصفير قيم الواجهة (Reset State)
|
|
// if (resetUI) {
|
|
// progressTimerDriverWaitPassenger5Minute = 0.0;
|
|
// remainingTimeDriverWaitPassenger5Minute = 0;
|
|
// stringRemainingTimeDriverWaitPassenger5Minute = '00:00';
|
|
|
|
// // ✅ تحديث الواجهة فوراً (GetX)
|
|
// update();
|
|
// }
|
|
// }
|
|
|
|
// void _executeBeginRideLogic() {
|
|
// Log.print('[executeBeginRideLogic] تنفيذ منطق بدء الرحلة...');
|
|
// _stopWaitPassengerTimer(resetUI: true); // <-- إضافة
|
|
|
|
// // 1. تصفير كل عدادات ما قبل الرحلة
|
|
// timeToPassengerFromDriverAfterApplied = 0;
|
|
// remainingTime = 0;
|
|
// remainingTimeToPassengerFromDriverAfterApplied = 0;
|
|
// remainingTimeDriverWaitPassenger5Minute = 0;
|
|
|
|
// // 2. تحديث الحالة والواجهة
|
|
// rideTimerBegin = true;
|
|
// statusRide = 'Begin';
|
|
// isDriverInPassengerWay = false;
|
|
// isDriverArrivePassenger = false; // لإخفاء واجهة "السائق وصل"
|
|
|
|
// // 3. (من كود الإشعار الخاص بك)
|
|
// box.write(BoxName.passengerWalletTotal, '0');
|
|
// update(); // تحديث الواجهة قبل بدء المؤقتات
|
|
|
|
// // 4. بدء مؤقتات الرحلة الفعلية
|
|
// rideIsBeginPassengerTimer(); // مؤقت عداد مدة الرحلة
|
|
// // runWhenRideIsBegin(); // مؤقت تتبع موقع السائق أثناء الرحلة
|
|
|
|
// // 5. إشعار الراكب (من كود الإشعار الخاص بك)
|
|
// 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,
|
|
// 'start');
|
|
// }
|
|
|
|
// // متغير لمنع التكرار
|
|
// bool _isRideStartedProcessed = false;
|
|
|
|
// /// **معالجة بدء الرحلة الموحدة (Unified Ride Start Handler)**
|
|
// ///
|
|
// /// تستدعى عند استلام حدث بدء الرحلة سواء من السوكيت أو FCM.
|
|
// /// تضمن انتقال التطبيق لحالة [RideState.inProgress] مرة واحدة فقط.
|
|
// Future<void> processRideBegin({String source = "Unknown"}) async {
|
|
// // منطقك الحالي
|
|
// if (currentRideState.value == RideState.inProgress ||
|
|
// _isRideStartedProcessed) {
|
|
// return;
|
|
// }
|
|
|
|
// _isRideStartedProcessed = true;
|
|
// currentRideState.value = RideState.inProgress;
|
|
// statusRide = 'Begin';
|
|
|
|
// // إيقاف مؤقت الانتظار
|
|
// remainingTimeDriverWaitPassenger5Minute = 0;
|
|
// _stopWaitPassengerTimer();
|
|
|
|
// // 1) بيانات السائق والرحلة
|
|
// rideIsBeginPassengerTimer();
|
|
// update();
|
|
// }
|
|
|
|
// 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 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();
|
|
// }
|
|
// }
|
|
|
|
// void convertHintTextDestinationNewPlaces(int index) {
|
|
// if (placesDestination.isEmpty) {
|
|
// hintTextDestinationPoint = 'Search for your destination'.tr;
|
|
// update();
|
|
// } else {
|
|
// var res = placesDestination[index];
|
|
// 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);
|
|
// // 🔥 الحل: تحريك الكاميرا فوراً للهدف حتى لا يتم مسحه عند إغلاق الكيبورد 🔥
|
|
// mapController
|
|
// ?.animateCamera(CameraUpdate.newLatLngZoom(newMyLocation, 16));
|
|
// }
|
|
// 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);
|
|
|
|
// // 🔥 تحريك الكاميرا فوراً 🔥
|
|
// mapController?.animateCamera(CameraUpdate.newLatLngZoom(newMyLocation, 16));
|
|
// update();
|
|
// }
|
|
|
|
// // final mainBottomMenuMap = GlobalKey<AnimatedContainer>();
|
|
// void changeBottomSheetShown({bool? forceValue}) {
|
|
// if (forceValue != null) {
|
|
// isBottomSheetShown = forceValue;
|
|
// } else {
|
|
// 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);
|
|
// }
|
|
|
|
// String formatSyrianPhone(String phone) {
|
|
// // Remove spaces and +
|
|
// phone = phone.replaceAll(' ', '').replaceAll('+', '');
|
|
|
|
// // If starts with 00963 → remove 00 → 963
|
|
// if (phone.startsWith('00963')) {
|
|
// phone = phone.replaceFirst('00963', '963');
|
|
// }
|
|
|
|
// // If starts with 0963 (common mistake) → fix it
|
|
// if (phone.startsWith('0963')) {
|
|
// phone = phone.replaceFirst('0963', '963');
|
|
// }
|
|
|
|
// // If starts with 963 (already correct)
|
|
// if (phone.startsWith('963')) {
|
|
// return phone; // nothing to do
|
|
// }
|
|
|
|
// // If starts with 09 → remove leading 0 → add 963
|
|
// if (phone.startsWith('09')) {
|
|
// return '963' + phone.substring(1); // 9xxxxxxxxx
|
|
// }
|
|
|
|
// // If starts with 9xxxxxxxxx (no country code)
|
|
// if (phone.startsWith('9') && phone.length == 9) {
|
|
// return '963' + phone;
|
|
// }
|
|
|
|
// // Otherwise return raw phone
|
|
// return phone;
|
|
// }
|
|
|
|
// void sendWhatsapp(String to) async {
|
|
// // Normalize phone number before sending
|
|
// String formattedPhone = formatSyrianPhone(to);
|
|
|
|
// // Message body
|
|
// 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.';
|
|
|
|
// // Send WhatsApp message
|
|
// launchCommunication('whatsapp', formattedPhone, 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;
|
|
// }
|
|
|
|
// 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 = 10; // Interval in seconds for getBeginRideFromDriver
|
|
|
|
// void startTimerFromDriverToPassengerAfterApplied() {
|
|
// stopTimerFromDriverToPassengerAfterApplied();
|
|
// if (isTimerRunning) return;
|
|
// isTimerRunning = true;
|
|
// isTimerFromDriverToPassengerAfterAppliedRunning = true;
|
|
|
|
// int secondsElapsed = 0;
|
|
|
|
// // استدعاء فوري لأول مرة
|
|
// // getDriverCarsLocationToPassengerAfterApplied();
|
|
|
|
// Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
// // --- التغيير الجوهري هنا ---
|
|
// // شرط الإيقاف: نتوقف فقط إذا انتهت الرحلة أو ألغيت، أو تم إيقاف التايمر يدوياً
|
|
// // لم نعد نعتمد على تجاوز الوقت المقدر (timeToPassenger) كشرط للإيقاف
|
|
// bool isRideActive = (statusRide == 'Apply' ||
|
|
// statusRide == 'Arrived' ||
|
|
// statusRide == 'Begin' ||
|
|
// currentRideState.value == RideState.driverApplied ||
|
|
// currentRideState.value == RideState.driverArrived ||
|
|
// currentRideState.value == RideState.inProgress);
|
|
|
|
// if (!isRideActive || !isTimerFromDriverToPassengerAfterAppliedRunning) {
|
|
// timer.cancel();
|
|
// isTimerRunning = false;
|
|
// if (!_timerStreamController.isClosed) {
|
|
// _timerStreamController.close();
|
|
// }
|
|
// return;
|
|
// }
|
|
|
|
// secondsElapsed++;
|
|
// if (!_timerStreamController.isClosed) {
|
|
// _timerStreamController.add(secondsElapsed);
|
|
// }
|
|
|
|
// // تحديث الواجهة للوقت المتبقي (شكلياً فقط للراكب)
|
|
// // حتى لو أصبح الوقت سالباً (تأخر السائق)، سنظهره كـ 00:00 أو نتركه سالباً
|
|
// remainingTimeToPassengerFromDriverAfterApplied =
|
|
// timeToPassengerFromDriverAfterApplied - secondsElapsed;
|
|
|
|
// if (remainingTimeToPassengerFromDriverAfterApplied < 0) {
|
|
// remainingTimeToPassengerFromDriverAfterApplied = 0;
|
|
// }
|
|
|
|
// int minutes =
|
|
// (remainingTimeToPassengerFromDriverAfterApplied / 60).floor();
|
|
// int seconds = remainingTimeToPassengerFromDriverAfterApplied % 60;
|
|
// stringRemainingTimeToPassenger =
|
|
// '$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
// // تحويل الوقت أو المسافة إلى نسبة من 0.0 إلى 1.0
|
|
// double currentProgress = 1 -
|
|
// (remainingTimeToPassengerFromDriverAfterApplied /
|
|
// timeToPassengerFromDriverAfterApplied);
|
|
|
|
// // 🔴 التعديل هنا: نحدث الآيفون كل 5 ثواني فقط للحفاظ على البطارية وتجنب حظر أبل
|
|
// if (secondsElapsed % 5 == 0) {
|
|
// double currentProgress = 1 -
|
|
// (remainingTimeToPassengerFromDriverAfterApplied /
|
|
// (timeToPassengerFromDriverAfterApplied == 0
|
|
// ? 1
|
|
// : timeToPassengerFromDriverAfterApplied));
|
|
|
|
// IosLiveActivityService.updateRideActivity(
|
|
// status: 'waiting',
|
|
// driverName: driverName ?? 'السائق',
|
|
// carDetails:
|
|
// '$make • $model • $carColor', // من الأفضل إظهار اللون أيضاً
|
|
// etaText: stringRemainingTimeToPassenger,
|
|
// progress: currentProgress.clamp(0.0, 1.0),
|
|
// );
|
|
// }
|
|
// // جلب موقع السائق كل 4 ثواني (Polling) ما دامت الرحلة نشطة
|
|
// if (secondsElapsed % beginRideInterval == 0) {
|
|
// // 2. تحديث موقع الراكب للسائق
|
|
// uploadPassengerLocation();
|
|
// } else {
|
|
// update();
|
|
// }
|
|
// });
|
|
// }
|
|
|
|
// // Function to stop the timer
|
|
// void stopTimerFromDriverToPassengerAfterApplied() {
|
|
// isTimerFromDriverToPassengerAfterAppliedRunning = false;
|
|
// update();
|
|
// }
|
|
|
|
// void startTimerDriverWaitPassenger5Minute() {
|
|
// // لا تبدأ إلا إذا فعلاً وصلنا
|
|
// if (currentRideState.value != RideState.driverArrived) return;
|
|
|
|
// // 1) أوقف أي عداد سابق (تتبع وصول السائق)
|
|
// stopTimerFromDriverToPassengerAfterApplied();
|
|
// isTimerRunning = false;
|
|
|
|
// // 2) أوقف عداد الانتظار إن كان شغال من قبل (منع تكرار)
|
|
// _stopWaitPassengerTimer();
|
|
|
|
// // 3) جهّز UI الانتظار
|
|
// isDriverArrivePassenger = true;
|
|
// isDriverInPassengerWay = false;
|
|
// timeToPassengerFromDriverAfterApplied = 0;
|
|
|
|
// _waitPassengerElapsedSeconds = 0;
|
|
// remainingTimeDriverWaitPassenger5Minute = _waitPassengerTotalSeconds;
|
|
// progressTimerDriverWaitPassenger5Minute = 0;
|
|
|
|
// int m = (remainingTimeDriverWaitPassenger5Minute / 60).floor();
|
|
// int s = remainingTimeDriverWaitPassenger5Minute % 60;
|
|
// stringRemainingTimeDriverWaitPassenger5Minute =
|
|
// '$m:${s.toString().padLeft(2, '0')}';
|
|
|
|
// update();
|
|
|
|
// // 4) ابدأ Timer.periodic (يمكن إلغاؤه فوراً)
|
|
// _waitPassengerTimer = Timer.periodic(const Duration(seconds: 1), (t) {
|
|
// // أول ما تتحول إلى inProgress (أو أي حالة غير arrived) أوقف فوراً
|
|
// if (currentRideState.value != RideState.driverArrived) {
|
|
// _stopWaitPassengerTimer(resetUI: true);
|
|
// // إخفاء واجهة "السائق وصل" إذا بدأت الرحلة
|
|
// if (currentRideState.value == RideState.inProgress) {
|
|
// isDriverArrivePassenger = false;
|
|
// }
|
|
// update();
|
|
// return;
|
|
// }
|
|
|
|
// _waitPassengerElapsedSeconds++;
|
|
// int remaining = _waitPassengerTotalSeconds - _waitPassengerElapsedSeconds;
|
|
// if (remaining < 0) remaining = 0;
|
|
|
|
// remainingTimeDriverWaitPassenger5Minute = remaining;
|
|
// progressTimerDriverWaitPassenger5Minute =
|
|
// _waitPassengerElapsedSeconds / _waitPassengerTotalSeconds;
|
|
|
|
// int minutes = (remaining / 60).floor();
|
|
// int seconds = remaining % 60;
|
|
// stringRemainingTimeDriverWaitPassenger5Minute =
|
|
// '$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
|
|
// update();
|
|
|
|
// if (remaining == 0) {
|
|
// _stopWaitPassengerTimer();
|
|
// // هنا إذا بدك: طبّق غرامة انتظار / اعرض رسالة / إلخ
|
|
// }
|
|
// });
|
|
// }
|
|
|
|
// // 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();
|
|
// }
|
|
|
|
// Timer? _rideProgressTimer;
|
|
// bool _hasShownSpeedWarning = false; // متغير لحالة التنبيه
|
|
|
|
// /// **بدء مؤقت الرحلة للراكب (Passenger Ride Timer)**
|
|
// ///
|
|
// /// تقوم هذه الدالة بإدارة العداد الزمني للرحلة بمجرد بدئها (حالة [RideState.inProgress]).
|
|
// ///
|
|
// /// **المهام الرئيسية:**
|
|
// /// 1. **دقة التوقيت:** تعتمد على فرق الوقت الحقيقي (`DateTime.difference`) لضمان دقة العداد حتى لو خرج المستخدم من التطبيق وعاد.
|
|
// /// 2. **مراقبة السرعة:** تفحص سرعة المركبة كل ثانية، وتطلق تحذيراً [_triggerSpeedWarning] إذا تجاوزت 100 كم/س.
|
|
// /// 3. **تحديث الواجهة:** تقوم بتحديث شريط التقدم والوقت المتبقي لحظياً.
|
|
// /// 4. **الإيقاف التلقائي:** تتوقف تلقائياً عند انتهاء الوقت أو تغير حالة الرحلة.
|
|
// void rideIsBeginPassengerTimer() {
|
|
// // 1. تنظيف أي تايمر سابق
|
|
// _rideProgressTimer?.cancel();
|
|
// _hasShownSpeedWarning = false; // تصفير حالة التنبيه
|
|
|
|
// // 2. تحديد وقت الوصول المتوقع بدقة
|
|
// DateTime now = DateTime.now();
|
|
// DateTime expectedArrivalTime = now.add(Duration(seconds: durationToRide));
|
|
|
|
// // تنسيق وقت الوصول للعرض
|
|
// var arrivalTime = DateFormat('hh:mm a').format(expectedArrivalTime);
|
|
// box.write(BoxName.arrivalTime, arrivalTime);
|
|
|
|
// Log.print("⏳ Ride Timer Started. Duration: $durationToRide sec");
|
|
|
|
// // 3. بدء التايمر الدوري
|
|
// _rideProgressTimer =
|
|
// Timer.periodic(const Duration(seconds: 1), (timer) async {
|
|
// // أ) شرط الإيقاف الحاسم: إذا انتهت الرحلة أو ألغيت
|
|
// if (currentRideState.value != RideState.inProgress) {
|
|
// timer.cancel();
|
|
// return;
|
|
// }
|
|
|
|
// // ب) حساب الوقت المتبقي بناءً على الساعة الحالية (أدق من العد)
|
|
// DateTime currentNow = DateTime.now();
|
|
// int remainingSeconds =
|
|
// expectedArrivalTime.difference(currentNow).inSeconds;
|
|
|
|
// if (remainingSeconds < 0) remainingSeconds = 0;
|
|
|
|
// // تحديث المتغيرات
|
|
// remainingTimeTimerRideBegin = remainingSeconds;
|
|
|
|
// // حساب النسبة المئوية (حماية من القسمة على صفر)
|
|
// progressTimerRideBegin =
|
|
// durationToRide > 0 ? 1 - (remainingSeconds / durationToRide) : 1.0;
|
|
|
|
// // ج) تنسيق الوقت للعرض
|
|
// int minutes = (remainingSeconds / 60).floor();
|
|
// int seconds = remainingSeconds % 60;
|
|
// stringRemainingTimeRideBegin =
|
|
// '$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
|
|
// // نحول progressTimerRideBegin (0..1) إلى نسبة (0..100)
|
|
// final percent = (progressTimerRideBegin * 100).clamp(0, 100).toInt();
|
|
|
|
// // ==============================================================
|
|
// // 🔔 د) تحديث الإشعارات (هنا تم حل مشكلة الإزعاج)
|
|
// // ==============================================================
|
|
|
|
// // 1. تحديث الآيفون (Live Activity): يمكن تحديثه كل 5 ثواني لأنه "تحديث صامت" للشاشة فقط ولا يصدر صوتاً
|
|
// if (remainingSeconds % 5 == 0 || remainingSeconds == 0) {
|
|
// IosLiveActivityService.updateRideActivity(
|
|
// status: 'ongoing', // ['waiting', 'ongoing']
|
|
// driverName: driverName ?? '',
|
|
// carDetails: '$make • $model • $carColor',
|
|
// etaText: stringRemainingTimeRideBegin,
|
|
// progress: progressTimerRideBegin.clamp(0.0, 1.0),
|
|
// );
|
|
// }
|
|
|
|
// // 2. تحديث إشعار الهاتف العادي (RideLiveNotification):
|
|
// // نحدثه كل دقيقة (60 ثانية) بدلاً من 5 ثواني حتى لا يزعج الراكب بالرنين المستمر!
|
|
// if (remainingSeconds % 60 == 0 || remainingSeconds == 0) {
|
|
// await RideLiveNotification.showTripInProgress(
|
|
// percentage: percent,
|
|
// etaText: stringRemainingTimeRideBegin,
|
|
// );
|
|
// }
|
|
// // ==============================================================
|
|
|
|
// // هـ) منطق الإشعارات لمنتصف الرحلة (يصدر تنبيه مرة واحدة فقط)
|
|
// if (progressTimerRideBegin >= 0.25 &&
|
|
// progressTimerRideBegin < 0.26 &&
|
|
// !_hasShownSpeedWarning) {
|
|
// // يمكن إضافة منطق إشعار منتصف الرحلة هنا
|
|
// }
|
|
|
|
// // و) مراقبة السرعة (Speed Check)
|
|
// if (speed > 100 && !_hasShownSpeedWarning) {
|
|
// _hasShownSpeedWarning = true; // ✅ قفل التنبيه حتى لا يتكرر
|
|
// _triggerSpeedWarning();
|
|
// }
|
|
|
|
// // إعادة تفعيل التنبيه إذا انخفضت السرعة (إعادة ضبط الأمان)
|
|
// if (speed < 80 && _hasShownSpeedWarning) {
|
|
// _hasShownSpeedWarning = false;
|
|
// }
|
|
|
|
// // ز) إنهاء التايمر إذا انتهى الوقت
|
|
// if (remainingSeconds <= 0) {
|
|
// timer.cancel();
|
|
// }
|
|
|
|
// update();
|
|
// });
|
|
// }
|
|
|
|
// /// **عرض تحذير السرعة الزائدة (Speed Warning Trigger)**
|
|
// ///
|
|
// /// تظهر نافذة منبثقة (Dialog) وإشعاراً محلياً لتحذير الراكب عند اكتشاف سرعة عالية (> 100 كم/س).
|
|
// ///
|
|
// /// **الخيارات المتاحة للمستخدم:**
|
|
// /// * **مشاركة التفاصيل:** لإرسال رسالة استغاثة عبر واتساب.
|
|
// /// * **أنا بخير:** لإغلاق التنبيه والاستمرار في الرحلة.
|
|
// void _triggerSpeedWarning() {
|
|
// 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.copyWith(color: AppColor.redColor),
|
|
// content: Column(
|
|
// children: [
|
|
// Icon(Icons.speed, size: 50, color: AppColor.redColor),
|
|
// const SizedBox(height: 10),
|
|
// Text(
|
|
// "We noticed the speed is exceeding 100 km/h. Please slow down for your safety..."
|
|
// .tr,
|
|
// textAlign: TextAlign.center,
|
|
// style: AppStyle.title,
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// confirm: MyElevatedButton(
|
|
// title: "Share Trip Details".tr,
|
|
// kolor: AppColor.redColor,
|
|
// onPressed: () {
|
|
// Get.back();
|
|
// _shareTripDetailsSOS();
|
|
// },
|
|
// ),
|
|
// cancel: MyElevatedButton(
|
|
// title: "I'm Safe".tr,
|
|
// kolor: AppColor.greenColor,
|
|
// onPressed: () {
|
|
// Get.back();
|
|
// },
|
|
// ),
|
|
// );
|
|
// }
|
|
|
|
// /// **تفعيل وضع الطوارئ للمركبة (sosPassenger)**
|
|
// ///
|
|
// /// تقوم بإظهار حوار تأكيدي للمستخدم لسؤاله عما إذا كان يرغب في إرسال
|
|
// /// إشارة استغاثة عبر واتساب.
|
|
// void sosPassenger() {
|
|
// Get.defaultDialog(
|
|
// barrierDismissible: false,
|
|
// title: "Emergency SOS".tr,
|
|
// titleStyle: AppStyle.title.copyWith(color: AppColor.redColor),
|
|
// content: Column(
|
|
// children: [
|
|
// Icon(Icons.warning_amber_rounded, size: 50, color: AppColor.redColor),
|
|
// const SizedBox(height: 10),
|
|
// Text(
|
|
// "Do you want to send an emergency message to your SOS contact?".tr,
|
|
// textAlign: TextAlign.center,
|
|
// style: AppStyle.title,
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// confirm: MyElevatedButton(
|
|
// title: "Send SOS".tr,
|
|
// kolor: AppColor.redColor,
|
|
// onPressed: () {
|
|
// Get.back();
|
|
// _shareTripDetailsSOS();
|
|
// },
|
|
// ),
|
|
// cancel: MyElevatedButton(
|
|
// title: "I'm Safe".tr,
|
|
// kolor: AppColor.greenColor,
|
|
// onPressed: () {
|
|
// Get.back();
|
|
// },
|
|
// ),
|
|
// );
|
|
// }
|
|
|
|
// /// **مشاركة تفاصيل الرحلة للطوارئ (SOS Share)**
|
|
// ///
|
|
// /// تقوم بتجهيز رسالة نصية مفصلة تحتوي على بيانات الرحلة الحالية وإرسالها
|
|
// /// عبر تطبيق WhatsApp لرقم الطوارئ المحفوظ.
|
|
// ///
|
|
// /// **البيانات المرسلة:**
|
|
// /// * موقع الانطلاق والوصول.
|
|
// /// * اسم السائق، رقم الهاتف، ونوع السيارة.
|
|
// /// * رابط مباشر للموقع الحالي على خرائط جوجل.
|
|
// void _shareTripDetailsSOS() {
|
|
// String message = "**Emergency SOS from Passenger:**\n";
|
|
// String origin = startNameAddress;
|
|
// String destination = endNameAddress;
|
|
|
|
// message += "* ${'Origin'.tr}: $origin\n";
|
|
// message += "* ${'Destination'.tr}: $destination\n";
|
|
// message += "* ${'Driver Name'.tr}: $driverName\n";
|
|
// message += "* ${'Car'.tr}: $make - $model - $licensePlate\n";
|
|
// message += "* ${'Phone'.tr}: $driverPhone\n\n";
|
|
|
|
// // رابط جوجل مابس صحيح
|
|
// message +=
|
|
// "${'Location'.tr}: https://www.google.com/maps/search/?api=1&query=${passengerLocation.latitude},${passengerLocation.longitude}\n";
|
|
// message += "Please help! Contact me as soon as possible.".tr;
|
|
|
|
// launchCommunication(
|
|
// 'whatsapp', box.read(BoxName.sosPhonePassenger), message);
|
|
// }
|
|
|
|
// 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();
|
|
// }
|
|
// }
|
|
|
|
// Future<void> tripFinishedFromDriver() async {
|
|
// Log.print('🧹 Cleaning UI for Finish');
|
|
|
|
// // إغلاق أي ديالوج مفتوح
|
|
// if (Get.isDialogOpen == true) Get.back();
|
|
// if (Get.isBottomSheetOpen == true) Get.back();
|
|
|
|
// statusRide = 'Finished';
|
|
// currentRideState.value = RideState.finished; // تثبيت الحالة
|
|
|
|
// // إيقاف البحث والعدادات
|
|
// isSearchingWindow = false;
|
|
// rideTimerBegin = false;
|
|
// shouldFetch = false;
|
|
|
|
// // إيقاف التايمرات
|
|
// stopAllTimers();
|
|
// resetAllMapStates();
|
|
// clearPolyline();
|
|
// clearMarkersExceptStartEnd();
|
|
// markers.clear();
|
|
|
|
// update();
|
|
// }
|
|
|
|
// StreamController<String> _beginRideStreamController =
|
|
// StreamController<String>.broadcast();
|
|
// Stream<String> get beginRideStream => _beginRideStreamController.stream;
|
|
|
|
// bool isBeginRideFromDriverRunning = false;
|
|
|
|
// // Call this method to listen to the stream
|
|
// void listenToBeginRideStream() {
|
|
// beginRideStream.listen((status) {
|
|
// Log.print("Ride status: $status");
|
|
// // Perform additional actions based on the status
|
|
// }, onError: (error) {
|
|
// Log.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 = {};
|
|
// bool isStartAppHasRide = false;
|
|
// getRideStatusFromStartApp() async {
|
|
// try {
|
|
// var res = await CRUD().get(
|
|
// link: AppLink.getRideStatusFromStartApp,
|
|
// payload: {'passenger_id': box.read(BoxName.passengerID)});
|
|
// // Log.print(res);
|
|
// Log.print('rideStatusFromStartApp: $res');
|
|
// // Log.print('1070');
|
|
// if (res == 'failure') {
|
|
// rideStatusFromStartApp = {
|
|
// 'data': {'status': 'NoRide', 'needsReview': false}
|
|
// };
|
|
// isStartAppHasRide = false;
|
|
// Log.print(
|
|
// "No rides found for the given passenger ID within the last hour.");
|
|
// } else {
|
|
// var decoded = jsonDecode(res);
|
|
// if (decoded['status'] == 'failure') {
|
|
// rideStatusFromStartApp = {
|
|
// 'data': {'status': 'NoRide', 'needsReview': false}
|
|
// };
|
|
// isStartAppHasRide = false;
|
|
// } else {
|
|
// rideStatusFromStartApp = decoded;
|
|
// }
|
|
// }
|
|
// if (rideStatusFromStartApp['data']['status'] == 'Begin' ||
|
|
// rideStatusFromStartApp['data']['status'] == 'Apply' ||
|
|
// rideStatusFromStartApp['data']['status'] == 'Applied') {
|
|
// statusRide = rideStatusFromStartApp['data']['status'];
|
|
// isStartAppHasRide = true;
|
|
// RideState.inProgress;
|
|
// driverId = rideStatusFromStartApp['data']['driver_id'];
|
|
// passengerName = rideStatusFromStartApp['data']['driverName'];
|
|
// driverRate = rideStatusFromStartApp['data']['rateDriver'].toString();
|
|
// statusRideFromStart = true;
|
|
|
|
// update();
|
|
|
|
// Map<String, dynamic> tripData =
|
|
// box.read(BoxName.tripData) as Map<String, dynamic>;
|
|
// final String pointsString = tripData['polyline'];
|
|
// 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('main_route'),
|
|
// points: polylineCoordinates,
|
|
// width: 6,
|
|
// color: const Color(0xFF2196F3),
|
|
// );
|
|
|
|
// polyLines = {...polyLines, polyline};
|
|
// timeToPassengerFromDriverAfterApplied = 0;
|
|
// remainingTime = 0;
|
|
// remainingTimeToPassengerFromDriverAfterApplied = 0;
|
|
// remainingTimeDriverWaitPassenger5Minute = 0;
|
|
// rideTimerBegin = true;
|
|
// isDriverInPassengerWay = false;
|
|
// isDriverArrivePassenger = false;
|
|
// // update();
|
|
// // isCancelRidePageShown = true;
|
|
// durationToAdd = tripData['distance_m'];
|
|
// 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();
|
|
// }
|
|
|
|
// int selectedReason = -1;
|
|
// String? cancelNote;
|
|
// void selectReason0(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();
|
|
// }));
|
|
// }
|
|
|
|
// Future<Map<String, double>?> extractCoordinatesFromLinkAsync(
|
|
// String link) async {
|
|
// try {
|
|
// // 1. معالجة روابط الخرائط المباشرة (geo: و google.navigation:)
|
|
// if (link.startsWith('geo:') || link.startsWith('google.navigation:')) {
|
|
// RegExp regex = RegExp(r'(-?\d+\.\d+)[,/~=](-?\d+\.\d+)');
|
|
// var match = regex.firstMatch(link);
|
|
// if (match != null) {
|
|
// double lat = double.parse(match.group(1)!);
|
|
// double lng = double.parse(match.group(2)!);
|
|
// if (lat > 40 && lat > lng) {
|
|
// double temp = lat;
|
|
// lat = lng;
|
|
// lng = temp;
|
|
// }
|
|
// return {'latitude': lat, 'longitude': lng};
|
|
// }
|
|
// }
|
|
|
|
// // 2. معالجة الروابط العادية (http/https)
|
|
// int urlStartIndex = link.indexOf(RegExp(r'https?://'));
|
|
// if (urlStartIndex == -1) return null;
|
|
// String cleanLink = link.substring(urlStartIndex).trim();
|
|
|
|
// Uri uri = Uri.parse(cleanLink);
|
|
// String finalUrl = cleanLink;
|
|
|
|
// // فك التوجيه للروابط المختصرة
|
|
// if (cleanLink.contains('goo.gl') ||
|
|
// cleanLink.contains('maps.google.com')) {
|
|
// try {
|
|
// var response =
|
|
// await http.get(uri).timeout(const Duration(seconds: 5));
|
|
// finalUrl = response.request?.url.toString() ?? cleanLink;
|
|
// } catch (e) {
|
|
// Log.print('Redirect logic failed, using original: $e');
|
|
// }
|
|
// }
|
|
|
|
// // الأنماط المشتركة لخرائط جوجل (تكون دائماً Lat ثم Lng)
|
|
// RegExp regex = RegExp(r'(-?\d+\.\d+)[,/~](-?\d+\.\d+)');
|
|
// var match = regex.firstMatch(finalUrl);
|
|
|
|
// if (match != null) {
|
|
// double lat = double.parse(match.group(1)!);
|
|
// double lng = double.parse(match.group(2)!);
|
|
|
|
// // 🔥 منطق التصحيح الذاتي (Smart Swap) للمنطقة (سوريا/الأردن/مصر)
|
|
// // إذا كان الرقم الأول أكبر من الرقم الثاني بشكل واضح، فهذا يعني أن الرابط مقلوب أو أننا نحتاج للتأكد
|
|
// // في منطقتنا Latitude حوالي 30-35 و Longitude حوالي 36-44
|
|
// if (lat > 40 && lat > lng) {
|
|
// Log.print("⚠️ Detected Swapped Coordinates in Link. Correcting...");
|
|
// double temp = lat;
|
|
// lat = lng;
|
|
// lng = temp;
|
|
// }
|
|
|
|
// return {
|
|
// 'latitude': lat,
|
|
// 'longitude': lng,
|
|
// };
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print('Error parsing location link: $e');
|
|
// }
|
|
// return null;
|
|
// }
|
|
|
|
// double latitudeWhatsApp = 0;
|
|
// double longitudeWhatsApp = 0;
|
|
// void handleWhatsAppLink(String link) async {
|
|
// Map<String, double>? coordinates =
|
|
// await extractCoordinatesFromLinkAsync(link);
|
|
|
|
// if (coordinates != null) {
|
|
// latitudeWhatsApp = coordinates['latitude']!;
|
|
// longitudeWhatsApp = coordinates['longitude']!;
|
|
|
|
// Log.print(
|
|
// 'Extracted coordinates: Lat: $latitudeWhatsApp, Long: $longitudeWhatsApp');
|
|
// // Use these coordinates in your app as needed
|
|
// } else {
|
|
// Log.print('Failed to extract coordinates from the link');
|
|
// }
|
|
// }
|
|
|
|
// void goToWhatappLocation() async {
|
|
// if (sosFormKey.currentState!.validate()) {
|
|
// // 1. استخراج الإحداثيات أولاً بشكل محلي لضمان عدم حدوث سباق بيانات (Race Condition)
|
|
// Map<String, double>? coordinates =
|
|
// await extractCoordinatesFromLinkAsync(whatsAppLocationText.text);
|
|
|
|
// if (coordinates != null) {
|
|
// latitudeWhatsApp = coordinates['latitude']!;
|
|
// longitudeWhatsApp = coordinates['longitude']!;
|
|
|
|
// Log.print(
|
|
// '📍 Final Coordinates for OSM: Lat: $latitudeWhatsApp, Lng: $longitudeWhatsApp');
|
|
|
|
// changeIsWhatsAppOrder(true);
|
|
// Get.back();
|
|
|
|
// // إعداد الوجهة
|
|
// myDestination = LatLng(latitudeWhatsApp, longitudeWhatsApp);
|
|
|
|
// // تحريك الكاميرا لموقع الراكب (البداية) وليس الوجهة فوراً لضمان تحميل الخريطة
|
|
// if (passengerLocation != null) {
|
|
// await mapController?.animateCamera(CameraUpdate.newLatLng(
|
|
// LatLng(passengerLocation.latitude, passengerLocation.longitude)));
|
|
// }
|
|
|
|
// changeMainBottomMenuMap();
|
|
// passengerStartLocationFromMap = true;
|
|
// isPickerShown = true;
|
|
// update();
|
|
// } else {
|
|
// mySnackbarWarning('لم نتمكن من استخراج الموقع من الرابط');
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// 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 = {};
|
|
|
|
// /// [إضافة جديدة]
|
|
// /// دالة مخصصة لإضافة الرحلة إلى جدول الانتظار (waiting_ride)
|
|
|
|
// Future<void> _addRideToWaitingTable() async {
|
|
// try {
|
|
// await CRUD().post(link: AppLink.addWaitingRide, payload: {
|
|
// 'id': rideId.toString(),
|
|
// "start_location":
|
|
// '${startLocation.latitude},${startLocation.longitude}',
|
|
// "end_location": '${endLocation.latitude},${endLocation.longitude}',
|
|
// "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),
|
|
// 'payment_method':
|
|
// Get.find<PaymentController>().isWalletChecked ? 'wallet' : 'cash',
|
|
// "passenger_wallet": box.read(BoxName.passengerWalletTotal).toString(),
|
|
// });
|
|
// Log.print('[WaitingTable] Ride $rideId added to waiting_ride table.');
|
|
// } catch (e) {
|
|
// Log.print('Error adding ride to waiting_ride table: $e');
|
|
// }
|
|
// }
|
|
|
|
// String driversStatusForSearchWindow = '';
|
|
|
|
// 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: TextStyle(color: AppColor.greenColor)),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
// barrierDismissible: false,
|
|
// );
|
|
// }
|
|
|
|
// Future<bool> postRideDetailsToServer() async {
|
|
// // التأكد من وجود مسار
|
|
// if (polylineCoordinates.isEmpty) return false;
|
|
|
|
// startLocation = polylineCoordinates.first;
|
|
// endLocation = polylineCoordinates.last;
|
|
|
|
// // تجهيز البيانات الكاملة (Data Enrichment) لإرسالها للـ PHP
|
|
// Map<String, dynamic> payload = {
|
|
// // 1. البيانات الأساسية
|
|
// "start_location": '${startLocation.latitude},${startLocation.longitude}',
|
|
// "end_location": '${endLocation.latitude},${endLocation.longitude}',
|
|
// "date": DateTime.now().toString(),
|
|
// "time": DateTime.now().toString(),
|
|
// "endtime": "00:00:00", // أو حسب حساباتك
|
|
// "price": totalPassenger.toStringAsFixed(2),
|
|
// "passenger_id": box.read(BoxName.passengerID).toString(),
|
|
// "driver_id": "0", // لم يحدد بعد
|
|
// "status": "waiting",
|
|
// "carType": box.read(BoxName.carType),
|
|
// "price_for_driver": totalPassenger.toString(), // أو المعادلة الخاصة بك
|
|
// "price_for_passenger": totalME.toString(),
|
|
// "distance": distance.toString(),
|
|
|
|
// // 2. بيانات الراكب (ليستخدمها PHP لبناء الـ Payload دون استعلام)
|
|
// "passenger_name": box.read(BoxName.name).toString(),
|
|
// "passenger_phone": box.read(BoxName.phone).toString(),
|
|
// "passenger_token": box.read(BoxName.tokenFCM).toString(),
|
|
// "passenger_email": box.read(BoxName.email).toString(),
|
|
// "passenger_wallet": box.read(BoxName.passengerWalletTotal).toString(),
|
|
// "passenger_rating": (passengerRate ?? 5.0).toString(),
|
|
|
|
// // 3. بيانات الواجهة الإضافية
|
|
// "start_name": startNameAddress,
|
|
// "end_name": endNameAddress,
|
|
// "duration_text": "${(durationToRide / 60).floor()}", // نص الوقت
|
|
// "distance_text": "$distance", // نص المسافة
|
|
// "is_wallet": Get.find<PaymentController>().isWalletChecked.toString(),
|
|
// "has_steps": Get.find<WayPointController>().wayPoints.length > 1
|
|
// ? 'true'
|
|
// : 'false',
|
|
|
|
// // نقاط التوقف (إذا وجدت)
|
|
// "step0": placesCoordinate.length > 0 ? placesCoordinate[0] : "",
|
|
// "step1": placesCoordinate.length > 1 ? placesCoordinate[1] : "",
|
|
// "step2": placesCoordinate.length > 2 ? placesCoordinate[2] : "",
|
|
// "step3": placesCoordinate.length > 3 ? placesCoordinate[3] : "",
|
|
// "step4": placesCoordinate.length > 4 ? placesCoordinate[4] : "",
|
|
// };
|
|
// Log.print(
|
|
// '🏁 Ride Registration Detail: $startNameAddress -> $endNameAddress');
|
|
// Log.print(' 📦 Payload: $payload');
|
|
|
|
// try {
|
|
// // الاتصال بـ add_ride.php
|
|
// var response = await CRUD().post(
|
|
// link: "${AppLink.server}/ride/rides/add_ride.php", // تأكد من المسار
|
|
// payload: payload);
|
|
|
|
// var jsonResponse = (response is String) ? jsonDecode(response) : response;
|
|
|
|
// if (jsonResponse['status'] == 'success') {
|
|
// rideId = jsonResponse['message'].toString(); // حفظ ID الرحلة
|
|
// Log.print("✅ Ride Created ID: $rideId");
|
|
// return true;
|
|
// } else {
|
|
// Log.print("❌ Ride Creation Failed: $response");
|
|
// return false;
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print("❌ Exception in postRide: $e");
|
|
// return false;
|
|
// }
|
|
// }
|
|
|
|
// late LatLng endLocation;
|
|
// late LatLng startLocation;
|
|
|
|
// StreamController<String> _rideStatusStreamController =
|
|
// StreamController<String>.broadcast();
|
|
// Stream<String> get rideStatusStream => _rideStatusStreamController.stream;
|
|
|
|
// int maxAttempts = 28;
|
|
|
|
// Future<void> rideAppliedFromDriver(bool isApplied) async {
|
|
// Log.print('[rideAppliedFromDriver] 🚀 Starting logic...');
|
|
|
|
// // 1. جلب بيانات السائق والسيارة المحدثة من السيرفر
|
|
// await getUpdatedRideForDriverApply(rideId);
|
|
|
|
// // تنبيهات الأسعار حسب نوع السيارة
|
|
// if (['Speed', 'Awfar Car'].contains(box.read(BoxName.carType))) {
|
|
// NotificationController().showNotification('Fixed Price'.tr,
|
|
// 'The captain is responsible for the route.'.tr, 'ding');
|
|
// } else if (['Comfort', 'Lady'].contains(box.read(BoxName.carType))) {
|
|
// NotificationController().showNotification('Attention'.tr,
|
|
// 'The price may increase if the route changes.'.tr, 'ding');
|
|
// }
|
|
|
|
// isApplied = true;
|
|
// statusRide = 'Apply';
|
|
// rideConfirm = false;
|
|
// isSearchingWindow = false;
|
|
// _isDriverAppliedLogicExecuted = true; // ضمان عدم التكرار
|
|
|
|
// update(); // تحديث أولي
|
|
|
|
// // 2. جلب موقع السائق الأولي فوراً (Blocking await)
|
|
// await getDriverCarsLocationToPassengerAfterApplied();
|
|
|
|
// // 3. إذا توفر الموقع: حساب المسافة/الزمن ورسم المسار
|
|
// if (driverCarsLocationToPassengerAfterApplied.isNotEmpty) {
|
|
// LatLng driverPos = driverCarsLocationToPassengerAfterApplied.last;
|
|
|
|
// Log.print(
|
|
// '[rideAppliedFromDriver] 📍 Driver at: $driverPos, Passenger at: $passengerLocation');
|
|
|
|
// // أ) استدعاء API لحساب المسافة والزمن الدقيق (بدون رسم)
|
|
// await getInitialDriverDistanceAndDuration(driverPos, passengerLocation);
|
|
|
|
// // ب) رسم خط المسار (Visual only)
|
|
// await drawDriverPathOnly(driverPos, passengerLocation);
|
|
|
|
// // ج) ضبط الكاميرا لتشمل السائق والراكب
|
|
// _fitCameraToPoints(driverPos, passengerLocation);
|
|
// } else {
|
|
// Log.print(
|
|
// '[rideAppliedFromDriver] ⚠️ Warning: Driver location not found yet.');
|
|
// }
|
|
|
|
// // 4. تشغيل تايمر التتبع المستمر (الذي سيقوم بتناقص الوقت الذي جلبناه من API)
|
|
// startTimerFromDriverToPassengerAfterApplied();
|
|
|
|
// // إغلاق الستريم القديم
|
|
// if (!_rideStatusStreamController.isClosed)
|
|
// _rideStatusStreamController.close();
|
|
// }
|
|
|
|
// /// دالة لجلب المسافة والزمن بين السائق والراكب عند قبول الطلب
|
|
// /// تستخدم API سريع (overview=false)
|
|
// Future<void> getInitialDriverDistanceAndDuration(
|
|
// LatLng driverPos, LatLng passengerPos) async {
|
|
// final String apiUrl = 'https://routec.intaleq.xyz/route';
|
|
// final String apiKey = Env.mapKeyOsm;
|
|
|
|
// final String origin = '${driverPos.latitude},${driverPos.longitude}';
|
|
// final String dest = '${passengerPos.latitude},${passengerPos.longitude}';
|
|
|
|
// // الرابط المطلوب: steps=false&overview=false (سريع جداً للبيانات فقط)
|
|
// final Uri uri = Uri.parse(
|
|
// '$apiUrl?origin=$origin&destination=$dest&steps=false&overview=false');
|
|
|
|
// try {
|
|
// Log.print('[InitialCalc] Fetching distance/duration from: $uri');
|
|
// final response = await http.get(uri, headers: {'X-API-KEY': apiKey});
|
|
|
|
// if (response.statusCode == 200) {
|
|
// final data = jsonDecode(response.body);
|
|
|
|
// if (data['status'] == 'ok') {
|
|
// // 1. استخراج الزمن (بالثواني)
|
|
// // نستخدم المعامل 1.5348 أو 1.4 حسب منطقك السابق لتقدير الوقت الواقعي
|
|
// double durationSecondsRaw = (data['duration_s'] as num).toDouble();
|
|
// int finalDurationSeconds = (durationSecondsRaw * kDurationScalar)
|
|
// .toInt(); // kDurationScalar = 1.5348
|
|
|
|
// // 2. استخراج المسافة (بالأمتار)
|
|
// double distanceMeters = (data['distance_m'] as num).toDouble();
|
|
|
|
// // 3. تحديث المتغيرات في الكنترولر
|
|
// timeToPassengerFromDriverAfterApplied = finalDurationSeconds;
|
|
// remainingTimeToPassengerFromDriverAfterApplied = finalDurationSeconds;
|
|
|
|
// distanceByPassenger =
|
|
// (distanceMeters).toStringAsFixed(0); // المسافة نصاً
|
|
|
|
// // يمكنك أيضاً تحديث durationToPassenger إذا كنت تستخدمها
|
|
// durationToPassenger = finalDurationSeconds;
|
|
|
|
// Log.print(
|
|
// '[InitialCalc] ✅ Success: Duration=${finalDurationSeconds}s, Distance=${distanceMeters}m');
|
|
// update(); // تحديث الواجهة لعرض الوقت الجديد فوراً
|
|
// }
|
|
// } else {
|
|
// Log.print('[InitialCalc] ❌ API Error: ${response.statusCode}');
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print('[InitialCalc] 💥 Exception: $e');
|
|
// }
|
|
// }
|
|
|
|
// // دالة خفيفة وسريعة لرسم خط المسار فقط (بدون أسعار أو خطوات)
|
|
// Future<void> drawDriverPathOnly(LatLng driverPos, LatLng passengerPos) async {
|
|
// final String apiUrl = 'https://routec.intaleq.xyz/route';
|
|
// final String apiKey = Env.mapKeyOsm;
|
|
|
|
// final String origin = '${driverPos.latitude},${driverPos.longitude}';
|
|
// final String dest = '${passengerPos.latitude},${passengerPos.longitude}';
|
|
|
|
// // استخدام overview=full للدقة، و steps=false للسرعة
|
|
// final Uri uri = Uri.parse(
|
|
// '$apiUrl?origin=$origin&destination=$dest&steps=false&overview=full');
|
|
|
|
// try {
|
|
// final response = await http.get(uri, headers: {'X-API-KEY': apiKey});
|
|
|
|
// if (response.statusCode == 200) {
|
|
// final data = jsonDecode(response.body);
|
|
|
|
// if (data['status'] == 'ok' && data['polyline'] != null) {
|
|
// final String pointsString = data['polyline'];
|
|
|
|
// // فك التشفير
|
|
// List<LatLng> decodedPoints =
|
|
// await compute(decodePolylineIsolate, pointsString);
|
|
|
|
// // إزالة خط مسار السائق القديم فقط
|
|
// polyLines = polyLines
|
|
// .where((p) => p.polylineId.value != 'driver_route')
|
|
// .toSet();
|
|
|
|
// // إضافة الخط الجديد
|
|
// polyLines = {
|
|
// ...polyLines,
|
|
// Polyline(
|
|
// polylineId: const PolylineId('driver_route'),
|
|
// points: decodedPoints,
|
|
// color: const Color(0xFF333333), // لون مميز لمسار السائق
|
|
// width: 5,
|
|
// )
|
|
// };
|
|
|
|
// // لا تستدعي update هنا، سيتم استدعاؤها في الدالة الأب (getDriverCars...) لتقليل عدد التحديثات
|
|
// }
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print('Error drawing driver path: $e');
|
|
// }
|
|
// }
|
|
|
|
// // دالة مساعدة لضبط الكاميرا
|
|
// void _fitCameraToPoints(LatLng p1, LatLng p2) async {
|
|
// if (mapController == null) return;
|
|
|
|
// // 1. معالجة حالة النقاط المتطابقة (تمنع الكراش في Android)
|
|
// if (p1.latitude == p2.latitude && p1.longitude == p2.longitude) {
|
|
// try {
|
|
// mapController?.animateCamera(CameraUpdate.newLatLngZoom(p1, 17));
|
|
// } catch (e) {
|
|
// Log.print("Error animating to single point: $e");
|
|
// }
|
|
// return;
|
|
// }
|
|
|
|
// // 2. حساب الحدود
|
|
// double minLat = min(p1.latitude, p2.latitude);
|
|
// double maxLat = max(p1.latitude, p2.latitude);
|
|
// double minLng = min(p1.longitude, p2.longitude);
|
|
// double maxLng = max(p1.longitude, p2.longitude);
|
|
|
|
// // 3. تقليل الهوامش لتجنب خطأ "View size too small"
|
|
// // نستخدم 50 بدلاً من 100 ليكون آمناً مع الخرائط الصغيرة
|
|
// double padding = 50.0;
|
|
|
|
// try {
|
|
// await mapController?.animateCamera(
|
|
// CameraUpdate.newLatLngBounds(
|
|
// LatLngBounds(
|
|
// southwest: LatLng(minLat, minLng),
|
|
// northeast: LatLng(maxLat, maxLng),
|
|
// ),
|
|
// left: padding,
|
|
// top: padding,
|
|
// right: padding,
|
|
// bottom: padding,
|
|
// ),
|
|
// );
|
|
// } catch (e) {
|
|
// Log.print("Error animating bounds (Map might be resizing): $e");
|
|
// // محاولة بديلة آمنة: تحريك الكاميرا للمنتصف فقط دون Bounds
|
|
// try {
|
|
// LatLng center = LatLng((minLat + maxLat) / 2, (minLng + maxLng) / 2);
|
|
// mapController?.animateCamera(CameraUpdate.newLatLngZoom(center, 14));
|
|
// } catch (e) {
|
|
// Log.print("Error: $e");
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// // Listening to the Stream
|
|
// void listenToRideStatusStream() {
|
|
// rideStatusStream.listen((rideStatus) {
|
|
// Log.print("Ride Status: $rideStatus");
|
|
// // Handle updates based on the ride status
|
|
// }, onError: (error) {
|
|
// Log.print("Error in Ride Status Stream: $error");
|
|
// // Handle stream errors
|
|
// }, onDone: () {
|
|
// Log.print("Ride status stream closed.");
|
|
// });
|
|
// }
|
|
|
|
// void start15SecondTimer(String rideId) {
|
|
// Timer(const Duration(seconds: 15), () {
|
|
// // delayAndFetchRideStatusForAllDriverAvailable(rideId);
|
|
// });
|
|
// }
|
|
|
|
// // Replaces void startTimer()
|
|
// Timer?
|
|
// _uiCountdownTimer; // Add this variable to your class to manage lifecycle
|
|
|
|
// void startUiCountdown() {
|
|
// // Cancel any existing timer to avoid duplicates
|
|
// _uiCountdownTimer?.cancel();
|
|
|
|
// // Reset variables
|
|
// progress = 0;
|
|
// remainingTime = durationTimer;
|
|
|
|
// _uiCountdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
// // Logic from your loop, but non-blocking
|
|
// int i = timer.tick; // current tick
|
|
|
|
// progress = i / durationTimer;
|
|
// remainingTime = durationTimer - i;
|
|
|
|
// if (remainingTime <= 0) {
|
|
// timer.cancel(); // Stop this specific timer
|
|
// rideConfirm = false;
|
|
|
|
// // Add the duration to the tracking time logic
|
|
// timeToPassengerFromDriverAfterApplied += durationToPassenger;
|
|
|
|
// // Note: We do NOT call startTimerFromDriverToPassengerAfterApplied() here
|
|
// // because we already started it in rideAppliedFromDriver!
|
|
|
|
// timerEnded(); // Call your existing completion logic
|
|
// }
|
|
// update(); // Update the UI progress bar
|
|
// });
|
|
// }
|
|
|
|
// void timerEnded() async {
|
|
// runEvery30SecondsUntilConditionMet();
|
|
// isCancelRidePageShown = false;
|
|
// Log.print('isCancelRidePageShown: $isCancelRidePageShown');
|
|
// update();
|
|
// }
|
|
|
|
// Future<String> getRideStatus(String rideId) async {
|
|
// final response = await CRUD().get(
|
|
// link: "${AppLink.rideServerSide}/ride/rides/getRideStatus.php",
|
|
// payload: {'id': rideId});
|
|
// Log.print(response);
|
|
// Log.print('2176');
|
|
// return jsonDecode(response)['data'];
|
|
// }
|
|
|
|
// late String driverCarModel,
|
|
// driverCarMake,
|
|
// driverLicensePlate,
|
|
// driverName = '';
|
|
// Future<void> getUpdatedRideForDriverApply(String rideId) async {
|
|
// // حماية مبدئية: إذا كان المعرف غير صالح لا تكمل
|
|
// if (rideId == 'yet' || rideId.isEmpty) return;
|
|
|
|
// try {
|
|
// final res = await CRUD().get(
|
|
// link: "${AppLink.server}/ride/rides/getRideOrderID.php",
|
|
// payload: {'passengerID': box.read(BoxName.passengerID).toString()});
|
|
|
|
// if (res != 'failure') {
|
|
// var response = jsonDecode(res);
|
|
// Log.print('getUpdatedRideForDriverApply Response: $response');
|
|
|
|
// // [هام] التحقق من أن data عبارة عن Map وليست false أو null
|
|
// // هذا يمنع الخطأ: Class 'bool' has no instance method '[]'
|
|
// if (response['status'] == 'success' &&
|
|
// response['data'] != null &&
|
|
// response['data'] is Map) {
|
|
// var data = response['data'];
|
|
|
|
// // استخدام ?.toString() ?? '' للحماية من القيم الفارغة (Null Safety)
|
|
// driverId = data['driver_id']?.toString() ?? '';
|
|
// driverPhone = data['phone']?.toString() ?? '';
|
|
// driverCarMake = data['make']?.toString() ?? '';
|
|
// model = data['model']?.toString() ?? '';
|
|
// colorHex = data['color_hex']?.toString() ?? '';
|
|
// carColor = data['color']?.toString() ?? '';
|
|
// make = data['make']?.toString() ?? '';
|
|
// licensePlate = data['car_plate']?.toString() ?? '';
|
|
|
|
// // دمج الاسم الأول والأخير للراكب
|
|
// String firstName = data['passengerName']?.toString() ?? '';
|
|
// String lastName = data['last_name']?.toString() ?? '';
|
|
// passengerName =
|
|
// lastName.isNotEmpty ? "$firstName $lastName" : firstName;
|
|
|
|
// driverName = data['driverName']?.toString() ?? '';
|
|
|
|
// // [هام] التوكن ضروري للإشعارات
|
|
// driverToken = data['token']?.toString() ?? '';
|
|
|
|
// carYear = data['year']?.toString() ?? '';
|
|
// driverRate = data['ratingDriver']?.toString() ?? '5.0';
|
|
|
|
// update(); // تحديث الواجهة بالبيانات الجديدة
|
|
// } else {
|
|
// Log.print(
|
|
// "Warning: Ride data not found or invalid (data is false/null)");
|
|
// // اختياري: يمكنك هنا التعامل مع حالة عدم العثور على السائق بعد
|
|
// }
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print("Error in getUpdatedRideForDriverApply: $e");
|
|
// }
|
|
// }
|
|
|
|
// late LatLng currentDriverLocation;
|
|
// late double headingList;
|
|
|
|
// 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();
|
|
// 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();
|
|
|
|
// 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();
|
|
// Log.print('foundCars: $foundCars');
|
|
|
|
// if (foundCars) {
|
|
// timer.cancel();
|
|
// } else {
|
|
// attempt++;
|
|
// 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) {
|
|
// LatLng passengerPoint = LatLng(latitude, longitude);
|
|
|
|
// // 1. فحص الأردن
|
|
// if (isPointInPolygon(passengerPoint, CountryPolygons.jordanBoundary)) {
|
|
// box.write(BoxName.countryCode, 'Jordan');
|
|
// // يمكنك تعيين AppLink.endPoint هنا إذا كان منطقك الداخلي لا يزال يعتمد عليه
|
|
// box.write(BoxName.serverChosen,
|
|
// AppLink.server); // مثال: اختر سيرفر سوريا للبيانات
|
|
// return 'Jordan';
|
|
// }
|
|
|
|
// // 2. فحص سوريا
|
|
// if (isPointInPolygon(passengerPoint, CountryPolygons.syriaBoundary)) {
|
|
// box.write(BoxName.countryCode, 'Syria');
|
|
// box.write(BoxName.serverChosen, AppLink.server);
|
|
// return 'Syria';
|
|
// }
|
|
|
|
// // 3. فحص مصر
|
|
// if (isPointInPolygon(passengerPoint, CountryPolygons.egyptBoundary)) {
|
|
// box.write(BoxName.countryCode, 'Egypt');
|
|
// box.write(BoxName.serverChosen, AppLink.server);
|
|
// return 'Egypt';
|
|
// }
|
|
|
|
// // 4. الافتراضي (إذا كان خارج المناطق المخدومة)
|
|
// box.write(BoxName.countryCode, 'Jordan');
|
|
// box.write(BoxName.serverChosen, AppLink.server);
|
|
// return 'Unknown Location (Defaulting to Jordan)';
|
|
// }
|
|
|
|
// Future<bool> getCarsLocationByPassengerAndReloadMarker() async {
|
|
// // 1. تنظيف القائمة والماركرز
|
|
// carsLocationByPassenger = [];
|
|
|
|
// if (passengerLocation.latitude == 0 && passengerLocation.longitude == 0) {
|
|
// return false; // لا يوجد موقع للراكب
|
|
// }
|
|
|
|
// // 2. طلب بسيط ومباشر (أنا هنا، أعطني السائقين حولي)
|
|
// var res = await CRUD().get(
|
|
// link: AppLink.getCarsLocationByPassenger,
|
|
// payload: {
|
|
// 'lat': passengerLocation.latitude.toString(),
|
|
// 'lng': passengerLocation.longitude.toString(),
|
|
// 'radius': '5', // نصف القطر ثابت (مثلاً 5 كم) أو يمكنك جعله ديناميكياً
|
|
// 'limit': '50', // أقصى عدد سيارات للعرض
|
|
// },
|
|
// );
|
|
|
|
// if (res == 'failure') {
|
|
// noCarString = true;
|
|
// update();
|
|
// return false;
|
|
// }
|
|
|
|
// // 3. معالجة البيانات
|
|
// noCarString = false;
|
|
// var responseData = jsonDecode(res);
|
|
|
|
// // دعم التنسيقين (data أو message) لضمان عدم حدوث كراش
|
|
// List driversList = [];
|
|
// if (responseData['status'] == true && responseData['data'] != null) {
|
|
// driversList = responseData['data'];
|
|
// } else if (responseData['message'] != null) {
|
|
// driversList = responseData['message']; // للكود القديم احتياطاً
|
|
// }
|
|
|
|
// if (driversList.isEmpty) {
|
|
// carsLocationByPassenger.clear();
|
|
// update();
|
|
// return false;
|
|
// }
|
|
|
|
// carsLocationByPassenger.clear(); // تنظيف الماركرز القديمة
|
|
|
|
// // 4. رسم السيارات على الخريطة
|
|
// for (var i = 0; i < driversList.length; i++) {
|
|
// var carData = driversList[i];
|
|
|
|
// // التحقق من الإحداثيات لضمان عدم رسم سيارة في المحيط
|
|
// double lat = double.tryParse(carData['latitude'].toString()) ?? 0.0;
|
|
// double lng = double.tryParse(carData['longitude'].toString()) ?? 0.0;
|
|
// double heading = double.tryParse(carData['heading'].toString()) ?? 0.0;
|
|
|
|
// if (lat == 0.0 || lng == 0.0) continue;
|
|
|
|
// _updateOrCreateMarker(
|
|
// carData['id'].toString(),
|
|
// LatLng(lat, lng),
|
|
// heading,
|
|
// // الدالة هذه تقرر شكل الأيقونة بناءً على نوع السيارة القادم من السيرفر
|
|
// _getIconForCar(carData),
|
|
// );
|
|
// }
|
|
|
|
// update();
|
|
// return true;
|
|
// }
|
|
|
|
// 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(
|
|
// carData['id'].toString(),
|
|
// LatLng(carData['latitude'], carData['longitude']),
|
|
// carData['heading'],
|
|
// _getIconForCar(carData),
|
|
// );
|
|
// }
|
|
// }
|
|
|
|
// String _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, String icon) {
|
|
// final mId = MarkerId(markerId);
|
|
// final existingMarker = markers.cast<Marker?>().firstWhere(
|
|
// (m) => m?.markerId == mId,
|
|
// orElse: () => null,
|
|
// );
|
|
|
|
// if (existingMarker == null) {
|
|
// markers = {
|
|
// ...markers,
|
|
// Marker(
|
|
// markerId: mId,
|
|
// position: newPosition,
|
|
// rotation: newHeading,
|
|
// icon: InlqBitmap.fromStyleImage(icon),
|
|
// anchor: const Offset(0.5, 0.5),
|
|
// ),
|
|
// };
|
|
// update();
|
|
// } else {
|
|
// double distance =
|
|
// _calculateDistance(existingMarker.position, newPosition);
|
|
// if (distance >= minMovementThreshold) {
|
|
// _smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon);
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// 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';
|
|
// }
|
|
|
|
// String generateTrackingLink(String rideId, String driverId) {
|
|
// String cleanRideId = rideId.toString().trim();
|
|
// String cleanDriverId = driverId.toString().trim();
|
|
|
|
// // الكلمة السرية للمطابقة مع السيرفر
|
|
// const String secretSalt = "Siro_Secure_Track_2025";
|
|
|
|
// // الدمج والتشفير
|
|
// String rawString = "$cleanRideId$cleanDriverId$secretSalt";
|
|
// var bytes = utf8.encode(rawString);
|
|
// var digest = md5.convert(bytes);
|
|
// String token = digest.toString();
|
|
|
|
// // الرابط المباشر لصفحة التتبع
|
|
// return "https://intaleqapp.com/track/index.php?id=$cleanRideId&token=$token";
|
|
// }
|
|
|
|
// // 2. الدالة الرئيسية (تم تعديلها لإرسال واتساب بدلاً من الإشعارات)
|
|
// Future shareTripWithFamily() async {
|
|
// // التحقق أولاً: هل الرقم موجود؟
|
|
// String? storedPhone = box.read(BoxName.sosPhonePassenger);
|
|
|
|
// if (storedPhone == 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);
|
|
|
|
// // استدعاء الدالة مرة أخرى للمتابعة
|
|
// shareTripWithFamily();
|
|
// }
|
|
// }));
|
|
// return;
|
|
// }
|
|
|
|
// // --- (المنطق الجديد: إرسال واتساب مباشرة) ---
|
|
|
|
// // 1. التأكد من وجود بيانات للرحلة
|
|
// if (rideId == 'yet' || driverId.isEmpty) {
|
|
// Get.snackbar("Alert".tr, "Wait for the trip to start first".tr);
|
|
// return;
|
|
// }
|
|
|
|
// // 2. تنسيق الرقم
|
|
// var numberPhone = formatSyrianPhoneNumber(storedPhone);
|
|
|
|
// // 3. توليد الرابط
|
|
// String trackingLink = generateTrackingLink(rideId, driverId);
|
|
|
|
// // 4. تجهيز الرسالة (بالإنجليزية وجاهزة للترجمة)
|
|
// // لاحظ: استخدمت المتغيرات الموجودة في الكنترولر (passengerName هنا عادة يحمل اسم السائق في الكنترولر الخاص بك حسب الكود السابق)
|
|
// String message = """
|
|
// مرحباً، تابع رحلتي مباشرة على تطبيق سيرو 🚗
|
|
|
|
// يمكنك تتبع مسار الرحلة من هنا:
|
|
// $trackingLink
|
|
|
|
// السائق: $passengerName
|
|
// السيارة: $model - $licensePlate
|
|
// شكراً لاستخدامك سيرو!
|
|
// """
|
|
// .tr;
|
|
|
|
// String messageEn = """Hello, follow my trip live on Siro 🚗
|
|
|
|
// Track my ride here:
|
|
// $trackingLink
|
|
|
|
// Driver: $passengerName
|
|
// Car: $model - $licensePlate
|
|
// Thank you for using Siro!
|
|
// """;
|
|
|
|
// // اختر الرسالة بناءً على اللغة المفضلة (مثال بسيط)
|
|
// String userLanguage = box.read(BoxName.lang) ?? 'ar';
|
|
// message = (userLanguage == 'ar') ? message : messageEn;
|
|
// // وضعنا .tr لكي تتمكن من ترجمتها للعربية في ملفات اللغة إذا أردت، أو تركها إنجليزية
|
|
|
|
// Log.print("Sending WhatsApp to: $numberPhone");
|
|
|
|
// // 5. فتح واتساب
|
|
// launchCommunication('whatsapp', numberPhone, message);
|
|
|
|
// // (اختياري) حفظ أن التتبع مفعل لتغيير حالة الأيقونة في الواجهة
|
|
// box.write(BoxName.parentTripSelected, true);
|
|
// update();
|
|
// }
|
|
|
|
// Future getTokenForParent() async {
|
|
// // 1. التحقق أولاً: هل الرقم موجود؟
|
|
// String? storedPhone = box.read(BoxName.sosPhonePassenger);
|
|
|
|
// if (storedPhone == 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);
|
|
|
|
// // استدعاء الدالة مرة أخرى
|
|
// getTokenForParent();
|
|
// }
|
|
// }));
|
|
// return;
|
|
// }
|
|
// generateTrackingLink(rideId, driverId);
|
|
// // --- حالة الرقم موجود: نكمل التنفيذ ---
|
|
// var numberPhone = formatSyrianPhoneNumber(storedPhone);
|
|
// Log.print("Searching for Parent Token with Phone: $numberPhone");
|
|
|
|
// // استدعاء السكريبت (استخدم POST بدلاً من GET)
|
|
// var res = await CRUD()
|
|
// .post(link: AppLink.getTokenParent, payload: {'phone': numberPhone});
|
|
|
|
// // التعامل مع الاستجابة
|
|
// if (res is Map<String, dynamic>) {
|
|
// handleResponse(res);
|
|
// } else {
|
|
// try {
|
|
// // var jsonRes = jsonDecode(res);
|
|
// handleResponse(res);
|
|
// } catch (e) {
|
|
// Log.print("Error parsing response: $res");
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// void handleResponse(Map<String, dynamic> res) {
|
|
// Log.print("Handle Response: $res"); // للتأكد من دخول الدالة
|
|
|
|
// // الحالة 1: الرقم غير مسجل (Failure)
|
|
// if (res['status'] == 'failure') {
|
|
// // إذا كان هناك أي ديالوج تحميل مفتوح، نغلقه أولاً، لكن بحذر
|
|
// if (Get.isDialogOpen ?? false) Get.back();
|
|
|
|
// Get.defaultDialog(
|
|
// title: "No user found".tr, // اختصرت العنوان ليظهر بشكل أفضل
|
|
// titleStyle: AppStyle.title,
|
|
// content: Column(
|
|
// children: [
|
|
// Text(
|
|
// "No passenger found for the given phone number".tr,
|
|
// style: AppStyle.title, // غيرت الستايل ليكون أصغر قليلاً
|
|
// textAlign: TextAlign.center,
|
|
// ),
|
|
// const SizedBox(height: 10),
|
|
// Text(
|
|
// "Send Siro app to him".tr,
|
|
// style: AppStyle.title
|
|
// .copyWith(color: AppColor.greenColor, fontSize: 14),
|
|
// textAlign: TextAlign.center,
|
|
// )
|
|
// ],
|
|
// ),
|
|
// confirm: MyElevatedButton(
|
|
// title: 'Send Invite'.tr,
|
|
// onPressed: () {
|
|
// Get.back(); // إغلاق الديالوج
|
|
|
|
// var rawPhone = box.read(BoxName.sosPhonePassenger);
|
|
// // تأكد أن rawPhone ليس null
|
|
// if (rawPhone == null) return;
|
|
|
|
// var phone = formatSyrianPhoneNumber(rawPhone);
|
|
|
|
// // تصحيح نص الرسالة
|
|
// var message = '''Dear Friend,
|
|
|
|
// 🚀 I have just started an exciting trip on Siro!
|
|
// Download the app to track my ride:
|
|
|
|
// 👉 Android: https://play.google.com/store/apps/details?id=com.Siro.siro&hl=en-US
|
|
// 👉 iOS: https://apps.apple.com/st/app/siro-rider/id6748075179
|
|
|
|
// See you there!
|
|
// Siro Team''';
|
|
|
|
// launchCommunication('whatsapp', phone, message);
|
|
// }),
|
|
// cancel: MyElevatedButton(
|
|
// title: 'Cancel'.tr,
|
|
// onPressed: () {
|
|
// Get.back();
|
|
// }));
|
|
// }
|
|
// // الحالة 2: نجاح (Success)
|
|
// else if (res['status'] == 'success') {
|
|
// // إغلاق أي ديالوج سابق (مثل Loading)
|
|
// if (Get.isDialogOpen ?? false) Get.back();
|
|
|
|
// Get.snackbar("Success".tr, "The invitation was sent successfully".tr,
|
|
// backgroundColor: AppColor.greenColor, colorText: Colors.white);
|
|
|
|
// List tokensData = res['data'];
|
|
|
|
// for (var device in tokensData) {
|
|
// String tokenParent = device['token'];
|
|
|
|
// NotificationService.sendNotification(
|
|
// category: "Trip Monitoring",
|
|
// target: tokenParent,
|
|
// title: "Trip Monitoring".tr,
|
|
// body: "Click to track the trip".tr,
|
|
// isTopic: false,
|
|
// tone: 'tone1',
|
|
// driverList: [rideId, driverId],
|
|
// );
|
|
// // حفظ آخر توكن
|
|
// box.write(BoxName.tokenParent, tokenParent);
|
|
// }
|
|
// box.write(BoxName.parentTripSelected, true);
|
|
// }
|
|
// }
|
|
|
|
// // 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', "");
|
|
// }
|
|
// Log.print(passengerLocationStringUnvirsity);
|
|
// }
|
|
|
|
// // Initialize polygons from UniversitiesPolygons
|
|
// void _initializePolygons() {
|
|
// List<List<LatLng>> universityPolygons =
|
|
// UniversitiesPolygons.universityPolygons;
|
|
|
|
// for (int i = 0; i < universityPolygons.length; i++) {
|
|
// Polygon polygon = Polygon(
|
|
// polygonId: PolygonId('univ_$i'),
|
|
// points: universityPolygons[i],
|
|
// fillColor: Colors.blueAccent.withOpacity(0.2),
|
|
// strokeColor: Colors.blueAccent,
|
|
// strokeWidth: 2,
|
|
// );
|
|
// polygons.add(polygon);
|
|
// }
|
|
// update();
|
|
// }
|
|
|
|
// LatLng driverLocationToPassenger = const LatLng(32, 35);
|
|
// Future getDriverCarsLocationToPassengerAfterApplied() async {
|
|
// // driverCarsLocationToPassengerAfterApplied
|
|
// // 1. الشرط الأمني: تتبع فقط إذا كانت الرحلة نشطة
|
|
// bool isRideActive = (statusRide == 'Apply' ||
|
|
// statusRide == 'Arrived' ||
|
|
// statusRide == 'Begin' ||
|
|
// currentRideState.value == RideState.driverApplied ||
|
|
// currentRideState.value == RideState.driverArrived ||
|
|
// currentRideState.value == RideState.inProgress);
|
|
|
|
// if (!isRideActive ||
|
|
// statusRide == 'Finished' ||
|
|
// statusRide == 'Cancel' ||
|
|
// currentRideState.value == RideState.finished ||
|
|
// currentRideState.value == RideState.noRide ||
|
|
// currentRideState.value == RideState.preCheckReview) {
|
|
// return;
|
|
// }
|
|
|
|
// // 2. منع التداخل (Blocking)
|
|
// if (_isFetchingDriverLocation) return;
|
|
// _isFetchingDriverLocation = true;
|
|
|
|
// try {
|
|
// var res = await CRUD().get(
|
|
// link: AppLink.getDriverCarsLocationToPassengerAfterApplied,
|
|
// payload: {'driver_id': driverId});
|
|
|
|
// if (res != 'failure') {
|
|
// datadriverCarsLocationToPassengerAfterApplied = jsonDecode(res);
|
|
|
|
// if (datadriverCarsLocationToPassengerAfterApplied['message'] != null &&
|
|
// datadriverCarsLocationToPassengerAfterApplied['message']
|
|
// .isNotEmpty) {
|
|
// var _data =
|
|
// datadriverCarsLocationToPassengerAfterApplied['message'][0];
|
|
|
|
// LatLng newDriverPos = LatLng(
|
|
// double.parse(_data['latitude'].toString()),
|
|
// double.parse(_data['longitude'].toString()));
|
|
// // أضف هذا السطر لتقليل استهلاك الذاكرة
|
|
// if (driverCarsLocationToPassengerAfterApplied.length > 10) {
|
|
// driverCarsLocationToPassengerAfterApplied.removeAt(0);
|
|
// }
|
|
// driverLocationToPassenger = newDriverPos;
|
|
// driverCarsLocationToPassengerAfterApplied.add(newDriverPos);
|
|
// // 🔥 الإضافة هنا أيضاً 🔥
|
|
// // 🔥 تحديث التوقيت حتى لو جاءت من API لكي يهدأ الحارس قليلاً
|
|
// _lastSocketLocationTime = DateTime.now();
|
|
// _checkAndRecalculateIfDeviated(newDriverPos);
|
|
// // [تعديل هام] تنظيف آمن: لا نحذف ماركر السائق الحالي
|
|
// clearMarkersExceptStartEndAndDriver();
|
|
|
|
// // تحريك الماركر
|
|
// reloadMarkerDriverCarsLocationToPassengerAfterApplied();
|
|
// }
|
|
// }
|
|
// update();
|
|
// } catch (e) {
|
|
// Log.print('Error fetching driver location: $e');
|
|
// } finally {
|
|
// _isFetchingDriverLocation = false;
|
|
// }
|
|
// }
|
|
|
|
// 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 clearMarkersExceptStartEndAndDriver() {
|
|
// markers.removeWhere((marker) {
|
|
// String id = marker.markerId.value;
|
|
// // لا تحذف نقطة البداية
|
|
// if (id == 'start') return false;
|
|
// // لا تحذف نقطة النهاية
|
|
// if (id == 'end') return false;
|
|
// // لا تحذف السائق الحالي
|
|
// if (id == currentDriverMarkerId) return false;
|
|
|
|
// // احذف أي شيء آخر (مثل السيارات التي ظهرت وقت البحث)
|
|
// return true;
|
|
// });
|
|
|
|
// // ملاحظة: لا نستدعي update() هنا لأننا سنستدعيها في نهاية الدالة الرئيسية
|
|
// }
|
|
|
|
// void clearMarkersExceptStartEnd() {
|
|
// markers.removeWhere((marker) {
|
|
// String id = marker.markerId.value;
|
|
// return id != 'start' && id != 'end';
|
|
// });
|
|
|
|
// update();
|
|
// }
|
|
|
|
// // 1. تعريف ID ثابت للسائق طوال الرحلة
|
|
// String get currentDriverMarkerId => 'driver_marker_$driverId';
|
|
|
|
// void reloadMarkerDriverCarsLocationToPassengerAfterApplied() {
|
|
// if (datadriverCarsLocationToPassengerAfterApplied == null ||
|
|
// datadriverCarsLocationToPassengerAfterApplied['message'] == null ||
|
|
// datadriverCarsLocationToPassengerAfterApplied['message'].isEmpty) {
|
|
// return;
|
|
// }
|
|
|
|
// var driverData =
|
|
// datadriverCarsLocationToPassengerAfterApplied['message'][0];
|
|
|
|
// // جلب الإحداثيات الجديدة
|
|
// LatLng newPosition = LatLng(double.parse(driverData['latitude'].toString()),
|
|
// double.parse(driverData['longitude'].toString()));
|
|
|
|
// double newHeading =
|
|
// double.tryParse(driverData['heading'].toString()) ?? 0.0;
|
|
|
|
// // تحديد الأيقونة
|
|
// String icon;
|
|
// if (driverData['model'].toString().contains('دراجة') ||
|
|
// driverData['make'].toString().contains('دراجة')) {
|
|
// icon = motoIcon;
|
|
// } else if (driverData['gender'] == 'Female') {
|
|
// icon = ladyIcon;
|
|
// } else {
|
|
// icon = carIcon;
|
|
// }
|
|
|
|
// // 2. البحث عن الماركر الجديد وتحديثه أو إنشاء جديد
|
|
// final String markerId = currentDriverMarkerId;
|
|
// final mId = MarkerId(markerId);
|
|
// final existingMarker = markers.cast<Marker?>().firstWhere(
|
|
// (m) => m?.markerId == mId,
|
|
// orElse: () => null,
|
|
// );
|
|
|
|
// if (existingMarker != null) {
|
|
// _smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon);
|
|
// } else {
|
|
// markers = {
|
|
// ...markers,
|
|
// Marker(
|
|
// markerId: mId,
|
|
// position: newPosition,
|
|
// rotation: newHeading,
|
|
// icon: InlqBitmap.fromStyleImage(icon),
|
|
// anchor: const Offset(0.5, 0.5),
|
|
// ),
|
|
// };
|
|
// update();
|
|
// }
|
|
// }
|
|
|
|
// // التأكد من دالة التحريك السلس
|
|
// void _smoothlyUpdateMarker(
|
|
// Marker oldMarker, LatLng newPosition, double newHeading, String icon) {
|
|
// double distance = Geolocator.distanceBetween(
|
|
// oldMarker.position.latitude,
|
|
// oldMarker.position.longitude,
|
|
// newPosition.latitude,
|
|
// newPosition.longitude);
|
|
|
|
// if (distance < 2.0) return;
|
|
|
|
// final MarkerId markerIdKey = oldMarker.markerId;
|
|
|
|
// _animationTimers[markerIdKey.value]?.cancel();
|
|
|
|
// int ticks = 0;
|
|
// const int totalSteps = 20;
|
|
// const int stepDuration = 50;
|
|
|
|
// double latStep =
|
|
// (newPosition.latitude - oldMarker.position.latitude) / totalSteps;
|
|
// double lngStep =
|
|
// (newPosition.longitude - oldMarker.position.longitude) / totalSteps;
|
|
// double headingStep = (newHeading - oldMarker.rotation) / totalSteps;
|
|
|
|
// LatLng currentPos = oldMarker.position;
|
|
// double currentHeading = oldMarker.rotation;
|
|
|
|
// _animationTimers[markerIdKey.value] =
|
|
// Timer.periodic(const Duration(milliseconds: stepDuration), (timer) {
|
|
// ticks++;
|
|
|
|
// currentPos =
|
|
// LatLng(currentPos.latitude + latStep, currentPos.longitude + lngStep);
|
|
// currentHeading += headingStep;
|
|
|
|
// // Update the marker in the set
|
|
// final updatedMarker = oldMarker.copyWith(
|
|
// position: currentPos,
|
|
// rotation: currentHeading,
|
|
// icon: InlqBitmap.fromStyleImage(icon),
|
|
// );
|
|
|
|
// markers = {
|
|
// ...markers.where((m) => m.markerId != markerIdKey),
|
|
// updatedMarker,
|
|
// };
|
|
|
|
// // Native update through controller to avoid UI rebuild
|
|
// if (mapController != null) {
|
|
// mapController!.animateCamera(CameraUpdate.newLatLng(
|
|
// currentPos)); // Optional: Follow car if needed
|
|
// // Note: SiroMapController doesn't expose raw symbol update yet for Marker object,
|
|
// // but declarative update via GetBuilder is fast.
|
|
// }
|
|
|
|
// update();
|
|
|
|
// if (ticks >= totalSteps) {
|
|
// timer.cancel();
|
|
// _animationTimers.remove(markerIdKey.value);
|
|
// }
|
|
// });
|
|
// }
|
|
|
|
// void _updateMarkerPosition(
|
|
// LatLng newPosition, double newHeading, String icon) {
|
|
// const String markerId = 'driverToPassengers';
|
|
|
|
// final mId = MarkerId(markerId);
|
|
// final existingMarker = markers.cast<Marker?>().firstWhere(
|
|
// (m) => m?.markerId == mId,
|
|
// orElse: () => null,
|
|
// );
|
|
|
|
// if (existingMarker != null) {
|
|
// _smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon);
|
|
// } else {
|
|
// markers = {
|
|
// ...markers,
|
|
// Marker(
|
|
// markerId: mId,
|
|
// position: newPosition,
|
|
// rotation: newHeading,
|
|
// icon: InlqBitmap.fromStyleImage(icon),
|
|
// anchor: const Offset(0.5, 0.5),
|
|
// ),
|
|
// };
|
|
// update();
|
|
// }
|
|
|
|
// mapController?.animateCamera(CameraUpdate.newLatLng(newPosition));
|
|
// }
|
|
|
|
// @override
|
|
// void onClose() {
|
|
// Log.print(
|
|
// "--- MapPassengerController: Closing and cleaning up all resources. ---");
|
|
|
|
// // 1. إلغاء المؤقتات الفردية (باستخدام ?. الآمن)
|
|
|
|
// timerToPassengerFromDriverAfterApplied?.cancel();
|
|
// _timer?.cancel();
|
|
// _masterTimer?.cancel(); // (أضف المؤقت الرئيسي)
|
|
// _camThrottle?.cancel(); // (أضف مؤقت الكاميرا)
|
|
// _heartbeatTimer?.cancel();
|
|
// EmergencySignalService.instance.stopListening();
|
|
// if (isSocketConnected) {
|
|
// socket.emit('unsubscribe_all',
|
|
// {'passenger_id': box.read(BoxName.passengerID).toString()});
|
|
// socket.disconnect();
|
|
// socket.dispose();
|
|
// }
|
|
|
|
// // 2. إلغاء جميع المؤقتات في الخريطة (للتحريكات السلسة)
|
|
// _animationTimers.forEach((key, timer) {
|
|
// timer.cancel();
|
|
// });
|
|
// _animationTimers.clear();
|
|
|
|
// // 3. إغلاق متحكمات البث (StreamControllers) لمنع تسريب الذاكرة
|
|
// if (!_timerStreamController.isClosed) {
|
|
// _timerStreamController.close();
|
|
// }
|
|
// if (!_beginRideStreamController.isClosed) {
|
|
// _beginRideStreamController.close();
|
|
// }
|
|
// if (!_rideStatusStreamController.isClosed) {
|
|
// _rideStatusStreamController.close();
|
|
// }
|
|
// if (!timerController.isClosed) {
|
|
// timerController.close();
|
|
// }
|
|
|
|
// // 4. التخلص من متحكم الخريطة (ممارسة جيدة)
|
|
// mapController = null;
|
|
|
|
// Log.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) {
|
|
// Log.print("⚠️ السائق انحرف عن المسار!");
|
|
// }
|
|
// }
|
|
|
|
// void detectStops(Position currentPosition) {
|
|
// if (currentPosition.speed < 0.5) {
|
|
// Log.print("🚦 السائق توقف في موقع غير متوقع!");
|
|
// }
|
|
// }
|
|
|
|
// Future<void> cancelRideAfterRejectFromAll() async {
|
|
// clearPlacesDestination();
|
|
// clearPolyline();
|
|
// data = [];
|
|
// await CRUD().post(
|
|
// link: "${AppLink.server}/ride/rides/cancel_ride_by_passenger.php",
|
|
// payload: {
|
|
// "ride_id": rideId.toString(), // Convert to String
|
|
// "reason": 'notApplyFromAnyDriver'
|
|
// });
|
|
|
|
// rideConfirm = false;
|
|
// statusRide == 'Cancel';
|
|
// isSearchingWindow = false;
|
|
// shouldFetch = false;
|
|
// isPassengerChosen = false;
|
|
// isCashConfirmPageShown = false;
|
|
// // totalStepDurations = 0;
|
|
// isCashSelectedBeforeConfirmRide = false;
|
|
// timeToPassengerFromDriverAfterApplied = 0;
|
|
// changeCancelRidePageShow();
|
|
// remainingTime = 0;
|
|
|
|
// update();
|
|
// }
|
|
|
|
// // متغيرات أسباب الإلغاء
|
|
// int selectedReasonIndex = -1;
|
|
// String selectedReasonText = "";
|
|
// TextEditingController otherReasonController = TextEditingController();
|
|
|
|
// /// تحديث السبب المختار
|
|
// void selectReason(int index, String reason) {
|
|
// selectedReasonIndex = index;
|
|
// selectedReasonText = reason;
|
|
// update();
|
|
// }
|
|
|
|
// /// **دالة إلغاء الرحلة (النهائية)**
|
|
// Future<void> cancelRide() async {
|
|
// // 1. التحقق من اختيار سبب
|
|
// if (selectedReasonIndex == -1) {
|
|
// Get.snackbar(
|
|
// 'Attention'.tr,
|
|
// 'Please select a reason first'.tr,
|
|
// snackPosition: SnackPosition.BOTTOM,
|
|
// backgroundColor: Colors.orange,
|
|
// colorText: Colors.white,
|
|
// );
|
|
// return;
|
|
// }
|
|
|
|
// // 2. تجهيز نص السبب النهائي
|
|
// String finalReason = selectedReasonText;
|
|
// if (finalReason == "Other".tr) {
|
|
// if (otherReasonController.text.trim().isEmpty) {
|
|
// Get.snackbar("Attention".tr, "Please write the reason...".tr,
|
|
// backgroundColor: Colors.red, colorText: Colors.white);
|
|
// return;
|
|
// }
|
|
// finalReason = otherReasonController.text.trim();
|
|
// }
|
|
|
|
// // 3. التنظيف المحلي الفوري (UX Optimization)
|
|
// Get.back(); // إغلاق الـ BottomSheet
|
|
// if (isCancelRidePageShown)
|
|
// changeCancelRidePageShow(); // إخفاء زر الإلغاء إن وجد
|
|
|
|
// // 🔥 استدعاء دالة التنظيف الشاملة هنا 🔥
|
|
// resetAllMapStates();
|
|
|
|
// // إيقاف جميع التايمرات
|
|
// // إيقاف جميع التايمرات
|
|
// stopAllTimers();
|
|
// currentRideState.value = RideState.cancelled;
|
|
// await RideLiveNotification.cancel(); // إغلاق أندرويد
|
|
// IosLiveActivityService.endRideActivity(); // ✅ إغلاق iOS
|
|
// PipService.disablePip(); // ✅ إيقاف PiP عند الإلغاء
|
|
|
|
// // 4. الاتصال بالسيرفر لإلغاء الرحلة وإبلاغ السائق
|
|
// if (rideId != 'yet' && rideId != null) {
|
|
// Log.print(
|
|
// '📡 Sending Cancel Request to Server with Reason: $finalReason');
|
|
|
|
// try {
|
|
// await CRUD().post(
|
|
// link: "${AppLink.server}/ride/rides/cancel_ride_by_passenger.php",
|
|
// payload: {
|
|
// "ride_id": rideId.toString(),
|
|
// "reason": finalReason // ✅ إرسال السبب للسيرفر
|
|
// },
|
|
// );
|
|
// // لا داعي لإرسال FCM أو Socket يدوياً من هنا، PHP يقوم بذلك
|
|
// } catch (e) {
|
|
// Log.print("Error cancelling on server: $e");
|
|
// }
|
|
// }
|
|
|
|
// // 5. العودة للصفحة الرئيسية
|
|
// Get.offAll(() => const MapPagePassenger());
|
|
// }
|
|
|
|
// void changePickerShown() {
|
|
// isPickerShown = !isPickerShown;
|
|
// heightPickerContainer = isPickerShown == true ? 150 : 90;
|
|
// update();
|
|
// }
|
|
|
|
// // ── Multi-Waypoint Methods ──────────────────────────────────────────────────
|
|
// void addMenuWaypoint() {
|
|
// if (activeMenuWaypointCount >= 2) return;
|
|
// activeMenuWaypointCount++;
|
|
// // Increase expanded bottom menu height to accommodate new waypoint row
|
|
// mainBottomMenuMapHeight = Get.height * .6 + (activeMenuWaypointCount * 56);
|
|
// update();
|
|
// }
|
|
|
|
// void removeMenuWaypoint(int index) {
|
|
// if (index < 0 || index >= 2) return;
|
|
// // Shift items if removing first waypoint while second exists
|
|
// if (index == 0 && activeMenuWaypointCount == 2) {
|
|
// menuWaypoints[0] = menuWaypoints[1];
|
|
// menuWaypointNames[0] = menuWaypointNames[1];
|
|
// }
|
|
// menuWaypoints[activeMenuWaypointCount - 1] = null;
|
|
// menuWaypointNames[activeMenuWaypointCount - 1] = '';
|
|
// activeMenuWaypointCount--;
|
|
// mainBottomMenuMapHeight = Get.height * .6 + (activeMenuWaypointCount * 56);
|
|
// update();
|
|
// }
|
|
|
|
// void clearAllMenuWaypoints() {
|
|
// menuWaypoints = [null, null];
|
|
// menuWaypointNames = ['', ''];
|
|
// activeMenuWaypointCount = 0;
|
|
// isPickingWaypoint = false;
|
|
// pickingWaypointIndex = -1;
|
|
// update();
|
|
// }
|
|
|
|
// void startPickingWaypointOnMap(int index) {
|
|
// pickingWaypointIndex = index;
|
|
// isPickingWaypoint = true;
|
|
// isPickerShown = true;
|
|
// heightPickerContainer = 150;
|
|
// // Close the expanded menu to show the map picker
|
|
// isMainBottomMenuMap = true;
|
|
// mainBottomMenuMapHeight = Get.height * .22;
|
|
// update();
|
|
// }
|
|
|
|
// void setMenuWaypointFromMap(int index, LatLng position) {
|
|
// Log.print('📍 setMenuWaypointFromMap called: index=$index, pos=$position');
|
|
// if (index < 0 || index >= 2) return;
|
|
// menuWaypoints[index] = position;
|
|
// menuWaypointNames[index] =
|
|
// '${position.latitude.toStringAsFixed(4)}, ${position.longitude.toStringAsFixed(4)}';
|
|
// isPickingWaypoint = false;
|
|
// pickingWaypointIndex = -1;
|
|
// isPickerShown = false;
|
|
// // Re-open expanded menu
|
|
// isMainBottomMenuMap = false;
|
|
// mainBottomMenuMapHeight = Get.height * .6 + (activeMenuWaypointCount * 56);
|
|
// update();
|
|
// }
|
|
|
|
// void setMenuWaypointFromSearch(int index, LatLng pos, String name) {
|
|
// if (index < 0 || index >= 2) return;
|
|
// menuWaypoints[index] = pos;
|
|
// menuWaypointNames[index] = name;
|
|
// update();
|
|
// }
|
|
|
|
// /// Build OSRM waypoint coordinate string for the route URL
|
|
// String _buildOsrmWaypointCoords() {
|
|
// String coords = '';
|
|
// for (int i = 0; i < activeMenuWaypointCount; i++) {
|
|
// final wp = menuWaypoints[i];
|
|
// if (wp != null) {
|
|
// coords += ';${wp.longitude},${wp.latitude}';
|
|
// }
|
|
// }
|
|
// return coords;
|
|
// }
|
|
|
|
// 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 {
|
|
// final q = placeDestinationController.text.trim();
|
|
// if (q.isEmpty || q.length < 3) {
|
|
// placesDestination = [];
|
|
// update();
|
|
// return;
|
|
// }
|
|
|
|
// final lat = passengerLocation.latitude;
|
|
// final lng = passengerLocation.longitude;
|
|
// final country = CountryPolygons.getCountryName(passengerLocation);
|
|
|
|
// try {
|
|
// final url =
|
|
// '${AppLink.searchGeocoding}?q=${Uri.encodeComponent(q)}&lat=$lat&lng=$lng&radius=15000&country=$country';
|
|
// final response = await CRUD().getMapSaas(link: url);
|
|
|
|
// if (response != null && response['results'] is List) {
|
|
// List results = List.from(response['results']);
|
|
// final List filteredResults = [];
|
|
// final Set<String> seenPlaces = {};
|
|
|
|
// for (final p in results) {
|
|
// final name = p['name_ar'] ?? p['name'] ?? '';
|
|
// final district = p['district'] ?? '';
|
|
// final plat = p['latitude']?.toString() ?? '0';
|
|
// final plng = p['longitude']?.toString() ?? '0';
|
|
|
|
// final dedupeKey =
|
|
// "${name.trim().toLowerCase()}_${district.trim().toLowerCase()}";
|
|
|
|
// if (!seenPlaces.contains(dedupeKey)) {
|
|
// seenPlaces.add(dedupeKey);
|
|
|
|
// p['distanceKm'] = (p['distance'] as num).toDouble() / 1000.0;
|
|
// p['latitude'] = plat;
|
|
// p['longitude'] = plng;
|
|
// p['name'] = name;
|
|
// p['address'] = p['full_address'] ??
|
|
// (district.isNotEmpty
|
|
// ? "$district، ${p['governorate'] ?? ''}"
|
|
// : (p['governorate'] ?? ''));
|
|
|
|
// filteredResults.add(p);
|
|
// }
|
|
// }
|
|
|
|
// placesDestination = filteredResults;
|
|
// update();
|
|
// }
|
|
// } catch (e) {
|
|
// Log.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);
|
|
// // Log.print('response: ${response.statusCode} - ${response.body}');
|
|
|
|
// // if (response.statusCode == 200) {
|
|
// // final data = jsonDecode(response.body);
|
|
// // placesDestination = data['places'] ?? [];
|
|
// // update();
|
|
// // } else {
|
|
// // Log.print('Error: ${response.statusCode} - ${response.reasonPhrase}');
|
|
// // }
|
|
// // } catch (e) {
|
|
// // Log.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> getPlacesStart() async {
|
|
// final q = placeStartController.text.trim();
|
|
// if (q.isEmpty || q.length < 3) {
|
|
// placesStart = [];
|
|
// update();
|
|
// return;
|
|
// }
|
|
|
|
// final lat = passengerLocation.latitude;
|
|
// final lng = passengerLocation.longitude;
|
|
// final country = CountryPolygons.getCountryName(passengerLocation);
|
|
|
|
// try {
|
|
// final url =
|
|
// '${AppLink.searchGeocoding}?q=${Uri.encodeComponent(q)}&lat=$lat&lng=$lng&radius=15000&country=$country';
|
|
// final response = await CRUD().getMapSaas(link: url);
|
|
|
|
// if (response != null && response['results'] is List) {
|
|
// List list = List.from(response['results']);
|
|
// for (final p in list) {
|
|
// p['distanceKm'] = (p['distance'] as num).toDouble() / 1000.0;
|
|
// p['latitude'] = p['latitude'].toString();
|
|
// p['longitude'] = p['longitude'].toString();
|
|
// p['name'] = p['name_ar'] ?? p['name'] ?? '';
|
|
// p['address'] = p['full_address'] ??
|
|
// (p['district'] != null
|
|
// ? "${p['district']}، ${p['governorate'] ?? ''}"
|
|
// : (p['governorate'] ?? ''));
|
|
// }
|
|
// placesStart = list;
|
|
// update();
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print('Exception in getPlacesStart: $e');
|
|
// }
|
|
// }
|
|
|
|
// Future<void> getPlacesListsWayPoint(int index) async {
|
|
// final q = wayPoint0Controller.text.trim();
|
|
// if (q.length < 3) return;
|
|
|
|
// final lat = passengerLocation.latitude;
|
|
// final lng = passengerLocation.longitude;
|
|
// final country = CountryPolygons.getCountryName(passengerLocation);
|
|
|
|
// try {
|
|
// final url =
|
|
// '${AppLink.searchGeocoding}?q=${Uri.encodeComponent(q)}&lat=$lat&lng=$lng&radius=15000&country=$country';
|
|
// final response = await CRUD().getMapSaas(link: url);
|
|
|
|
// if (response != null && response['results'] is List) {
|
|
// List list = List.from(response['results']);
|
|
// for (final p in list) {
|
|
// p['distanceKm'] = (p['distance'] as num).toDouble() / 1000.0;
|
|
// p['latitude'] = p['latitude'].toString();
|
|
// p['longitude'] = p['longitude'].toString();
|
|
// p['name'] = p['name_ar'] ?? p['name'] ?? '';
|
|
// p['address'] = p['full_address'] ??
|
|
// (p['district'] != null
|
|
// ? "${p['district']}، ${p['governorate'] ?? ''}"
|
|
// : (p['governorate'] ?? ''));
|
|
// }
|
|
// wayPoint0 = list;
|
|
// placeListResponseAll[index] = list;
|
|
// update();
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print('Error fetching places in WayPoint: $e');
|
|
// }
|
|
// }
|
|
|
|
// // داخل MapPassengerController
|
|
// bool lowPerf = false;
|
|
// Timer? _camThrottle;
|
|
// DateTime _lastUiUpdate = DateTime.fromMillisecondsSinceEpoch(0);
|
|
|
|
// 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) {
|
|
// _camThrottle?.cancel();
|
|
// _camThrottle = Timer(const Duration(milliseconds: 160), () {
|
|
// Log.print('📸 onCameraMoveThrottled: ${pos.target}');
|
|
// // ضع فقط المنطق الضروري هنا لتقليل الحمل
|
|
// int waypointsLength = Get.find<WayPointController>().wayPoints.length;
|
|
// int index = wayPointIndex;
|
|
// if (waypointsLength > 0) {
|
|
// placesCoordinate[index] =
|
|
// '${pos.target.latitude},${pos.target.longitude}';
|
|
// }
|
|
// newMyLocation = pos.target;
|
|
// });
|
|
// }
|
|
|
|
// // Removed legacy light polylines since MapLibre vectors handle high-point geometries natively.
|
|
|
|
// 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) {
|
|
// Log.print('Error: $e');
|
|
// }
|
|
// }
|
|
|
|
// Future<void> getLocation() async {
|
|
// Log.print('🛰️ getLocation() called');
|
|
// // 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 with a timeout to prevent hanging UI
|
|
// LocationData? _locationData;
|
|
// try {
|
|
// _locationData = await location.getLocation().timeout(
|
|
// const Duration(seconds: 5),
|
|
// onTimeout: () {
|
|
// Log.print("⚠️ Location fetch timed out after 5s.");
|
|
// return LocationData.fromMap({
|
|
// "latitude": passengerLocation.latitude,
|
|
// "longitude": passengerLocation.longitude,
|
|
// "speed": 0.0
|
|
// });
|
|
// },
|
|
// );
|
|
// } catch (e) {
|
|
// Log.print("⚠️ Error fetching location: $e");
|
|
// }
|
|
|
|
// if (_locationData == null) {
|
|
// isLoading = false;
|
|
// update();
|
|
// return;
|
|
// }
|
|
// 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;
|
|
// newMyLocation = passengerLocation;
|
|
|
|
// // Resolve current location address
|
|
// try {
|
|
// getReverseGeocoding(passengerLocation).then((address) {
|
|
// currentLocationString = address;
|
|
// update();
|
|
// });
|
|
// } catch (e) {
|
|
// Log.print('Error resolving current location: $e');
|
|
// }
|
|
|
|
// // Trigger offline map caching for a 10km radius
|
|
// OfflineMapService.instance
|
|
// .downloadRegion(passengerLocation, radiusKm: 10.0);
|
|
|
|
// speed = _locationData.speed!;
|
|
// // //print location details
|
|
// isLoading = false;
|
|
// update();
|
|
// }
|
|
|
|
// void clearPolyline() {
|
|
// polyLines.clear();
|
|
// 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),
|
|
// );
|
|
// }
|
|
|
|
// void onMapCreated(SiroMapController controller) {
|
|
// mapController = controller;
|
|
// update();
|
|
// }
|
|
|
|
// void onStyleLoaded() async {
|
|
// Log.print('🗺️ Siro Map Style Loaded. Initializing...');
|
|
// isStyleLoaded = true;
|
|
// _loadMapIcons();
|
|
|
|
// // Smart Camera Reset logic:
|
|
// if (mapController != null) {
|
|
// if (markers.isNotEmpty && lastComputedBounds != null) {
|
|
// await _safeAnimateCameraBounds(lastComputedBounds);
|
|
// } else {
|
|
// mapController!.animateCamera(
|
|
// CameraUpdate.newLatLng(passengerLocation),
|
|
// );
|
|
// }
|
|
// }
|
|
// update();
|
|
// }
|
|
|
|
// /// Safe wrapper for animateCamera Bounds to prevent native std::domain_error crash on iOS.
|
|
// Future<void> _safeAnimateCameraBounds(LatLngBounds? bounds,
|
|
// {double left = 60,
|
|
// double top = 60,
|
|
// double right = 60,
|
|
// double bottom = 60}) async {
|
|
// if (bounds == null || mapController == null) return;
|
|
|
|
// try {
|
|
// // Ensure the coordinates are valid
|
|
// if (bounds.northeast.latitude == bounds.southwest.latitude &&
|
|
// bounds.northeast.longitude == bounds.southwest.longitude) {
|
|
// Log.print(
|
|
// '⚠️ _safeAnimateCameraBounds: Bounds are a single point, zooming to point instead.');
|
|
// await mapController
|
|
// ?.animateCamera(CameraUpdate.newLatLngZoom(bounds.northeast, 15));
|
|
// return;
|
|
// }
|
|
|
|
// // Small delay to ensure iOS view layout is fully ready
|
|
// await Future.delayed(const Duration(milliseconds: 200));
|
|
|
|
// await mapController?.animateCamera(
|
|
// CameraUpdate.newLatLngBounds(
|
|
// bounds,
|
|
// left: left,
|
|
// top: top,
|
|
// right: right,
|
|
// bottom: bottom,
|
|
// ),
|
|
// );
|
|
// } catch (e) {
|
|
// Log.print('❌ _safeAnimateCameraBounds CRASH PREVENTED: $e');
|
|
// // Final fallback to prevent device freeze
|
|
// try {
|
|
// await mapController
|
|
// ?.animateCamera(CameraUpdate.newLatLngZoom(bounds.northeast, 14));
|
|
// } catch (_) {}
|
|
// }
|
|
// }
|
|
|
|
// Future<void> _loadMapIcons() async {
|
|
// // Wait up to 3 seconds for the map style to finish loading
|
|
// for (int i = 0; i < 15; i++) {
|
|
// if (mapController != null && isStyleLoaded) break;
|
|
// await Future.delayed(const Duration(milliseconds: 200));
|
|
// }
|
|
|
|
// if (mapController == null || !isStyleLoaded) {
|
|
// Log.print(
|
|
// '⚠️ _loadMapIcons: mapController or style not ready. Icons may not load.');
|
|
// }
|
|
|
|
// await _addMapImage(startIcon, 'assets/images/A.png');
|
|
// await _addMapImage(endIcon, 'assets/images/b.png');
|
|
// await _addMapImage(carIcon, 'assets/images/car.png');
|
|
// await _addMapImage(motoIcon, 'assets/images/moto.png');
|
|
// await _addMapImage(ladyIcon, 'assets/images/lady.png');
|
|
// await _addMapImage('picker_icon', 'assets/images/picker.png');
|
|
// // Waypoint markers - use moto1 & lady1 as colored waypoint icons
|
|
// await _addMapImage('orange_marker', 'assets/images/moto1.png');
|
|
// await _addMapImage('violet_marker', 'assets/images/lady1.png');
|
|
// }
|
|
|
|
// Future<void> _addMapImage(String id, String path) async {
|
|
// try {
|
|
// final ByteData bytes = await rootBundle.load(path);
|
|
// // Resize car icons for better visibility on map (e.g. 120px)
|
|
// final size = _getImageSize(id);
|
|
// if (size != null && (id == carIcon || id == motoIcon || id == ladyIcon)) {
|
|
// final resized = await _resizeImage(bytes.buffer.asUint8List(), size);
|
|
// await mapController?.addImage(id, resized);
|
|
// Log.print(
|
|
// '✅ Successfully added resized map image: $id (${size}x${size})');
|
|
// } else {
|
|
// await mapController?.addImage(id, bytes.buffer.asUint8List());
|
|
// Log.print('✅ Successfully added map image: $id');
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print('❌ Error loading map icon $id: $e');
|
|
// }
|
|
// }
|
|
|
|
// int? _getImageSize(String id) {
|
|
// if (id == carIcon || id == motoIcon || id == ladyIcon) return 120;
|
|
// return null;
|
|
// }
|
|
|
|
// Future<Uint8List> _resizeImage(Uint8List bytes, int size) async {
|
|
// return await compute((Uint8List data) {
|
|
// final image = img.decodeImage(data);
|
|
// if (image == null) return data;
|
|
// final resized = img.copyResize(image, width: size, height: size);
|
|
// return Uint8List.fromList(img.encodePng(resized));
|
|
// }, bytes);
|
|
// }
|
|
|
|
// // Wait up to 3 seconds for the map style to finish loading
|
|
|
|
// void updateCurrentLocationFromCamera(LatLng target) {
|
|
// Log.print('📍 updateCurrentLocationFromCamera: $target');
|
|
// newMyLocation = target;
|
|
|
|
// if (startLocationFromMap == true) {
|
|
// Log.print('📍 Updating startLocationFromMap to $target');
|
|
// newStartPointLocation = target;
|
|
// } else if (passengerStartLocationFromMap == true) {
|
|
// Log.print('📍 Updating passengerStartLocationFromMap to $target');
|
|
// newStartPointLocation = target;
|
|
// }
|
|
|
|
// int waypointsLength = Get.find<WayPointController>().wayPoints.length;
|
|
// if (waypointsLength > 0 &&
|
|
// wayPointIndex >= 0 &&
|
|
// wayPointIndex < placesCoordinate.length) {
|
|
// Log.print('📍 Updating wayPointIndex $wayPointIndex to $target');
|
|
// placesCoordinate[wayPointIndex] =
|
|
// '${target.latitude},${target.longitude}';
|
|
// }
|
|
// update();
|
|
// }
|
|
|
|
// 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 (!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
|
|
// }
|
|
|
|
// Future<String> getReverseGeocoding(LatLng location) async {
|
|
// final lat = location.latitude;
|
|
// final lng = location.longitude;
|
|
// final url = '${AppLink.reverseGeocoding}?lat=$lat&lng=$lng';
|
|
|
|
// try {
|
|
// final response = await CRUD().getMapSaas(link: url);
|
|
|
|
// if (response != null && response is List && response.isNotEmpty) {
|
|
// final data = response[0];
|
|
// String name = data['name_ar'] ?? data['name'] ?? 'Unknown Location'.tr;
|
|
// return name;
|
|
// }
|
|
// return 'Unknown Location'.tr;
|
|
// } catch (e) {
|
|
// Log.print('ReverseGeocoding Exception: $e');
|
|
// return 'Unknown Location'.tr;
|
|
// }
|
|
// }
|
|
|
|
// bool isDrawingRoute = false;
|
|
// void showDrawingBottomSheet() {
|
|
// Log.print(
|
|
// '🔔 showDrawingBottomSheet called. isDrawingRoute: $isDrawingRoute');
|
|
|
|
// final context = Get.context;
|
|
// if (context == null) return;
|
|
|
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
// // Close any existing open dialogs first
|
|
// if (Get.isDialogOpen == true) {
|
|
// Get.back();
|
|
// }
|
|
|
|
// Get.dialog(
|
|
// Dialog(
|
|
// backgroundColor: Colors.transparent,
|
|
// elevation: 0,
|
|
// child: Container(
|
|
// padding: const EdgeInsets.all(24),
|
|
// width: 180,
|
|
// decoration: BoxDecoration(
|
|
// color: Colors.white.withOpacity(0.95),
|
|
// borderRadius: BorderRadius.circular(24),
|
|
// boxShadow: [
|
|
// BoxShadow(
|
|
// color: Colors.black.withOpacity(0.15),
|
|
// blurRadius: 20,
|
|
// spreadRadius: 5,
|
|
// )
|
|
// ],
|
|
// ),
|
|
// child: Column(
|
|
// mainAxisSize: MainAxisSize.min,
|
|
// mainAxisAlignment: MainAxisAlignment.center,
|
|
// crossAxisAlignment: CrossAxisAlignment.center,
|
|
// children: [
|
|
// // App Logo
|
|
// Image.asset(
|
|
// 'assets/images/logo.gif',
|
|
// height: 64,
|
|
// errorBuilder: (context, error, stackTrace) => const Icon(
|
|
// Icons.map,
|
|
// size: 64,
|
|
// color: AppColor.primaryColor,
|
|
// ),
|
|
// ),
|
|
// const SizedBox(height: 16),
|
|
// const SizedBox(
|
|
// width: 24,
|
|
// height: 24,
|
|
// child: MyCircularProgressIndicator(),
|
|
// ),
|
|
// const SizedBox(height: 16),
|
|
// Text(
|
|
// 'Drawing route on map...'.tr,
|
|
// style: const TextStyle(
|
|
// fontWeight: FontWeight.bold,
|
|
// fontSize: 14,
|
|
// color: AppColor.primaryColor,
|
|
// ),
|
|
// textAlign: TextAlign.center,
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// barrierDismissible: false,
|
|
// );
|
|
|
|
// // Auto-dismiss after exactly 2 seconds
|
|
// Future.delayed(const Duration(seconds: 2), () {
|
|
// if (Get.isDialogOpen == true) {
|
|
// Get.back();
|
|
// }
|
|
// });
|
|
// });
|
|
// }
|
|
|
|
// String dynamicApiUrl = 'https://routec.intaleq.xyz/route';
|
|
// Future<void> getDistanceFromDriverAfterAcceptedRide(
|
|
// String origin, String destination) async {
|
|
// String apiKey = Env.mapKeyOsm; // مفتاح API الخاص بك
|
|
// if (origin.isEmpty) {
|
|
// origin = '${passengerLocation.latitude},${passengerLocation.longitude}';
|
|
// }
|
|
// // 2. بناء الرابط (URI)
|
|
// // Waypoints غير مدعومة حالياً في OSRM، لذلك تم تجاهلها
|
|
// var uri = Uri.parse(
|
|
// '$dynamicApiUrl?origin=$origin&destination=$destination&steps=false&overview=false');
|
|
// Log.print('uri: $uri');
|
|
|
|
// // 3. إرسال الطلب مع الهيدر
|
|
// http.Response response;
|
|
// Map<String, dynamic> responseData;
|
|
|
|
// try {
|
|
// response = await http.get(
|
|
// uri,
|
|
// headers: {
|
|
// 'X-API-KEY': apiKey,
|
|
// },
|
|
// ).timeout(const Duration(seconds: 20)); // تايم آوت 20 ثانية
|
|
|
|
// if (response.statusCode != 200) {
|
|
// Log.print('Error from API: ${response.statusCode}');
|
|
// isLoading = false;
|
|
// update();
|
|
// return; // خروج في حالة الخطأ
|
|
// }
|
|
// if (Get.isBottomSheetOpen ?? false) {
|
|
// Get.back(); // لإغلاق شاشة "جاري الرسم"
|
|
// }
|
|
// isDrawingRoute = false; // Reset state
|
|
|
|
// responseData = json.decode(response.body);
|
|
// Log.print('responseData: $responseData');
|
|
|
|
// if (responseData['status'] != 'ok') {
|
|
// Log.print('API returned an error: ${responseData['message']}');
|
|
// isLoading = false;
|
|
// update();
|
|
// return; // خروج في حالة خطأ منطقي (مثل "no path")
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print('Failed to get directions: $e');
|
|
// isLoading = false;
|
|
// update();
|
|
// return; // خروج عند فشل الاتصال
|
|
// }
|
|
// }
|
|
|
|
// // (b = 1.5348) هو المعامل الذي تم حسابه من مقارنة 60 رحلة بين Google و OSRM
|
|
// double kDurationScalar =
|
|
// 1.5348; //this from colab 60 random locations from google and routec
|
|
|
|
// // -----------------------------------------------------------------------------------------
|
|
// // GET DIRECTION MAP (FULL)
|
|
// // -----------------------------------------------------------------------------------------
|
|
// // -----------------------------------------------------------------------------------------
|
|
// // GET DIRECTION MAP (With Auto-Retry Logic)
|
|
// // -----------------------------------------------------------------------------------------
|
|
// // أضفنا attemptCount لتتبع عدد المحاولات
|
|
// // -----------------------------------------------------------------------------------------
|
|
// // GET DIRECTION MAP (Retry or Fail Strict Logic)
|
|
// // -----------------------------------------------------------------------------------------
|
|
// Future<void> getDirectionMap(String origin, String destination,
|
|
// [List<String> waypoints = const [], int attemptCount = 0]) async {
|
|
// // 1. إظهار التحميل فقط في المحاولة الأولى
|
|
// if (attemptCount == 0) {
|
|
// // NOTE: Do NOT set isLoading = true here!
|
|
// // isLoading destroys the MapLibreMap widget entirely (replaced by spinner),
|
|
// // which means markers/polylines cannot be added to the new map instance
|
|
// // until its style finishes loading asynchronously — causing a race condition.
|
|
// // The showDrawingBottomSheet() overlay provides sufficient user feedback.
|
|
// isDrawingRoute = true;
|
|
// update();
|
|
// if (isDrawingRoute) showDrawingBottomSheet();
|
|
|
|
// await getCarsLocationByPassengerAndReloadMarker();
|
|
// }
|
|
|
|
// // تجهيز الإحداثيات
|
|
// if (origin.isEmpty) {
|
|
// origin = '${passengerLocation.latitude},${passengerLocation.longitude}';
|
|
// }
|
|
|
|
// var coordDestination = destination.split(',');
|
|
// double latDest = double.parse(coordDestination[0]);
|
|
// double lngDest = double.parse(coordDestination[1]);
|
|
// myDestination = LatLng(latDest, lngDest);
|
|
|
|
// // ── 2. Unified SaaS Routing Strategy ──────────────────────────
|
|
// final bool isSaaSRequest = true;
|
|
// Uri uri;
|
|
|
|
// var originCoords = origin.split(',');
|
|
// final Map<String, String> queryParams = {
|
|
// 'fromLat': originCoords[0].trim(),
|
|
// 'fromLng': originCoords[1].trim(),
|
|
// 'toLat': latDest.toString(),
|
|
// 'toLng': lngDest.toString(),
|
|
// };
|
|
|
|
// // Add multi-stop waypoints to the query parameters
|
|
// for (int i = 0; i < activeMenuWaypointCount; i++) {
|
|
// final wp = menuWaypoints[i];
|
|
// if (wp != null) {
|
|
// queryParams['stop${i + 1}Lat'] = wp.latitude.toString();
|
|
// queryParams['stop${i + 1}Lng'] = wp.longitude.toString();
|
|
// }
|
|
// }
|
|
|
|
// uri = Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: queryParams);
|
|
|
|
// Log.print(
|
|
// 'Requesting Route URI (${isSaaSRequest ? "SaaS" : "OSRM"}, Attempt: ${attemptCount + 1}): $uri');
|
|
|
|
// http.Response response;
|
|
// Map<String, dynamic> responseData;
|
|
|
|
// try {
|
|
// response = await http.get(uri, headers: {
|
|
// 'x-api-key': Env.mapSaasKey,
|
|
// }).timeout(const Duration(seconds: 20));
|
|
|
|
// responseData = json.decode(response.body);
|
|
|
|
// // Validation: SaaS returns 200 with data, OSRM returns code: 'Ok'
|
|
// bool isRequestValid = response.statusCode == 200 &&
|
|
// (isSaaSRequest || responseData['code'] == 'Ok');
|
|
|
|
// if (!isRequestValid) {
|
|
// if (attemptCount < 2) {
|
|
// await _retryProcess(origin, destination, waypoints, attemptCount);
|
|
// return;
|
|
// }
|
|
// _handleFatalError(
|
|
// "Server Error".tr, "Connection failed. Please try again.".tr);
|
|
// return;
|
|
// }
|
|
|
|
// // ============================================================
|
|
// // 🛑 الفحص الأمني (Sanity Check)
|
|
// // ============================================================
|
|
// double apiDistanceMeters;
|
|
// String pointsString;
|
|
// dynamic routeData;
|
|
|
|
// // SaaS parsing
|
|
// apiDistanceMeters = (responseData['distance'] as num).toDouble();
|
|
// pointsString = responseData['points'] ?? "";
|
|
// routeData = responseData; // For box storage
|
|
|
|
// var originCoords = origin.split(',');
|
|
// double startLat = double.parse(originCoords[0]);
|
|
// double startLng = double.parse(originCoords[1]);
|
|
|
|
// // المسافة الجوية
|
|
// double aerialDistance =
|
|
// Geolocator.distanceBetween(startLat, startLng, latDest, lngDest);
|
|
|
|
// if (apiDistanceMeters < 50.0 && aerialDistance > 200.0) {
|
|
// Log.print(
|
|
// "⚠️ Suspicious Route detected! Server: $apiDistanceMeters m | Aerial: $aerialDistance m");
|
|
|
|
// if (attemptCount < 2) {
|
|
// Log.print("🔄 Retrying request (Attempt ${attemptCount + 2})...");
|
|
// await Future.delayed(const Duration(seconds: 1));
|
|
// await getDirectionMap(
|
|
// origin, destination, waypoints, attemptCount + 1);
|
|
// return;
|
|
// } else {
|
|
// Log.print("❌ All retries failed. Calculating Route is impossible.");
|
|
// _handleFatalError(
|
|
// "Route Not Found".tr,
|
|
// "We couldn't find a valid route to this destination. Please try selecting a different point."
|
|
// .tr);
|
|
// return;
|
|
// }
|
|
// }
|
|
|
|
// // 3. معالجة البيانات
|
|
// box.remove(BoxName.tripData);
|
|
// box.write(BoxName.tripData, routeData);
|
|
|
|
// durationToRide =
|
|
// ((routeData['duration'] as num) * kDurationScalar).toInt();
|
|
// double distanceOfTrip = apiDistanceMeters / 1000.0;
|
|
// distance = distanceOfTrip;
|
|
|
|
// data = routeData['legs'] != null && routeData['legs'].isNotEmpty
|
|
// ? (routeData['legs'][0]['steps'] ?? [])
|
|
// : [];
|
|
|
|
// List<LatLng> decodedPoints = [];
|
|
// if (pointsString.isNotEmpty) {
|
|
// decodedPoints = await compute(decodePolylineIsolate, pointsString);
|
|
// }
|
|
|
|
// if (decodedPoints.isEmpty) {
|
|
// _handleFatalError("Map Error".tr, "Received empty route data.".tr);
|
|
// return;
|
|
// }
|
|
|
|
// polylineCoordinates.clear();
|
|
// polylineCoordinates.addAll(decodedPoints);
|
|
|
|
// final LatLng startLoc = polylineCoordinates.first;
|
|
// final LatLng endLoc = polylineCoordinates.last;
|
|
|
|
// // ── 4. العناوين والتحديثات ──────────────────────────────────
|
|
// startNameAddress = responseData['startName'] ?? 'Start Point'.tr;
|
|
// endNameAddress = responseData['endName'] ?? 'Destination'.tr;
|
|
// Log.print('📍 ROUTE START: $startNameAddress');
|
|
// Log.print('📍 ROUTE END: $endNameAddress');
|
|
|
|
// // ── 5. Bounds Calculation (SaaS bbox vs OSRM manual) ──────────
|
|
// if (isSaaSRequest && responseData['bbox'] != null) {
|
|
// List<dynamic> bbox = responseData['bbox'];
|
|
// if (bbox.length == 4) {
|
|
// // SaaS format: [minLng, minLat, maxLng, maxLat]
|
|
// lastComputedBounds = LatLngBounds(
|
|
// southwest: LatLng(bbox[1], bbox[0]),
|
|
// northeast: LatLng(bbox[3], bbox[2]),
|
|
// );
|
|
// }
|
|
// } else {
|
|
// double? minLat, maxLat, minLng, maxLng;
|
|
// for (LatLng point in polylineCoordinates) {
|
|
// minLat =
|
|
// minLat == null ? point.latitude : min(minLat, point.latitude);
|
|
// maxLat =
|
|
// maxLat == null ? point.latitude : max(maxLat, point.latitude);
|
|
// minLng =
|
|
// minLng == null ? point.longitude : min(minLng, point.longitude);
|
|
// maxLng =
|
|
// maxLng == null ? point.longitude : max(maxLng, point.longitude);
|
|
// }
|
|
// if (minLat != null) {
|
|
// lastComputedBounds = LatLngBounds(
|
|
// northeast: LatLng(maxLat!, maxLng!),
|
|
// southwest: LatLng(minLat!, minLng!));
|
|
// }
|
|
// }
|
|
// // isDrawingRoute = false;
|
|
// // 5b. Reset state when finished
|
|
// if (isDrawingRoute) {
|
|
// Log.print('🔔 Finalizing route drawing state');
|
|
// isDrawingRoute = false;
|
|
// isLoading = false;
|
|
// update();
|
|
// }
|
|
|
|
// // 6. إضافة الماركرز
|
|
// durationToAdd = Duration(seconds: durationToRide);
|
|
// hours = durationToAdd.inHours;
|
|
// minutes = (durationToAdd.inMinutes % 60).round();
|
|
|
|
// markers = {
|
|
// Marker(
|
|
// markerId: const MarkerId('start'),
|
|
// position: startLoc,
|
|
// icon: InlqBitmap.fromStyleImage('orange_marker'),
|
|
// infoWindow: const InfoWindow(title: 'A'),
|
|
// anchor: const Offset(0.5, 1.0),
|
|
// ),
|
|
// Marker(
|
|
// markerId: const MarkerId('end'),
|
|
// position: endLoc,
|
|
// icon: InlqBitmap.fromStyleImage('violet_marker'),
|
|
// infoWindow: const InfoWindow(title: 'B'),
|
|
// anchor: const Offset(0.5, 1.0),
|
|
// ),
|
|
// };
|
|
|
|
// for (int i = 0; i < activeMenuWaypointCount; i++) {
|
|
// final wp = menuWaypoints[i];
|
|
// if (wp != null) {
|
|
// final bool isFirstWaypoint = i == 0;
|
|
// markers.add(Marker(
|
|
// markerId: MarkerId('waypoint_$i'),
|
|
// position: wp,
|
|
// icon: InlqBitmap.fromStyleImage(
|
|
// isFirstWaypoint ? 'orange_marker' : 'violet_marker'),
|
|
// infoWindow:
|
|
// InfoWindow(title: isFirstWaypoint ? 'Stop 1' : 'Stop 2'),
|
|
// anchor: const Offset(0.5, 1.0),
|
|
// ));
|
|
// }
|
|
// }
|
|
|
|
// // 7. رسم الخط
|
|
// if (polyLines.isNotEmpty) clearPolyline();
|
|
|
|
// rideConfirm = false;
|
|
// isMarkersShown = true;
|
|
// update(); // تحديث أولي لإظهار الخريطة والماركرز
|
|
|
|
// // إظهار الباتم شيت للسعر
|
|
// await bottomSheet();
|
|
|
|
// // تشغيل الأنيميشن الخفيف لومضات المسار + fit camera after
|
|
// await _playRouteAnimation(polylineCoordinates, lastComputedBounds);
|
|
// } catch (e, stackTrace) {
|
|
// // 🚨 Cleanup on error to prevent UI freeze
|
|
// if (isDrawingRoute) {
|
|
// isDrawingRoute = false;
|
|
// isLoading = false;
|
|
// update();
|
|
// }
|
|
|
|
// Log.print('🚨 CRITICAL ERROR IN getDirectionMap: $e');
|
|
// Log.print('🚨 STACKTRACE: $stackTrace');
|
|
|
|
// if (attemptCount < 2) {
|
|
// await _retryProcess(origin, destination, waypoints, attemptCount);
|
|
// } else {
|
|
// _handleFatalError("Connection Error".tr,
|
|
// "Please check your internet and try again.".tr);
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// // --- رسم المسار النهائي مع تقسيم ملون حسب نقاط التوقف ---
|
|
// Future<void> _playRouteAnimation(
|
|
// List<LatLng> coords, LatLngBounds? bounds) async {
|
|
// // Segment colors matching UI dots: green → amber → purple → red
|
|
// const List<Color> segmentColors = [
|
|
// Color(0xFF109642), // Green (start → stop 1)
|
|
// Color(0xFFF59E0B), // Amber (stop 1 → stop 2)
|
|
// Color(0xFF7C3AED), // Purple (last segment → dest)
|
|
// Color(0xFFEF4444), // Red (fallback)
|
|
// ];
|
|
|
|
// // ── Build final polyline segments ───────────────────────────────────
|
|
// // Build all segments in a temporary Set first, then assign once
|
|
// Set<Polyline> newPolylines = {};
|
|
|
|
// if (activeMenuWaypointCount > 0) {
|
|
// List<int> splitIndices = [];
|
|
// for (int w = 0; w < activeMenuWaypointCount; w++) {
|
|
// final wp = menuWaypoints[w];
|
|
// if (wp == null) continue;
|
|
// int bestIdx = 0;
|
|
// double bestDist = double.infinity;
|
|
// for (int j = 0; j < coords.length; j++) {
|
|
// final dx = coords[j].latitude - wp.latitude;
|
|
// final dy = coords[j].longitude - wp.longitude;
|
|
// final d = dx * dx + dy * dy;
|
|
// if (d < bestDist) {
|
|
// bestDist = d;
|
|
// bestIdx = j;
|
|
// }
|
|
// }
|
|
// splitIndices.add(bestIdx);
|
|
// }
|
|
// splitIndices.sort();
|
|
|
|
// List<int> boundaries = [0, ...splitIndices, coords.length - 1];
|
|
// for (int s = 0; s < boundaries.length - 1; s++) {
|
|
// int from = boundaries[s];
|
|
// int to = boundaries[s + 1] + 1;
|
|
// if (to > coords.length) to = coords.length;
|
|
// if (from >= to - 1) continue;
|
|
// final segCoords = coords.sublist(from, to);
|
|
// if (segCoords.length < 2) continue;
|
|
// final color = segmentColors[s % segmentColors.length];
|
|
|
|
// newPolylines.add(Polyline(
|
|
// polylineId: PolylineId('segment_$s'),
|
|
// points: segCoords,
|
|
// color: color,
|
|
// width: 6,
|
|
// ));
|
|
// }
|
|
// } else {
|
|
// newPolylines.add(Polyline(
|
|
// polylineId: const PolylineId('route_primary'),
|
|
// points: coords,
|
|
// color: AppColor.primaryColor,
|
|
// width: 6,
|
|
// ));
|
|
// }
|
|
|
|
// polyLines = newPolylines;
|
|
// update();
|
|
|
|
// Log.print(
|
|
// '🗺️ Drawing ${markers.length} markers + ${polyLines.length} polylines on map');
|
|
|
|
// update();
|
|
|
|
// // ── Fit camera to full route bounds ────────────────────────────────
|
|
// if (bounds != null) {
|
|
// await _safeAnimateCameraBounds(bounds);
|
|
// }
|
|
// }
|
|
|
|
// // --- دالة المساعدة لإعادة المحاولة ---
|
|
// Future<void> _retryProcess(String origin, String dest, List<String> waypoints,
|
|
// int currentAttempt) async {
|
|
// Log.print(
|
|
// "🔄 Exception or Error caught. Retrying in 1s... (Attempt ${currentAttempt + 1})");
|
|
// await Future.delayed(const Duration(seconds: 1));
|
|
// getDirectionMap(origin, dest, waypoints, currentAttempt + 1);
|
|
// }
|
|
|
|
// // --- دالة جديدة لتنظيف الخريطة بالكامل ومنع تداخل الرحلات ---
|
|
// void resetAllMapStates() {
|
|
// Log.print('🧹 Resetting all map states to prevent sticky location bug');
|
|
|
|
// clearPlacesDestination();
|
|
// clearPlacesStart();
|
|
// clearPolyline();
|
|
// data = [];
|
|
|
|
// passengerStartLocationFromMap = false;
|
|
// startLocationFromMap = false;
|
|
// isPickerShown = false;
|
|
// workLocationFromMap = false;
|
|
// homeLocationFromMap = false;
|
|
// isAnotherOreder = false;
|
|
// isWhatsAppOrder = false;
|
|
|
|
// // ✅ أضف هذا: reset الوجهة لموقع الراكب حتى لا تبقى قيمة الرحلة القديمة
|
|
// myDestination = passengerLocation;
|
|
// hintTextDestinationPoint = 'Select your destination'.tr;
|
|
|
|
// placeDestinationController.clear();
|
|
// placeStartController.clear();
|
|
|
|
// rideConfirm = false;
|
|
// shouldFetch = false;
|
|
// isDrawingRoute = false;
|
|
// isLoading = false;
|
|
|
|
// update();
|
|
// }
|
|
|
|
// // -----------------------------------------------------------------------------------------
|
|
// // 🛑 دالة الخطأ القاتل (تغلق كل شيء وتعيد المستخدم للخريطة)
|
|
// // -----------------------------------------------------------------------------------------
|
|
// void _handleFatalError(String title, String message) {
|
|
// // 1. إغلاق شاشة التحميل (Drawing route...)
|
|
// if (Get.isBottomSheetOpen == true || Get.isDialogOpen == true) {
|
|
// Get.back();
|
|
// }
|
|
// if (Get.isSnackbarOpen) Get.closeCurrentSnackbar();
|
|
|
|
// // 2. تصفير المتغيرات
|
|
// isDrawingRoute = false;
|
|
// isLoading = false;
|
|
// update();
|
|
|
|
// // 3. إظهار الديالوج الإجباري
|
|
// Get.defaultDialog(
|
|
// title: title,
|
|
// titleStyle: AppStyle.title.copyWith(color: AppColor.redColor),
|
|
// middleText: message,
|
|
// middleTextStyle: AppStyle.subtitle,
|
|
// barrierDismissible: false, // لا يمكن إغلاقه بالضغط خارجاً
|
|
// confirm: MyElevatedButton(
|
|
// title: "Close".tr,
|
|
// kolor: AppColor.redColor,
|
|
// onPressed: () {
|
|
// Get.back(); // إغلاق الديالوج
|
|
|
|
// // 4. إعادة تحميل الصفحة بالكامل (تنظيف الحالة)
|
|
// // تأكد من استيراد MapPagePassenger
|
|
// Get.offAll(() => const MapPagePassenger());
|
|
// },
|
|
// ),
|
|
// );
|
|
// }
|
|
|
|
// // Legacy gradient and layered animations removed for MapLibre migration
|
|
|
|
// 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();
|
|
// // 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.isEmpty) {
|
|
// var polyline = Polyline(
|
|
// polylineId: PolylineId('route_$index'),
|
|
// points: polylineCoordinatesPointsAll[index],
|
|
// width: 6,
|
|
// color: const Color(0xFF2196F3),
|
|
// );
|
|
|
|
// polyLines = {...polyLines, polyline};
|
|
// rideConfirm = false;
|
|
// 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,
|
|
// left: 180, top: 180, right: 180, bottom: 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;
|
|
// void 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 minPromoLow${CurrencyHelper.currency} = 172; // Speed / Balash
|
|
// const double minPromoHigh${CurrencyHelper.currency} = 200; // 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;
|
|
|
|
// final bool isWalletNegative = walletVal < 0;
|
|
|
|
// // --------------------------
|
|
// // دالة تُطبّق الخصم دون النزول تحت الحد الأدنى
|
|
// // --------------------------
|
|
// double _applyDiscountPerTier({
|
|
// required double fare,
|
|
// required double minThreshold,
|
|
// required bool isWalletNegative,
|
|
// }) {
|
|
// if (fare < minThreshold) return fare; // غير مؤهل أصلاً
|
|
|
|
// final double discount = fare * (discountPercentage / 100.0);
|
|
// double result;
|
|
|
|
// if (isWalletNegative) {
|
|
// double neg = (-1) * walletVal; // walletVal < 0 => neg positive
|
|
// result = fare + neg - discount;
|
|
// } else {
|
|
// result = fare - discount;
|
|
// }
|
|
|
|
// // لا نسمح بالنزول دون الحد الأدنى
|
|
// if (result < minThreshold) {
|
|
// result = minThreshold;
|
|
// }
|
|
|
|
// // ولا نسمح بمبلغ سالب
|
|
// return result.clamp(0.0, double.infinity);
|
|
// }
|
|
|
|
// // 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 minFare${CurrencyHelper.currency} = 160; // حد أدنى
|
|
// const double minBillableKm = 0.3; // حد أدنى للمسافة المفوترة
|
|
// const double ladyFlatAddon = 20; // إضافة ثابتة لـ Lady
|
|
// const double airportAddon${CurrencyHelper.currency} = 200; // إضافة المطار
|
|
|
|
// // --- ⬇️ الإضافة الجديدة: إضافة حدود مطار دمشق ⬇️ ---
|
|
// const double damascusAirportBoundAddon = 1400; // إضافة المطار (حدود)
|
|
// // --- ⬆️ نهاية الإضافة ⬆️ ---
|
|
|
|
// // كهرباء
|
|
// const double electricPerKmUplift = 4; // زيادة/كم
|
|
// const double electricFlatAddon = 10; // زيادة ثابتة
|
|
|
|
// // Long Speed
|
|
// const double longSpeedThresholdKm = 40.0;
|
|
// const double longSpeedPerKm = 26.0; // Speed عند >40كم
|
|
|
|
// // قواعد الرحلات البعيدة للدقائق (تعمل لكل الأوقات)
|
|
// const double mediumDistThresholdKm = 25.0; // >25كم
|
|
// const double longDistThresholdKm = 35.0; // >35كم
|
|
// const double longTripPerMin = 6.0;
|
|
// const int minuteCapMedium = 60; // سقف دقائق عند >25كم
|
|
// const int minuteCapLong = 80; // سقف دقائق عند >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;
|
|
// // +5 minutes per waypoint stop surcharge
|
|
// final int waypointSurchargeMinutes = activeMenuWaypointCount * 5;
|
|
// final int totalMinutes =
|
|
// (durationToRide / 60).floor() + waypointSurchargeMinutes;
|
|
|
|
// // ====== أدوات مساعدة ======
|
|
// 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('ملهى ليلي');
|
|
// }
|
|
|
|
// // --- ⬇️ الإضافة الجديدة: دالة التحقق من حدود المطار ⬇️ ---
|
|
// // (P1: 33.415313, 36.499687) (P2: 33.400265, 36.531505)
|
|
// bool _isInsideDamascusAirportBounds(double lat, double lng) {
|
|
// final double northLat = 33.415313;
|
|
// final double southLat = 33.400265;
|
|
// final double eastLng = 36.531505;
|
|
// final double westLng = 36.499687;
|
|
|
|
// // التحقق من خط العرض (بين الشمال والجنوب)
|
|
// bool isLatInside = (lat <= northLat) && (lat >= southLat);
|
|
// // التحقق من خط الطول (بين الشرق والغرب)
|
|
// bool isLngInside = (lng <= eastLng) && (lng >= westLng);
|
|
|
|
// return isLatInside && isLngInside;
|
|
// }
|
|
// // --- ⬆️ نهاية الإضافة ⬆️ ---
|
|
|
|
// // أسعار الدقيقة من السيرفر
|
|
// 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) ? minFare${CurrencyHelper.currency} : 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);
|
|
|
|
// // --- ⬇️ الإضافة الجديدة: التحقق من سياق حدود المطار ⬇️ ---
|
|
// // !! ⚠️ تأكد من أن هذه هي المتغيرات الصحيحة لإحداثيات نقطة النهاية !!
|
|
// double destLat = 0.0;
|
|
// double destLng = 0.0;
|
|
// try {
|
|
// destLat = myDestination.latitude;
|
|
// destLng = myDestination.longitude;
|
|
// } catch (_) {
|
|
// if (coordinatesWithoutEmpty.isNotEmpty) {
|
|
// destLat =
|
|
// double.tryParse(coordinatesWithoutEmpty.last.split(',')[0]) ?? 0.0;
|
|
// destLng =
|
|
// double.tryParse(coordinatesWithoutEmpty.last.split(',')[1]) ?? 0.0;
|
|
// }
|
|
// }
|
|
|
|
// final bool damascusAirportBoundCtx = _isInsideDamascusAirportBounds(
|
|
// destLat,
|
|
// destLng,
|
|
// );
|
|
// final bool isInDamascusAirportBoundCtx = _isInsideDamascusAirportBounds(
|
|
// newMyLocation.latitude.toDouble(), // <-- ⚠️ غيّر هذا للمتغير الصحيح
|
|
// newMyLocation.longitude.toDouble(), // <-- ⚠️ غيّر هذا للمتغير الصحيح
|
|
// );
|
|
// // --- ⬆️ نهاية الإضافة ⬆️ ---
|
|
|
|
// // ====== مسافة مفوترة ======
|
|
// final double billableDistance =
|
|
// (distance < minBillableKm) ? minBillableKm : distance;
|
|
|
|
// // ====== Speed (قصير/طويل) ======
|
|
// final bool isLongSpeed = billableDistance > longSpeedThresholdKm;
|
|
// final double perKmSpeedBaseFromServer =
|
|
// speedPrice; // مثال: 2900 يأتي من السيرفر
|
|
// final double perKmSpeed =
|
|
// isLongSpeed ? longSpeedPerKm : perKmSpeedBaseFromServer;
|
|
|
|
// // ====== تخفيضات الفئات الأخرى حسب بُعد الرحلة ======
|
|
// // ... (الكود كما هو) ...
|
|
// double reductionPct40 = 0.0;
|
|
// if (perKmSpeedBaseFromServer > 0) {
|
|
// reductionPct40 = (1.0 - (longSpeedPerKm / perKmSpeedBaseFromServer))
|
|
// .clamp(0.0, maxReductionCap);
|
|
// }
|
|
// 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) {
|
|
// effectivePerMin = longTripPerMin;
|
|
// final int capped =
|
|
// (billableMinutes > minuteCapLong) ? minuteCapLong : billableMinutes;
|
|
// billableMinutes = capped - freeMinutesLong;
|
|
// if (billableMinutes < 0) billableMinutes = 0;
|
|
// } else if (billableDistance > mediumDistThresholdKm) {
|
|
// effectivePerMin = longTripPerMin;
|
|
// billableMinutes = (billableMinutes > minuteCapMedium)
|
|
// ? minuteCapMedium
|
|
// : billableMinutes;
|
|
// }
|
|
|
|
// // ====== أسعار/كم قبل التخفيض ======
|
|
// // ... (الكود كما هو) ...
|
|
// final double perKmComfortRaw = comfortPrice;
|
|
// final double perKmDelivery = deliveryPrice;
|
|
// final double perKmVanRaw =
|
|
// (familyPrice > 0 ? familyPrice : (speedPrice + 13));
|
|
// final double perKmElectricRaw = perKmComfortRaw + electricPerKmUplift;
|
|
|
|
// // ====== تطبيق التخفيضات على الفئات (نفس نسبة Speed للبعيد) ======
|
|
// // ... (الكود كما هو) ...
|
|
// 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);
|
|
// final double perKmBalash = (perKmSpeed - 5).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;
|
|
|
|
// // --- ⬇️ الإضافة الجديدة: تطبيق إضافة حدود المطار ⬇️ ---
|
|
// if (damascusAirportBoundCtx || isInDamascusAirportBoundCtx) {
|
|
// fare += damascusAirportBoundAddon;
|
|
// }
|
|
// // --- ⬆️ نهاية الإضافة ⬆️ ---
|
|
|
|
// 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;
|
|
|
|
// // --- ⬇️ الإضافة الجديدة: تطبيق إضافة حدود المطار ⬇️ ---
|
|
// // تنطبق أيضاً على رحلات الذهاب والعودة لأنها "تصل" إلى الوجهة
|
|
// if (damascusAirportBoundCtx || isInDamascusAirportBoundCtx) {
|
|
// fare += damascusAirportBoundAddon;
|
|
// }
|
|
// // --- ⬆️ نهاية الإضافة ⬆️ ---
|
|
|
|
// 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 (e) {
|
|
// Log.print("Error: $e");
|
|
// }
|
|
|
|
// update();
|
|
// changeBottomSheetShown(forceValue: true);
|
|
// }
|
|
|
|
// 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 12km range
|
|
// double latitudeOffset = 0.1; // 20km range in latitude
|
|
// double longitudeOffset = 0.12; // 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
|
|
// Log.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
|
|
// Log.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.server}/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}');
|
|
// }
|
|
// });
|
|
|
|
// 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);
|
|
|
|
// await NotificationService.sendNotification(
|
|
// category: 'OrderVIP',
|
|
// target: driver['token'].toString(),
|
|
// title: 'OrderVIP'.tr,
|
|
// 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']}');
|
|
|
|
// await NotificationService.sendNotification(
|
|
// category: 'Order VIP Canceld',
|
|
// 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');
|
|
// }
|
|
// }
|
|
|
|
// Future<void> cancelVip(String token, tripId) async {
|
|
// var res = await CRUD()
|
|
// .post(link: AppLink.cancelMishwari, payload: {'id': tripId});
|
|
// if (res != 'failur') {
|
|
// Get.back();
|
|
// mySnackbarSuccess('You canceled VIP trip'.tr);
|
|
// }
|
|
// }
|
|
|
|
// void sendToDriverAgain(String token) {
|
|
// NotificationService.sendNotification(
|
|
// category: 'Order VIP Canceld',
|
|
// target: token.toString(),
|
|
// title: 'Order VIP Canceld'.tr,
|
|
// body: 'Passenger cancel order'.tr,
|
|
// isTopic: false, // Important: this is a token
|
|
// tone: 'cancel',
|
|
// driverList: [],
|
|
// );
|
|
// }
|
|
|
|
// // دالة الفحص عند بدء التطبيق
|
|
// Future<void> detectAndCacheDeviceTier() async {
|
|
// // 1. استخدام الكلاس الذي أنشأناه سابقاً للفحص
|
|
// bool isHighEnd = await DevicePerformanceManager.isHighEndDevice();
|
|
|
|
// // 2. طباعة النتيجة للتأكد
|
|
// Log.print("Device Analysis - Is Flagship/HighEnd? $isHighEnd");
|
|
|
|
// // 3. تخزين النتيجة بشكل منطقي صحيح
|
|
// // إذا كان الجهاز قوياً (true)، فإن وضع الـ LowEnd يكون (false)
|
|
// // والعكس صحيح
|
|
// box.write(BoxName.lowEndMode, !isHighEnd);
|
|
// }
|
|
|
|
// initilizeGetStorage() async {
|
|
// if (box.read(BoxName.addWork) == null) {
|
|
// box.write(BoxName.addWork, 'addWork');
|
|
// }
|
|
// if (box.read(BoxName.addHome) == null) {
|
|
// box.write(BoxName.addHome, 'addHome');
|
|
// }
|
|
// if (box.read(BoxName.lowEndMode) == null) {
|
|
// detectAndCacheDeviceTier();
|
|
// }
|
|
// }
|
|
|
|
// late List recentPlaces = [];
|
|
|
|
// getFavioratePlaces() async {
|
|
// recentPlaces = await sql.getCustomQuery(
|
|
// 'SELECT * FROM ${TableName.recentLocations} ORDER BY createdAt DESC');
|
|
// // Log.print('recentPlaces: ${recentPlaces}');
|
|
// }
|
|
|
|
// double passengerRate = 5;
|
|
// double comfortPrice = 45;
|
|
// double speedPrice = 40;
|
|
// double mashwariPrice = 40;
|
|
// double familyPrice = 55;
|
|
// double deliveryPrice = 1.2;
|
|
// double minFare${CurrencyHelper.currency} = 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);
|
|
// // التحقق الديناميكي من 'data' أو 'message'
|
|
// var dataList = json['data'] ?? json['message'];
|
|
|
|
// if (dataList != null && dataList is List && dataList.isNotEmpty) {
|
|
// var firstRow = dataList[0];
|
|
// kazan = double.parse(firstRow['kazan'].toString());
|
|
// naturePrice = double.parse(firstRow['naturePrice'].toString());
|
|
// heavyPrice = double.parse(firstRow['heavyPrice'].toString());
|
|
// latePrice = double.parse(firstRow['latePrice'].toString());
|
|
// comfortPrice = double.parse(firstRow['comfortPrice'].toString());
|
|
// speedPrice = double.parse(firstRow['speedPrice'].toString());
|
|
// deliveryPrice = double.parse(firstRow['deliveryPrice'].toString());
|
|
// mashwariPrice = double.parse(firstRow['freePrice'].toString());
|
|
// familyPrice = double.parse(firstRow['familyPrice'].toString());
|
|
// fuelPrice = double.parse(firstRow['fuelPrice'].toString());
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// getPassengerRate() async {
|
|
// var res = await CRUD().get(
|
|
// link: AppLink.getPassengerRate,
|
|
// payload: {'passenger_id': box.read(BoxName.passengerID)});
|
|
// if (res != 'failure') {
|
|
// var json = jsonDecode(res);
|
|
// var message = json['data'] ?? json['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.rawDeepLink, (String? link) async {
|
|
// if (link != null && link.isNotEmpty) {
|
|
// Log.print('📍 MapPassengerController processing link: $link');
|
|
|
|
// // 1. استخراج الإحداثيات باستخدام الدالة الموجودة لديك مسبقاً
|
|
// Map<String, double>? coordinates =
|
|
// await extractCoordinatesFromLinkAsync(link);
|
|
|
|
// if (coordinates != null) {
|
|
// double destLat = coordinates['latitude']!;
|
|
// double destLng = coordinates['longitude']!;
|
|
// myDestination = LatLng(destLat, destLng);
|
|
|
|
// // 2. التحقق من موقع الراكب الحالي
|
|
// if (passengerLocation == null ||
|
|
// (passengerLocation.latitude == 0 &&
|
|
// passengerLocation.longitude == 0)) {
|
|
// Log.print('⏳ Waiting for current location to calculate route...');
|
|
// await getLocation(); // جلب موقع الراكب إذا لم يكن متاحاً
|
|
// }
|
|
|
|
// if (passengerLocation != null) {
|
|
// String originStr =
|
|
// '${passengerLocation.latitude},${passengerLocation.longitude}';
|
|
// String destStr = '$destLat,$destLng';
|
|
|
|
// Log.print(
|
|
// '🚀 Drawing route from Deep Link: $originStr to $destStr');
|
|
|
|
// // 3. مسح أي مسارات ونقاط توقف سابقة
|
|
// clearPolyline();
|
|
// waypoints.clear();
|
|
// clearAllMenuWaypoints();
|
|
|
|
// // 4. استدعاء دالة رسم المسار وحساب التكلفة التي برمجتها
|
|
// await getDirectionMap(originStr, destStr);
|
|
|
|
// // 5. إظهار الواجهة السفلية للرحلة ليكون الطلب جاهزاً بنقرة واحدة
|
|
// isBottomSheetShown = true;
|
|
// heightBottomSheetShown = 250;
|
|
// update();
|
|
|
|
// Get.snackbar(
|
|
// 'Location Received'.tr,
|
|
// 'Route and prices have been calculated successfully!'.tr,
|
|
// backgroundColor: AppColor.greenColor,
|
|
// colorText: Colors.white,
|
|
// );
|
|
// }
|
|
// } else {
|
|
// Log.print('⚠️ Could not extract valid coordinates from link: $link');
|
|
// }
|
|
|
|
// // تفريغ الرابط بعد معالجته حتى لا يتم استدعاؤه مرة أخرى بالخطأ
|
|
// _deepLinkController.rawDeepLink.value = null;
|
|
// }
|
|
// });
|
|
|
|
// // معالجة الرابط إذا كان موجوداً مسبقاً (Cold Start) قبل تفعيل المستمع
|
|
// if (_deepLinkController.rawDeepLink.value != null &&
|
|
// _deepLinkController.rawDeepLink.value!.isNotEmpty) {
|
|
// String link = _deepLinkController.rawDeepLink.value!;
|
|
// _deepLinkController.rawDeepLink.value = null;
|
|
|
|
// // نؤجل التنفيذ قليلاً لضمان تحميل الخريطة
|
|
// Future.delayed(const Duration(milliseconds: 500), () async {
|
|
// Log.print(
|
|
// '📍 MapPassengerController processing link (Cold Start): $link');
|
|
|
|
// Map<String, double>? coordinates =
|
|
// await extractCoordinatesFromLinkAsync(link);
|
|
|
|
// if (coordinates != null) {
|
|
// double destLat = coordinates['latitude']!;
|
|
// double destLng = coordinates['longitude']!;
|
|
// myDestination = LatLng(destLat, destLng);
|
|
|
|
// if (passengerLocation == null ||
|
|
// (passengerLocation.latitude == 0 &&
|
|
// passengerLocation.longitude == 0)) {
|
|
// await getLocation();
|
|
// }
|
|
|
|
// if (passengerLocation != null) {
|
|
// String originStr =
|
|
// '${passengerLocation.latitude},${passengerLocation.longitude}';
|
|
// String destStr = '$destLat,$destLng';
|
|
|
|
// clearPolyline();
|
|
// waypoints.clear();
|
|
// clearAllMenuWaypoints();
|
|
// await getDirectionMap(originStr, destStr);
|
|
|
|
// isBottomSheetShown = true;
|
|
// heightBottomSheetShown = 250;
|
|
// update();
|
|
// }
|
|
// }
|
|
// });
|
|
// }
|
|
// }
|
|
|
|
// @override
|
|
// void onInit() async {
|
|
// super.onInit();
|
|
// _checkAndRefreshMapStyle(); // Verify style version and clear cache if needed
|
|
// // // --- إضافة جديدة: تهيئة وحدة التحكم في الروابط العميقة ---
|
|
// Get.put(DeepLinkController(), permanent: true);
|
|
// // // ----------------------------------------------------
|
|
// // مرحلة 0: الضروري جداً لعرض الخريطة سريعاً
|
|
// // mapAPIKEY = await storage.read(key: BoxName.mapAPIKEY);
|
|
// await initilizeGetStorage(); // إعداد سريع
|
|
// await _initMinimalIcons(); // start/end فقط
|
|
// // await addToken(); // لو لازم للمصادقة
|
|
// _listenForDeepLink();
|
|
// // initSocket();
|
|
// 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);
|
|
|
|
// // ابدأ الخريطة الآن (الشاشة ظهرت للمستخدم)
|
|
// Future.delayed(const Duration(seconds: 4), () {
|
|
// if (isLoading) {
|
|
// isLoading = false;
|
|
// update();
|
|
// }
|
|
// });
|
|
|
|
// // مرحلة 1: مهام ضرورية للتسعير لكن غير حرجة لظهور UI
|
|
// unawaited(_stagePricingAndState());
|
|
|
|
// // مرحلة 2: تحسينات/كماليات بالخلفية
|
|
// unawaited(_stageNiceToHave());
|
|
|
|
// // ابدأ إعادة تحميل الماركر لكن بثروتل داخلي
|
|
// // startMarkerReloading(); // تأكد أنه مَخنوق التحديث (throttled)
|
|
// _startMasterTimer();
|
|
|
|
// // Start listening to emergency shake gestures
|
|
// EmergencySignalService.instance.startListening(() {
|
|
// if (statusRide == 'Begin' || statusRide == 'start') {
|
|
// Log.print("🚨 Emergency shake verified! Prompting SOS...");
|
|
// if (isBottomSheetShown) {
|
|
// sosPassenger();
|
|
// } else {
|
|
// Get.snackbar(
|
|
// 'Emergency Mode Triggered'.tr,
|
|
// 'Stay calm. We are here to help.'.tr,
|
|
// backgroundColor: AppColor.redColor,
|
|
// colorText: Colors.white,
|
|
// duration: const Duration(seconds: 4),
|
|
// );
|
|
// sosPassenger();
|
|
// }
|
|
// }
|
|
// });
|
|
// }
|
|
|
|
// // === Helpers ===
|
|
|
|
// Future<void> _initMinimalIcons() async {
|
|
// // Icons are now loaded dynamically via MapLibre's _loadMapIcons onStyleLoaded
|
|
// }
|
|
|
|
// Future<void> _stagePricingAndState() async {
|
|
// try {
|
|
// await getKazanPercent(); // أسعار السيرفر
|
|
// } catch (e) {
|
|
// Log.print("Error: $e");
|
|
// }
|
|
// try {
|
|
// _checkInitialRideStatus(); // تحقق من حالة الرحلة الحالية
|
|
// } catch (e) {
|
|
// Log.print("Error: $e");
|
|
// }
|
|
// // لو عندك ضبط “وضع خفيف” حسب الجهاز:
|
|
// _applyLowEndModeIfNeeded();
|
|
// }
|
|
|
|
// Future<void> _stageNiceToHave() async {
|
|
// Log.print('🚀 MapPassengerController: Starting _stageNiceToHave');
|
|
|
|
// // 🔥 Fix: Future.wait uses ONE argument (the list).
|
|
// await Future.wait([
|
|
// Future(() async {
|
|
// try {
|
|
// Log.print('🔍 Loading Favorites...');
|
|
// getFavioratePlaces();
|
|
// } catch (e) {
|
|
// Log.print("Error: $e");
|
|
// }
|
|
// }),
|
|
// Future(() async {
|
|
// try {
|
|
// Log.print('🔍 Loading Waypoints...');
|
|
// readyWayPoints();
|
|
// } catch (e) {
|
|
// Log.print("Error: $e");
|
|
// }
|
|
// }),
|
|
// Future(() async {
|
|
// try {
|
|
// Log.print('🔍 Loading Rate...');
|
|
// getPassengerRate();
|
|
// } catch (e) {
|
|
// Log.print("Error: $e");
|
|
// }
|
|
// }),
|
|
// Future(() async {
|
|
// try {
|
|
// Log.print('🔍 Loading Coupons...');
|
|
// firstTimeRunToGetCoupon();
|
|
// } catch (e) {
|
|
// Log.print("Error: $e");
|
|
// }
|
|
// }),
|
|
// ]);
|
|
// Log.print('✅ MapPassengerController: _stageNiceToHave complete');
|
|
// try {
|
|
// cardNumber = await SecureStorage().readData(BoxName.cardNumber);
|
|
// } catch (e) {
|
|
// Log.print("Error: $e");
|
|
// }
|
|
// }
|
|
|
|
// void _applyLowEndModeIfNeeded() {
|
|
// // مثال بسيط: يمكنك حفظ فلاج بنظامك (من السيرفر/الإعدادات/الكاش) لتفعيل وضع خفيف
|
|
// // لاحقاً فعّل: map.trafficEnabled=false, buildingsEnabled=false, تبسيط polylines...
|
|
// // controller.lowEndMode = true;
|
|
// }
|
|
|
|
// 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()
|
|
// });
|
|
// }
|
|
|
|
// void _showRideStartNotifications() {
|
|
// // تنبيهات الأسعار حسب نوع السيارة
|
|
// if (['Speed', 'Awfar Car'].contains(box.read(BoxName.carType))) {
|
|
// NotificationController().showNotification('Fixed Price'.tr,
|
|
// 'The captain is responsible for the route.'.tr, 'ding');
|
|
// } else if (['Comfort', 'Lady'].contains(box.read(BoxName.carType))) {
|
|
// NotificationController().showNotification('Attention'.tr,
|
|
// 'The price may increase if the route changes.'.tr, 'ding');
|
|
// }
|
|
// }
|
|
|
|
// /// Checks the current version of assets/style.json and purges the map cache if it has changed.
|
|
// Future<void> _checkAndRefreshMapStyle() async {
|
|
// try {
|
|
// final String styleJson = await rootBundle.loadString('assets/style.json');
|
|
// final Map<String, dynamic> decoded = json.decode(styleJson);
|
|
// final String? currentVersion =
|
|
// decoded['metadata'] != null ? decoded['metadata']['version'] : null;
|
|
|
|
// if (currentVersion == null) return;
|
|
|
|
// final String lastVersion = box.read(BoxName.styleVersion) ?? "0.0.0";
|
|
|
|
// if (currentVersion != lastVersion) {
|
|
// Log.print(
|
|
// "♻️ Map Style Version mismatch ($lastVersion -> $currentVersion). Purging offline cache...");
|
|
// await OfflineMapService.instance.clearCache();
|
|
|
|
// // Final verification check: give native engine time to flush
|
|
// await Future.delayed(const Duration(milliseconds: 500));
|
|
|
|
// box.write(BoxName.styleVersion, currentVersion);
|
|
// Log.print("✅ Style Version updated to $currentVersion");
|
|
// }
|
|
// } catch (e) {
|
|
// Log.print("⚠️ Style version check failed: $e");
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// 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,
|
|
// });
|
|
// }
|