feat: refactor financial wallet UI components and add offline map service support

This commit is contained in:
Hamza-Ayed
2026-04-21 00:35:30 +03:00
parent 4293d20561
commit b92db3bb39
99 changed files with 22888 additions and 27387 deletions

View File

@@ -3,15 +3,9 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'dart:ui' as ui; // للألوان
// import 'package:google_maps_flutter/google_maps_flutter.dart';
// import 'package:flutter_map/flutter_map.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:http/http.dart' as http;
// import 'package:latlong2/latlong.dart'
// as latlng; // هذا مهم جداً للتعامل مع إحداثيات OSM
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
import 'dart:async';
import '../../../constant/links.dart';
@@ -35,7 +29,8 @@ class HomeCaptainController extends GetxController {
Timer? activeTimer;
Map data = {};
bool isHomeMapActive = true;
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
InlqBitmap carIcon = InlqBitmap.defaultMarker;
bool isMapReadyForCommands = false;
bool isLoading = true;
late double kazan = 0;
double latePrice = 0;
@@ -55,7 +50,8 @@ class HomeCaptainController extends GetxController {
String totalMoneyInSEFER = '0';
String totalDurationToday = '0';
Timer? timer;
late LatLng myLocation = const LatLng(33.5138, 36.2765);
Timer? _cameraFollowTimer;
LatLng myLocation = const LatLng(33.5138, 36.2765);
String totalPoints = '0';
String countRefuse = '0';
bool mapType = false;
@@ -81,10 +77,13 @@ class HomeCaptainController extends GetxController {
// دالة لتغيير حالة الهيت ماب (عرض/إخفاء)
void toggleHeatmap() async {
isHeatmapVisible = !isHeatmapVisible;
print("🔥 [Heatmap] Visibility toggled to: $isHeatmapVisible");
if (isHeatmapVisible) {
await fetchAndDrawHeatmap();
startHeatmapCycle();
} else {
_heatmapTimer?.cancel();
heatmapPolygons.clear();
print("🧹 [Heatmap] Polygons cleared.");
}
update(); // تحديث الواجهة
}
@@ -96,6 +95,7 @@ class HomeCaptainController extends GetxController {
// دالة جلب البيانات ورسم الخريطة
Future<void> fetchAndDrawHeatmap() async {
print("🚀 [Heatmap] Fetching live data...");
// استخدم الرابط المباشر لملف JSON لسرعة قصوى
final String jsonUrl =
"https://api.intaleq.xyz/intaleq/ride/rides/heatmap_live.json";
@@ -107,20 +107,26 @@ class HomeCaptainController extends GetxController {
if (response.statusCode == 200) {
final List<dynamic> data = json.decode(response.body);
print("✅ [Heatmap] Data received. Points count: ${data.length}");
_generatePolygons(data);
} else {
print("⚠️ [Heatmap] Server error: ${response.statusCode}");
}
} catch (e) {
print("Heatmap Error: $e");
print("❌ [Heatmap] Error: $e");
}
}
void _generatePolygons(List<dynamic> data) {
print("🎨 [Heatmap] Processing polygons...");
Set<Polygon> tempPolygons = {};
// الأوفست لرسم المربع (نصف حجم الشبكة)
// الشبكة دقتها 0.01 درجة، لذا نصفها 0.005
double offset = 0.005;
int highCount = 0, medCount = 0, lowCount = 0;
for (var point in data) {
double lat = double.parse(point['lat'].toString());
double lng = double.parse(point['lng'].toString());
@@ -133,24 +139,27 @@ class HomeCaptainController extends GetxController {
// 🧠 منطق الألوان: ندمج الذكاء (Intensity) مع العدد (Count)
if (intensity == 'high' || count >= 5) {
highCount++;
// منطقة مشتعلة (أحمر)
// إما فيها طلبات ضائعة (Timeout) أو فيها عدد كبير من الطلبات
color = Colors.red.withOpacity(0.35);
strokeColor = Colors.red.withOpacity(0.8);
color = Colors.red.withValues(alpha: 0.35);
strokeColor = Colors.red.withValues(alpha: 0.8);
} else if (intensity == 'medium' || count >= 3) {
medCount++;
// منطقة متوسطة (برتقالي)
color = Colors.orange.withOpacity(0.35);
strokeColor = Colors.orange.withOpacity(0.8);
color = Colors.orange.withValues(alpha: 0.35);
strokeColor = Colors.orange.withValues(alpha: 0.8);
} else {
lowCount++;
// منطقة خفيفة (أصفر)
color = Colors.yellow.withOpacity(0.3);
strokeColor = Colors.yellow.withOpacity(0.6);
color = Colors.yellow.withValues(alpha: 0.3);
strokeColor = Colors.yellow.withValues(alpha: 0.6);
}
// رسم المربع
tempPolygons.add(Polygon(
polygonId: PolygonId("$lat-$lng"),
consumeTapEvents: true, // للسماح بالضغط عليه مستقبلاً
// consumeTapEvents: true, // للسماح بالضغط عليه مستقبلاً
points: [
LatLng(lat - offset, lng - offset),
LatLng(lat + offset, lng - offset),
@@ -164,13 +173,27 @@ class HomeCaptainController extends GetxController {
}
heatmapPolygons = tempPolygons;
print(
"✨ [Heatmap] Rendering Done. (🔥 High: $highCount, 🟠 Med: $medCount, 🟡 Low: $lowCount)");
print("📍 [Heatmap] Total Polygons on Map: ${heatmapPolygons.length}");
update(); // تحديث الخريطة
}
// دالة لتشغيل الخريطة الحرارية كل فترة (مثلاً عند فتح الصفحة)
Timer? _heatmapTimer;
// دالة لتشغيل الخريطة الحرارية كل فترة (كل 5 دقائق) لضمان نشاط البيانات
void startHeatmapCycle() {
_heatmapTimer?.cancel();
fetchAndDrawHeatmap();
// يمكن تفعيل Timer هنا لو أردت تحديثها تلقائياً كل 5 دقائق
_heatmapTimer = Timer.periodic(const Duration(minutes: 5), (timer) {
if (isHeatmapVisible) {
print("🔄 [Heatmap] Periodic refresh started...");
fetchAndDrawHeatmap();
} else {
timer.cancel();
}
});
}
void goToWalletFromConnect() {
@@ -185,16 +208,8 @@ class HomeCaptainController extends GetxController {
}
void addCustomCarIcon() {
ImageConfiguration config = ImageConfiguration(
size: const Size(30, 35), devicePixelRatio: Get.pixelRatio);
BitmapDescriptor.asset(
config,
'assets/images/car.png',
// mipmaps: false,
).then((value) {
carIcon = value;
update();
});
carIcon = InlqBitmap.fromAsset('assets/images/car.png');
update();
}
String stringActiveDuration = '';
@@ -259,7 +274,7 @@ class HomeCaptainController extends GetxController {
// 3. إظهار الديالوج المانع
Get.defaultDialog(
title: "حسابك مقيد مؤقتاً ⛔",
title: "Your account is temporarily restricted ⛔".tr,
titleStyle:
const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
barrierDismissible: false, // 🚫 ممنوع الإغلاق بالضغط خارجاً
@@ -269,8 +284,9 @@ class HomeCaptainController extends GetxController {
const Icon(Icons.timer_off_outlined,
size: 50, color: Colors.orange),
const SizedBox(height: 15),
const Text(
"لقد تجاوزت حد الإلغاء المسموح به (3 مرات).\nلا يمكنك العمل حتى انتهاء العقوبة.",
Text(
"You have exceeded the allowed cancellation limit (3 times).\nYou cannot work until the penalty expires."
.tr,
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
@@ -296,11 +312,11 @@ class HomeCaptainController extends GetxController {
? () {
Get.back(); // إغلاق الديالوج
box.remove(BoxName.blockUntilDate); // إزالة الحظر
Get.snackbar("أهلاً بك", "يمكنك الآن استقبال الطلبات",
Get.snackbar("Welcome".tr, "You can now receive orders".tr,
backgroundColor: Colors.green);
}
: null, // زر معطل
child: Text(isFinished ? "Go Online" : "انتظر انتهاء الوقت"),
child: Text(isFinished ? "Go Online".tr : "Wait for timer".tr),
);
}),
);
@@ -334,7 +350,13 @@ class HomeCaptainController extends GetxController {
@override
void onClose() {
print("🔥 [HomeCaptain] onClose called. Tearing down map resources...");
_blockTimer?.cancel();
activeTimer?.cancel();
_cameraFollowTimer?.cancel();
_heatmapTimer?.cancel();
stopTimer();
mapHomeCaptainController = null;
super.onClose();
}
@@ -377,7 +399,8 @@ class HomeCaptainController extends GetxController {
confirm: MyElevatedButton(
title: 'Ok , See you Tomorrow'.tr,
onPressed: () {
Get.back();
// إغلاق الديالوج والعودة قسرياً
navigatorKey.currentState?.pop();
Get.back();
}));
}
@@ -395,26 +418,40 @@ class HomeCaptainController extends GetxController {
update();
}
// late GoogleMapController mapHomeCaptainController;
GoogleMapController? mapHomeCaptainController;
// final locationController = Get.find<LocationController>();
// late IntaleqMapController mapHomeCaptainController;
IntaleqMapController? mapHomeCaptainController;
// --- FIX 2: Smart Map Creation ---
void onMapCreated(GoogleMapController controller) {
void onMapCreated(IntaleqMapController controller) {
print("🔥 [HomeCaptain] onMapCreated started");
mapHomeCaptainController = controller;
// Check actual location before moving camera
var currentLoc = locationController.myLocation;
if (currentLoc.latitude != 0 && currentLoc.longitude != 0) {
controller.animateCamera(
CameraUpdate.newLatLng(currentLoc),
);
} else {
// Optional: Move to default city view instead of ocean
controller.animateCamera(
CameraUpdate.newLatLngZoom(myLocation, 10),
);
}
// We delay the first move to ensure the native side is fully ready
Future.delayed(const Duration(milliseconds: 800), () {
if (isClosed || mapHomeCaptainController == null) return;
try {
var currentLoc = locationController.myLocation;
if (currentLoc.latitude != 0 &&
currentLoc.latitude != null &&
!currentLoc.latitude.isNaN) {
print(
"🔥 [HomeCaptain] Safely moving camera to: ${currentLoc.latitude}");
mapHomeCaptainController!.moveCamera(
CameraUpdate.newLatLngZoom(currentLoc, 15),
);
} else {
print("🔥 [HomeCaptain] Safely moving to default Damascus");
mapHomeCaptainController!.moveCamera(
CameraUpdate.newLatLngZoom(myLocation, 12),
);
}
// Mark as ready for regular listener updates
isMapReadyForCommands = true;
} catch (e) {
print("❌ [HomeCaptain] Map move failed: $e");
}
});
}
void savePeriod(Duration period) {
@@ -441,7 +478,7 @@ class HomeCaptainController extends GetxController {
await getCaptainDurationOnToday();
String? initialDurationStr = totalDurationToday;
if (initialDurationStr != null) {
if (initialDurationStr != '0') {
// تحويل النص (01:20:30) إلى كائن Duration
List<String> parts = initialDurationStr.split(':');
_currentDuration = Duration(
@@ -490,16 +527,27 @@ class HomeCaptainController extends GetxController {
getlocation() async {
isLoading = true;
update();
// This ensures we try to get a fix, but map doesn't crash if it fails
await locationController.getLocation();
try {
// ننتظر جلب الموقع مع مهلة 10 ثوانٍ لتجنب التعليق
var locData = await locationController.getLocation().timeout(
const Duration(seconds: 10),
onTimeout: () => null,
);
var loc = locationController.myLocation;
if (loc.latitude != 0) {
myLocation = loc;
if (locData != null && locData.latitude != null) {
myLocation = LatLng(locData.latitude!, locData.longitude!);
print(
"📍 [HomeCaptain] Location updated: ${myLocation.latitude}, ${myLocation.longitude}");
} else {
print(
"⚠️ [HomeCaptain] Could not get current location, using default.");
}
} catch (e) {
print("❌ Error in getlocation: $e");
} finally {
isLoading = false;
update();
}
isLoading = false;
update();
}
Map walletDriverPointsDate = {};
@@ -535,27 +583,18 @@ class HomeCaptainController extends GetxController {
// 4. دالة نستدعيها عند العودة للصفحة الرئيسية
void resumeHomeMapUpdates() {
isHomeMapActive = true;
// إنعاش الخريطة عند العودة
if (mapHomeCaptainController != null) {
onMapCreated(mapHomeCaptainController!);
}
// تم حذف استدعاء onMapCreated المتكرر لمنع قفز الخريطة عند العودة
update();
}
@override
void onInit() async {
// ✅ طلب الإذونات أولاً
bool permissionsGranted = await PermissionsHelper.requestAllPermissions();
// ✅ تم إرجاعه كتعليق لمنع الديالوج عند التشغيل (كما كان في الكود الأصلي)
// bool permissionsGranted = await PermissionsHelper.requestAllPermissions();
// if (permissionsGranted) {
// await BackgroundServiceHelper.startService();
// }
if (permissionsGranted) {
// ✅ بدء الخدمة بعد الحصول على الإذونات
await BackgroundServiceHelper.startService();
print('✅ Background service started successfully');
} else {
print('❌ لم يتم منح الإذونات - الخدمة لن تعمل');
// اعرض رسالة للمستخدم
}
// await locationBackController.requestLocationPermission();
Get.put(FirebaseMessagesController());
addToken();
await getlocation();
@@ -575,29 +614,25 @@ class HomeCaptainController extends GetxController {
checkAndShowBlockDialog();
box.write(BoxName.statusDriverLocation, 'off');
// 2. عدل الليسنر ليصبح مشروطاً
locationController.addListener(() {
// الشرط الذهبي: إذا كانت الصفحة غير نشطة أو الخريطة غير موجودة، لا تفعل شيئاً
if (!isHomeMapActive || mapHomeCaptainController == null || isClosed)
return;
// 2. مؤقت التتبع التلقائي (كل 5 ثوانٍ كما في الكود السابق)
_cameraFollowTimer = Timer.periodic(const Duration(seconds: 5), (timer) {
if (isClosed ||
!isHomeMapActive ||
mapHomeCaptainController == null ||
!isActive) return;
if (isActive) {
// isActive الخاصة بالزر "متصل/غير متصل"
var loc = locationController.myLocation;
if (loc.latitude != 0 && loc.longitude != 0) {
try {
mapHomeCaptainController!.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: loc,
zoom: 17.5,
tilt: 50.0,
bearing: locationController.heading,
),
),
var loc = locationController.myLocation;
if (loc.latitude != 0 && loc.latitude != null && !loc.latitude.isNaN) {
try {
// 🔥 Safety double-check before animating
if (mapHomeCaptainController != null) {
print("🔥 [HomeCaptain] Safely moving camera to: ${loc.latitude}");
mapHomeCaptainController?.animateCamera(
CameraUpdate.newLatLngZoom(loc, 17.5),
);
} catch (e) {
// التقاط الخطأ بصمت إذا حدث أثناء الانتقال
}
} catch (e) {
print("❌ [HomeCaptain] Camera movement failed: $e");
}
}
});
@@ -724,11 +759,4 @@ class HomeCaptainController extends GetxController {
update();
}
@override
void dispose() {
activeTimer?.cancel();
stopTimer();
mapHomeCaptainController?.dispose(); // Dispose controller
super.dispose();
}
}

View File

@@ -5,28 +5,21 @@ import 'dart:math';
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:sefer_driver/controller/firebase/local_notification.dart';
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/controller/home/navigation/decode_polyline_isolate.dart';
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';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../constant/api_key.dart';
import '../../../constant/box_name.dart';
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';
@@ -36,6 +29,7 @@ import '../../firebase/notification_service.dart';
import '../../functions/crud.dart';
import '../../functions/location_controller.dart';
import '../../functions/tts.dart';
import 'behavior_controller.dart';
class MapDriverController extends GetxController {
bool isLoading = true;
@@ -48,10 +42,10 @@ class MapDriverController extends GetxController {
List data = [];
List dataDestination = [];
LatLngBounds? boundsData;
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
BitmapDescriptor passengerIcon = BitmapDescriptor.defaultMarker;
BitmapDescriptor startIcon = BitmapDescriptor.defaultMarker;
BitmapDescriptor endIcon = BitmapDescriptor.defaultMarker;
InlqBitmap carIcon = InlqBitmap.defaultMarker;
InlqBitmap passengerIcon = InlqBitmap.defaultMarker;
InlqBitmap startIcon = InlqBitmap.defaultMarker;
InlqBitmap endIcon = InlqBitmap.defaultMarker;
final List<LatLng> polylineCoordinates = [];
final List<LatLng> polylineCoordinatesDestination = [];
List<Polyline> polyLines = [];
@@ -104,7 +98,7 @@ class MapDriverController extends GetxController {
int remainingTimeToPassenger = 60;
int remainingTimeInPassengerLocatioWait = 60;
bool isDriverNearPassengerStart = false;
GoogleMapController? mapController;
IntaleqMapController? mapController;
late LatLng myLocation;
int remainingTimeTimerRideBegin = 60;
String stringRemainingTimeRideBegin = '';
@@ -158,16 +152,32 @@ class MapDriverController extends GetxController {
_posSub?.cancel();
_posSub = null;
mapController?.dispose();
// mapController?.dispose();
super.onClose();
}
void onMapCreated(GoogleMapController controller) {
void onMapCreated(IntaleqMapController controller) {
mapController = controller;
if (Get.isRegistered<LocationController>()) {
myLocation = Get.find<LocationController>().myLocation;
controller.animateCamera(CameraUpdate.newLatLngZoom(myLocation, 16));
}
// 🔥 رسم المسار فور جاهزية الخريطة
if (isRideStarted) {
// إذا كانت الرحلة بدأت، ارسم للمكان النهائي
getRoute(
origin: myLocation,
destination: latLngPassengerDestination,
routeColor: Colors.blue);
} else {
// إذا كان السائق ذاهب للراكب
getRoute(
origin: myLocation,
destination: latLngPassengerLocation,
routeColor: Colors.yellow);
}
// بدء الاستماع للموقع للملاحة وتحديث الماركر
startListeningStepNavigation();
}
@@ -239,7 +249,7 @@ class MapDriverController extends GetxController {
}
takeSnapMap() {
mapController!.takeSnapshot();
// mapController!.takeSnapshot();
}
@override
@@ -254,7 +264,7 @@ class MapDriverController extends GetxController {
_passengerTimer?.cancel();
_waitingTimer?.cancel();
_posSub?.cancel();
mapController?.dispose();
// mapController?.dispose();
}
Future openGoogleMapFromDriverToPassenger() async {
@@ -307,7 +317,9 @@ class MapDriverController extends GetxController {
box.remove(BoxName.rideArguments);
// 3. عرض رسالة للسائق
if (Get.isDialogOpen == true) Get.back(); // إغلاق أي ديالوج مفتوح
if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
Get.defaultDialog(
title: "تم إلغاء الرحلة".tr,
@@ -325,7 +337,7 @@ class MapDriverController extends GetxController {
),
confirm: ElevatedButton(
onPressed: () {
Get.back(); // إغلاق الديالوج
navigatorKey.currentState?.pop(); // إغلاق الديالوج
Get.offAll(() => HomeCaptain()); // العودة للرئيسية
},
child: Text("OK".tr),
@@ -367,8 +379,8 @@ class MapDriverController extends GetxController {
box.write(BoxName.statusDriverLocation, 'blocked');
// عرض رسالة العقوبة
Get.snackbar("تم تقييد حسابك مؤقتاً ⛔",
"بسبب كثرة الإلغاءات (3 مرات)، تم إيقاف استقبال الطلبات لمدة 4 ساعات.",
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,
@@ -402,15 +414,21 @@ class MapDriverController extends GetxController {
Get.put(HomeCaptainController()).getRefusedOrderByCaptain();
}
if (Get.isDialogOpen == true) Get.back();
if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
Get.offAll(
() => HomeCaptain()); // العودة للرئيسية ليتم تطبيق الحظر هناك
} else {
if (Get.isDialogOpen == true) Get.back();
if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
Get.snackbar("Error", "Failed to cancel ride");
}
} catch (e) {
if (Get.isDialogOpen == true) Get.back();
if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
Log.print("Error: $e");
}
}
@@ -707,7 +725,9 @@ class MapDriverController extends GetxController {
await calculateDistanceBetweenDriverAndPassengerLocation();
// إغلاق مؤشر التحميل لأننا حصلنا على النتيجة
if (Get.isDialogOpen == true) Get.back();
if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
if (distanceToPassenger < 100) {
// زدت المسافة قليلاً لمرونة أكبر (150م)
@@ -752,15 +772,17 @@ class MapDriverController extends GetxController {
});
} else {
// --- حالة الرفض (بعيد جداً) ---
MyDialog().getDialog(
'You are far from passenger location'.tr,
MyDialog().getDialog('You are far from passenger location'.tr,
'Please go closer to the passenger location (less than 150m)'.tr,
() => Get.back() // إغلاق الديالوج فقط
);
() {
// الديالوج يغلق نفسه الآن تلقائياً
});
}
} catch (e) {
// تنظيف اللودينج في حال حدوث خطأ غير متوقع
if (Get.isDialogOpen == true) Get.back();
if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
Log.print("Error starting ride: $e");
Get.snackbar("Error", "Could not start ride. Please check internet.");
}
@@ -1448,56 +1470,23 @@ class MapDriverController extends GetxController {
}
void addCustomCarIcon() {
ImageConfiguration config = ImageConfiguration(
size: const Size(30, 35), devicePixelRatio: Get.pixelRatio);
BitmapDescriptor.asset(
config,
'assets/images/car.png',
// mipmaps: false,
).then((value) {
carIcon = value;
update();
});
carIcon = InlqBitmap.fromAsset('assets/images/car.png');
update();
}
void addCustomStartIcon() async {
// Create the marker with the resized image
ImageConfiguration config = ImageConfiguration(
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
BitmapDescriptor.asset(
config,
'assets/images/A.png',
).then((value) {
startIcon = value;
update();
});
startIcon = InlqBitmap.fromAsset('assets/images/A.png');
update();
}
void addCustomEndIcon() {
ImageConfiguration config = ImageConfiguration(
size: const Size(25, 25), devicePixelRatio: Get.pixelRatio);
BitmapDescriptor.asset(
config,
'assets/images/b.png',
).then((value) {
endIcon = value;
update();
});
endIcon = InlqBitmap.fromAsset('assets/images/b.png');
update();
}
void addCustomPassengerIcon() {
ImageConfiguration config = ImageConfiguration(
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio
// scale: 1.0,
);
BitmapDescriptor.asset(
config,
'assets/images/picker.png',
).then((value) {
passengerIcon = value;
update();
});
passengerIcon = InlqBitmap.fromAsset('assets/images/picker.png');
update();
}
var activeRouteSteps = <Map<String, dynamic>>[];
@@ -1596,82 +1585,94 @@ class MapDriverController extends GetxController {
required LatLng destination,
required Color routeColor,
}) async {
// 1. استخدام الرابط الجديد والإعدادات الصحيحة
String coordinates =
'${origin.longitude},${origin.latitude};${destination.longitude},${destination.latitude}';
// استخدام الرابط من الكلاس المرجعي لأنه أحدث
var url =
"${AppLink.mapOSM}/route/v1/driving/$coordinates?steps=true&overview=full";
if (mapController == null) return;
try {
var response = await http.get(Uri.parse(url));
// 1. طلب المسار من الباكيج
final response =
await mapController!.getDirections(origin, destination, steps: true);
if (response.statusCode == 200) {
var decoded = jsonDecode(response.body);
// 2. التعامل مع الـ JSON المباشر (الذي أرسله المستخدم)
// إذا كان الـ response يحتوي على الحقول مباشرة في الجذر
final String? encodedPoints = response['points'];
if (decoded['code'] != 'Ok' || (decoded['routes'] as List).isEmpty) {
mySnackeBarError("لم يتم العثور على مسار");
return;
}
if (encodedPoints == null) {
mySnackeBarError("No route points found".tr);
return;
}
var route = decoded['routes'][0];
// 🔥 فك التشفير باستخدام compute لضمان أداء ممتاز
List<LatLng> fullRoute =
await compute(PolylineUtils.decode, encodedPoints);
// أ) تشغيل الـ Isolate لفك التشفير (ممتاز، ابقِ عليه)
final String pointsString = route["geometry"];
List<LatLng> fullRoute =
await compute(decodePolylineIsolate, pointsString);
if (fullRoute.isEmpty) {
mySnackeBarError("Failed to process route points".tr);
return;
}
// ب) تهيئة المتغيرات
upcomingPathPoints.assignAll(fullRoute);
traveledPathPoints.clear();
_lastTraveledIndex = 0;
// تحديث المسافة والوقت من الـ JSON الجديد
distance = (response['distance'] ?? 0).toString();
duration = (response['duration'] ?? 0).toString();
// ج) رسم المسار الأولي
polyLines.clear();
polyLines.add(Polyline(
polylineId: const PolylineId("upcoming_route"),
points: fullRoute,
width: 8,
color: routeColor,
startCap: Cap.roundCap,
endCap: Cap.roundCap,
));
// ب) تهيئة المتغيرات
upcomingPathPoints.assignAll(fullRoute);
traveledPathPoints.clear();
_lastTraveledIndex = 0;
// د) معالجة الخطوات (Legs & Steps)
List<dynamic> legs = route['legs'];
if (legs.isNotEmpty) {
final stepsList = List<Map<String, dynamic>>.from(legs[0]['steps']);
// ج) رسم المسار الأولي
polyLines.clear();
polyLines.add(Polyline(
polylineId: const PolylineId("upcoming_route"),
points: fullRoute,
width: 8,
color: routeColor,
));
// 🔥 استخدام دالة الترجمة المحسنة من الكلاس المرجعي
for (var step in stepsList) {
step['html_instructions'] =
_createInstructionFromManeuverSmart(step);
// تصحيح مواقع المناورات
if (step['maneuver'] != null &&
step['maneuver']['location'] != null) {
var loc = step['maneuver']['location'];
// د) معالجة الخطوات (Legs & Steps) إذا توفرت في الـ JSON
List<dynamic> legs = response['legs'] ?? [];
if (legs.isNotEmpty) {
final stepsList =
List<Map<String, dynamic>>.from(legs[0]['steps'] ?? []);
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;
}
}
}
routeSteps = stepsList;
currentStepIndex = 0;
routeSteps = stepsList;
currentStepIndex = 0;
// نطق أول تعليمة
if (routeSteps.isNotEmpty) {
currentInstruction = routeSteps[0]['html_instructions'];
// نطق أول تعليمة
if (routeSteps.isNotEmpty) {
currentInstruction = routeSteps[0]['html_instructions'];
if (Get.isRegistered<TextToSpeechController>()) {
Get.find<TextToSpeechController>().speakText(currentInstruction);
}
}
// هـ) تحريك الكاميرا لتشمل المسار
if (fullRoute.isNotEmpty) {
final bounds = _boundsFromLatLngList(fullRoute);
safeAnimateCamera(CameraUpdate.newLatLngBounds(bounds, 80));
}
update();
} else {
// في حال عدم وجود steps، نقوم بتصفيرها
routeSteps = [];
currentInstruction = "";
}
// هـ) تحريك الكاميرا لتشمل المسار
if (fullRoute.isNotEmpty) {
final bounds = _boundsFromLatLngList(fullRoute);
safeAnimateCamera(CameraUpdate.newLatLngBounds(bounds,
left: 80, top: 80, right: 80, bottom: 80));
}
update();
} catch (e) {
Log.print("Route Error: $e");
}
@@ -1679,7 +1680,7 @@ class MapDriverController extends GetxController {
// 🔥 دالة الترجمة المحسنة (من NavigationController)
String _createInstructionFromManeuverSmart(Map<String, dynamic> step) {
if (step['maneuver'] == null) return "تابع المسير";
if (step['maneuver'] == null) return "Continue straight".tr;
final maneuver = step['maneuver'];
final type = maneuver['type'] ?? 'continue';
@@ -1690,10 +1691,10 @@ class MapDriverController extends GetxController {
switch (type) {
case 'depart':
instruction = "انطلق";
instruction = "Go".tr;
break;
case 'arrive':
return "لقد وصلت إلى وجهتك، $name";
return "You have arrived at your destination, @name".trParams({'name': name});
case 'turn':
case 'fork':
case 'roundabout':
@@ -1705,14 +1706,14 @@ class MapDriverController extends GetxController {
_getTurnInstruction(modifier); // استخدم نفس دالتك المساعدة هنا
break;
default:
instruction = "تابع المسير";
instruction = "Continue straight".tr;
}
if (name.isNotEmpty) {
if (type == 'continue') {
instruction += " على $name";
instruction += " ${"on".tr} $name";
} else {
instruction += " نحو $name";
instruction += " ${"towards".tr} $name";
}
}
return instruction;
@@ -1728,10 +1729,10 @@ class MapDriverController extends GetxController {
switch (type) {
case 'depart':
instruction = "انطلق";
instruction = "Go".tr;
break;
case 'arrive':
instruction = "لقد وصلت إلى وجهتك";
instruction = "You have arrived at your destination".tr;
if (name.isNotEmpty) instruction += "، $name";
return instruction;
case 'turn':
@@ -1742,20 +1743,20 @@ class MapDriverController extends GetxController {
instruction = _getTurnInstruction(modifier);
break;
case 'continue':
instruction = "استمر";
instruction = "Continue".tr;
break;
default:
instruction = "اتجه";
instruction = "Head".tr;
}
if (name.isNotEmpty) {
if (instruction == "استمر") {
instruction += " على $name";
instruction += " ${"on".tr} $name";
} else {
instruction += " إلى $name";
instruction += " ${"to".tr} $name";
}
} else if (type == 'continue' && modifier == 'straight') {
instruction = "استمر بشكل مستقيم";
instruction = "Continue straight".tr;
}
return instruction;
@@ -1767,23 +1768,23 @@ class MapDriverController extends GetxController {
String _getTurnInstruction(String modifier) {
switch (modifier) {
case 'uturn':
return "قم بالاستدارة والعودة";
return "Make a U-turn".tr;
case 'sharp right':
return "انعطف يمينًا بحدة";
return "Turn sharp right".tr;
case 'right':
return "انعطف يمينًا";
return "Turn right".tr;
case 'slight right':
return "انعطف يمينًا قليلاً";
return "Turn slight right".tr;
case 'straight':
return "استمر بشكل مستقيم";
return "Continue straight".tr;
case 'slight left':
return "انعطف يسارًا قليلاً";
return "Turn slight left".tr;
case 'left':
return "انعطف يسارًا";
return "Turn left".tr;
case 'sharp left':
return "انعطف يسارًا بحدة";
return "Turn sharp left".tr;
default:
return "اتجه";
return "Head".tr;
}
}
@@ -1850,7 +1851,7 @@ class MapDriverController extends GetxController {
void _advanceStep() {
if (currentStepIndex >= _stepBounds.length - 1) {
// وصل للنهاية
currentInstruction = "لقد وصلت إلى وجهتك";
currentInstruction = "You have arrived at your destination".tr;
return;
}
@@ -1909,11 +1910,11 @@ class MapDriverController extends GetxController {
routeColor: Colors.blue);
} else {
if (Get.isDialogOpen == true) Get.back();
mySnackeBarError("يجب أن تكون أقرب من 100 متر للوصول");
mySnackeBarError("You must be closer than 100 meters to arrive".tr);
}
} catch (e) {
if (Get.isDialogOpen == true) Get.back();
mySnackeBarError("حدث خطأ في الاتصال");
mySnackeBarError("A connection error occurred".tr);
}
}
@@ -1941,9 +1942,7 @@ class MapDriverController extends GetxController {
// 2. فك تشفير البوليلاين الخاص بالخطوة وتحويله إلى LatLng
// -->> هنا تم التصحيح <<--
List<LatLng> pts = decodePolyline(s['polyline']['points'])
.map((point) => LatLng(point[0].toDouble(), point[1].toDouble()))
.toList();
List<LatLng> pts = PolylineUtils.decode(s['polyline']['points']);
// أضف نقاط البداية والنهاية إذا لم تكن موجودة في البوليلاين لضمان دقة الحدود
if (pts.isNotEmpty) {
@@ -1962,14 +1961,9 @@ class MapDriverController extends GetxController {
// 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<List<num>> decodedPoints = decodePolyline(polylineString);
List<LatLng> decodedPoints = PolylineUtils.decode(polylineString);
// 2. Map each [lat, lng] pair to a LatLng object, ensuring conversion to double
List<LatLng> latLngPoints = decodedPoints
.map((point) => LatLng(point[0].toDouble(), point[1].toDouble()))
.toList();
return latLngPoints;
return decodedPoints;
}
// =================================================================
@@ -1986,11 +1980,11 @@ 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,
duration: const Duration(seconds: 15),
mainButton: TextButton(
child: const Text("نعم، قم بالتحسين"),
child: Text("Yes, optimize".tr),
onPressed: () {
updateInterval.value = 8; // غير الفترة إلى 8 ثوانٍ
// save setting to shared_preferences
@@ -2017,7 +2011,8 @@ class MapDriverController extends GetxController {
Future<void> _fitToBounds(LatLngBounds b, {double padding = 60}) async {
// نستخدم الدالة الآمنة التي أنشأناها
await safeAnimateCamera(CameraUpdate.newLatLngBounds(b, padding));
await safeAnimateCamera(CameraUpdate.newLatLngBounds(b,
left: padding, top: padding, right: padding, bottom: padding));
}
double distanceBetweenDriverAndPassengerWhenConfirm = 0;
@@ -2154,7 +2149,8 @@ class MapDriverController extends GetxController {
LatLngBounds(northeast: northeast, southwest: southwest);
// Fit the camera to the bounds
var cameraUpdate = CameraUpdate.newLatLngBounds(boundsData, 140);
var cameraUpdate = CameraUpdate.newLatLngBounds(boundsData,
left: 140, top: 140, right: 140, bottom: 140);
safeAnimateCamera(cameraUpdate);
}
@@ -2395,22 +2391,19 @@ class MapDriverController extends GetxController {
polyLines.removeWhere((p) => p.polylineId.value == 'upcoming_route');
polyLines.removeWhere((p) => p.polylineId.value == 'traveled_route');
// المسار المتبقي (أزرق)
// المسار المتبقي
polyLines.add(Polyline(
polylineId: const PolylineId('upcoming_route'),
polylineId: const PolylineId("upcoming_route"),
points: remaining,
color: Colors.blue,
width: 8,
zIndex: 2,
startCap: Cap.roundCap,
endCap: Cap.roundCap,
color: isRideStarted ? Colors.blue : Colors.yellow,
));
// المسار المقطوع (رمادي)
polyLines.add(Polyline(
polylineId: const PolylineId('traveled_route'),
points: traveled,
color: Colors.grey.withOpacity(0.8),
color: Colors.grey.withValues(alpha: 0.8),
width: 7,
zIndex: 1,
));

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
class NavigationStep {
final String instruction;

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:sefer_driver/constant/api_key.dart';
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/constant/links.dart';
@@ -19,10 +18,10 @@ class NavigationService extends GetxService {
final RxSet<Polyline> polylines = <Polyline>{}.obs;
final RxString currentInstruction = "".obs;
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
BitmapDescriptor passengerIcon = BitmapDescriptor.defaultMarker;
BitmapDescriptor startIcon = BitmapDescriptor.defaultMarker;
BitmapDescriptor endIcon = BitmapDescriptor.defaultMarker;
InlqBitmap carIcon = InlqBitmap.defaultMarker;
InlqBitmap passengerIcon = InlqBitmap.defaultMarker;
InlqBitmap startIcon = InlqBitmap.defaultMarker;
InlqBitmap endIcon = InlqBitmap.defaultMarker;
@override
void onInit() {
@@ -30,19 +29,11 @@ class NavigationService extends GetxService {
_loadCustomIcons();
}
void _loadCustomIcons() async {
carIcon = await _createBitmapDescriptor('assets/images/car.png');
passengerIcon = await _createBitmapDescriptor('assets/images/picker.png');
startIcon = await _createBitmapDescriptor('assets/images/A.png');
endIcon = await _createBitmapDescriptor('assets/images/b.png');
}
Future<BitmapDescriptor> _createBitmapDescriptor(String assetName) {
return BitmapDescriptor.fromAssetImage(
ImageConfiguration(
size: const Size(30, 35), devicePixelRatio: Get.pixelRatio),
assetName,
);
void _loadCustomIcons() {
carIcon = InlqBitmap.fromAsset('assets/images/car.png');
passengerIcon = InlqBitmap.fromAsset('assets/images/picker.png');
startIcon = InlqBitmap.fromAsset('assets/images/A.png');
endIcon = InlqBitmap.fromAsset('assets/images/b.png');
}
Future<Map<String, dynamic>?> getRoute({
@@ -62,9 +53,7 @@ class NavigationService extends GetxService {
void drawRoute(Map<String, dynamic> routeData, {Color color = Colors.blue}) {
final pointsString = routeData["overview_polyline"]["points"];
final points = decodePolyline(pointsString)
.map((p) => LatLng(p[0].toDouble(), p[1].toDouble()))
.toList();
final points = PolylineUtils.decode(pointsString);
final polyline = Polyline(
polylineId: PolylineId(routeData["summary"] ?? DateTime.now().toString()),

View File

@@ -5,14 +5,16 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:geolocator/geolocator.dart';
import 'package:http/http.dart' as http;
import 'package:just_audio/just_audio.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'dart:math' as math;
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
import '../../../env/env.dart';
import '../../../main.dart';
import '../../../print.dart';
import '../../../views/home/Captin/driver_map_page.dart';
@@ -22,8 +24,6 @@ import '../../firebase/local_notification.dart';
import '../../functions/crud.dart';
import '../../functions/location_controller.dart';
import '../../home/captin/home_captain_controller.dart';
import '../../firebase/notification_service.dart';
import '../navigation/decode_polyline_isolate.dart';
class OrderRequestController extends GetxController
with WidgetsBindingObserver {
@@ -40,7 +40,7 @@ class OrderRequestController extends GetxController
bool _isRideTakenHandled = false;
// --- الأيقونات والماركرز ---
BitmapDescriptor? driverIcon;
InlqBitmap? driverIcon;
Map<MarkerId, Marker> markersMap = {};
Set<Marker> get markers => markersMap.values.toSet();
@@ -49,7 +49,7 @@ class OrderRequestController extends GetxController
List<dynamic>? myList;
Map<dynamic, dynamic>? myMapData;
GoogleMapController? mapController;
IntaleqMapController? mapController;
// الإحداثيات (أزلنا late لتجنب الأخطاء القاتلة)
double latPassenger = 0.0;
@@ -64,7 +64,7 @@ class OrderRequestController extends GetxController
String totalTripDuration = "--";
String tripPrice = "--";
String timeToPassenger = "جاري الحساب...";
String timeToPassenger = "Calculating...".tr;
String distanceToPassenger = "--";
// --- الخريطة ---
@@ -193,9 +193,25 @@ class OrderRequestController extends GetxController
void _parseExtraData() {
passengerRating = _safeGet(33).isEmpty ? "5.0" : _safeGet(33);
tripType = _safeGet(31);
totalTripDistance = _safeGet(5);
totalTripDuration = _safeGet(19);
tripPrice = _safeGet(2);
// Format numbers to avoid many decimal places
String rawDist = _safeGet(5);
if (rawDist.isNotEmpty) {
double? d = double.tryParse(rawDist);
totalTripDistance = d != null ? "${d.toStringAsFixed(1)} km" : rawDist;
}
String rawDur = _safeGet(19);
if (rawDur.isNotEmpty) {
double? d = double.tryParse(rawDur);
totalTripDuration = d != null ? "${d.toStringAsFixed(0)} min" : rawDur;
}
String rawPrice = _safeGet(2);
if (rawPrice.isNotEmpty) {
double? p = double.tryParse(rawPrice);
tripPrice = p != null ? p.toStringAsFixed(0) : rawPrice;
}
}
// ----------------------------------------------------------------------
@@ -203,6 +219,8 @@ class OrderRequestController extends GetxController
// ----------------------------------------------------------------------
Future<void> _calculateFullJourney() async {
if (mapController == null) return; // Wait for controller to draw
try {
Position driverPos = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
@@ -210,6 +228,9 @@ class OrderRequestController extends GetxController
updateDriverLocation(driverLatLng, driverPos.heading);
// Clear old polylines to avoid "ghost lines"
polylines.clear();
var pickupFuture = _fetchRouteData(
start: driverLatLng,
end: LatLng(latPassenger, lngPassenger),
@@ -246,7 +267,8 @@ class OrderRequestController extends GetxController
destTime: totalTripDuration,
destDist: totalTripDistance);
zoomToFitRide(driverLatLng);
// Now zoom to fit all polylines and markers
zoomToFitRide();
update();
} catch (e) {
@@ -254,6 +276,22 @@ class OrderRequestController extends GetxController
}
}
String _formatDistance(dynamic rawDist) {
if (rawDist == null || rawDist.toString().isEmpty) return "--";
double dist = double.tryParse(rawDist.toString()) ?? 0.0;
if (dist <= 0) return "--";
if (dist < 1000) return "${dist.toStringAsFixed(0)} m";
return "${(dist / 1000).toStringAsFixed(1)} km";
}
String _formatDuration(dynamic rawDur) {
if (rawDur == null || rawDur.toString().isEmpty) return "--";
double dur = double.tryParse(rawDur.toString()) ?? 0.0;
if (dur <= 0) return "1 min"; // Minimum 1 min for UI
if (dur < 60) return "${dur.toStringAsFixed(0)} sec";
return "${(dur / 60).toStringAsFixed(0)} min";
}
Future<Map<String, dynamic>?> _fetchRouteData(
{required LatLng start,
required LatLng end,
@@ -261,46 +299,56 @@ class OrderRequestController extends GetxController
required String id,
bool isDashed = false}) async {
try {
// حماية من الإحداثيات الصفرية
if (start.latitude == 0 || end.latitude == 0) return null;
if (mapController == null) return null;
String apiUrl = "https://routesy.intaleq.xyz/route/v1/driving";
String coords =
"${start.longitude},${start.latitude};${end.longitude},${end.latitude}";
String url = "$apiUrl/$coords?steps=false&overview=full";
final saasUrl = Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: {
'fromLat': start.latitude.toString(),
'fromLng': start.longitude.toString(),
'toLat': end.latitude.toString(),
'toLng': end.longitude.toString(),
'steps': 'false',
'alternatives': 'false',
});
var response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
var json = jsonDecode(response.body);
if (json['code'] == 'Ok' && json['routes'].isNotEmpty) {
var route = json['routes'][0];
final response = await http.get(saasUrl, headers: {
'x-api-key': Env.mapSaasKey,
'Content-Type': 'application/json',
});
double distM = double.parse(route['distance'].toString());
double durS = double.parse(route['duration'].toString());
if (response.statusCode != 200) {
throw Exception("Routing request failed: ${response.statusCode}");
}
String distText = "${(distM / 1000).toStringAsFixed(1)} كم";
String durText = "${(durS / 60).toStringAsFixed(0)} دقيقة";
final data = jsonDecode(response.body);
print("🛣️ Route API Response [$id]: ${data}");
String geometry = route['geometry'];
List<LatLng> points = await compute(decodePolylineIsolate, geometry);
// The map-saas API returns the route data directly at the root,
// with 'points' being an encoded polyline string.
final String? encodedPoints = data['points']?.toString();
Polyline polyline = Polyline(
polylineId: PolylineId(id),
color: color,
width: 5,
points: points,
patterns:
isDashed ? [PatternItem.dash(10), PatternItem.gap(5)] : [],
startCap: Cap.roundCap,
endCap: Cap.roundCap,
);
if (encodedPoints != null && encodedPoints.isNotEmpty) {
List<LatLng> path = controllerDecodePolyline(encodedPoints);
print("📍 Path for [$id] has ${path.length} points.");
return {
'distance_text': distText,
'duration_text': durText,
'polyline': polyline
};
}
final num? rawDist = data['distance'] is num ? data['distance'] : null;
final num? rawDur = data['duration'] is num ? data['duration'] : null;
final distanceText = data['distance_text'] ?? _formatDistance(rawDist);
final durationText = data['duration_text'] ?? _formatDuration(rawDur);
Polyline polyline = Polyline(
polylineId: PolylineId(id),
color: color,
width: 5,
points: path,
);
return {
'distance_text': distanceText,
'duration_text': durationText,
'polyline': polyline
};
}
} catch (e) {
print("Route Fetch Error: $e");
@@ -308,36 +356,47 @@ class OrderRequestController extends GetxController
return null;
}
void zoomToFitRide(LatLng driverPos) {
void zoomToFitRide() {
if (mapController == null) return;
// حماية من النقاط الصفرية
if (latPassenger == 0 || latDestination == 0) return;
List<LatLng> allPoints = [];
List<LatLng> points = [
driverPos,
LatLng(latPassenger, lngPassenger),
LatLng(latDestination, lngDestination),
];
// Add all polyline points to the bounds calculation
for (var polyline in polylines) {
allPoints.addAll(polyline.points);
}
double minLat = points.first.latitude;
double maxLat = points.first.latitude;
double minLng = points.first.longitude;
double maxLng = points.first.longitude;
// Fallback to basic markers if polylines are empty
if (allPoints.isEmpty) {
allPoints.addAll([
LatLng(latPassenger, lngPassenger),
LatLng(latDestination, lngDestination),
]);
}
for (var p in points) {
if (allPoints.isEmpty) return;
double minLat = allPoints.first.latitude;
double maxLat = allPoints.first.latitude;
double minLng = allPoints.first.longitude;
double maxLng = allPoints.first.longitude;
for (var p in allPoints) {
if (p.latitude < minLat) minLat = p.latitude;
if (p.latitude > maxLat) maxLat = p.latitude;
if (p.longitude < minLng) minLng = p.longitude;
if (p.longitude > maxLng) maxLng = p.longitude;
}
// Add some padding to the bounds
double latPad = (maxLat - minLat) * 0.25;
double lngPad = (maxLng - minLng) * 0.2;
mapController!.animateCamera(CameraUpdate.newLatLngBounds(
LatLngBounds(
southwest: LatLng(minLat, minLng),
northeast: LatLng(maxLat, maxLng),
southwest: LatLng(minLat - latPad, minLng - lngPad),
northeast: LatLng(maxLat + latPad, maxLng + lngPad),
),
100.0,
));
}
@@ -357,15 +416,15 @@ class OrderRequestController extends GetxController
// حماية إذا لم يتم جلب الإحداثيات
if (latPassenger == 0 || latDestination == 0) return;
final BitmapDescriptor pickupIcon =
final InlqBitmap pickupIcon =
await MarkerGenerator.createCustomMarkerBitmap(
title: paxTime,
subtitle: paxDist,
color: Colors.green.shade700,
color: Colors.amber.shade900, // Matching the amber pickup line
iconData: Icons.person_pin_circle,
);
final BitmapDescriptor dropoffIcon =
final InlqBitmap dropoffIcon =
await MarkerGenerator.createCustomMarkerBitmap(
title: destTime ?? totalTripDuration,
subtitle: destDist ?? totalTripDistance,
@@ -411,10 +470,9 @@ class OrderRequestController extends GetxController
points: [driverLatLng, LatLng(latPassenger, lngPassenger)],
color: Colors.grey,
width: 2,
patterns: [PatternItem.dash(10), PatternItem.gap(10)],
));
zoomToFitRide(driverLatLng);
zoomToFitRide();
}
update();
@@ -435,8 +493,9 @@ class OrderRequestController extends GetxController
}
}
void onMapCreated(GoogleMapController controller) {
void onMapCreated(IntaleqMapController controller) {
mapController = controller;
_calculateFullJourney();
}
// --- قبول الطلب وإدارة التايمر ---
@@ -488,10 +547,13 @@ class OrderRequestController extends GetxController
// 1. حذف الإشعار من شريط التنبيهات فوراً
NotificationController().cancelOrderNotification();
if (Get.isSnackbarOpen) Get.closeCurrentSnackbar();
if (Get.isDialogOpen ?? false) Get.back();
// إغلاق أي ديالوج مفتوح قسرياً
if (Get.isDialogOpen ?? false) {
navigatorKey.currentState?.pop();
}
Get.back();
Get.snackbar("تنبيه", "تم قبول الطلب من قبل سائق آخر",
backgroundColor: Colors.orange, colorText: Colors.white);
mySnackbarInfo("The order has been accepted by another driver.".tr);
}
});
}
@@ -546,9 +608,10 @@ class OrderRequestController extends GetxController
if (isFailure) {
// ⛔ حالة الفشل: الطلب مأخوذ
MyDialog().getDialog("عذراً، الطلب أخذه سائق آخر.", '', () {
Get.back(); // إغلاق الديالوج
Get.back(); // العودة للصفحة الرئيسية (إغلاق صفحة الطلب)
MyDialog().getDialog(
"Sorry, the order was taken by another driver.".tr, '', () {
// بما أن MyDialog يغلق نفسه الآن، نحتاج Get.back() واحدة فقط لإغلاق صفحة الطلب
Get.back();
});
} else {
// ✅ حالة النجاح
@@ -611,7 +674,37 @@ class OrderRequestController extends GetxController
audioPlayer.dispose();
WidgetsBinding.instance.removeObserver(this);
_timer?.cancel();
mapController?.dispose();
// mapController?.dispose();
super.onClose();
}
List<LatLng> controllerDecodePolyline(String encoded) {
List<LatLng> points = [];
int index = 0, len = encoded.length;
int lat = 0, lng = 0;
while (index < len) {
int b, shift = 0, result = 0;
do {
b = encoded.codeUnitAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lat += dlat;
shift = 0;
result = 0;
do {
b = encoded.codeUnitAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lng += dlng;
points.add(LatLng(lat / 1E5, lng / 1E5));
}
return points;
}
}

View File

@@ -1,16 +1,6 @@
import 'package:google_maps_flutter/google_maps_flutter.dart';
// تم تعديل الدالة لتقبل وسيط من نوع `dynamic` لحل مشكلة عدم تطابق الأنواع مع دالة `compute`.
// هذه الدالة لا تزال تعمل كدالة من المستوى الأعلى (Top-level function)
// وهو شرط أساسي لاستخدامها مع دالة compute.
List<LatLng> decodePolylineIsolate(dynamic encodedMessage) {
// التأكد من أن الرسالة المستقبلة هي من نوع String
if (encodedMessage is! String) {
// إرجاع قائمة فارغة أو إظهار خطأ إذا كان النوع غير صحيح
return [];
}
final String encoded = encodedMessage;
import 'package:intaleq_maps/intaleq_maps.dart';
List<LatLng> decodePolylineIsolate(String encoded) {
List<LatLng> points = [];
int index = 0, len = encoded.length;
int lat = 0, lng = 0;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
import 'dart:async';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'dart:math';
/// Worker entrypoint (spawnUri/spawn).

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:sefer_driver/constant/style.dart';
import '../../constant/api_key.dart';