This commit is contained in:
Hamza-Ayed
2025-08-09 14:35:09 +03:00
parent ba02d41e6d
commit 889c67a691
35 changed files with 1689 additions and 417 deletions

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:math' as math;
import 'package:sefer_driver/controller/home/captin/behavior_controller.dart';
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
@@ -399,6 +400,9 @@ class MapDriverController extends GetxController {
remainingTimeInPassengerLocatioWait = 0;
timeWaitingPassenger = 0;
box.write(BoxName.statusDriverLocation, 'on');
// // ابدأ التتبع الحيّ لخطوات الملاحة
await startListeningStepNavigation();
// box.write(BoxName.rideStatus, 'Begin'); //
// todo ride details
// Get.find<HomeCaptainController>().changeToAppliedRide('Begin');
@@ -615,7 +619,7 @@ class MapDriverController extends GetxController {
// originalDistanceM - distanceToDestination;
// 3. عتبة ثلث المسافة
final oneThirdDistanceM = originalDistanceM / 3;
final oneThirdDistanceM = originalDistanceM / 4;
// Logging للتتبع
Log.print('originalDistanceM: $originalDistanceM');
@@ -624,7 +628,7 @@ class MapDriverController extends GetxController {
Log.print('oneThirdDistanceM: $oneThirdDistanceM');
// 4. إذا لم يقطع السائق ثلث المسافة، نعرض التأكيد
if (movedDistanceM > oneThirdDistanceM * 2) {
if (movedDistanceM > oneThirdDistanceM) {
MyDialog().getDialog(
'Are you sure to exit ride?'.tr,
'',
@@ -1321,6 +1325,12 @@ class MapDriverController extends GetxController {
}
}
List<LatLngBounds> stepBounds = [];
List<LatLng> stepEndPoints = [];
List<String> stepInstructions = [];
StreamSubscription<Position>? _posSub;
DateTime _lastCameraUpdateTs = DateTime.fromMillisecondsSinceEpoch(0);
getMapDestination(String origin, destination) async {
var url =
('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}');
@@ -1329,56 +1339,251 @@ class MapDriverController extends GetxController {
dataDestination = response['routes'][0]['legs'];
final points =
decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
polylineCoordinatesDestination.clear();
for (int i = 0; i < points.length; i++) {
double lat = points[i][0].toDouble();
double lng = points[i][1].toDouble();
polylineCoordinatesDestination.add(LatLng(lat, lng));
}
// استخراج الخطوات
// الخطوات من أول leg
routeSteps = List<Map<String, dynamic>>.from(dataDestination[0]['steps']);
Log.print('routeSteps: ${routeSteps}');
// حضّر بيانات كل step
_prepareStepData(routeSteps);
currentStepIndex = 0;
if (routeSteps.isNotEmpty) {
currentInstruction =
_parseInstruction(routeSteps[0]['html_instructions']);
Log.print('currentInstruction: ${currentInstruction}');
if (stepInstructions.isNotEmpty) {
currentInstruction = _parseInstruction(stepInstructions[0]);
Log.print('currentInstruction: $currentInstruction');
Get.isRegistered<TextToSpeechController>()
? Get.find<TextToSpeechController>().speakText(currentInstruction)
: Get.put(TextToSpeechController()).speakText(currentInstruction);
}
// ارسم الـ polyline الرئيسي (مثل ما لديك)
final summary = response["routes"][0]["summary"];
final polyline = Polyline(
polylineId: PolylineId(summary.isEmpty ? 'route' : summary),
points: polylineCoordinatesDestination,
width: 10,
color: AppColor.redColor,
);
polyLinesDestination.add(polyline);
// fit للخريطة على أول step
if (stepBounds.isNotEmpty) {
_fitToBounds(stepBounds[0]);
}
update();
// دالة مساعدة لتنظيف التعليمات
// // ابدأ التتبع الحيّ لخطوات الملاحة
// await startListeningStepNavigation();
}
if (polyLinesDestination.isNotEmpty) {
// clearPolyline();
var polyline = Polyline(
polylineId: PolylineId(response["routes"][0]["summary"]),
points: polylineCoordinatesDestination,
width: 10,
color: AppColor.redColor,
);
polyLinesDestination.add(polyline);
// rideConfirm = false;
update();
} else {
var polyline = Polyline(
polylineId: PolylineId(response["routes"][0]["summary"]),
points: polylineCoordinatesDestination,
width: 10,
color: AppColor.redColor,
);
// final dataBounds = response["routes"][0]["bounds"];
// ————————————————————————————————————————————————————————————————
// يُنادى عند كل تحديث للموقع
void _onLocationTick(LatLng pos) async {
if (routeSteps.isEmpty || currentStepIndex >= stepBounds.length) return;
// updateCameraFromBoundsAfterGetMap(dataBounds);
// polyLinesDestination.add(polyline);
// rideConfirm = false;
// Define the northeast and southwest coordinates
// إذا تجاوزت نهاية الـ step الحالية بمسافة صغيرة -> انتقل
final double dToEnd = _distanceMeters(pos, stepEndPoints[currentStepIndex]);
final bool nearEnd = dToEnd <= 25; // عتبة 25م (عدّلها حسب حاجتك)
update();
// إذا دخلت نطاق الـ step التالية -> انتقل
bool insideNext = false;
if (currentStepIndex < stepBounds.length - 1) {
insideNext = _contains(stepBounds[currentStepIndex + 1], pos);
}
if (nearEnd || insideNext) {
_advanceStep();
return;
}
// تحديث الكاميرا بشكل خفيف أثناء الحركة (كل 2 ثانية مثلاً)
final now = DateTime.now();
if (now.difference(_lastCameraUpdateTs).inMilliseconds > 2000) {
_lastCameraUpdateTs = now;
// ركّز على bounds الحالية مع الحفاظ على تتبّع عام
_fitToBounds(stepBounds[currentStepIndex], padding: 60);
}
}
LatLngBounds _boundsFromPoints(List<LatLng> pts) {
double? minLat, maxLat, minLng, maxLng;
for (final p in pts) {
minLat = (minLat == null) ? p.latitude : math.min(minLat, p.latitude);
maxLat = (maxLat == null) ? p.latitude : math.max(maxLat, p.latitude);
minLng = (minLng == null) ? p.longitude : math.min(minLng, p.longitude);
maxLng = (maxLng == null) ? p.longitude : math.max(maxLng, p.longitude);
}
return LatLngBounds(
southwest: LatLng(minLat ?? 0, minLng ?? 0),
northeast: LatLng(maxLat ?? 0, maxLng ?? 0),
);
}
bool _contains(LatLngBounds b, LatLng p) {
final south = math.min(b.southwest.latitude, b.northeast.latitude);
final north = math.max(b.southwest.latitude, b.northeast.latitude);
final west = math.min(b.southwest.longitude, b.northeast.longitude);
final east = math.max(b.southwest.longitude, b.northeast.longitude);
return (p.latitude >= south &&
p.latitude <= north &&
p.longitude >= west &&
p.longitude <= east);
}
double _distanceMeters(LatLng a, LatLng b) {
// هافرساين مبسطة
const R = 6371000.0; // m
final dLat = _deg2rad(b.latitude - a.latitude);
final dLng = _deg2rad(b.longitude - a.longitude);
final s1 = math.sin(dLat / 2);
final s2 = math.sin(dLng / 2);
final aa = s1 * s1 +
math.cos(_deg2rad(a.latitude)) *
math.cos(_deg2rad(b.latitude)) *
s2 *
s2;
final c = 2 * math.atan2(math.sqrt(aa), math.sqrt(1 - aa));
return R * c;
}
double _deg2rad(double d) => d * math.pi / 180.0;
// تحريك الكاميرا لباوند معيّن
Future<void> _fitToBounds(LatLngBounds b, {double padding = 40}) async {
if (mapController == null) return;
try {
// أحياناً يلزم انتظار فريم حتى تكون الخريطة مرسومة
await Future.delayed(const Duration(milliseconds: 50));
await mapController!.animateCamera(
CameraUpdate.newLatLngBounds(b, padding),
);
} catch (_) {
// fallback لو حصلت مشكلة الحجم
final center = LatLng(
(b.northeast.latitude + b.southwest.latitude) / 2,
(b.northeast.longitude + b.southwest.longitude) / 2,
);
await mapController!.animateCamera(CameraUpdate.newLatLng(center));
}
}
// الانتقال للخطوة التالية وتحديث التعليمات والكاميرا
void _advanceStep() {
if (currentStepIndex >= stepBounds.length - 1) return;
currentStepIndex++;
currentInstruction = _parseInstruction(stepInstructions[currentStepIndex]);
// نطق التعليمات
if (Get.isRegistered<TextToSpeechController>()) {
Get.find<TextToSpeechController>().speakText(currentInstruction);
} else {
Get.put(TextToSpeechController()).speakText(currentInstruction);
}
// تركيز الكاميرا على باوند الخطوة الجديدة
_fitToBounds(stepBounds[currentStepIndex]);
update();
}
void _prepareStepData(List<Map<String, dynamic>> steps) {
stepBounds.clear();
stepEndPoints.clear();
stepInstructions.clear();
for (final s in steps) {
// 1) instruction
final html = (s['html_instructions'] ?? '').toString();
stepInstructions.add(html);
// 2) end point
final end = s['end_location'];
final endLatLng = LatLng(
(end['lat'] as num).toDouble(), (end['lng'] as num).toDouble());
stepEndPoints.add(endLatLng);
// 3) bounds من الـ polyline (إن لم يوجد bounds جاهز من الـ API)
List<LatLng> pts = [];
if (s['polyline'] != null && s['polyline']['points'] != null) {
final decoded = decodePolyline(s['polyline']['points']);
for (var p in decoded) {
pts.add(LatLng((p[0] as num).toDouble(), (p[1] as num).toDouble()));
}
} else {
// fallback: استخدم start/end فقط
final start = s['start_location'];
pts.add(LatLng((start['lat'] as num).toDouble(),
(start['lng'] as num).toDouble()));
pts.add(endLatLng);
}
stepBounds.add(_boundsFromPoints(pts));
}
}
// getMapDestination(String origin, destination) async {
// var url =
// ('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}');
// var response = await CRUD().getGoogleApi(link: url, payload: {});
// dataDestination = response['routes'][0]['legs'];
// final points =
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
// for (int i = 0; i < points.length; i++) {
// double lat = points[i][0].toDouble();
// double lng = points[i][1].toDouble();
// polylineCoordinatesDestination.add(LatLng(lat, lng));
// }
// // استخراج الخطوات
// routeSteps = List<Map<String, dynamic>>.from(dataDestination[0]['steps']);
// Log.print('routeSteps: ${routeSteps}');
// currentStepIndex = 0;
// if (routeSteps.isNotEmpty) {
// currentInstruction =
// _parseInstruction(routeSteps[0]['html_instructions']);
// Log.print('currentInstruction: ${currentInstruction}');
// Get.isRegistered<TextToSpeechController>()
// ? Get.find<TextToSpeechController>().speakText(currentInstruction)
// : Get.put(TextToSpeechController()).speakText(currentInstruction);
// }
// update();
// // دالة مساعدة لتنظيف التعليمات
// if (polyLinesDestination.isNotEmpty) {
// // clearPolyline();
// var polyline = Polyline(
// polylineId: PolylineId(response["routes"][0]["summary"]),
// points: polylineCoordinatesDestination,
// width: 10,
// color: AppColor.redColor,
// );
// polyLinesDestination.add(polyline);
// // rideConfirm = false;
// update();
// } else {
// var polyline = Polyline(
// polylineId: PolylineId(response["routes"][0]["summary"]),
// points: polylineCoordinatesDestination,
// width: 10,
// color: AppColor.redColor,
// );
// // final dataBounds = response["routes"][0]["bounds"];
// // updateCameraFromBoundsAfterGetMap(dataBounds);
// // polyLinesDestination.add(polyline);
// // rideConfirm = false;
// // Define the northeast and southwest coordinates
// update();
// }
// }
void updateCameraFromBoundsAfterGetMap(dynamic response) {
final bounds = response["routes"][0]["bounds"];
LatLng northeast =
@@ -1516,4 +1721,19 @@ class MapDriverController extends GetxController {
// checkIsDriverNearPassenger();
super.onInit();
}
Future<void> startListeningStepNavigation() async {
_posSub?.cancel();
_posSub = Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.best,
distanceFilter: 5, // حدّث كل ~5 متر لتقليل الاهتزاز
),
).listen((pos) => _onLocationTick(LatLng(pos.latitude, pos.longitude)));
}
void stopListeningStepNavigation() {
_posSub?.cancel();
_posSub = null;
}
}