25-8-9-1
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user