743 lines
24 KiB
Dart
Executable File
743 lines
24 KiB
Dart
Executable File
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
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: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';
|
|
import '../../../views/home/Captin/orderCaptin/marker_generator.dart';
|
|
import '../../../views/widgets/mydialoug.dart';
|
|
import '../../firebase/local_notification.dart';
|
|
import '../../functions/crud.dart';
|
|
import '../../functions/location_controller.dart';
|
|
import '../../home/captin/home_captain_controller.dart';
|
|
|
|
class OrderRequestController extends GetxController
|
|
with WidgetsBindingObserver {
|
|
// --- متغيرات التايمر ---
|
|
double progress = 1.0;
|
|
int duration = 15;
|
|
int remainingTime = 15;
|
|
Timer? _timer;
|
|
|
|
bool applied = false;
|
|
final locationController = Get.put(LocationController());
|
|
|
|
// 🔥 متغير لمنع تكرار القبول
|
|
bool _isRideTakenHandled = false;
|
|
|
|
// --- الأيقونات والماركرز ---
|
|
InlqBitmap? driverIcon;
|
|
Map<MarkerId, Marker> markersMap = {};
|
|
Set<Marker> get markers => markersMap.values.toSet();
|
|
|
|
// --- البيانات والتحكم ---
|
|
// 🔥 تم إضافة myMapData لدعم السوكيت الجديد
|
|
List<dynamic>? myList;
|
|
Map<dynamic, dynamic>? myMapData;
|
|
|
|
IntaleqMapController? mapController;
|
|
|
|
// الإحداثيات (أزلنا late لتجنب الأخطاء القاتلة)
|
|
double latPassenger = 0.0;
|
|
double lngPassenger = 0.0;
|
|
double latDestination = 0.0;
|
|
double lngDestination = 0.0;
|
|
|
|
// --- متغيرات العرض ---
|
|
String passengerRating = "5.0";
|
|
String tripType = "Standard";
|
|
String totalTripDistance = "--";
|
|
String totalTripDuration = "--";
|
|
String tripPrice = "--";
|
|
|
|
String timeToPassenger = "Calculating...".tr;
|
|
String distanceToPassenger = "--";
|
|
|
|
// --- الخريطة ---
|
|
Set<Polyline> polylines = {};
|
|
|
|
// حالة التطبيق والصوت
|
|
bool isInBackground = false;
|
|
final AudioPlayer audioPlayer = AudioPlayer();
|
|
|
|
@override
|
|
Future<void> onInit() async {
|
|
// 🛑 حماية من الفتح المتكرر لنفس الطلب
|
|
if (Get.arguments == null) {
|
|
print("❌ OrderController Error: No arguments received.");
|
|
Get.back(); // إغلاق الصفحة فوراً
|
|
return;
|
|
}
|
|
super.onInit();
|
|
WidgetsBinding.instance.addObserver(this);
|
|
|
|
_checkOverlay();
|
|
|
|
// 🔥 تهيئة البيانات هي الخطوة الأولى والأهم
|
|
_initializeData();
|
|
_parseExtraData();
|
|
|
|
// 1. تجهيز أيقونة السائق
|
|
await _prepareDriverIcon();
|
|
|
|
// 2. وضع الماركرز المبدئية
|
|
_updateMarkers(
|
|
paxTime: "...",
|
|
paxDist: "",
|
|
destTime: totalTripDuration,
|
|
destDist: totalTripDistance);
|
|
|
|
// 3. رسم مبدئي
|
|
_initialMapSetup();
|
|
|
|
// 4. الاستماع للسوكيت
|
|
_listenForRideTaken();
|
|
|
|
// 5. حساب المسارين
|
|
await _calculateFullJourney();
|
|
|
|
// 6. تشغيل التايمر
|
|
startTimer();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// 🔥🔥🔥 Smart Data Handling (List & Map Support) 🔥🔥🔥
|
|
// ----------------------------------------------------------------------
|
|
|
|
void _initializeData() {
|
|
var args = Get.arguments;
|
|
print("📦 Order Controller Received Type: ${args.runtimeType}");
|
|
print("📦 Order Controller Data: $args");
|
|
|
|
if (args != null) {
|
|
// الحالة 1: قائمة مباشرة (Legacy / Some Firebase formats)
|
|
if (args is List) {
|
|
myList = args;
|
|
}
|
|
// الحالة 2: خريطة (Map)
|
|
else if (args is Map) {
|
|
// أ) هل هي قادمة من Firebase وتحتوي على DriverList؟
|
|
if (args.containsKey('DriverList')) {
|
|
var listData = args['DriverList'];
|
|
if (listData is List) {
|
|
myList = listData;
|
|
} else if (listData is String) {
|
|
// أحياناً تصل كنص مشفر داخل الـ Map
|
|
try {
|
|
myList = jsonDecode(listData);
|
|
} catch (e) {
|
|
print("Error decoding DriverList: $e");
|
|
}
|
|
}
|
|
}
|
|
// ب) هل هي قادمة من Socket بالمفاتيح الرقمية ("0", "1", ...)؟
|
|
else {
|
|
myMapData = args;
|
|
}
|
|
}
|
|
}
|
|
|
|
// تعبئة الإحداثيات باستخدام الدالة الذكية _getValueAt
|
|
latPassenger = _parseCoord(_getValueAt(0));
|
|
lngPassenger = _parseCoord(_getValueAt(1));
|
|
latDestination = _parseCoord(_getValueAt(3));
|
|
lngDestination = _parseCoord(_getValueAt(4));
|
|
|
|
print(
|
|
"📍 Parsed Coordinates: Pax($latPassenger, $lngPassenger) -> Dest($latDestination, $lngDestination)");
|
|
}
|
|
|
|
/// 🔥 دالة ذكية تجلب القيمة سواء كانت البيانات في List أو Map
|
|
dynamic _getValueAt(int index) {
|
|
// الأولوية للقائمة
|
|
if (myList != null && index < myList!.length) {
|
|
return myList![index];
|
|
}
|
|
// ثم الخريطة (السوكيت) - المفاتيح عبارة عن String
|
|
if (myMapData != null && myMapData!.containsKey(index.toString())) {
|
|
return myMapData![index.toString()];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// الدالة التي يستخدمها باقي الكود لجلب البيانات كنصوص
|
|
String _safeGet(int index) {
|
|
var val = _getValueAt(index);
|
|
if (val != null) {
|
|
return val.toString();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
double _parseCoord(dynamic val) {
|
|
if (val == null) return 0.0;
|
|
String s = val.toString().replaceAll(',', '').trim();
|
|
if (s.contains(' ')) s = s.split(' ')[0];
|
|
return double.tryParse(s) ?? 0.0;
|
|
}
|
|
|
|
void _parseExtraData() {
|
|
passengerRating = _safeGet(33).isEmpty ? "5.0" : _safeGet(33);
|
|
tripType = _safeGet(31);
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// 🔥🔥🔥 Core Logic: Concurrent API Calls & Bounds 🔥🔥🔥
|
|
// ----------------------------------------------------------------------
|
|
|
|
Future<void> _calculateFullJourney() async {
|
|
// Don't block on mapController being null - we'll draw routes
|
|
// and markers first, then zoom when controller is ready
|
|
bool canZoom = mapController != null;
|
|
|
|
try {
|
|
// Reuse stored location from LocationController instead of
|
|
// making a duplicate GPS hardware call (already fetched in
|
|
// _initialMapSetup).
|
|
LatLng driverLatLng;
|
|
double driverHeading = 0.0;
|
|
if (Get.isRegistered<LocationController>()) {
|
|
final locCtrl = Get.find<LocationController>();
|
|
if (locCtrl.myLocation.latitude != 0 ||
|
|
locCtrl.myLocation.longitude != 0) {
|
|
driverLatLng = locCtrl.myLocation;
|
|
driverHeading = locCtrl.heading;
|
|
} else {
|
|
Position driverPos = await Geolocator.getCurrentPosition(
|
|
desiredAccuracy: LocationAccuracy.high);
|
|
driverLatLng = LatLng(driverPos.latitude, driverPos.longitude);
|
|
driverHeading = driverPos.heading;
|
|
}
|
|
} else {
|
|
Position driverPos = await Geolocator.getCurrentPosition(
|
|
desiredAccuracy: LocationAccuracy.high);
|
|
driverLatLng = LatLng(driverPos.latitude, driverPos.longitude);
|
|
driverHeading = driverPos.heading;
|
|
}
|
|
|
|
updateDriverLocation(driverLatLng, driverHeading);
|
|
|
|
// Clear old polylines to avoid "ghost lines"
|
|
polylines.clear();
|
|
|
|
var pickupFuture = _fetchRouteData(
|
|
start: driverLatLng,
|
|
end: LatLng(latPassenger, lngPassenger),
|
|
color: Colors.amber,
|
|
id: 'pickup_route');
|
|
|
|
var tripFuture = _fetchRouteData(
|
|
start: LatLng(latPassenger, lngPassenger),
|
|
end: LatLng(latDestination, lngDestination),
|
|
color: Colors.black,
|
|
id: 'trip_route',
|
|
getSteps: true); // 🔥 نطلب الخطوات للمسار
|
|
|
|
var results = await Future.wait([pickupFuture, tripFuture]);
|
|
|
|
var pickupResult = results[0];
|
|
var tripResult = results[1];
|
|
|
|
if (pickupResult != null) {
|
|
distanceToPassenger = pickupResult['distance_text'];
|
|
timeToPassenger = pickupResult['duration_text'];
|
|
polylines.add(pickupResult['polyline']);
|
|
}
|
|
|
|
if (tripResult != null) {
|
|
totalTripDistance = tripResult['distance_text'];
|
|
totalTripDuration = tripResult['duration_text'];
|
|
polylines.add(tripResult['polyline']);
|
|
|
|
// 🔥 تخزين استجابة السيرفر كاملة (بما فيها الـ points والـ instructions)
|
|
if (tripResult['raw_response'] != null) {
|
|
box.write('cached_trip_route', tripResult['raw_response']);
|
|
}
|
|
}
|
|
|
|
await _updateMarkers(
|
|
paxTime: timeToPassenger,
|
|
paxDist: distanceToPassenger,
|
|
destTime: totalTripDuration,
|
|
destDist: totalTripDistance);
|
|
|
|
// Now zoom to fit all polylines and markers (if controller available)
|
|
if (canZoom) {
|
|
zoomToFitRide();
|
|
}
|
|
|
|
update();
|
|
} catch (e) {
|
|
print("❌ Error in Journey Calculation: $e");
|
|
}
|
|
}
|
|
|
|
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,
|
|
required Color color,
|
|
required String id,
|
|
bool getSteps = false}) async {
|
|
try {
|
|
if (start.latitude == 0 || end.latitude == 0) return null;
|
|
// Don't block on mapController — route data fetch is independent
|
|
|
|
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': getSteps ? 'true' : 'false',
|
|
'alternatives': 'false',
|
|
'locale': 'ar',
|
|
});
|
|
|
|
final response = await http.get(saasUrl, headers: {
|
|
'x-api-key': Env.mapSaasKey,
|
|
'Content-Type': 'application/json',
|
|
});
|
|
|
|
if (response.statusCode != 200) {
|
|
throw Exception("Routing request failed: ${response.statusCode}");
|
|
}
|
|
|
|
final data = jsonDecode(response.body);
|
|
print("🛣️ Route API Response [$id]: ${data}");
|
|
|
|
// 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();
|
|
|
|
if (encodedPoints != null && encodedPoints.isNotEmpty) {
|
|
List<LatLng> path = controllerDecodePolyline(encodedPoints);
|
|
print("📍 Path for [$id] has ${path.length} points.");
|
|
|
|
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,
|
|
'encoded_polyline': encodedPoints,
|
|
'raw_response': response.body, // 🔥 نمرر الـ JSON كاملاً
|
|
};
|
|
}
|
|
} catch (e) {
|
|
print("Route Fetch Error: $e");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void zoomToFitRide() {
|
|
if (mapController == null) return;
|
|
|
|
List<LatLng> allPoints = [];
|
|
|
|
// Add all polyline points to the bounds calculation
|
|
for (var polyline in polylines) {
|
|
allPoints.addAll(polyline.points);
|
|
}
|
|
|
|
// Fallback to basic markers if polylines are empty
|
|
if (allPoints.isEmpty) {
|
|
allPoints.addAll([
|
|
LatLng(latPassenger, lngPassenger),
|
|
LatLng(latDestination, lngDestination),
|
|
]);
|
|
}
|
|
|
|
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 - latPad, minLng - lngPad),
|
|
northeast: LatLng(maxLat + latPad, maxLng + lngPad),
|
|
),
|
|
));
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Markers & Setup
|
|
// ----------------------------------------------------------------------
|
|
|
|
Future<void> _prepareDriverIcon() async {
|
|
driverIcon = await MarkerGenerator.createDriverMarker();
|
|
}
|
|
|
|
Future<void> _updateMarkers(
|
|
{required String paxTime,
|
|
required String paxDist,
|
|
String? destTime,
|
|
String? destDist}) async {
|
|
// حماية إذا لم يتم جلب الإحداثيات
|
|
if (latPassenger == 0 || latDestination == 0) return;
|
|
|
|
final InlqBitmap pickupIcon =
|
|
await MarkerGenerator.createCustomMarkerBitmap(
|
|
title: paxTime,
|
|
subtitle: paxDist,
|
|
color: Colors.amber.shade900, // Matching the amber pickup line
|
|
iconData: Icons.person_pin_circle,
|
|
);
|
|
|
|
final InlqBitmap dropoffIcon =
|
|
await MarkerGenerator.createCustomMarkerBitmap(
|
|
title: destTime ?? totalTripDuration,
|
|
subtitle: destDist ?? totalTripDistance,
|
|
color: Colors.red.shade800,
|
|
iconData: Icons.flag,
|
|
);
|
|
|
|
markersMap[const MarkerId('pax')] = Marker(
|
|
markerId: const MarkerId('pax'),
|
|
position: LatLng(latPassenger, lngPassenger),
|
|
icon: pickupIcon,
|
|
anchor: const Offset(0.5, 0.85),
|
|
);
|
|
|
|
markersMap[const MarkerId('dest')] = Marker(
|
|
markerId: const MarkerId('dest'),
|
|
position: LatLng(latDestination, lngDestination),
|
|
icon: dropoffIcon,
|
|
anchor: const Offset(0.5, 0.85),
|
|
);
|
|
|
|
update();
|
|
}
|
|
|
|
void _initialMapSetup() async {
|
|
Position driverPos = await Geolocator.getCurrentPosition();
|
|
LatLng driverLatLng = LatLng(driverPos.latitude, driverPos.longitude);
|
|
|
|
if (driverIcon != null) {
|
|
markersMap[const MarkerId('driver')] = Marker(
|
|
markerId: const MarkerId('driver'),
|
|
position: driverLatLng,
|
|
icon: driverIcon!,
|
|
rotation: driverPos.heading,
|
|
anchor: const Offset(0.5, 0.5),
|
|
flat: true,
|
|
zIndex: 10);
|
|
}
|
|
|
|
if (latPassenger != 0 && lngPassenger != 0) {
|
|
polylines.add(Polyline(
|
|
polylineId: const PolylineId('temp_line'),
|
|
points: [driverLatLng, LatLng(latPassenger, lngPassenger)],
|
|
color: Colors.grey,
|
|
width: 2,
|
|
));
|
|
|
|
zoomToFitRide();
|
|
}
|
|
|
|
update();
|
|
}
|
|
|
|
void updateDriverLocation(LatLng newPos, double heading) {
|
|
if (driverIcon != null) {
|
|
markersMap[const MarkerId('driver')] = Marker(
|
|
markerId: const MarkerId('driver'),
|
|
position: newPos,
|
|
icon: driverIcon!,
|
|
rotation: heading,
|
|
anchor: const Offset(0.5, 0.5),
|
|
flat: true,
|
|
zIndex: 10,
|
|
);
|
|
update();
|
|
}
|
|
}
|
|
|
|
void onMapCreated(IntaleqMapController controller) {
|
|
mapController = controller;
|
|
_calculateFullJourney();
|
|
}
|
|
|
|
// --- قبول الطلب وإدارة التايمر ---
|
|
void startTimer() {
|
|
_timer?.cancel();
|
|
remainingTime = duration;
|
|
_playAudio();
|
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
if (remainingTime <= 0) {
|
|
timer.cancel();
|
|
_stopAudio();
|
|
if (!applied) Get.back();
|
|
} else {
|
|
remainingTime--;
|
|
progress = remainingTime / duration;
|
|
update();
|
|
}
|
|
});
|
|
}
|
|
|
|
void endTimer() => _timer?.cancel();
|
|
void changeApplied() => applied = true;
|
|
|
|
void _playAudio() async {
|
|
try {
|
|
await audioPlayer.setAsset('assets/order.mp3', preload: true);
|
|
await audioPlayer.setLoopMode(LoopMode.one);
|
|
await audioPlayer.play();
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
}
|
|
|
|
void _stopAudio() => audioPlayer.stop();
|
|
|
|
void _listenForRideTaken() {
|
|
if (locationController.socket != null) {
|
|
locationController.socket!.off('ride_taken');
|
|
locationController.socket!.on('ride_taken', (data) {
|
|
if (_isRideTakenHandled) return;
|
|
String takenRideId = data['ride_id'].toString();
|
|
String myCurrentRideId = _safeGet(16);
|
|
String whoTookIt = data['taken_by_driver_id'].toString();
|
|
String myDriverId = box.read(BoxName.driverID).toString();
|
|
|
|
if (takenRideId == myCurrentRideId && whoTookIt != myDriverId) {
|
|
_isRideTakenHandled = true;
|
|
endTimer();
|
|
// 1. حذف الإشعار من شريط التنبيهات فوراً
|
|
NotificationController().cancelOrderNotification();
|
|
if (Get.isSnackbarOpen) Get.closeCurrentSnackbar();
|
|
|
|
// إغلاق أي ديالوج مفتوح قسرياً
|
|
if (Get.isDialogOpen ?? false) {
|
|
navigatorKey.currentState?.pop();
|
|
}
|
|
Get.back();
|
|
mySnackbarInfo("The order has been accepted by another driver.".tr);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Lifecycle
|
|
@override
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
super.didChangeAppLifecycleState(state);
|
|
if (state == AppLifecycleState.paused ||
|
|
state == AppLifecycleState.detached) {
|
|
isInBackground = true;
|
|
} else if (state == AppLifecycleState.resumed) {
|
|
isInBackground = false;
|
|
FlutterOverlayWindow.closeOverlay();
|
|
}
|
|
}
|
|
|
|
void _checkOverlay() async {
|
|
if (Platform.isAndroid && await FlutterOverlayWindow.isActive()) {
|
|
await FlutterOverlayWindow.closeOverlay();
|
|
}
|
|
}
|
|
|
|
// Accept Order Logic
|
|
Future<void> acceptOrder() async {
|
|
endTimer();
|
|
_stopAudio();
|
|
|
|
// 1. إرسال الطلب
|
|
var res = await CRUD()
|
|
.post(link: "${AppLink.ride}/rides/acceptRide.php", payload: {
|
|
'id': _safeGet(16),
|
|
'rideTimeStart': DateTime.now().toString(),
|
|
'status': 'Apply',
|
|
'passengerToken': _safeGet(9),
|
|
'driver_id': box.read(BoxName.driverID),
|
|
});
|
|
|
|
Log.print('res from orderrequestpage: ${res}');
|
|
|
|
// ============================================================
|
|
// تصحيح: فحص الرد بدقة (Map أو String)
|
|
// ============================================================
|
|
bool isFailure = false;
|
|
|
|
if (res is Map && res['status'] == 'failure') {
|
|
isFailure = true;
|
|
} else if (res == 'failure') {
|
|
isFailure = true;
|
|
}
|
|
|
|
if (isFailure) {
|
|
// ⛔ حالة الفشل: الطلب مأخوذ
|
|
MyDialog().getDialog(
|
|
"Sorry, the order was taken by another driver.".tr, '', () {
|
|
// بما أن MyDialog يغلق نفسه الآن، نحتاج Get.back() واحدة فقط لإغلاق صفحة الطلب
|
|
Get.back();
|
|
});
|
|
} else {
|
|
// ✅ حالة النجاح
|
|
|
|
// حماية من الكراش: التأكد من وجود HomeCaptainController قبل استخدامه
|
|
if (!Get.isRegistered<HomeCaptainController>()) {
|
|
Get.put(HomeCaptainController());
|
|
} else {
|
|
Get.find<HomeCaptainController>().changeRideId();
|
|
}
|
|
|
|
box.write(BoxName.statusDriverLocation, 'on');
|
|
changeApplied();
|
|
|
|
var rideArgs = {
|
|
'passengerLocation': '${_safeGet(0)},${_safeGet(1)}',
|
|
'passengerDestination': '${_safeGet(3)},${_safeGet(4)}',
|
|
'Duration': totalTripDuration,
|
|
'totalCost': _safeGet(26),
|
|
'Distance': totalTripDistance,
|
|
'name': _safeGet(8),
|
|
'phone': _safeGet(10),
|
|
'email': _safeGet(28),
|
|
'WalletChecked': _safeGet(13),
|
|
'tokenPassenger': _safeGet(9),
|
|
'direction':
|
|
'https://www.google.com/maps/dir/${_safeGet(0)}/${_safeGet(1)}/',
|
|
'DurationToPassenger': timeToPassenger,
|
|
'rideId': _safeGet(16),
|
|
'passengerId': _safeGet(7),
|
|
'driverId': _safeGet(18),
|
|
'durationOfRideValue': totalTripDuration,
|
|
'paymentAmount': _safeGet(2),
|
|
'paymentMethod': _safeGet(13) == 'true' ? 'visa' : 'cash',
|
|
'isHaveSteps': _safeGet(20),
|
|
'step0': _safeGet(21),
|
|
'step1': _safeGet(22),
|
|
'step2': _safeGet(23),
|
|
'step3': _safeGet(24),
|
|
'step4': _safeGet(25),
|
|
'passengerWalletBurc': _safeGet(26),
|
|
'timeOfOrder': DateTime.now().toString(),
|
|
'totalPassenger': _safeGet(2),
|
|
'carType': _safeGet(31),
|
|
'kazan': _safeGet(32),
|
|
'startNameLocation': _safeGet(29),
|
|
'endNameLocation': _safeGet(30),
|
|
};
|
|
|
|
box.write(BoxName.rideArguments, rideArgs);
|
|
|
|
// الانتقال النهائي
|
|
Get.off(() => PassengerLocationMapPage(), arguments: rideArgs);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
locationController.socket?.off('ride_taken');
|
|
audioPlayer.dispose();
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
_timer?.cancel();
|
|
// 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;
|
|
}
|
|
}
|