new backend 29-04-2026
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
@@ -10,6 +11,7 @@ import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
||||
import 'package:bubble_head/bubble.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.dart' as geo;
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
@@ -20,6 +22,7 @@ import '../../../constant/colors.dart';
|
||||
import '../../../constant/country_polygons.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../constant/table_names.dart';
|
||||
import '../../../env/env.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../../print.dart';
|
||||
import '../../../views/Rate/rate_passenger.dart';
|
||||
@@ -31,7 +34,8 @@ import '../../functions/location_controller.dart';
|
||||
import '../../functions/tts.dart';
|
||||
import 'behavior_controller.dart';
|
||||
|
||||
class MapDriverController extends GetxController {
|
||||
class MapDriverController extends GetxController
|
||||
with GetSingleTickerProviderStateMixin {
|
||||
bool isLoading = true;
|
||||
final formKey1 = GlobalKey<FormState>();
|
||||
final formKey2 = GlobalKey<FormState>();
|
||||
@@ -97,6 +101,26 @@ class MapDriverController extends GetxController {
|
||||
int remainingTimeToShowPassengerInfoWindowFromDriver = 25;
|
||||
int remainingTimeToPassenger = 60;
|
||||
int remainingTimeInPassengerLocatioWait = 60;
|
||||
|
||||
// ─── Navigation & Smoothing ──────────────────────────────────────────
|
||||
AnimationController? _animController;
|
||||
LatLng? smoothedLocation;
|
||||
double smoothedHeading = 0.0;
|
||||
LatLng? _oldLoc;
|
||||
LatLng? _targetLoc;
|
||||
double _oldHeading = 0.0;
|
||||
double _targetHeading = 0.0;
|
||||
|
||||
List<Map<String, dynamic>> routeSteps = [];
|
||||
int currentStepIndex = 0;
|
||||
String currentInstruction = "";
|
||||
String nextInstruction = "";
|
||||
String distanceToNextStep = "";
|
||||
int currentManeuverModifier = 0;
|
||||
bool _nextInstructionSpoken = false;
|
||||
bool isTtsEnabled = true;
|
||||
StreamSubscription<geo.Position>? _locationSubscription;
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
bool isDriverNearPassengerStart = false;
|
||||
IntaleqMapController? mapController;
|
||||
late LatLng myLocation;
|
||||
@@ -111,10 +135,7 @@ class MapDriverController extends GetxController {
|
||||
LatLng latLngPassengerLocation = LatLng(0, 0);
|
||||
late LatLng latLngPassengerDestination = LatLng(0, 0);
|
||||
|
||||
List<Map<String, dynamic>> routeSteps = [];
|
||||
String currentInstruction = "";
|
||||
int currentStepIndex = 0;
|
||||
bool isTtsEnabled = false;
|
||||
|
||||
|
||||
// في MapDriverController
|
||||
|
||||
@@ -152,7 +173,9 @@ class MapDriverController extends GetxController {
|
||||
_posSub?.cancel();
|
||||
_posSub = null;
|
||||
|
||||
// mapController?.dispose();
|
||||
_animController?.dispose();
|
||||
_locationSubscription?.cancel();
|
||||
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
@@ -224,7 +247,7 @@ class MapDriverController extends GetxController {
|
||||
if (isCameraLocked && mapController != null) {
|
||||
double bearing = (speedKmh > 5) ? heading : 0.0;
|
||||
// ملاحظة: يمكنك تخزين آخر bearing معروف واستخدامه عند التوقف لتحسين التجربة
|
||||
_animateCameraToNavigationMode(newLoc, heading);
|
||||
_animateCameraToNavigationMode(newLoc, bearing);
|
||||
}
|
||||
|
||||
// 4. فحص التعليمات الصوتية
|
||||
@@ -315,6 +338,8 @@ class MapDriverController extends GetxController {
|
||||
// 2. تنظيف الحالة
|
||||
box.write(BoxName.rideStatus, 'Canceled'); // أو الحالة الافتراضية
|
||||
box.remove(BoxName.rideArguments);
|
||||
box.remove(BoxName.passengerID);
|
||||
box.remove(BoxName.rideId);
|
||||
|
||||
// 3. عرض رسالة للسائق
|
||||
if (Get.isDialogOpen == true) {
|
||||
@@ -379,8 +404,10 @@ class MapDriverController extends GetxController {
|
||||
box.write(BoxName.statusDriverLocation, 'blocked');
|
||||
|
||||
// عرض رسالة العقوبة
|
||||
Get.snackbar("Your account is temporarily restricted ⛔".tr,
|
||||
"Due to excessive cancellations (3 times), receiving orders has been suspended for 4 hours.".tr,
|
||||
Get.snackbar(
|
||||
"Your account is temporarily restricted ⛔".tr,
|
||||
"Due to excessive cancellations (3 times), receiving orders has been suspended for 4 hours."
|
||||
.tr,
|
||||
duration: Duration(seconds: 8),
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
@@ -396,6 +423,8 @@ class MapDriverController extends GetxController {
|
||||
// تنظيف البيانات
|
||||
box.remove(BoxName.rideArgumentsFromBackground);
|
||||
box.remove(BoxName.rideArguments);
|
||||
box.remove(BoxName.passengerID);
|
||||
box.remove(BoxName.rideId);
|
||||
box.write(BoxName.rideStatus, 'Cancel');
|
||||
|
||||
// تسجيل محلي (اختياري)
|
||||
@@ -726,10 +755,10 @@ class MapDriverController extends GetxController {
|
||||
|
||||
// إغلاق مؤشر التحميل لأننا حصلنا على النتيجة
|
||||
if (Get.isDialogOpen == true) {
|
||||
navigatorKey.currentState?.pop();
|
||||
Get.back();
|
||||
}
|
||||
|
||||
if (distanceToPassenger < 100) {
|
||||
if (distanceToPassenger < 150) {
|
||||
// زدت المسافة قليلاً لمرونة أكبر (150م)
|
||||
|
||||
// --- أ) تحديث الحالة المحلية (Optimistic Update) ---
|
||||
@@ -772,16 +801,26 @@ class MapDriverController extends GetxController {
|
||||
});
|
||||
} else {
|
||||
// --- حالة الرفض (بعيد جداً) ---
|
||||
MyDialog().getDialog('You are far from passenger location'.tr,
|
||||
'Please go closer to the passenger location (less than 150m)'.tr,
|
||||
() {
|
||||
// الديالوج يغلق نفسه الآن تلقائياً
|
||||
});
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('You are far from passenger location'.tr),
|
||||
content: Text(
|
||||
'Please go closer to the passenger location (less than 150m)'
|
||||
.tr),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('OK'.tr),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// تنظيف اللودينج في حال حدوث خطأ غير متوقع
|
||||
if (Get.isDialogOpen == true) {
|
||||
navigatorKey.currentState?.pop();
|
||||
Get.back();
|
||||
}
|
||||
Log.print("Error starting ride: $e");
|
||||
Get.snackbar("Error", "Could not start ride. Please check internet.");
|
||||
@@ -1066,6 +1105,8 @@ class MapDriverController extends GetxController {
|
||||
isPriceWindow = false;
|
||||
box.write(BoxName.rideStatus, 'Finished');
|
||||
box.write(BoxName.statusDriverLocation, 'off');
|
||||
box.remove(BoxName.passengerID);
|
||||
box.remove(BoxName.rideId);
|
||||
|
||||
// 4. حساب التكلفة النهائية (Logic)
|
||||
_calculateFinalTotalCost();
|
||||
@@ -1382,7 +1423,7 @@ class MapDriverController extends GetxController {
|
||||
|
||||
// 🟢 2. المنطق المتغير
|
||||
const double longTripPerMin = 600.0;
|
||||
const double mediumDistThresholdKm = 25.0;
|
||||
|
||||
const double longDistThresholdKm = 35.0;
|
||||
|
||||
// نسبة التخفيض
|
||||
@@ -1588,9 +1629,26 @@ class MapDriverController extends GetxController {
|
||||
if (mapController == null) return;
|
||||
|
||||
try {
|
||||
// 1. طلب المسار من الباكيج
|
||||
final response =
|
||||
await mapController!.getDirections(origin, destination, steps: true);
|
||||
// 1. طلب المسار من السيرفر الموحد (SaaS) لضمان الدقة وتفادي الـ 401
|
||||
final saasUrl = Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: {
|
||||
'fromLat': origin.latitude.toString(),
|
||||
'fromLng': origin.longitude.toString(),
|
||||
'toLat': destination.latitude.toString(),
|
||||
'toLng': destination.longitude.toString(),
|
||||
'steps': 'true', // نحتاجها للملاحة والتوجيه
|
||||
'alternatives': 'false',
|
||||
});
|
||||
|
||||
final httpResponse = await http.get(saasUrl, headers: {
|
||||
'x-api-key': Env.mapSaasKey,
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
|
||||
if (httpResponse.statusCode != 200) {
|
||||
throw Exception("Routing request failed: ${httpResponse.statusCode}");
|
||||
}
|
||||
|
||||
final response = jsonDecode(httpResponse.body);
|
||||
|
||||
// 2. التعامل مع الـ JSON المباشر (الذي أرسله المستخدم)
|
||||
// إذا كان الـ response يحتوي على الحقول مباشرة في الجذر
|
||||
@@ -1628,41 +1686,36 @@ class MapDriverController extends GetxController {
|
||||
color: routeColor,
|
||||
));
|
||||
|
||||
// د) معالجة الخطوات (Legs & Steps) إذا توفرت في الـ JSON
|
||||
List<dynamic> legs = response['legs'] ?? [];
|
||||
if (legs.isNotEmpty) {
|
||||
final stepsList =
|
||||
List<Map<String, dynamic>>.from(legs[0]['steps'] ?? []);
|
||||
// د) معالجة الخطوات (Instructions) للسيرفر الموحد
|
||||
final List<dynamic> instructions = response['instructions'] ?? [];
|
||||
if (instructions.isNotEmpty) {
|
||||
routeSteps = List<Map<String, dynamic>>.from(instructions.map((e) {
|
||||
int endIdx = (e['interval'] as List)[1];
|
||||
// التأكد من أن الـ index لا يتجاوز طول المسار
|
||||
if (endIdx >= fullRoute.length) endIdx = fullRoute.length - 1;
|
||||
|
||||
for (var step in stepsList) {
|
||||
step['html_instructions'] = _createInstructionFromManeuverSmart(step);
|
||||
|
||||
if (step['maneuver'] != null &&
|
||||
step['maneuver']['location'] != null) {
|
||||
var loc = step['maneuver']['location'];
|
||||
// التعامل مع تنسيق OSRM [lng, lat]
|
||||
if (loc is List && loc.length >= 2) {
|
||||
step['end_location'] = {'lat': loc[1], 'lng': loc[0]};
|
||||
} else if (loc is Map) {
|
||||
step['end_location'] = loc;
|
||||
return {
|
||||
'html_instructions': e['text'] ?? "",
|
||||
'sign': e['sign'] ?? 0,
|
||||
'end_location': {
|
||||
'lat': fullRoute[endIdx].latitude,
|
||||
'lng': fullRoute[endIdx].longitude,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}));
|
||||
|
||||
routeSteps = stepsList;
|
||||
currentStepIndex = 0;
|
||||
currentInstruction = routeSteps[0]['html_instructions'];
|
||||
currentManeuverModifier = routeSteps[0]['sign'];
|
||||
_nextInstructionSpoken = false;
|
||||
|
||||
// نطق أول تعليمة
|
||||
if (routeSteps.isNotEmpty) {
|
||||
currentInstruction = routeSteps[0]['html_instructions'];
|
||||
if (Get.isRegistered<TextToSpeechController>()) {
|
||||
Get.find<TextToSpeechController>().speakText(currentInstruction);
|
||||
}
|
||||
if (Get.isRegistered<TextToSpeechController>() && isTtsEnabled) {
|
||||
Get.find<TextToSpeechController>().speakText(currentInstruction);
|
||||
}
|
||||
} else {
|
||||
// في حال عدم وجود steps، نقوم بتصفيرها
|
||||
routeSteps = [];
|
||||
currentInstruction = "";
|
||||
currentManeuverModifier = 0;
|
||||
}
|
||||
|
||||
// هـ) تحريك الكاميرا لتشمل المسار
|
||||
@@ -1678,119 +1731,7 @@ class MapDriverController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 دالة الترجمة المحسنة (من NavigationController)
|
||||
String _createInstructionFromManeuverSmart(Map<String, dynamic> step) {
|
||||
if (step['maneuver'] == null) return "Continue straight".tr;
|
||||
|
||||
final maneuver = step['maneuver'];
|
||||
final type = maneuver['type'] ?? 'continue';
|
||||
final modifier = maneuver['modifier'] ?? 'straight';
|
||||
final name = step['name'] ?? '';
|
||||
|
||||
String instruction = "";
|
||||
|
||||
switch (type) {
|
||||
case 'depart':
|
||||
instruction = "Go".tr;
|
||||
break;
|
||||
case 'arrive':
|
||||
return "You have arrived at your destination, @name".trParams({'name': name});
|
||||
case 'turn':
|
||||
case 'fork':
|
||||
case 'roundabout':
|
||||
case 'merge':
|
||||
case 'on ramp':
|
||||
case 'off ramp':
|
||||
case 'end of road':
|
||||
instruction =
|
||||
_getTurnInstruction(modifier); // استخدم نفس دالتك المساعدة هنا
|
||||
break;
|
||||
default:
|
||||
instruction = "Continue straight".tr;
|
||||
}
|
||||
|
||||
if (name.isNotEmpty) {
|
||||
if (type == 'continue') {
|
||||
instruction += " ${"on".tr} $name";
|
||||
} else {
|
||||
instruction += " ${"towards".tr} $name";
|
||||
}
|
||||
}
|
||||
return instruction;
|
||||
}
|
||||
|
||||
String _createInstructionFromManeuver(Map<String, dynamic> step) {
|
||||
final maneuver = step['maneuver'];
|
||||
final type = maneuver['type'] ?? 'continue';
|
||||
final modifier = maneuver['modifier'] ?? 'straight';
|
||||
final name = step['name'] ?? '';
|
||||
|
||||
String instruction = "";
|
||||
|
||||
switch (type) {
|
||||
case 'depart':
|
||||
instruction = "Go".tr;
|
||||
break;
|
||||
case 'arrive':
|
||||
instruction = "You have arrived at your destination".tr;
|
||||
if (name.isNotEmpty) instruction += "، $name";
|
||||
return instruction;
|
||||
case 'turn':
|
||||
case 'fork':
|
||||
case 'off ramp':
|
||||
case 'on ramp':
|
||||
case 'roundabout':
|
||||
instruction = _getTurnInstruction(modifier);
|
||||
break;
|
||||
case 'continue':
|
||||
instruction = "Continue".tr;
|
||||
break;
|
||||
default:
|
||||
instruction = "Head".tr;
|
||||
}
|
||||
|
||||
if (name.isNotEmpty) {
|
||||
if (instruction == "استمر") {
|
||||
instruction += " ${"on".tr} $name";
|
||||
} else {
|
||||
instruction += " ${"to".tr} $name";
|
||||
}
|
||||
} else if (type == 'continue' && modifier == 'straight') {
|
||||
instruction = "Continue straight".tr;
|
||||
}
|
||||
|
||||
return instruction;
|
||||
}
|
||||
|
||||
/**
|
||||
* دالة مساعدة لترجمة تعليمات الانعطاف
|
||||
*/
|
||||
String _getTurnInstruction(String modifier) {
|
||||
switch (modifier) {
|
||||
case 'uturn':
|
||||
return "Make a U-turn".tr;
|
||||
case 'sharp right':
|
||||
return "Turn sharp right".tr;
|
||||
case 'right':
|
||||
return "Turn right".tr;
|
||||
case 'slight right':
|
||||
return "Turn slight right".tr;
|
||||
case 'straight':
|
||||
return "Continue straight".tr;
|
||||
case 'slight left':
|
||||
return "Turn slight left".tr;
|
||||
case 'left':
|
||||
return "Turn left".tr;
|
||||
case 'sharp left':
|
||||
return "Turn sharp left".tr;
|
||||
default:
|
||||
return "Head".tr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* دالة لحساب حدود الخريطة (Bounds) من قائمة نقاط
|
||||
*/
|
||||
LatLngBounds _boundsFromLatLngList(List<LatLng> list) {
|
||||
assert(list.isNotEmpty);
|
||||
double? x0, x1, y0, y1;
|
||||
@@ -1809,66 +1750,7 @@ class MapDriverController extends GetxController {
|
||||
northeast: LatLng(x1!, y1!), southwest: LatLng(x0!, y0!));
|
||||
}
|
||||
|
||||
// الدالة التي يتم استدعاؤها من خدمة الموقع كل 5 ثوان (أو حسب الفترة المحددة)
|
||||
void onLocationUpdated(Position newPosition) {
|
||||
myLocation = LatLng(newPosition.latitude, newPosition.longitude);
|
||||
heading = newPosition.heading;
|
||||
|
||||
// -->> منطق قياس الأداء يبدأ هنا <<--
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
// -->> منطق الملاحة وتحديث المسار <<--
|
||||
_onLocationTick(myLocation);
|
||||
|
||||
stopwatch.stop();
|
||||
|
||||
// -->> تحليل الأداء واتخاذ القرار <<--
|
||||
if (!_hasMadeDecision) {
|
||||
_performanceReadings.add(stopwatch.elapsedMilliseconds);
|
||||
if (_performanceReadings.length >= _readingsToCollect) {
|
||||
_analyzePerformance();
|
||||
_hasMadeDecision = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// =================================================================
|
||||
// 3. منطق الملاحة الداخلي (Internal Navigation Logic)
|
||||
// =================================================================
|
||||
|
||||
void _onLocationTick(LatLng pos) {
|
||||
if (activeRouteSteps.isEmpty || currentStepIndex >= _stepBounds.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
final double dToEnd =
|
||||
_distanceMeters(pos, _stepEndPoints[currentStepIndex]);
|
||||
if (dToEnd <= 35) {
|
||||
// 35 متر عتبة للوصول لنهاية الخطوة
|
||||
_advanceStep();
|
||||
}
|
||||
}
|
||||
|
||||
void _advanceStep() {
|
||||
if (currentStepIndex >= _stepBounds.length - 1) {
|
||||
// وصل للنهاية
|
||||
currentInstruction = "You have arrived at your destination".tr;
|
||||
return;
|
||||
}
|
||||
|
||||
currentStepIndex++;
|
||||
currentInstruction = _parseInstruction(
|
||||
activeRouteSteps[currentStepIndex]['html_instructions']);
|
||||
Get.isRegistered<TextToSpeechController>()
|
||||
? Get.find<TextToSpeechController>().speakText(currentInstruction)
|
||||
: Get.put(TextToSpeechController()).speakText(currentInstruction);
|
||||
|
||||
// -->> هنا يتم تحديث لون المسار <<--
|
||||
_updateTraveledPath();
|
||||
|
||||
_fitToBounds(_stepBounds[currentStepIndex],
|
||||
padding: 80); // تقريب الكاميرا على الخطوة التالية
|
||||
update();
|
||||
}
|
||||
// داخل MapDriverController
|
||||
|
||||
Future<void> markDriverAsArrived() async {
|
||||
@@ -1918,53 +1800,7 @@ class MapDriverController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
void _updateTraveledPath() {
|
||||
// استخراج كل النقاط للخطوات التي تم اجتيازها
|
||||
List<LatLng> pointsForTraveledSteps = [];
|
||||
for (int i = 0; i < currentStepIndex; i++) {
|
||||
final stepPolyline = activeRouteSteps[i]['polyline']['points'];
|
||||
pointsForTraveledSteps.addAll(decodePolylineToLatLng(stepPolyline));
|
||||
}
|
||||
traveledPathPoints.assignAll(pointsForTraveledSteps);
|
||||
}
|
||||
|
||||
void _prepareStepData(List<Map<String, dynamic>> steps) {
|
||||
_stepBounds.clear();
|
||||
_stepEndPoints.clear();
|
||||
|
||||
for (final s in steps) {
|
||||
// 1. استخراج نقطة النهاية (الكود الحالي سليم)
|
||||
final end = s['end_location'];
|
||||
_stepEndPoints.add(LatLng(
|
||||
(end['lat'] as num).toDouble(),
|
||||
(end['lng'] as num).toDouble(),
|
||||
));
|
||||
|
||||
// 2. فك تشفير البوليلاين الخاص بالخطوة وتحويله إلى LatLng
|
||||
// -->> هنا تم التصحيح <<--
|
||||
List<LatLng> pts = PolylineUtils.decode(s['polyline']['points']);
|
||||
|
||||
// أضف نقاط البداية والنهاية إذا لم تكن موجودة في البوليلاين لضمان دقة الحدود
|
||||
if (pts.isNotEmpty) {
|
||||
final start = s['start_location'];
|
||||
final startLatLng = LatLng(
|
||||
(start['lat'] as num).toDouble(), (start['lng'] as num).toDouble());
|
||||
if (pts.first != startLatLng) {
|
||||
pts.insert(0, startLatLng);
|
||||
}
|
||||
}
|
||||
|
||||
_stepBounds.add(_boundsFromPoints(pts));
|
||||
}
|
||||
}
|
||||
|
||||
// A helper function to decode and convert the polyline string
|
||||
List<LatLng> decodePolylineToLatLng(String polylineString) {
|
||||
// 1. Decode the string into a list of number lists (e.g., [[lat, lng], ...])
|
||||
List<LatLng> decodedPoints = PolylineUtils.decode(polylineString);
|
||||
|
||||
return decodedPoints;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 4. منطق الأداء الذكي (Smart Performance Logic)
|
||||
@@ -1981,7 +1817,8 @@ class MapDriverController extends GetxController {
|
||||
void _suggestOptimization() {
|
||||
Get.snackbar(
|
||||
"Improve app performance".tr,
|
||||
"To ensure the best experience, we suggest adjusting the settings to suit your device. Would you like to proceed?".tr,
|
||||
"To ensure the best experience, we suggest adjusting the settings to suit your device. Would you like to proceed?"
|
||||
.tr,
|
||||
duration: const Duration(seconds: 15),
|
||||
mainButton: TextButton(
|
||||
child: Text("Yes, optimize".tr),
|
||||
@@ -1998,13 +1835,7 @@ class MapDriverController extends GetxController {
|
||||
// =================================================================
|
||||
// 5. دوال مساعدة (Helper Functions)
|
||||
// =================================================================
|
||||
void _resetRouteState() {
|
||||
activeRouteSteps.clear();
|
||||
traveledPathPoints.clear();
|
||||
upcomingPathPoints.clear();
|
||||
_allPointsForActiveRoute.clear();
|
||||
currentStepIndex = 0;
|
||||
}
|
||||
|
||||
|
||||
String _parseInstruction(String html) =>
|
||||
html.replaceAll(RegExp(r'<[^>]*>'), '');
|
||||
@@ -2108,16 +1939,7 @@ class MapDriverController extends GetxController {
|
||||
);
|
||||
}
|
||||
|
||||
bool _contains(LatLngBounds b, LatLng p) {
|
||||
final south = math.min(b.southwest.latitude, b.northeast.latitude);
|
||||
final north = math.max(b.southwest.latitude, b.northeast.latitude);
|
||||
final west = math.min(b.southwest.longitude, b.northeast.longitude);
|
||||
final east = math.max(b.southwest.longitude, b.northeast.longitude);
|
||||
return (p.latitude >= south &&
|
||||
p.latitude <= north &&
|
||||
p.longitude >= west &&
|
||||
p.longitude <= east);
|
||||
}
|
||||
|
||||
|
||||
double _distanceMeters(LatLng a, LatLng b) {
|
||||
// هافرساين مبسطة
|
||||
@@ -2188,6 +2010,10 @@ class MapDriverController extends GetxController {
|
||||
durationOfRideValue = Get.arguments['durationOfRideValue'];
|
||||
paymentAmount = Get.arguments['paymentAmount'];
|
||||
paymentMethod = Get.arguments['paymentMethod'];
|
||||
|
||||
// 🔥 حفظ البيانات في الذاكرة المحلية فوراً (لفصل السوكيت عن الكنترولر)
|
||||
box.write(BoxName.passengerID, passengerId.toString());
|
||||
box.write(BoxName.rideId, rideId.toString());
|
||||
isHaveSteps = Get.arguments['isHaveSteps'];
|
||||
step0 = Get.arguments['step0'];
|
||||
step1 = Get.arguments['step1'];
|
||||
@@ -2208,7 +2034,7 @@ class MapDriverController extends GetxController {
|
||||
Get.find<LocationController>().myLocation.latitude.toString();
|
||||
String lng =
|
||||
Get.find<LocationController>().myLocation.longitude.toString();
|
||||
String origin = '$lat,$lng';
|
||||
|
||||
// Set the origin and destination coordinates for the Google Maps directions request.
|
||||
Future.delayed(const Duration(seconds: 1));
|
||||
getRoute(
|
||||
@@ -2222,7 +2048,7 @@ class MapDriverController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
latlng(String passengerLocation, passengerDestination) {
|
||||
void latlng(String passengerLocation, String passengerDestination) {
|
||||
double latPassengerLocation =
|
||||
double.parse(passengerLocation.toString().split(',')[0]);
|
||||
double lngPassengerLocation =
|
||||
@@ -2257,7 +2083,6 @@ class MapDriverController extends GetxController {
|
||||
@override
|
||||
void onInit() async {
|
||||
mapAPIKEY = await storage.read(key: BoxName.mapAPIKEY);
|
||||
// Get the passenger location from the arguments.
|
||||
await argumentLoading();
|
||||
Get.put(FirebaseMessagesController());
|
||||
runGoogleMapDirectly();
|
||||
@@ -2265,24 +2090,141 @@ class MapDriverController extends GetxController {
|
||||
addCustomPassengerIcon();
|
||||
addCustomStartIcon();
|
||||
addCustomEndIcon();
|
||||
|
||||
if (!Get.isRegistered<TextToSpeechController>()) {
|
||||
Get.put(TextToSpeechController(), permanent: true);
|
||||
// permanent: true تمنع حذفه عند تغيير الصفحات
|
||||
}
|
||||
// updateMarker();
|
||||
// updateLocation();
|
||||
|
||||
_animController = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 1000));
|
||||
_animController!.addListener(() {
|
||||
if (_oldLoc != null && _targetLoc != null) {
|
||||
final t = _animController!.value;
|
||||
final lat = lerpDouble(_oldLoc!.latitude, _targetLoc!.latitude, t)!;
|
||||
final lng = lerpDouble(_oldLoc!.longitude, _targetLoc!.longitude, t)!;
|
||||
smoothedLocation = LatLng(lat, lng);
|
||||
smoothedHeading = _lerpAngle(_oldHeading, _targetHeading, t);
|
||||
update();
|
||||
}
|
||||
});
|
||||
|
||||
_startLocationListening();
|
||||
|
||||
startTimerToShowPassengerInfoWindowFromDriver();
|
||||
// durationToAdd = Duration(seconds: int.parse(duration));
|
||||
durationToAdd = Duration(seconds: parseDurationToInt(duration));
|
||||
hours = durationToAdd.inHours;
|
||||
minutes = (durationToAdd.inMinutes % 60).round();
|
||||
calculateConsumptionFuel();
|
||||
// updateLocation();// for now to test it
|
||||
// cancelCheckRidefromPassenger();
|
||||
// checkIsDriverNearPassenger();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
void _startLocationListening() {
|
||||
_locationSubscription?.cancel();
|
||||
_locationSubscription = geo.Geolocator.getPositionStream(
|
||||
locationSettings: const geo.LocationSettings(
|
||||
accuracy: geo.LocationAccuracy.bestForNavigation,
|
||||
distanceFilter: 2,
|
||||
),
|
||||
).listen((geo.Position pos) {
|
||||
_handleLocationUpdate(pos);
|
||||
});
|
||||
}
|
||||
|
||||
void _handleLocationUpdate(geo.Position pos) {
|
||||
final newLoc = LatLng(pos.latitude, pos.longitude);
|
||||
_oldLoc = smoothedLocation ?? newLoc;
|
||||
_targetLoc = newLoc;
|
||||
|
||||
_oldHeading = smoothedHeading;
|
||||
if (pos.speed > 0.5) {
|
||||
_targetHeading = pos.heading;
|
||||
} else {
|
||||
_targetHeading = _oldHeading;
|
||||
}
|
||||
|
||||
_animController?.forward(from: 0.0);
|
||||
|
||||
if (routeSteps.isNotEmpty) {
|
||||
_checkNavigationStep(newLoc);
|
||||
}
|
||||
}
|
||||
|
||||
double _lerpAngle(double from, double to, double t) {
|
||||
final double diff = ((to - from + 540.0) % 360.0) - 180.0;
|
||||
return (from + diff * t + 360.0) % 360.0;
|
||||
}
|
||||
|
||||
void _checkNavigationStep(LatLng pos) {
|
||||
if (routeSteps.isEmpty || currentStepIndex >= routeSteps.length) return;
|
||||
|
||||
final step = routeSteps[currentStepIndex];
|
||||
final stepLoc = step['end_location'];
|
||||
if (stepLoc == null) return;
|
||||
|
||||
final double stepLat = stepLoc['lat'];
|
||||
final double stepLng = stepLoc['lng'];
|
||||
|
||||
final distance = geo.Geolocator.distanceBetween(
|
||||
pos.latitude, pos.longitude, stepLat, stepLng);
|
||||
|
||||
distanceToNextStep = distance > 1000
|
||||
? "${(distance / 1000).toStringAsFixed(1)} km"
|
||||
: "${distance.toStringAsFixed(0)} m";
|
||||
|
||||
if (distance < 50 &&
|
||||
!_nextInstructionSpoken &&
|
||||
(currentStepIndex + 1) < routeSteps.length) {
|
||||
final nextText =
|
||||
routeSteps[currentStepIndex + 1]['html_instructions'] ?? "";
|
||||
if (isTtsEnabled) {
|
||||
Get.find<TextToSpeechController>().speakText(nextText);
|
||||
}
|
||||
_nextInstructionSpoken = true;
|
||||
}
|
||||
|
||||
if (distance < 25) {
|
||||
_advanceStep();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
IconData get currentManeuverIcon {
|
||||
switch (currentManeuverModifier) {
|
||||
case 4: // Arrive
|
||||
return Icons.place_rounded;
|
||||
case 6: // Roundabout
|
||||
return Icons.roundabout_right_rounded;
|
||||
case 2: // Right
|
||||
return Icons.turn_right_rounded;
|
||||
case 3: // Slight Right
|
||||
return Icons.turn_slight_right_rounded;
|
||||
case -2: // Left
|
||||
return Icons.turn_left_rounded;
|
||||
case -1: // Slight Left
|
||||
return Icons.turn_slight_left_rounded;
|
||||
case 7: // Keep Right
|
||||
return Icons.turn_right_rounded;
|
||||
case -7: // Keep Left
|
||||
return Icons.turn_left_rounded;
|
||||
case 0: // Straight
|
||||
return Icons.straight_rounded;
|
||||
default:
|
||||
return Icons.straight_rounded;
|
||||
}
|
||||
}
|
||||
|
||||
void _advanceStep() {
|
||||
currentStepIndex++;
|
||||
if (currentStepIndex < routeSteps.length) {
|
||||
currentInstruction =
|
||||
routeSteps[currentStepIndex]['html_instructions'] ?? "";
|
||||
currentManeuverModifier = routeSteps[currentStepIndex]['sign'] ?? 0;
|
||||
_nextInstructionSpoken = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int parseDurationToInt(dynamic value) {
|
||||
if (value == null) return 0;
|
||||
String text = value.toString();
|
||||
|
||||
Reference in New Issue
Block a user