25-10-11/1

This commit is contained in:
Hamza-Ayed
2025-11-06 12:29:17 +03:00
parent 14484fcd8f
commit a69e4c6912
46 changed files with 14145 additions and 13529 deletions

1
.env
View File

@@ -5,6 +5,7 @@ accountSIDTwillo=QFx0qy456juj383n9xuy2194q629q1fj0y7XrXlBl
serverAPI=QQQQobSrrFi:QVQ87xU7zwCvmZzZdaxuS2f23Y4mz7MzyOzr8od2br6KYyeFaTVLG3K3hx5ZaUyx7eYvAYpAVdKk-286NTRi3zs9iSOnXtXRIxswg3KecBmsl3VxJ9wO-vIpwu4Pv7dkHkXniuxMSDgWXrXlBl serverAPI=QQQQobSrrFi:QVQ87xU7zwCvmZzZdaxuS2f23Y4mz7MzyOzr8od2br6KYyeFaTVLG3K3hx5ZaUyx7eYvAYpAVdKk-286NTRi3zs9iSOnXtXRIxswg3KecBmsl3VxJ9wO-vIpwu4Pv7dkHkXniuxMSDgWXrXlBl
mapAPIKEY=AIzaSyAPFR_XbRN0XZ5Iz3AYDjNYHGJG2s2QWwM mapAPIKEY=AIzaSyAPFR_XbRN0XZ5Iz3AYDjNYHGJG2s2QWwM
email=@intaleqapp.com email=@intaleqapp.com
mapKeyOsm=maldev@route-dollars
mapAPIKEYIOS=AIzaSyDdqkLMCrqjVrn7XmadIqynyoBa7P27OeM mapAPIKEYIOS=AIzaSyDdqkLMCrqjVrn7XmadIqynyoBa7P27OeM
twilloRecoveryCode=CAU79DHPH1BjE9PUH4ETXTSXZXrXlBl twilloRecoveryCode=CAU79DHPH1BjE9PUH4ETXTSXZXrXlBl
apiKeyHere=g_WNUb5L-tripz7-F8omHpUmgIzH7ETeH9xZ8RwGG9_G8zX9A apiKeyHere=g_WNUb5L-tripz7-F8omHpUmgIzH7ETeH9xZ8RwGG9_G8zX9A

View File

@@ -44,8 +44,8 @@ android {
applicationId = "com.intaleq_driver" applicationId = "com.intaleq_driver"
minSdk = 29 minSdk = 29
targetSdk = 36 targetSdk = 36
versionCode = 20 versionCode = 29
versionName = '1.0.20' // I've used the higher version name versionName = '1.0.29' // I've used the higher version name
multiDexEnabled = true multiDexEnabled = true
ndk { ndk {

View File

@@ -0,0 +1,107 @@
// في ملف: constant/country_polygons.dart
import 'package:google_maps_flutter/google_maps_flutter.dart';
class CountryPolygons {
// ==========================================================
// 1. الأردن: تغطية الممر الحضري الرئيسي (من إربد شمالاً حتى العقبة جنوباً)
// حوالي 12 نقطة
// ==========================================================
static final List<LatLng> jordanBoundary = [
// شمال إربد (قرب الحدود)
const LatLng(32.65, 35.80),
// شمال شرق المفرق
const LatLng(32.35, 37.00),
// شرق الزرقاء / الأزرق
const LatLng(31.85, 36.80),
// جنوب شرق (نهاية الزحف السكاني)
const LatLng(31.00, 36.50),
// جنوب / معان
const LatLng(30.30, 35.75),
// العقبة
const LatLng(29.50, 35.00),
// البحر الأحمر / الحدود الغربية
const LatLng(29.50, 34.85),
// غرب وادي عربة
const LatLng(30.80, 35.25),
// منطقة البحر الميت / السلط
const LatLng(32.00, 35.50),
// العودة عبر وادي الأردن إلى الشمال
const LatLng(32.45, 35.60),
// العودة لنقطة إربد
const LatLng(32.65, 35.80),
];
// ==========================================================
// 2. سوريا: تغطية الممر الغربي والساحلي (درعا، دمشق، حمص، حماة، حلب، الساحل)
// حوالي 14 نقطة
// ==========================================================
static final List<LatLng> syriaBoundary = [
// درعا / الجنوب
const LatLng(32.65, 35.95),
// شرق السويداء (حدود المنطقة المأهولة)
const LatLng(32.85, 37.10),
// أطراف دمشق الشرقية
const LatLng(33.50, 36.65),
// تدمر (أقصى امتداد شرقي للمضلع)
const LatLng(34.50, 38.30),
// الرقة (شمال شرق)
const LatLng(35.95, 38.80),
// حلب (الشمال)
const LatLng(36.45, 37.15),
// الحدود الشمالية الغربية (إدلب / تركيا)
const LatLng(36.50, 36.50),
// اللاذقية (الساحل)
const LatLng(35.50, 35.75),
// طرطوس (الساحل)
const LatLng(34.80, 35.85),
// حمص
const LatLng(34.70, 36.70),
// حماة
const LatLng(35.10, 36.70),
// العودة إلى منطقة دمشق
const LatLng(33.40, 36.30),
// العودة إلى درعا
const LatLng(32.65, 35.95),
];
// ==========================================================
// 3. مصر: تغطية القاهرة الكبرى، الدلتا، والإسكندرية والإسماعيلية
// حوالي 10 نقاط
// ==========================================================
static final List<LatLng> egyptBoundary = [
// جنوب الفيوم (أقصى امتداد جنوبي غربي)
const LatLng(29.20, 30.60),
// جنوب القاهرة (العياط)
const LatLng(29.80, 31.30),
// شرق السويس
const LatLng(29.95, 32.70),
// الإسماعيلية / القناة
const LatLng(30.60, 32.25),
// بورسعيد / أطراف الدلتا الشمالية الشرقية
const LatLng(31.30, 31.80),
// دمياط / ساحل الدلتا
const LatLng(31.50, 31.25),
// الإسكندرية (أقصى الشمال الغربي)
const LatLng(31.20, 29.80),
// غرب الدلتا
const LatLng(30.50, 30.20),
// العودة لنقطة البداية
const LatLng(29.20, 30.60),
];
// دالة تُرجع رابط API بناءً على اسم الدولة
static String getRoutingApiUrl(String countryName) {
switch (countryName) {
case 'Jordan':
return 'https://routec.intaleq.xyz/route-jo';
case 'Syria':
return 'https://routec.intaleq.xyz/route';
case 'Egypt':
return 'https://routec.intaleq.xyz/route-eg';
default:
// الافتراضي في حالة لم يقع الموقع ضمن أي من المضلعات
return 'https://routec.intaleq.xyz/route';
}
}
}

View File

@@ -1,7 +1,5 @@
// import 'package:sefer_driver/env/env.dart'; // import 'package:sefer_driver/env/env.dart';
import 'package:sefer_driver/env/env.dart';
import '../main.dart'; import '../main.dart';
import 'box_name.dart'; import 'box_name.dart';
@@ -9,13 +7,16 @@ class AppLink {
static String serverPHP = box.read('serverPHP'); static String serverPHP = box.read('serverPHP');
static String paymentServer = 'https://walletintaleq.intaleq.xyz/v1/main'; static String paymentServer = 'https://walletintaleq.intaleq.xyz/v1/main';
static String seferPaymentServer0 =
'https://walletintaleq.intaleq.xyz/v1/main';
static final String endPoint = 'https://intaleq.xyz/intaleq'; static String locationServer =
'https://location.intaleq.xyz/intaleq/ride/location';
static final String endPoint = 'https://api.intaleq.xyz/intaleq';
static final String syria = 'https://syria.intaleq.xyz/intaleq'; static final String syria = 'https://syria.intaleq.xyz/intaleq';
// 'https://api.tripz-egypt.com/tripz';
static final String server = endPoint; static final String server = endPoint;
static String ride = '$endPoint/ride';
static String rideServer = 'https://rides.intaleq.xyz/intaleq/ride';
static String seferCairoServer = endPoint; static String seferCairoServer = endPoint;
static String seferGizaServer = static String seferGizaServer =
box.read('Giza') ?? box.read(BoxName.serverChosen); box.read('Giza') ?? box.read(BoxName.serverChosen);
@@ -24,8 +25,7 @@ class AppLink {
// static final String server = Env.serverPHP; // static final String server = Env.serverPHP;
static String loginJwtDriver = "$server/loginJwtDriver.php"; static String loginJwtDriver = "$server/loginJwtDriver.php";
static String loginJwtWalletDriver = static String loginJwtWalletDriver = "$server/loginJwtWalletDriver.php";
"$paymentServer/loginJwtWalletDriver.php";
static String loginFirstTimeDriver = "$server/loginFirstTimeDriver.php"; static String loginFirstTimeDriver = "$server/loginFirstTimeDriver.php";
static String googleMapsLink = 'https://maps.googleapis.com/maps/api/'; static String googleMapsLink = 'https://maps.googleapis.com/maps/api/';
@@ -34,7 +34,7 @@ class AppLink {
'https://generativelanguage.googleapis.com/v1beta3/models/text-bison-001:generateText'; 'https://generativelanguage.googleapis.com/v1beta3/models/text-bison-001:generateText';
static String test = "$server/test.php"; static String test = "$server/test.php";
static String ride = '$server/ride';
//===============contact========================== //===============contact==========================
static String savePhones = "$ride/egyptPhones/add.php"; static String savePhones = "$ride/egyptPhones/add.php";
static String savePhonesSyria = "$ride/egyptPhones/syrianAdd.php"; static String savePhonesSyria = "$ride/egyptPhones/syrianAdd.php";
@@ -97,19 +97,20 @@ class AppLink {
////=======================cancelRide=================== ////=======================cancelRide===================
static String addCancelRideFromPassenger = "$ride/cancelRide/add.php"; static String addCancelRideFromPassenger = "$rideServer/cancelRide/add.php";
static String addCancelTripFromDriverAfterApplied = static String addCancelTripFromDriverAfterApplied =
"$ride/cancelRide/addCancelTripFromDriverAfterApplied.php"; "$rideServer/cancelRide/addCancelTripFromDriverAfterApplied.php";
static String cancelRide = "$ride/cancelRide/get.php"; static String cancelRide = "$rideServer/cancelRide/get.php";
//-----------------ridessss------------------ //-----------------ridessss------------------
static String addRides = "$ride/rides/add.php"; static String addRides = "$rideServer/rides/add.php";
static String getRides = "$ride/rides/get.php"; static String getRides = "$rideServer/rides/get.php";
static String getPlacesSyria = "$ride/places_syria/get.php"; static String getPlacesSyria = "$rideServer/places_syria/get.php";
static String getMishwari = "$ride/mishwari/get.php"; static String getMishwari = "$rideServer/mishwari/get.php";
static String getMishwariDriver = "$ride/mishwari/getDriver.php"; static String getMishwariDriver = "$rideServer/mishwari/getDriver.php";
static String getTripCountByCaptain = "$ride/rides/getTripCountByCaptain.php"; static String getTripCountByCaptain =
static String getRideOrderID = "$ride/rides/getRideOrderID.php"; "$rideServer/rides/getTripCountByCaptain.php";
static String getRideStatus = "$ride/rides/getRideStatus.php"; static String getRideOrderID = "$rideServer/rides/getRideOrderID.php";
static String getRideStatus = "$rideServer/rides/getRideStatus.php";
static String getOverLayStatus = "$ride/overLay/get.php"; static String getOverLayStatus = "$ride/overLay/get.php";
static String getArgumentAfterAppliedFromBackground = static String getArgumentAfterAppliedFromBackground =
"$ride/overLay/getArgumentAfterAppliedFromBackground.php"; "$ride/overLay/getArgumentAfterAppliedFromBackground.php";
@@ -117,14 +118,15 @@ class AppLink {
static String getapiKey = "$ride/apiKey/get.php"; static String getapiKey = "$ride/apiKey/get.php";
static String getapiKeySefer = "$ride/apiKey/get.php"; static String getapiKeySefer = "$ride/apiKey/get.php";
static String getRideStatusBegin = "$ride/rides/getRideStatusBegin.php"; static String getRideStatusBegin = "$rideServer/rides/getRideStatusBegin.php";
static String getRideStatusFromStartApp = static String getRideStatusFromStartApp =
"$ride/rides/getRideStatusFromStartApp.php"; "$rideServer/rides/getRideStatusFromStartApp.php";
static String updateRides = "$ride/rides/update.php"; static String updateRides = "$rideServer/rides/update.php";
static String updateRideAndCheckIfApplied = static String updateRideAndCheckIfApplied =
"$ride/rides/updateRideAndCheckIfApplied.php"; "$rideServer/rides/updateRideAndCheckIfApplied.php";
static String updateStausFromSpeed = "$ride/rides/updateStausFromSpeed.php"; static String updateStausFromSpeed =
static String deleteRides = "$ride/rides/delete.php"; "$rideServer/rides/updateStausFromSpeed.php";
static String deleteRides = "$rideServer/rides/delete.php";
//-----------------DriverPayment------------------ //-----------------DriverPayment------------------
static String addDriverScam = "$ride/driver_scam/add.php"; static String addDriverScam = "$ride/driver_scam/add.php";
@@ -158,7 +160,7 @@ class AppLink {
static String getDriverPaymentPoints = static String getDriverPaymentPoints =
"$paymentServer/ride/driverWallet/get.php"; "$paymentServer/ride/driverWallet/get.php";
static String getDriverPaymentToday = "$paymentServer/ride/payment/get.php"; static String getDriverPaymentToday = "$paymentServer/ride/payment/get.php";
static String getCountRide = "$ride/payment/getCountRide.php"; static String getCountRide = "$rideServer/payment/getCountRide.php";
static String getAllPaymentFromRide = static String getAllPaymentFromRide =
"$paymentServer/ride/payment/getAllPayment.php"; "$paymentServer/ride/payment/getAllPayment.php";
static String getAllPaymentVisa = static String getAllPaymentVisa =
@@ -266,27 +268,31 @@ class AppLink {
static String uploadEgypt1 = "$server/uploadEgypt1.php"; static String uploadEgypt1 = "$server/uploadEgypt1.php";
//==================certifcate========== //==================certifcate==========
static String location = '$endPoint/ride/location'; // static String location = '$endPoint/ride/location';
static String getCarsLocationByPassenger = "$location/get.php";
static String addpassengerLocation = "$location/addpassengerLocation.php"; static String getCarsLocationByPassenger = "$locationServer/get.php";
static String getLocationAreaLinks = "$location/get_location_area_links.php"; static String addpassengerLocation =
"$locationServer/addpassengerLocation.php";
static String getLocationAreaLinks =
"$locationServer/get_location_area_links.php";
static String getLatestLocationPassenger = static String getLatestLocationPassenger =
"$location/getLatestLocationPassenger.php"; "$locationServer/getLatestLocationPassenger.php";
static String getFemalDriverLocationByPassenger = static String getFemalDriverLocationByPassenger =
"$location/getFemalDriver.php"; "$locationServer/getFemalDriver.php";
static String getDriverCarsLocationToPassengerAfterApplied = static String getDriverCarsLocationToPassengerAfterApplied =
"$location/getDriverCarsLocationToPassengerAfterApplied.php"; "$locationServer/getDriverCarsLocationToPassengerAfterApplied.php";
static String addCarsLocationByPassenger = "$location/add.php"; static String addCarsLocationByPassenger = "$locationServer/add.php";
static String saveBehavior = "$location/save_behavior.php"; static String saveBehavior = "$locationServer/save_behavior.php";
static String addCarsLocationGizaEndpoint = "$location/add.php"; static String addCarsLocationGizaEndpoint = "$locationServer/add.php";
static String addCarsLocationAlexandriaEndpoint = "$location/add.php"; static String addCarsLocationAlexandriaEndpoint = "$locationServer/add.php";
static String addCarsLocationCairoEndpoint = "$location/add.php"; static String addCarsLocationCairoEndpoint = "$locationServer/add.php";
static String deleteCarsLocationByPassenger = "$location/delete.php"; static String deleteCarsLocationByPassenger = "$locationServer/delete.php";
static String updateCarsLocationByPassenger = "$location/update.php"; static String updateCarsLocationByPassenger = "$locationServer/update.php";
static String getTotalDriverDuration = "$location/getTotalDriverDuration.php"; static String getTotalDriverDuration =
static String getRidesDriverByDay = "$location/getRidesDriverByDay.php"; "$locationServer/getTotalDriverDuration.php";
static String getRidesDriverByDay = "$locationServer/getRidesDriverByDay.php";
static String getTotalDriverDurationToday = static String getTotalDriverDurationToday =
"$location/getTotalDriverDurationToday.php"; "$locationServer/getTotalDriverDurationToday.php";
//==================get_driver_behavior.php============= //==================get_driver_behavior.php=============
static String get_driver_behavior = static String get_driver_behavior =

View File

@@ -68,8 +68,8 @@ class LoginDriverController extends GetxController {
void onInit() async { void onInit() async {
box.write(BoxName.countryCode, 'Syria'); box.write(BoxName.countryCode, 'Syria');
// box.write(BoxName.driverID, '34feffd3fa72d6bee56b'); // box.write(BoxName.driverID, '34feffd3fa72d6bee56b');
await getAppTester(); // await getAppTester();
getJWT();
super.onInit(); super.onInit();
} }
@@ -150,8 +150,7 @@ class LoginDriverController extends GetxController {
// Log.print('response.request: ${response1.request}'); // Log.print('response.request: ${response1.request}');
// Log.print('response.body: ${response1.body}'); // Log.print('response.body: ${response1.body}');
// print(payload); // print(payload);
// Log.print( Log.print('payment["jwt"]: ${jsonDecode(response1.body)['jwt']}');
// 'jsonDecode(response1.body)["jwt"]: ${jsonDecode(response1.body)['jwt']}');
await box.write(BoxName.hmac, jsonDecode(response1.body)['hmac']); await box.write(BoxName.hmac, jsonDecode(response1.body)['hmac']);
return jsonDecode(response1.body)['jwt'].toString(); return jsonDecode(response1.body)['jwt'].toString();
} }
@@ -257,17 +256,19 @@ class LoginDriverController extends GetxController {
loginWithGoogleCredential(String driverID, email) async { loginWithGoogleCredential(String driverID, email) async {
isloading = true; isloading = true;
update(); update();
await SecurityHelper.performSecurityChecks(); // await SecurityHelper.performSecurityChecks();
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}'); // Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
// await getJWT(); // await getJWT();
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: { var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
'email': email ?? 'yet', // 'email': email ?? 'yet',
'id': driverID, 'id': driverID,
}); });
Log.print('res: ${res}');
// print('res is $res');
if (res == 'failure') { if (res == 'failure') {
await isPhoneVerified(); await isPhoneVerified();
isloading = false; // <--- أضفت هذا أيضاً
update();
return false;
// Get.snackbar('Failure', '', backgroundColor: Colors.red); // Get.snackbar('Failure', '', backgroundColor: Colors.red);
} else { } else {
var jsonDecoeded = jsonDecode(res); var jsonDecoeded = jsonDecode(res);
@@ -358,17 +359,24 @@ class LoginDriverController extends GetxController {
// } // }
} }
Get.offAll(() => HomeCaptain()); Get.offAll(() => HomeCaptain()); // افترض أن هذا الكلاس موجود
isloading = false; // <--- أضفت هذا
update(); // <--- أضفت هذا
return true;
} else { } else {
Get.off(() => DriverVerificationScreen()); Get.offAll(
() => DriverVerificationScreen()); // افترض أن هذا الكلاس موجود
isloading = false; // <--- أضفت هذا
update(); // <--- أضفت هذا
return false;
} }
// Get.off(() => HomeCaptain()); // Get.off(() => HomeCaptain());
} else { } else {
Get.offAll(() => PhoneNumberScreen()); Get.offAll(() => PhoneNumberScreen());
isloading = false; isloading = false;
update(); update();
return false; // <--- ✅ وهذا السطر موجود للحالات الأخرى
} }
} else { } else {
mySnackbarSuccess(''); mySnackbarSuccess('');
@@ -399,8 +407,10 @@ class LoginDriverController extends GetxController {
var jsonDecoeded = jsonDecode(res); var jsonDecoeded = jsonDecode(res);
var d = jsonDecoeded['data'][0]; var d = jsonDecoeded['data'][0];
if (jsonDecoeded.isNotEmpty) { if (jsonDecoeded.isNotEmpty) {
if (jsonDecoeded['status'] == 'success' && if (jsonDecoeded['status'] == 'success')
d['is_verified'].toString() == '1') { // &&
// d['is_verified'].toString() == '1')
{
box.write(BoxName.emailDriver, d['email']); box.write(BoxName.emailDriver, d['email']);
box.write(BoxName.firstTimeLoadKey, 'false'); box.write(BoxName.firstTimeLoadKey, 'false');
box.write(BoxName.driverID, (d['id'])); box.write(BoxName.driverID, (d['id']));
@@ -546,11 +556,11 @@ class LoginDriverController extends GetxController {
// 'ding.wav'); // 'ding.wav');
NotificationService.sendNotification( NotificationService.sendNotification(
target: (jsonDecode(token)['data'][0]['token']).toString(), target: (jsonDecode(token)['data'][0]['token']).toString(),
title: 'token change', title: 'token change'.tr,
body: 'token change'.tr, body: 'token change'.tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'cancel', tone: 'cancel',
driverList: [], driverList: [], category: 'token change',
); );
Get.defaultDialog( Get.defaultDialog(
title: 'you will use this device?'.tr, title: 'you will use this device?'.tr,

View File

@@ -111,11 +111,11 @@ class OtpVerificationController extends GetxController {
// ); // );
await NotificationService.sendNotification( await NotificationService.sendNotification(
target: ptoken.toString(), target: ptoken.toString(),
title: 'token change', title: 'token change'.tr,
body: 'token change'.tr, body: 'token change'.tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'cancel', tone: 'cancel',
driverList: [], driverList: [], category: 'token change',
); );
Get.offAll(() => HomeCaptain()); Get.offAll(() => HomeCaptain());

View File

@@ -59,7 +59,7 @@ class PhoneAuthHelper {
if (data['status'] == 'success') { if (data['status'] == 'success') {
final isRegistered = data['message']['isRegistered'] ?? false; final isRegistered = data['message']['isRegistered'] ?? false;
box.write(BoxName.phoneVerified, true); box.write(BoxName.phoneVerified, '1');
box.write(BoxName.phoneDriver, phoneNumber); box.write(BoxName.phoneDriver, phoneNumber);
box.write(BoxName.driverID, data['message']['driverID']); box.write(BoxName.driverID, data['message']['driverID']);

View File

@@ -8,6 +8,7 @@ import 'package:image_cropper/image_cropper.dart';
import 'package:image/image.dart' as img; import 'package:image/image.dart' as img;
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:sefer_driver/constant/links.dart'; import 'package:sefer_driver/constant/links.dart';
import 'package:sefer_driver/controller/firebase/notification_service.dart';
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
// --- Final Submission --- // --- Final Submission ---
@@ -564,11 +565,18 @@ class RegistrationController extends GetxController {
'token': (box.read(BoxName.tokenDriver)).toString(), 'token': (box.read(BoxName.tokenDriver)).toString(),
'fingerPrint': fingerPrint.toString(), 'fingerPrint': fingerPrint.toString(),
}); });
await CRUD().post(link: AppLink.addTokensDriverWallet, payload: { // CRUD().post(link: AppLink.addTokensDriverWallet, payload: {
'token': box.read(BoxName.tokenDriver).toString(), // 'token': box.read(BoxName.tokenDriver).toString(),
'fingerPrint': fingerPrint.toString(), // 'fingerPrint': fingerPrint.toString(),
'captain_id': box.read(BoxName.driverID).toString(), // 'captain_id': box.read(BoxName.driverID).toString(),
}); // });
NotificationService.sendNotification(
target: 'service', // الإرسال لجميع المشتركين في "service"
title: 'طلب خدمة جديد',
body: 'تم استلام طلب خدمة جديد. الرجاء مراجعة التفاصيل.',
isTopic: true,
category: 'new_service_request', // فئة توضح نوع الإشعار
);
c.loginWithGoogleCredential(driverID, email); c.loginWithGoogleCredential(driverID, email);
} else { } else {

View File

@@ -1,32 +1,25 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:sefer_driver/constant/api_key.dart';
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart'; import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart'; import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart'; import 'package:sefer_driver/views/widgets/mydialoug.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:sefer_driver/views/widgets/elevated_btn.dart'; import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import '../../constant/box_name.dart'; import '../../constant/box_name.dart';
import '../../constant/colors.dart'; import '../../constant/colors.dart';
import '../../constant/style.dart'; import '../../constant/style.dart';
import '../../env/env.dart';
import '../../main.dart'; import '../../main.dart';
import '../../print.dart'; import '../../print.dart';
import '../../views/auth/captin/criminal_documents_page.dart'; import '../../views/auth/captin/criminal_documents_page.dart';
import '../../views/home/Captin/home_captain/home_captin.dart'; import '../../views/home/Captin/home_captain/home_captin.dart';
import '../../views/home/Captin/orderCaptin/order_speed_request.dart';
import '../../views/home/Captin/orderCaptin/order_request_page.dart'; import '../../views/home/Captin/orderCaptin/order_request_page.dart';
import '../../views/home/Captin/orderCaptin/vip_order_page.dart'; import '../../views/home/Captin/orderCaptin/vip_order_page.dart';
import '../auth/google_sign.dart'; import '../auth/google_sign.dart';
import '../functions/encrypt_decrypt.dart';
import '../functions/face_detect.dart'; import '../functions/face_detect.dart';
import 'access_token.dart';
import 'local_notification.dart'; import 'local_notification.dart';
import 'notification_service.dart';
class FirebaseMessagesController extends GetxController { class FirebaseMessagesController extends GetxController {
final fcmToken = FirebaseMessaging.instance; final fcmToken = FirebaseMessaging.instance;
@@ -76,6 +69,9 @@ class FirebaseMessagesController extends GetxController {
Log.print('token fcm driver: ${token}'); Log.print('token fcm driver: ${token}');
box.write(BoxName.tokenDriver, (token!)); box.write(BoxName.tokenDriver, (token!));
}); });
// 🔹 الاشتراك في topic
await fcmToken.subscribeToTopic("drivers"); // أو "users" حسب نوع المستخدم
print("Subscribed to 'drivers' topic ✅");
FirebaseMessaging.onMessage.listen((RemoteMessage message) { FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// If the app is in the background or terminated, show a system tray message // If the app is in the background or terminated, show a system tray message
@@ -100,113 +96,89 @@ class FirebaseMessagesController extends GetxController {
} }
Future<void> fireBaseTitles(RemoteMessage message) async { Future<void> fireBaseTitles(RemoteMessage message) async {
if (message.notification!.title! == 'Order') { // [!! تعديل جوهري !!]
if (Platform.isAndroid) { // اقرأ "النوع" من حمولة البيانات، وليس من العنوان
notificationController.showNotification( String category = message.data['category'] ?? '';
message.notification!.title.toString(),
message.notification!.body.toString(),
'tone1',
'');
}
// await FirebaseMessagesController().showOverlayNotification(message);
var myListString = message.data['DriverList'];
// var points = message.data['PolylineJson'];
// اقرأ العنوان والنص (للعرض)
String title = message.notification?.title ?? '';
String body = message.notification?.body ?? '';
// استخدم switch لسهولة القراءة والصيانة
switch (category) {
case 'ORDER':
case 'Order': // Handle both cases for backward compatibility
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'tone1', '');
}
var myListString = message.data['DriverList'];
if (myListString != null) {
var myList = jsonDecode(myListString) as List<dynamic>; var myList = jsonDecode(myListString) as List<dynamic>;
// var myPoints = jsonDecode(points) as List<dynamic>;
driverToken = myList[14].toString(); driverToken = myList[14].toString();
// This is for location using and uploading status
Get.put(HomeCaptainController()).changeRideId(); Get.put(HomeCaptainController()).changeRideId();
update(); update();
Get.to(() => OrderRequestPage(), arguments: { Get.to(() => OrderRequestPage(), arguments: {
// Get.to(() => OrderRequestPage(), arguments: {
'myListString': myListString, 'myListString': myListString,
'DriverList': myList, 'DriverList': myList,
// 'PolylineJson': myPoints, 'body': body
'body': message.notification!.body
}); });
} else if (message.notification!.title == 'OrderVIP') { }
break;
case 'OrderVIP':
var myListString = message.data['DriverList']; var myListString = message.data['DriverList'];
if (myListString != null) {
var myList = jsonDecode(myListString) as List<dynamic>; var myList = jsonDecode(myListString) as List<dynamic>;
// driverToken = myList[10].toString();
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(title, body, 'order', '');
'OrderVIP'.tr, 'OrderVIP'.tr, 'order', '');
} }
Get.to(VipOrderPage(), arguments: { Get.to(VipOrderPage(), arguments: {
'myListString': myListString, 'myListString': myListString,
'DriverList': myList, 'DriverList': myList,
// 'PolylineJson': myPoints, 'body': body
'body': message.notification!.body
}); });
} else if (message.notification!.title == 'Cancel Trip'.tr) { }
break;
case 'Cancel Trip':
case 'TRIP_CANCELLED':
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(
'Cancel Trip'.tr, 'Passenger Cancel Trip'.tr, 'cancel', ''); title, 'Passenger Cancel Trip'.tr, 'cancel', '');
} }
cancelTripDialog(); cancelTripDialog();
} else if (message.notification!.title == 'VIP Order') { break;
var myListString = message.data['DriverList'];
var driverList = jsonDecode(myListString) as List<dynamic>;
if (Platform.isAndroid) {
notificationController.showNotification(
'VIP Order'.tr, '', 'order', '');
}
MyDialog().getDialog('VIP Order'.tr, 'midTitle', () {
// sendNotificationToPassengerToken(
// 'VIP Order Accepted'.tr,
// 'The driver accepted your trip'.tr,
// driverList[0],
// [driverList[1]],
// 'order');
NotificationService.sendNotification(
target: driverList[0].toString(),
title: 'VIP Order Accepted'.tr,
body: 'The driver accepted your trip'.tr,
isTopic: false, // Important: this is a token
tone: 'order',
driverList: [],
);
});
// Get.to(const VipOrderPage()); case 'VIP Order Accepted':
} else if (message.notification!.title == 'message From passenger') { // This seems to be a notification for the passenger, but if the driver needs to see it:
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(title, body, 'order', '');
'message From passenger'.tr, ''.tr, 'ding', '');
} }
MyDialog().getDialog( // Maybe show a simple snackbar confirmation
'message From passenger'.tr, message.notification!.body!, () { mySnackbarSuccess('You accepted the VIP order.'.tr);
break;
case 'message From passenger':
case 'MSG_FROM_PASSENGER':
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'ding', '');
}
MyDialog().getDialog(title, body, () {
Get.back(); Get.back();
}); });
} else if (message.notification!.title == 'Cancel') { break;
if (Platform.isAndroid) {
notificationController.showNotification( case 'token change':
'Cancel'.tr, ''.tr, 'cancel', ''); case 'TOKEN_CHANGE':
}
MyDialog().getDialog(
'Passenger Cancel Trip'.tr,
'Trip Cancelled. The cost of the trip will be added to your wallet.'
.tr, () {
box.write(BoxName.rideStatus, 'Cancel');
Log.print('rideStatus from 184 : ${box.read(BoxName.rideStatus)}');
Get.offAll(HomeCaptain());
});
// cancelTripDialog1();
} else if (message.notification!.title! == 'token change') {
// notificationController
// .showNotification('token change'.tr, 'token change', 'cancel');
// GoogleSignInHelper.signOut();
GoogleSignInHelper.signOut(); GoogleSignInHelper.signOut();
} else if (message.notification!.title! == 'face detect') { break;
case 'face detect':
case 'FACE_DETECT':
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(title, body, 'tone2', '');
'face detect'.tr, ''.tr, 'tone2', '');
} }
String result0 = await faceDetector(); String result0 = await faceDetector();
// Handle the result here, e.g., show a dialog or update the UI
var result = jsonDecode(result0); var result = jsonDecode(result0);
MyDialogContent().getDialog( MyDialogContent().getDialog(
'Face Detection Result'.tr, 'Face Detection Result'.tr,
@@ -220,105 +192,39 @@ class FirebaseMessagesController extends GetxController {
Get.back(); Get.back();
}, },
); );
update();
break;
update(); case 'Hi ,I will go now':
} else if (message.notification!.title! == 'Hi ,I will go now') { case 'PASSENGER_COMING':
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(title, body, 'tone2', '');
'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'tone2', '');
} }
update(); update();
} else if (message.notification!.title! == 'Call Income'.tr) { break;
try {
var myListString = message.data['passengerList']; case 'Criminal Document Required':
var driverList = jsonDecode(myListString) as List<dynamic>; case 'DOC_REQUIRED':
// if (Platform.isAndroid) {
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification('Call Income'.tr, notificationController.showNotification(title, body, 'tone2', '');
message.notification!.body!, 'iphone_ringtone', '');
} }
// } MyDialog().getDialog(title, 'You should have upload it .'.tr, () {
// Assuming GetMaterialApp is initialized and context is valid for navigation
// Get.to(() => PassengerCallPage(
// channelName: driverList[1].toString(),
// token: driverList[0].toString(),
// remoteID: driverList[2].toString(),
// ));
} catch (e) {}
} else if (message.notification!.title! ==
'Call Income from Passenger'.tr) {
try {
var myListString = message.data['passengerList'];
var driverList = jsonDecode(myListString) as List<dynamic>;
// if (Platform.isAndroid) {
if (Platform.isAndroid) {
notificationController.showNotification('Call Income'.tr,
message.notification!.body!, 'iphone_ringtone', '');
}
// }
// Assuming GetMaterialApp is initialized and context is valid for navigation
// Get.to(() => CallPage(
// // channelName: driverList[1].toString(),
// // token: driverList[0].toString(),
// // remoteID: driverList[2].toString(),
// ));
} catch (e) {}
} else if (message.notification!.title! ==
"Criminal Document Required".tr) {
if (Platform.isAndroid) {
notificationController.showNotification("Criminal Document Required".tr,
message.notification!.body!, 'tone2', '');
}
MyDialog().getDialog(
"Criminal Document Required".tr, 'You should have upload it .'.tr,
() {
Get.to(() => const CriminalDocumemtPage()); Get.to(() => const CriminalDocumemtPage());
}); });
Get.to(() => const CriminalDocumemtPage()); break;
} else if (message.notification!.title! == 'Call End'.tr) {
try { case 'Order Applied':
var myListString = message.data['passengerList']; case 'ORDER_TAKEN':
var driverList = jsonDecode(myListString) as List<dynamic>;
if (Platform.isAndroid) {
notificationController.showNotification(
'Call End'.tr, message.notification!.body!, 'tone2', '');
}
// Assuming GetMaterialApp is initialized and context is valid for navigation
// Get.off(const CallPage());
} catch (e) {}
} else if (message.notification!.title! == 'Order Applied'.tr) {
mySnackbarSuccess("The order has been accepted by another driver.".tr); mySnackbarSuccess("The order has been accepted by another driver.".tr);
} else if (message.notification!.title! == 'Order') { break;
if (Platform.isAndroid) {
notificationController.showNotification(
message.notification!.title.toString(),
message.notification!.body.toString(),
'order',
'');
}
var myListString = message.data['DriverList'];
// var points = message.data['PolylineJson'];
var myList = jsonDecode(myListString) as List<dynamic>; default:
// var myPoints = jsonDecode(points) as List<dynamic>; Log.print('Received unhandled notification category: $category');
driverToken = myList[14].toString(); // Optionally show a generic notification
Get.put(HomeCaptainController()).changeRideId();
update();
Get.to(() => OrderSpeedRequest(), arguments: {
'myListString': myListString,
'DriverList': myList,
// 'PolylineJson': myPoints,
'body': message.notification!.body
});
} else if (message.notification!.title! == 'Order Applied'.tr) {
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(title, body, 'default', '');
'The order Accepted by another Driver'.tr,
'We regret to inform you that another driver has accepted this order.'
.tr,
'order',
'');
} }
break;
} }
} }
@@ -376,380 +282,6 @@ class FirebaseMessagesController extends GetxController {
Get.offAll(HomeCaptain()); Get.offAll(HomeCaptain());
})); }));
} }
// Future<dynamic> driverArrivePassengerDialoge() {
// return Get.defaultDialog(
// barrierDismissible: false,
// title: 'Hi ,I Arrive your site'.tr,
// middleText: 'Please go to Car Driver'.tr,
// confirm: MyElevatedButton(
// title: 'Ok I will go now.'.tr,
// onPressed: () {
// FirebaseMessagesController().sendNotificationToPassengerToken(
// 'Hi ,I will go now'.tr,
// 'I will go now'.tr,
// Get.find<MapDriverController>().driverToken, []);
// Get.find<MapPassengerController>()
// .startTimerDriverWaitPassenger5Minute();
// Get.back();
// }));
// }
// late String serviceAccountKeyJson;
// @override
// Future<void> onInit() async {
// super.onInit();
// try {
// // getToken();
// var encryptedKey = Env.privateKeyFCM;
// // Log.print('encryptedKey: ${encryptedKey}');
// serviceAccountKeyJson =
// EncryptionHelper.instance.decryptData(encryptedKey);
// // Log.print('serviceAccountKeyJson: ${serviceAccountKeyJson}');
// } catch (e) {
// print('🔴 Error decrypting FCM key: $e');
// }
// }
// void sendNotificationAll(String title, body, tone) async {
// // توكني الحالي (لا أرسل لنفسي)
// final String myToken = box.read(BoxName.tokenFCM) ?? '';
// // اقرأ قائمة كل التوكنات
// final List<String> all =
// List<String>.from(box.read(BoxName.tokens) ?? const []);
// // استبعد توكنك واحذف الفارغ
// final targets = all.where((t) => t.isNotEmpty && t != myToken).toList();
// if (serviceAccountKeyJson.isEmpty) {
// print("🔴 Error: Service Account Key is empty");
// return;
// }
// final accessTokenManager = AccessTokenManager(serviceAccountKeyJson);
// final accessToken = await accessTokenManager.getAccessToken();
// for (final t in targets) {
// // ⚠️ المهم: استخدم t (توكن الهدف)، وليس المتغير myToken
// final response = await http.post(
// Uri.parse(
// 'https://fcm.googleapis.com/v1/projects/ride-b1bd8/messages:send'),
// headers: {
// 'Content-Type': 'application/json',
// 'Authorization': 'Bearer $accessToken',
// },
// body: jsonEncode({
// 'message': {
// 'token': t,
// 'notification': {'title': title, 'body': body},
// 'android': {
// 'priority': 'HIGH', // القيم الصحيحة: HIGH/NORMAL
// 'notification': {'sound': tone},
// // (اختياري) TTL لتجنّب رسائل قديمة
// 'ttl': '30s',
// },
// 'apns': {
// 'headers': {
// 'apns-priority': '10',
// // لو iOS: حدد نوع الدفع
// 'apns-push-type': 'alert',
// },
// 'payload': {
// 'aps': {'sound': tone}
// },
// },
// },
// }),
// );
// if (response.statusCode != 200) {
// // حاول تقرأ الخطأ وتشيل التوكنات التالفة
// _handleV1Error(response, badToken: t);
// await Future.delayed(const Duration(milliseconds: 50)); // تخفيف ضغط
// }
// }
// }
// void _handleV1Error(http.Response res, {required String badToken}) {
// try {
// final body = jsonDecode(res.body);
// final err = body['error']?['status']?.toString() ?? '';
// // أمثلة شائعة:
// if (err.contains('UNREGISTERED') || err.contains('NOT_FOUND')) {
// removeInvalidToken(badToken);
// } else if (err.contains('INVALID_ARGUMENT')) {
// // payload غير صحيح
// print(
// '⚠️ INVALID_ARGUMENT for $badToken: ${body['error']?['message']}');
// } else if (err.contains('RESOURCE_EXHAUSTED') ||
// err.contains('QUOTA_EXCEEDED')) {
// // تجاوزت الحصة—خفّف السرعة/قسّم الإرسال (FCM v1 له حصة/دقيقة)
// // https docs: 600k req/min per project (token bucket)
// print('⏳ Throttled by FCM: slow down sending rate.');
// } else {
// print('FCM v1 error: ${res.statusCode} ${res.body}');
// }
// } catch (_) {
// print('FCM v1 error: ${res.statusCode} ${res.body}');
// }
// }
// void sendNotificationToPassengerToken(
// String title, body, token, List<String> map, String tone,
// {int retryCount = 2}) async {
// try {
// if (serviceAccountKeyJson.isEmpty) {
// print("🔴 Error: Service Account Key is empty");
// return;
// }
// // Initialize AccessTokenManager
// final accessTokenManager = AccessTokenManager(serviceAccountKeyJson);
// // Obtain an OAuth 2.0 access token
// final accessToken = await accessTokenManager.getAccessToken();
// // Log.print('accessToken: ${accessToken}');
// // Send the notification
// final response = await http.post(
// Uri.parse(
// 'https://fcm.googleapis.com/v1/projects/ride-b1bd8/messages:send'),
// headers: <String, String>{
// 'Content-Type': 'application/json',
// 'Authorization': 'Bearer $accessToken',
// },
// body: jsonEncode({
// 'message': {
// 'token': token,
// 'notification': {
// 'title': title,
// 'body': body,
// },
// 'data': {
// 'passengerList': jsonEncode(map),
// },
// 'android': {
// 'priority': 'HIGH ', // Set priority to high
// 'notification': {
// 'sound': tone,
// },
// },
// 'apns': {
// 'headers': {
// 'apns-priority': '10', // Set APNs priority to 10
// },
// 'payload': {
// 'aps': {
// 'sound': tone,
// },
// },
// },
// },
// }),
// );
// if (response.statusCode == 200) {
// print(
// 'Notification sent successfully. Status code: ${response.statusCode}');
// print('Response body: ${response.body}');
// } else {
// print(
// 'Failed to send notification. Status code: ${response.statusCode}');
// print('Response body: ${response.body}');
// if (retryCount > 0) {
// print('Retrying... Attempts remaining: $retryCount');
// await Future.delayed(
// const Duration(seconds: 2)); // Optional delay before retrying
// return sendNotificationToPassengerToken(title, body, token, map, tone,
// retryCount: retryCount - 1);
// }
// }
// } catch (e) {
// print('Error sending notification: $e');
// if (retryCount > 0) {
// print('Retrying... Attempts remaining: $retryCount');
// await Future.delayed(
// const Duration(seconds: 2)); // Optional delay before retrying
// return sendNotificationToPassengerToken(title, body, token, map, tone,
// retryCount: retryCount - 1);
// }
// }
// }
// void sendNotificationToPassengerTokenCALL(
// String title, body, token, List<String> map, String tone,
// {int retryCount = 2}) async {
// try {
// if (serviceAccountKeyJson.isEmpty) {
// print("🔴 Error: Service Account Key is empty");
// return;
// }
// // Initialize AccessTokenManager
// final accessTokenManager = AccessTokenManager(serviceAccountKeyJson);
// // Obtain an OAuth 2.0 access token
// final accessToken = await accessTokenManager.getAccessToken();
// // Log.print('accessToken: ${accessToken}');
// // Send the notification
// final response = await http.post(
// Uri.parse(
// 'https://fcm.googleapis.com/v1/projects/ride-b1bd8/messages:send'),
// headers: <String, String>{
// 'Content-Type': 'application/json',
// 'Authorization': 'Bearer $accessToken',
// },
// body: jsonEncode({
// 'message': {
// 'token': token,
// 'notification': {
// 'title': title,
// 'body': body,
// },
// 'data': {
// 'passengerList': jsonEncode(map),
// },
// 'android': {
// 'priority': 'HIGH ', // Set priority to high
// 'notification': {
// 'sound': tone,
// },
// },
// 'apns': {
// 'headers': {
// 'apns-priority': '10', // Set APNs priority to 10
// },
// 'payload': {
// 'aps': {
// 'sound': tone,
// },
// },
// },
// },
// }),
// );
// if (response.statusCode == 200) {
// print(
// 'Notification sent successfully. Status code: ${response.statusCode}');
// print('Response body: ${response.body}');
// } else {
// print(
// 'Failed to send notification. Status code: ${response.statusCode}');
// print('Response body: ${response.body}');
// if (retryCount > 0) {
// print('Retrying... Attempts remaining: $retryCount');
// await Future.delayed(
// const Duration(seconds: 2)); // Optional delay before retrying
// return sendNotificationToPassengerTokenCALL(
// title, body, token, map, tone,
// retryCount: retryCount - 1);
// }
// }
// } catch (e) {
// print('Error sending notification: $e');
// if (retryCount > 0) {
// print('Retrying... Attempts remaining: $retryCount');
// await Future.delayed(
// const Duration(seconds: 2)); // Optional delay before retrying
// return sendNotificationToPassengerTokenCALL(
// title, body, token, map, tone,
// retryCount: retryCount - 1);
// }
// }
// }
// Future<void> sendNotificationToDriverMAP(
// String title, String body, String token, List<String> data, String tone,
// {int retryCount = 2}) async {
// try {
// if (serviceAccountKeyJson.isEmpty) {
// print("🔴 Error: Service Account Key is empty");
// return;
// }
// // Initialize AccessTokenManager
// final accessTokenManager = AccessTokenManager(serviceAccountKeyJson);
// Log.print(
// 'accessTokenManager: ${accessTokenManager.serviceAccountJsonKey}');
// // Obtain an OAuth 2.0 access token
// final accessToken = await accessTokenManager.getAccessToken();
// // Log.print('accessToken: ${accessToken}');
// // Send the notification
// final response = await http.post(
// Uri.parse(
// 'https://fcm.googleapis.com/v1/projects/intaleq-d48a7/messages:send'),
// headers: <String, String>{
// 'Content-Type': 'application/json',
// 'Authorization': 'Bearer $accessToken',
// },
// body: jsonEncode({
// 'message': {
// 'token': token,
// 'notification': {
// 'title': title,
// 'body': body,
// },
// 'data': {
// 'DriverList': jsonEncode(data),
// },
// 'android': {
// 'priority': 'HIGH ', // Set priority to high
// 'notification': {
// 'sound': tone,
// },
// },
// 'apns': {
// 'headers': {
// 'apns-priority': '10', // Set APNs priority to 10
// },
// 'payload': {
// 'aps': {
// 'sound': tone,
// },
// },
// },
// },
// }),
// );
// if (response.statusCode == 200) {
// print(
// 'Notification sent successfully. Status code: ${response.statusCode}');
// // print('Response token: ${token}');
// } else {
// print(
// 'Failed to send notification. Status code: ${response.statusCode}');
// print('Response body: ${response.body}');
// if (retryCount > 0) {
// print('Retrying... Attempts remaining: $retryCount');
// await Future.delayed(
// Duration(seconds: 2)); // Optional delay before retrying
// return sendNotificationToDriverMAP(title, body, token, data, tone,
// retryCount: retryCount - 1);
// }
// }
// } catch (e) {
// print('Error sending notification: $e');
// if (retryCount > 0) {
// print('Retrying... Attempts remaining: $retryCount');
// await Future.delayed(
// Duration(seconds: 2)); // Optional delay before retrying
// return sendNotificationToDriverMAP(title, body, token, data, tone,
// retryCount: retryCount - 1);
// }
// }
// }
// Future<void> removeInvalidToken(String token) async {
// // Remove token from your database/storage
// // This prevents future attempts to send to invalid tokens
// print('Removing invalid token from database: $token');
// // Your database cleanup logic here
// }
} }
class OverlayContent extends StatelessWidget { class OverlayContent extends StatelessWidget {

View File

@@ -1,8 +1,10 @@
import 'package:http/http.dart' as http;
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http;
import '../../print.dart';
class NotificationService { class NotificationService {
// استبدل هذا الرابط بالرابط الصحيح لملف PHP على السيرفر الخاص بك // تأكد من أن هذا هو الرابط الصحيح لملف الإرسال
static const String _serverUrl = static const String _serverUrl =
'https://syria.intaleq.xyz/intaleq/fcm/send_fcm.php'; 'https://syria.intaleq.xyz/intaleq/fcm/send_fcm.php';
@@ -10,8 +12,9 @@ class NotificationService {
required String target, required String target,
required String title, required String title,
required String body, required String body,
required String? category, // <-- [الإضافة الأولى]
String? tone, String? tone,
List<String>? driverList, // <-- [تعديل 1] : إضافة المتغير الجديد List<String>? driverList,
bool isTopic = false, bool isTopic = false,
}) async { }) async {
try { try {
@@ -22,14 +25,17 @@ class NotificationService {
'isTopic': isTopic, 'isTopic': isTopic,
}; };
// نضيف النغمة فقط إذا لم تكن فارغة if (category != null) {
payload['category'] = category; // <-- [الإضافة الثانية]
}
if (tone != null) { if (tone != null) {
payload['tone'] = tone; payload['tone'] = tone;
} }
// <-- [تعديل 2] : نضيف قائمة البيانات بعد تشفيرها إلى JSON
if (driverList != null) { if (driverList != null) {
payload['driverList'] = jsonEncode(driverList); // [مهم] تطبيق السائق يرسل passengerList
payload['passengerList'] = jsonEncode(driverList);
} }
final response = await http.post( final response = await http.post(
@@ -41,15 +47,13 @@ class NotificationService {
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
print('✅ Notification sent successfully.'); Log.print('✅ Notification sent successfully.');
print('Server Response: ${response.body}');
} else { } else {
print( Log.print(
'❌ Failed to send notification. Status code: ${response.statusCode}'); '❌ Failed to send notification. Status code: ${response.statusCode}');
print('Server Error: ${response.body}');
} }
} catch (e) { } catch (e) {
print('❌ An error occurred while sending notification: $e'); Log.print('❌ An error occurred while sending notification: $e');
} }
} }
} }

View File

@@ -191,9 +191,6 @@ class CRUD {
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}' 'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}'
}, },
); );
Log.print('response: ${response.body}');
Log.print('req: ${response.request}');
Log.print('payload: ${payload}');
if (response.statusCode == 200) { if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body); var jsonData = jsonDecode(response.body);

View File

@@ -119,7 +119,7 @@ class AI extends GetxController {
body: 'for '.tr + box.read(BoxName.phoneDriver).toString(), body: 'for '.tr + box.read(BoxName.phoneDriver).toString(),
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'tone2', tone: 'tone2',
driverList: [], driverList: [], category: 'You have received a gift token!',
); );
} else { } else {
// mySnackeBarError( // mySnackeBarError(

View File

@@ -224,15 +224,15 @@ class LocationController extends GetxController {
} }
_lastPosForDistance = pos; _lastPosForDistance = pos;
// ✅ تحديث الكاميرا // ✅ تحديث الكاميرا
_homeCtrl.mapHomeCaptainController?.animateCamera( // _homeCtrl.mapHomeCaptainController?.animateCamera(
CameraUpdate.newCameraPosition( // CameraUpdate.newCameraPosition(
CameraPosition( // CameraPosition(
bearing: Get.find<LocationController>().heading, // bearing: Get.find<LocationController>().heading,
target: myLocation, // target: myLocation,
zoom: 17, // Adjust zoom level as needed // zoom: 17, // Adjust zoom level as needed
), // ),
), // ),
); // );
update(); // تحديث الواجهة الرسومية بالبيانات الجديدة update(); // تحديث الواجهة الرسومية بالبيانات الجديدة
await _smartSend(pos, loc); await _smartSend(pos, loc);
@@ -354,7 +354,7 @@ class LocationController extends GetxController {
final payload = _buildPayload(pos, status, loc); final payload = _buildPayload(pos, status, loc);
try { try {
await CRUD().post( await CRUD().post(
link: '${AppLink.server}/ride/location/update.php', link: '${AppLink.locationServer}/update.php',
payload: payload, payload: payload,
); );
_lastSentLoc = pos; _lastSentLoc = pos;
@@ -403,7 +403,7 @@ class LocationController extends GetxController {
try { try {
print('⏱️ Adding a single point to car_track... $payload'); print('⏱️ Adding a single point to car_track... $payload');
await CRUD().post( await CRUD().post(
link: '${AppLink.server}/ride/location/add.php', link: '${AppLink.locationServer}/add.php',
payload: payload, // ← الآن Map<String,String> payload: payload, // ← الآن Map<String,String>
); );
} catch (e) { } catch (e) {
@@ -438,7 +438,7 @@ class LocationController extends GetxController {
try { try {
await CRUD().post( await CRUD().post(
link: '${AppLink.server}/ride/location/update.php', link: '${AppLink.locationServer}/update.php',
payload: payload, payload: payload,
); );
_lastSentAt = DateTime.now(); _lastSentAt = DateTime.now();

View File

@@ -36,6 +36,7 @@ Future<void> showDriverGiftClaim(BuildContext context) async {
box.read(BoxName.is_claimed) == null) { box.read(BoxName.is_claimed) == null) {
MyDialog().getDialog( MyDialog().getDialog(
'You have gift 30000 SYP'.tr, 'This for new registration'.tr, () async { 'You have gift 30000 SYP'.tr, 'This for new registration'.tr, () async {
Get.back();
var res = await CRUD().post(link: AppLink.updateDriverClaim, payload: { var res = await CRUD().post(link: AppLink.updateDriverClaim, payload: {
'driverId': box.read(BoxName.driverID), 'driverId': box.read(BoxName.driverID),
}); });
@@ -49,7 +50,6 @@ Future<void> showDriverGiftClaim(BuildContext context) async {
); );
box.write(BoxName.is_claimed, '1'); box.write(BoxName.is_claimed, '1');
} }
Get.back();
}); });
} }
} }

View File

@@ -428,18 +428,26 @@ class HomeCaptainController extends GetxController {
} }
Future<void> getCaptainDurationOnToday() async { Future<void> getCaptainDurationOnToday() async {
try {
var res = await CRUD().get( var res = await CRUD().get(
link: AppLink.getTotalDriverDurationToday, link: AppLink.getTotalDriverDurationToday,
payload: {'driver_id': box.read(BoxName.driverID).toString()}); payload: {'driver_id': box.read(BoxName.driverID).toString()},
if (res == 'failure') { );
if (res == null || res == 'failure') {
totalDurationToday = '0'; totalDurationToday = '0';
update(); update();
return; return;
} else {
data = jsonDecode(res);
totalDurationToday = data['message'][0]['total_duration'];
update();
} }
var data = jsonDecode(res);
totalDurationToday = data['message']?[0]?['total_duration'] ?? '0';
} catch (e) {
print('Error in getCaptainDurationOnToday: $e');
totalDurationToday = '0';
}
update();
} }
@override @override

View File

@@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:http/http.dart' as http;
import 'package:sefer_driver/controller/home/captin/behavior_controller.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/captin/home_captain_controller.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart'; import 'package:sefer_driver/views/widgets/mydialoug.dart';
@@ -17,8 +18,10 @@ import 'package:url_launcher/url_launcher.dart';
import '../../../constant/api_key.dart'; import '../../../constant/api_key.dart';
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/country_polygons.dart';
import '../../../constant/links.dart'; import '../../../constant/links.dart';
import '../../../constant/table_names.dart'; import '../../../constant/table_names.dart';
import '../../../env/env.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../../print.dart'; import '../../../print.dart';
import '../../../views/Rate/rate_passenger.dart'; import '../../../views/Rate/rate_passenger.dart';
@@ -196,23 +199,16 @@ class MapDriverController extends GetxController {
cancelTripFromDriverAfterApplied() async { cancelTripFromDriverAfterApplied() async {
if (formKeyCancel.currentState!.validate()) { if (formKeyCancel.currentState!.validate()) {
box.write(BoxName.statusDriverLocation, 'off'); box.write(BoxName.statusDriverLocation, 'off');
// Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
// "Cancel Trip from driver",
// "Trip Cancelled from driver. We are looking for a new driver. Please wait."
// .tr,
// tokenPassenger,
// [],
// 'cancel.wav',
// );
NotificationService.sendNotification( NotificationService.sendNotification(
target: tokenPassenger.toString(), target: tokenPassenger.toString(),
title: "Cancel Trip from driver".tr, title: "Cancel Trip from driver",
body: body:
"Trip Cancelled from driver. We are looking for a new driver. Please wait." "Trip Cancelled from driver. We are looking for a new driver. Please wait."
.tr, .tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'cancel', tone: 'cancel',
driverList: [], driverList: [], category: "Cancel Trip from driver",
); );
await CRUD().post( await CRUD().post(
link: "${AppLink.seferCairoServer}/ride/rides/update.php", link: "${AppLink.seferCairoServer}/ride/rides/update.php",
@@ -343,21 +339,13 @@ class MapDriverController extends GetxController {
'status': 'Applied' 'status': 'Applied'
}); });
// Get.find<HomeCaptainController>().changeToAppliedRide('Applied');
// Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
// 'Driver Is Going To Passenger',
// box.read(BoxName.nameDriver).toString(), //todo name driver
// tokenPassenger,
// [],
// 'start.wav');
NotificationService.sendNotification( NotificationService.sendNotification(
target: tokenPassenger.toString(), target: tokenPassenger.toString(),
title: 'Driver Is Going To Passenger'.tr, title: 'Driver Is Going To Passenger'.tr,
body: box.read(BoxName.nameDriver).toString(), body: box.read(BoxName.nameDriver).toString(),
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'start', tone: 'start',
driverList: [], driverList: [], category: 'Driver Is Going To Passenger',
); );
} }
@@ -449,11 +437,11 @@ class MapDriverController extends GetxController {
NotificationService.sendNotification( NotificationService.sendNotification(
target: tokenPassenger.toString(), target: tokenPassenger.toString(),
title: 'Trip is Begin', title: 'Trip is Begin'.tr,
body: box.read(BoxName.nameDriver).toString(), body: box.read(BoxName.nameDriver).toString(),
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'start', tone: 'start',
driverList: [], driverList: [], category: 'Trip is Begin',
); );
rideIsBeginPassengerTimer(); rideIsBeginPassengerTimer();
@@ -546,7 +534,7 @@ class MapDriverController extends GetxController {
} else { } else {
double costOfWaiting5Minute = box.read(BoxName.countryCode) == 'Egypt' double costOfWaiting5Minute = box.read(BoxName.countryCode) == 'Egypt'
? (distanceBetweenDriverAndPassengerWhenConfirm * .08) + (5 * 1) ? (distanceBetweenDriverAndPassengerWhenConfirm * .08) + (5 * 1)
: (distanceBetweenDriverAndPassengerWhenConfirm * .06) + : (distanceBetweenDriverAndPassengerWhenConfirm * 1100) +
(5 * .06); //for Eygpt other like jordan .06 per minute (5 * .06); //for Eygpt other like jordan .06 per minute
await CRUD().post(link: AppLink.updateRides, payload: { await CRUD().post(link: AppLink.updateRides, payload: {
'id': (rideId), 'id': (rideId),
@@ -771,20 +759,9 @@ class MapDriverController extends GetxController {
Get.put(DriverBehaviorController()) Get.put(DriverBehaviorController())
.sendSummaryToServer(driverId, rideId); .sendSummaryToServer(driverId, rideId);
// Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
// "Driver Finish Trip",
// '${'you will pay to Driver'.tr} $paymentAmount \$',
// tokenPassenger,
// [
// box.read(BoxName.driverID),
// rideId,
// box.read(BoxName.tokenDriver),
// paymentAmount.toString()
// ],
// 'ding.wav');
NotificationService.sendNotification( NotificationService.sendNotification(
target: tokenPassenger.toString(), target: tokenPassenger.toString(),
title: "Driver Finish Trip", title: "Driver Finish Trip".tr,
body: '${'you will pay to Driver'.tr} $paymentAmount \$'.tr, body: '${'you will pay to Driver'.tr} $paymentAmount \$'.tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'ding', tone: 'ding',
@@ -794,6 +771,7 @@ class MapDriverController extends GetxController {
box.read(BoxName.tokenDriver), box.read(BoxName.tokenDriver),
paymentAmount.toString() paymentAmount.toString()
], ],
category: 'Driver Finish Trip',
); );
Get.to(() => RatePassenger(), arguments: { Get.to(() => RatePassenger(), arguments: {
@@ -1303,21 +1281,115 @@ class MapDriverController extends GetxController {
var _stepBounds = <LatLngBounds>[]; var _stepBounds = <LatLngBounds>[];
var _stepEndPoints = <LatLng>[]; var _stepEndPoints = <LatLng>[];
var _allPointsForActiveRoute = <LatLng>[]; var _allPointsForActiveRoute = <LatLng>[];
bool _rayIntersectsSegment(LatLng point, LatLng vertex1, LatLng vertex2) {
double px = point.longitude;
double py = point.latitude;
double v1x = vertex1.longitude;
double v1y = vertex1.latitude;
double v2x = vertex2.longitude;
double v2y = vertex2.latitude;
// Check if the point is outside the vertical bounds of the segment
if ((py < v1y && py < v2y) || (py > v1y && py > v2y)) {
return false;
}
// Calculate the intersection of the ray and the segment
double intersectX = v1x + (py - v1y) * (v2x - v1x) / (v2y - v1y);
// Check if the intersection is to the right of the point
return intersectX > px;
}
// Function to check if the point is inside the polygon
bool isPointInPolygon(LatLng point, List<LatLng> polygon) {
int intersections = 0;
for (int i = 0; i < polygon.length; i++) {
LatLng vertex1 = polygon[i];
LatLng vertex2 =
polygon[(i + 1) % polygon.length]; // Loop back to the start
if (_rayIntersectsSegment(point, vertex1, vertex2)) {
intersections++;
}
}
// If the number of intersections is odd, the point is inside
return intersections % 2 != 0;
}
String getLocationArea(double latitude, double longitude) {
LatLng passengerPoint = LatLng(latitude, longitude);
// 1. فحص الأردن
if (isPointInPolygon(passengerPoint, CountryPolygons.jordanBoundary)) {
box.write(BoxName.countryCode, 'Jordan');
// يمكنك تعيين AppLink.endPoint هنا إذا كان منطقك الداخلي لا يزال يعتمد عليه
// box.write(BoxName.serverChosen,
// AppLink.IntaleqSyriaServer); // مثال: اختر سيرفر سوريا للبيانات
return 'Jordan';
}
// 2. فحص سوريا
if (isPointInPolygon(passengerPoint, CountryPolygons.syriaBoundary)) {
box.write(BoxName.countryCode, 'Syria');
// box.write(BoxName.serverChosen, AppLink.IntaleqSyriaServer);
return 'Syria';
}
// 3. فحص مصر
if (isPointInPolygon(passengerPoint, CountryPolygons.egyptBoundary)) {
box.write(BoxName.countryCode, 'Egypt');
// box.write(BoxName.serverChosen, AppLink.IntaleqAlexandriaServer);
return 'Egypt';
}
// 4. الافتراضي (إذا كان خارج المناطق المخدومة)
box.write(BoxName.countryCode, 'Jordan');
// box.write(BoxName.serverChosen, AppLink.IntaleqSyriaServer);
return 'Unknown Location (Defaulting to Jordan)';
}
// دالة موحّدة وقوية لجلب أي مسار
Future<void> getRoute({ Future<void> getRoute({
required LatLng origin, required LatLng origin,
required LatLng destination, required LatLng destination,
required Color routeColor, required Color routeColor, // تم الإبقاء عليه كما طلبت
}) async { }) async {
// إظهار مؤشر التحميل لو رغبت // إظهار مؤشر التحميل لو رغبت
// isLoading.value = true; // isLoading.value = true;
getLocationArea(origin.latitude, origin.longitude);
String _dynamicApiUrl = 'https://routec.intaleq.xyz/route';
// استخدام مفتاح الـ API الخاص بك (افترضتُ أنه موجود في Env)
final String _routeApiKey = Env.mapKeyOsm;
var url = var url =
('${AppLink.googleMapsLink}directions/json?language=ar&destination=${destination.latitude},${destination.longitude}&origin=${origin.latitude},${origin.longitude}&key=${AK.mapAPIKEY}'); '$_dynamicApiUrl?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&steps=true&overview=full';
var response = await CRUD().getGoogleApi(link: url, payload: {});
if (response == null || response['routes'].isEmpty) { var response;
try {
response = await http.get(
Uri.parse(url),
headers: {'X-API-KEY': _routeApiKey},
);
} catch (e) {
print("Error calling route API: $e");
// isLoading.value = false;
// أظهر رسالة خطأ
return;
}
if (response.statusCode != 200) {
print("Route API returned error: ${response.statusCode}");
// isLoading.value = false;
return;
}
final responseData = jsonDecode(response.body);
if (responseData == null || responseData['status'] != 'ok') {
// isLoading.value = false; // isLoading.value = false;
// أظهر رسالة خطأ // أظهر رسالة خطأ
return; return;
@@ -1325,17 +1397,36 @@ class MapDriverController extends GetxController {
_resetRouteState(); // تنظيف الحالة القديمة قبل رسم الجديد _resetRouteState(); // تنظيف الحالة القديمة قبل رسم الجديد
final route = response['routes'][0]; // --- 2. "ترجمة" الاستجابة الجديدة لتناسب الكود القديم ---
final leg = route['legs'][0];
// استخراج النقاط ورسم المسار المبدئي بالكامل // استخراج النقاط ورسم المسار المبدئي
final pointsString = route["overview_polyline"]["points"]; // الكود القديم كان يتوقع: route["overview_polyline"]["points"]
// الكود الجديد يوفر: responseData["polyline"]
final pointsString = responseData["polyline"];
// افترضتُ أن لديك دالة اسمها decodePolylineToLatLng
// إذا كان اسمها decodePolylineIsolate، قم بتغيير الاسم هنا
_allPointsForActiveRoute = decodePolylineToLatLng(pointsString); _allPointsForActiveRoute = decodePolylineToLatLng(pointsString);
upcomingPathPoints.assignAll(_allPointsForActiveRoute); upcomingPathPoints.assignAll(_allPointsForActiveRoute);
// استخراج خطوات الملاحة // استخراج خطوات الملاحة
activeRouteSteps.assignAll(List<Map<String, dynamic>>.from(leg['steps'])); // الكود القديم كان يتوقع: List<Map<String, dynamic>>.from(leg['steps'])
_prepareStepData(activeRouteSteps); // الكود الجديد يوفر: List<Map<String, dynamic>>.from(responseData['steps'])
final stepsList = List<Map<String, dynamic>>.from(responseData['steps']);
// [مهم جداً] إضافة الحقول التي يتوقعها الكود القديم
for (var step in stepsList) {
// الكود القديم يتوقع 'html_instructions'
// سنقوم بإنشائها من بيانات 'maneuver'
step['html_instructions'] = _createInstructionFromManeuver(step);
// الكود القديم قد يتوقع 'end_location'
var loc = step['maneuver']['location']; // [lng, lat]
step['end_location'] = {'lat': loc[1], 'lng': loc[0]};
}
activeRouteSteps.assignAll(stepsList);
_prepareStepData(activeRouteSteps); // هذه الدالة ستعمل الآن كما هي
// تحديث التعليمات الأولية // تحديث التعليمات الأولية
if (activeRouteSteps.isNotEmpty) { if (activeRouteSteps.isNotEmpty) {
@@ -1347,18 +1438,161 @@ class MapDriverController extends GetxController {
} }
// تحديث الكاميرا لتناسب المسار الجديد // تحديث الكاميرا لتناسب المسار الجديد
final boundsData = route["bounds"]; // الكود القديم كان يتوقع: route["bounds"]
_fitToBounds(LatLngBounds( // الكود الجديد لا يوفرها، لذا سنقوم بحسابها يدوياً
northeast: LatLng( if (_allPointsForActiveRoute.isNotEmpty) {
boundsData['northeast']['lat'], boundsData['northeast']['lng']), final bounds = _boundsFromLatLngList(_allPointsForActiveRoute);
southwest: LatLng( _fitToBounds(bounds); // ستعمل هذه الدالة الآن كما هي
boundsData['southwest']['lat'], boundsData['southwest']['lng']), }
));
// isLoading.value = false; // isLoading.value = false;
update(); // تحديث الواجهة مرة واحدة بعد كل العمليات update(); // تحديث الواجهة مرة واحدة بعد كل العمليات
} }
String _createInstructionFromManeuver(Map<String, dynamic> step) {
final maneuver = step['maneuver'];
final type = maneuver['type'] ?? 'continue';
final modifier = maneuver['modifier'] ?? 'straight';
final name = step['name'] ?? '';
String instruction = "";
switch (type) {
case 'depart':
instruction = "انطلق";
break;
case 'arrive':
instruction = "لقد وصلت إلى وجهتك";
if (name.isNotEmpty) instruction += "، $name";
return instruction;
case 'turn':
case 'fork':
case 'off ramp':
case 'on ramp':
case 'roundabout':
instruction = _getTurnInstruction(modifier);
break;
case 'continue':
instruction = "استمر";
break;
default:
instruction = "اتجه";
}
if (name.isNotEmpty) {
if (instruction == "استمر") {
instruction += " على $name";
} else {
instruction += " إلى $name";
}
} else if (type == 'continue' && modifier == 'straight') {
instruction = "استمر بشكل مستقيم";
}
return instruction;
}
/**
* دالة مساعدة لترجمة تعليمات الانعطاف
*/
String _getTurnInstruction(String modifier) {
switch (modifier) {
case 'uturn':
return "قم بالاستدارة والعودة";
case 'sharp right':
return "انعطف يمينًا بحدة";
case 'right':
return "انعطف يمينًا";
case 'slight right':
return "انعطف يمينًا قليلاً";
case 'straight':
return "استمر بشكل مستقيم";
case 'slight left':
return "انعطف يسارًا قليلاً";
case 'left':
return "انعطف يسارًا";
case 'sharp left':
return "انعطف يسارًا بحدة";
default:
return "اتجه";
}
}
/**
* دالة لحساب حدود الخريطة (Bounds) من قائمة نقاط
*/
LatLngBounds _boundsFromLatLngList(List<LatLng> list) {
assert(list.isNotEmpty);
double? x0, x1, y0, y1;
for (LatLng latLng in list) {
if (x0 == null) {
x0 = x1 = latLng.latitude;
y0 = y1 = latLng.longitude;
} else {
if (latLng.latitude > x1!) x1 = latLng.latitude;
if (latLng.latitude < x0) x0 = latLng.latitude;
if (latLng.longitude > y1!) y1 = latLng.longitude;
if (latLng.longitude < y0!) y0 = latLng.longitude;
}
}
return LatLngBounds(
northeast: LatLng(x1!, y1!), southwest: LatLng(x0!, y0!));
}
// // دالة موحّدة وقوية لجلب أي مسار
// Future<void> getRoute({
// required LatLng origin,
// required LatLng destination,
// required Color routeColor,
// }) async {
// // إظهار مؤشر التحميل لو رغبت
// // isLoading.value = true;
// var url =
// ('${AppLink.googleMapsLink}directions/json?language=ar&destination=${destination.latitude},${destination.longitude}&origin=${origin.latitude},${origin.longitude}&key=${AK.mapAPIKEY}');
// var response = await CRUD().getGoogleApi(link: url, payload: {});
// if (response == null || response['routes'].isEmpty) {
// // isLoading.value = false;
// // أظهر رسالة خطأ
// return;
// }
// _resetRouteState(); // تنظيف الحالة القديمة قبل رسم الجديد
// final route = response['routes'][0];
// final leg = route['legs'][0];
// // استخراج النقاط ورسم المسار المبدئي بالكامل
// final pointsString = route["overview_polyline"]["points"];
// _allPointsForActiveRoute = decodePolylineToLatLng(pointsString);
// upcomingPathPoints.assignAll(_allPointsForActiveRoute);
// // استخراج خطوات الملاحة
// activeRouteSteps.assignAll(List<Map<String, dynamic>>.from(leg['steps']));
// _prepareStepData(activeRouteSteps);
// // تحديث التعليمات الأولية
// if (activeRouteSteps.isNotEmpty) {
// currentInstruction =
// _parseInstruction(activeRouteSteps[0]['html_instructions']);
// Get.isRegistered<TextToSpeechController>()
// ? Get.find<TextToSpeechController>().speakText(currentInstruction)
// : Get.put(TextToSpeechController()).speakText(currentInstruction);
// }
// // تحديث الكاميرا لتناسب المسار الجديد
// final boundsData = route["bounds"];
// _fitToBounds(LatLngBounds(
// northeast: LatLng(
// boundsData['northeast']['lat'], boundsData['northeast']['lng']),
// southwest: LatLng(
// boundsData['southwest']['lat'], boundsData['southwest']['lng']),
// ));
// // isLoading.value = false;
// update(); // تحديث الواجهة مرة واحدة بعد كل العمليات
// }
// الدالة التي يتم استدعاؤها من خدمة الموقع كل 5 ثوان (أو حسب الفترة المحددة) // الدالة التي يتم استدعاؤها من خدمة الموقع كل 5 ثوان (أو حسب الفترة المحددة)
void onLocationUpdated(Position newPosition) { void onLocationUpdated(Position newPosition) {
myLocation = LatLng(newPosition.latitude, newPosition.longitude); myLocation = LatLng(newPosition.latitude, newPosition.longitude);
@@ -1533,7 +1767,7 @@ class MapDriverController extends GetxController {
('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}'); ('${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: {}); var response = await CRUD().getGoogleApi(link: url, payload: {});
Log.print('response: ${response}'); Log.print('response: $response');
data = response['routes'][0]['legs']; data = response['routes'][0]['legs'];
distanceBetweenDriverAndPassengerWhenConfirm = distanceBetweenDriverAndPassengerWhenConfirm =
(data[0]['distance']['value']) / 1000; (data[0]['distance']['value']) / 1000;
@@ -1663,7 +1897,7 @@ class MapDriverController extends GetxController {
// ); // );
NotificationService.sendNotification( NotificationService.sendNotification(
target: tokenPassenger.toString(), target: tokenPassenger.toString(),
title: "You are near the destination", title: "You are near the destination".tr,
body: "You are near the destination".tr, body: "You are near the destination".tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'ding', tone: 'ding',
@@ -1673,6 +1907,7 @@ class MapDriverController extends GetxController {
box.read(BoxName.tokenDriver), box.read(BoxName.tokenDriver),
paymentAmount.toString() paymentAmount.toString()
], ],
category: "You are near the destination",
); );
// يمكن إضافة أي إجراء آخر هنا عند الاقتراب من الوجهة // يمكن إضافة أي إجراء آخر هنا عند الاقتراب من الوجهة
} }

View File

@@ -224,13 +224,13 @@ class OrderRequestController extends GetxController {
'status': 'Refused', 'status': 'Refused',
'driver_id': box.read(BoxName.driverID), 'driver_id': box.read(BoxName.driverID),
}); });
if (AppLink.endPoint != AppLink.seferCairoServer) { // if (AppLink.endPoint != AppLink.seferCairoServer) {
CRUD().post(link: '${AppLink.endPoint}/rides/update.php', payload: { // CRUD().post(link: '${AppLink.endPoint}/rides/update.php', payload: {
'id': (orderID), // 'id': (orderID),
'status': 'Refused', // 'status': 'Refused',
'driver_id': box.read(BoxName.driverID), // 'driver_id': box.read(BoxName.driverID),
}); // });
} // }
update(); update();
} }
@@ -264,24 +264,5 @@ class OrderRequestController extends GetxController {
'distance': distance, 'distance': distance,
'duration': duration, 'duration': duration,
}); });
if (AppLink.endPoint != AppLink.seferCairoServer) {
CRUD().post(
link: '${AppLink.endPoint}/notificationCaptain/addWaitingRide.php',
payload: {
'id': (orderID),
'start_location': startLocation,
'end_location': endLocation,
'date': date,
'time': time,
'price': price,
'passenger_id': (passengerId),
'status': status,
'carType': carType,
'passengerRate': passengerRate,
'price_for_passenger': priceForPassenger,
'distance': distance,
'duration': duration,
});
}
} }
} }

View File

@@ -1,19 +1,23 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:flutter/foundation.dart'; // <<<--- إضافة مهمة لاستخدام دالة compute import 'dart:convert'; // <<<--- إضافة جديدة
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http; // <<<--- إضافة جديدة
import 'package:sefer_driver/constant/colors.dart'; import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/env/env.dart'; import 'package:sefer_driver/controller/functions/crud.dart';
// استخدام نفس مسارات الاستيراد التي قدمتها import '../../../constant/box_name.dart';
import '../../../constant/api_key.dart'; import '../../../constant/country_polygons.dart';
import '../../../constant/links.dart'; import '../../../constant/links.dart';
import '../../../env/env.dart';
import '../../../main.dart';
import '../../../print.dart'; import '../../../print.dart';
import '../../functions/crud.dart'; // import '../../functions/crud.dart'; // <<<--- تم إلغاء الاعتماد عليه
import '../../functions/tts.dart'; import '../../functions/tts.dart';
import 'decode_polyline_isolate.dart'; import 'decode_polyline_isolate.dart';
@@ -33,9 +37,8 @@ class NavigationController extends GetxController {
BitmapDescriptor destinationIcon = BitmapDescriptor.defaultMarker; BitmapDescriptor destinationIcon = BitmapDescriptor.defaultMarker;
// --- متغيرات النظام الذكي للتحديث --- // --- متغيرات النظام الذكي للتحديث ---
Timer? _locationUpdateTimer; // المؤقت الرئيسي للتحكم في التحديثات Timer? _locationUpdateTimer;
Duration _currentUpdateInterval = Duration _currentUpdateInterval = const Duration(seconds: 2);
const Duration(seconds: 2); // القيمة الافتراضية
// --- متغيرات البحث عن الأماكن --- // --- متغيرات البحث عن الأماكن ---
List<dynamic> placesDestination = []; List<dynamic> placesDestination = [];
@@ -45,16 +48,22 @@ class NavigationController extends GetxController {
LatLng? _finalDestination; LatLng? _finalDestination;
List<Map<String, dynamic>> routeSteps = []; List<Map<String, dynamic>> routeSteps = [];
List<LatLng> _fullRouteCoordinates = []; List<LatLng> _fullRouteCoordinates = [];
List<List<LatLng>> _stepPolylines = []; // لتخزين نقاط كل خطوة على حدة List<List<LatLng>> _stepPolylines = [];
bool _nextInstructionSpoken = false; bool _nextInstructionSpoken = false;
String currentInstruction = ""; String currentInstruction = "";
String nextInstruction = ""; String nextInstruction = "";
int currentStepIndex = 0; int currentStepIndex = 0;
// <<<--- [تعديل] ---: متغير جديد لتتبع المسار المقطوع بدقة
int _lastTraveledIndexInFullRoute = 0;
double currentSpeed = 0.0; double currentSpeed = 0.0;
String distanceToNextStep = ""; String distanceToNextStep = "";
final List<LatLngBounds> _stepBounds = []; final List<LatLngBounds> _stepBounds = [];
// --- ثوابت الـ API الجديد ---
static const String _routeApiBaseUrl = 'https://routec.intaleq.xyz/';
static final String _routeApiKey = Env.mapKeyOsm;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@@ -71,7 +80,7 @@ class NavigationController extends GetxController {
@override @override
void onClose() { void onClose() {
_locationUpdateTimer?.cancel(); // إيقاف المؤقت عند إغلاق الصفحة _locationUpdateTimer?.cancel();
mapController?.dispose(); mapController?.dispose();
_debounce?.cancel(); _debounce?.cancel();
placeDestinationController.dispose(); placeDestinationController.dispose();
@@ -82,32 +91,100 @@ class NavigationController extends GetxController {
// ١. النظام الذكي لتحديد الموقع والتحديث // ١. النظام الذكي لتحديد الموقع والتحديث
// ======================================================================= // =======================================================================
// Helper function to check if a ray from the point intersects with a polygon segment
bool _rayIntersectsSegment(LatLng point, LatLng vertex1, LatLng vertex2) {
double px = point.longitude;
double py = point.latitude;
double v1x = vertex1.longitude;
double v1y = vertex1.latitude;
double v2x = vertex2.longitude;
double v2y = vertex2.latitude;
// Check if the point is outside the vertical bounds of the segment
if ((py < v1y && py < v2y) || (py > v1y && py > v2y)) {
return false;
}
// Calculate the intersection of the ray and the segment
double intersectX = v1x + (py - v1y) * (v2x - v1x) / (v2y - v1y);
// Check if the intersection is to the right of the point
return intersectX > px;
}
// Function to check if the point is inside the polygon
bool isPointInPolygon(LatLng point, List<LatLng> polygon) {
int intersections = 0;
for (int i = 0; i < polygon.length; i++) {
LatLng vertex1 = polygon[i];
LatLng vertex2 =
polygon[(i + 1) % polygon.length]; // Loop back to the start
if (_rayIntersectsSegment(point, vertex1, vertex2)) {
intersections++;
}
}
// If the number of intersections is odd, the point is inside
return intersections % 2 != 0;
}
String getLocationArea(double latitude, double longitude) {
LatLng passengerPoint = LatLng(latitude, longitude);
// 1. فحص الأردن
if (isPointInPolygon(passengerPoint, CountryPolygons.jordanBoundary)) {
box.write(BoxName.countryCode, 'Jordan');
// يمكنك تعيين AppLink.endPoint هنا إذا كان منطقك الداخلي لا يزال يعتمد عليه
// box.write(BoxName.serverChosen,
// AppLink.IntaleqSyriaServer); // مثال: اختر سيرفر سوريا للبيانات
return 'Jordan';
}
// 2. فحص سوريا
if (isPointInPolygon(passengerPoint, CountryPolygons.syriaBoundary)) {
box.write(BoxName.countryCode, 'Syria');
// box.write(BoxName.serverChosen, AppLink.IntaleqSyriaServer);
return 'Syria';
}
// 3. فحص مصر
if (isPointInPolygon(passengerPoint, CountryPolygons.egyptBoundary)) {
box.write(BoxName.countryCode, 'Egypt');
// box.write(BoxName.serverChosen, AppLink.IntaleqAlexandriaServer);
return 'Egypt';
}
// 4. الافتراضي (إذا كان خارج المناطق المخدومة)
box.write(BoxName.countryCode, 'Jordan');
// box.write(BoxName.serverChosen, AppLink.IntaleqSyriaServer);
return 'Unknown Location (Defaulting to Jordan)';
}
Future<void> _getCurrentLocationAndStartUpdates() async { Future<void> _getCurrentLocationAndStartUpdates() async {
try { try {
Position position = await Geolocator.getCurrentPosition( Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high); desiredAccuracy: LocationAccuracy.high);
myLocation = LatLng(position.latitude, position.longitude); myLocation = LatLng(position.latitude, position.longitude);
getLocationArea(myLocation!.latitude, myLocation!.longitude);
update(); update();
animateCameraToPosition(myLocation!); animateCameraToPosition(myLocation!);
// بدء التحديثات باستخدام المؤقت بدلاً من الـ Stream
_startLocationTimer(); _startLocationTimer();
} catch (e) { } catch (e) {
print("Error getting location: $e"); print("Error getting location: $e");
} }
} }
// --- تم استبدال الـ Stream بمؤقت للتحكم الكامل ---
void _startLocationTimer() { void _startLocationTimer() {
_locationUpdateTimer?.cancel(); // إلغاء أي مؤقت قديم _locationUpdateTimer?.cancel();
_locationUpdateTimer = Timer.periodic(_currentUpdateInterval, (timer) { _locationUpdateTimer = Timer.periodic(_currentUpdateInterval, (timer) {
_updateLocationAndProcess(); _updateLocationAndProcess();
}); });
} }
// --- هذه الدالة هي التي تعمل الآن بشكل دوري ---
Future<void> _updateLocationAndProcess() async { Future<void> _updateLocationAndProcess() async {
try { try {
// طلب موقع واحد فقط عند كل مرة يعمل فيها المؤقت
final position = await Geolocator.getCurrentPosition( final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high); desiredAccuracy: LocationAccuracy.high);
myLocation = LatLng(position.latitude, position.longitude); myLocation = LatLng(position.latitude, position.longitude);
@@ -131,27 +208,20 @@ class NavigationController extends GetxController {
} }
} }
// --- الدالة المسؤولة عن تغيير سرعة التحديث ديناميكياً ---
void _adjustUpdateInterval() { void _adjustUpdateInterval() {
if (currentStepIndex >= routeSteps.length) return; if (currentStepIndex >= routeSteps.length) return;
final currentStepDistance = final currentStepDistance = routeSteps[currentStepIndex]['distance'];
routeSteps[currentStepIndex]['distance']['value'];
// إذا كانت الخطوة الحالية طويلة (شارع سريع > 1.5 كم)
if (currentStepDistance > 1500) { if (currentStepDistance > 1500) {
_currentUpdateInterval = const Duration(seconds: 4); _currentUpdateInterval = const Duration(seconds: 4);
} } else {
// إذا كانت الخطوة قصيرة (منعطفات داخل المدينة < 1.5 كم)
else {
_currentUpdateInterval = const Duration(seconds: 2); _currentUpdateInterval = const Duration(seconds: 2);
} }
// إعادة تشغيل المؤقت بالسرعة الجديدة
_startLocationTimer(); _startLocationTimer();
} }
// ... باقي دوال إعداد الخريطة ...
void onMapCreated(GoogleMapController controller) { void onMapCreated(GoogleMapController controller) {
mapController = controller; mapController = controller;
if (myLocation != null) { if (myLocation != null) {
@@ -193,11 +263,13 @@ class NavigationController extends GetxController {
currentStepIndex >= routeSteps.length || currentStepIndex >= routeSteps.length ||
_finalDestination == null) return; _finalDestination == null) return;
// <<<--- [تعديل] ---: استدعاء الدالة الجديدة والمحسنة
_updateTraveledPolyline(currentPosition); _updateTraveledPolyline(currentPosition);
final step = routeSteps[currentStepIndex]; final step = routeSteps[currentStepIndex];
final endLatLng = final endLatLng =
LatLng(step['end_location']['lat'], step['end_location']['lng']); LatLng(step['end_location']['lat'], step['end_location']['lng']);
final distance = Geolocator.distanceBetween( final distance = Geolocator.distanceBetween(
currentPosition.latitude, currentPosition.latitude,
currentPosition.longitude, currentPosition.longitude,
@@ -224,15 +296,12 @@ class NavigationController extends GetxController {
void _advanceStep() { void _advanceStep() {
currentStepIndex++; currentStepIndex++;
if (currentStepIndex < routeSteps.length) { if (currentStepIndex < routeSteps.length) {
currentInstruction = currentInstruction = routeSteps[currentStepIndex]['instruction_text'];
_parseInstruction(routeSteps[currentStepIndex]['html_instructions']);
nextInstruction = ((currentStepIndex + 1) < routeSteps.length) nextInstruction = ((currentStepIndex + 1) < routeSteps.length)
? _parseInstruction( ? routeSteps[currentStepIndex + 1]['instruction_text']
routeSteps[currentStepIndex + 1]['html_instructions'])
: "الوجهة النهائية"; : "الوجهة النهائية";
_nextInstructionSpoken = false; _nextInstructionSpoken = false;
// **هنا يتم تعديل سرعة التحديث عند الانتقال لخطوة جديدة**
_adjustUpdateInterval(); _adjustUpdateInterval();
if (currentStepIndex < _stepBounds.length) { if (currentStepIndex < _stepBounds.length) {
@@ -244,7 +313,7 @@ class NavigationController extends GetxController {
currentInstruction = "لقد وصلت إلى وجهتك."; currentInstruction = "لقد وصلت إلى وجهتك.";
nextInstruction = ""; nextInstruction = "";
distanceToNextStep = ""; distanceToNextStep = "";
_locationUpdateTimer?.cancel(); // إيقاف التحديثات عند الوصول _locationUpdateTimer?.cancel();
Get.find<TextToSpeechController>().speakText(currentInstruction); Get.find<TextToSpeechController>().speakText(currentInstruction);
update(); update();
} }
@@ -254,41 +323,51 @@ class NavigationController extends GetxController {
// ٣. تحسين خوارزمية البحث ورسم المسار المقطوع // ٣. تحسين خوارزمية البحث ورسم المسار المقطوع
// ======================================================================= // =======================================================================
// <<<--- [تعديل] ---: تم إعادة كتابة الدالة بالكامل
void _updateTraveledPolyline(LatLng currentPosition) { void _updateTraveledPolyline(LatLng currentPosition) {
// **التحسين:** البحث فقط في الخطوة الحالية والخطوة التالية // 1. التأكد من أن المسار الكامل محمل
int searchEndIndex = (currentStepIndex + 1 < _stepPolylines.length) if (_fullRouteCoordinates.isEmpty) return;
? currentStepIndex + 1
: currentStepIndex;
int overallClosestIndex = -1;
double minDistance = double.infinity; double minDistance = double.infinity;
// 2. ابدأ البحث دائماً من النقطة الأخيرة التي تم الوصول إليها
int newClosestIndex = _lastTraveledIndexInFullRoute;
// البحث في نقاط الخطوة الحالية والتالية فقط // 3. ابحث للأمام فقط (من آخر نقطة مسجلة إلى نهاية المسار)
for (int i = currentStepIndex; i <= searchEndIndex; i++) { for (int i = _lastTraveledIndexInFullRoute;
for (int j = 0; j < _stepPolylines[i].length; j++) { i < _fullRouteCoordinates.length;
i++) {
final point = _fullRouteCoordinates[i];
final distance = Geolocator.distanceBetween( final distance = Geolocator.distanceBetween(
currentPosition.latitude, currentPosition.latitude,
currentPosition.longitude, currentPosition.longitude,
_stepPolylines[i][j].latitude, point.latitude,
_stepPolylines[i][j].longitude); point.longitude,
);
if (distance < minDistance) { if (distance < minDistance) {
minDistance = distance; minDistance = distance;
// نحتاج إلى حساب الفهرس العام في القائمة الكاملة newClosestIndex = i;
overallClosestIndex = _getOverallIndex(i, j); } else if (distance > minDistance + 50) {
} // 4. تحسين: إذا بدأت المسافة بالزيادة، فتوقف عن البحث
// هذا يعني أننا تجاوزنا أقرب نقطة (50 متر هامش أمان)
break;
} }
} }
if (overallClosestIndex == -1) return; // 5. قم بتحديث آخر نقطة مسجلة
_lastTraveledIndexInFullRoute = newClosestIndex;
// 6. ارسم المسار المقطوع (من البداية إلى أقرب نقطة)
List<LatLng> traveledPoints = List<LatLng> traveledPoints =
_fullRouteCoordinates.sublist(0, overallClosestIndex + 1); _fullRouteCoordinates.sublist(0, newClosestIndex + 1);
traveledPoints.add(currentPosition); traveledPoints.add(currentPosition); // أضف الموقع الحالي لنعومة الخط
// 7. ارسم المسار المتبقي (من أقرب نقطة إلى النهاية)
List<LatLng> remainingPoints = List<LatLng> remainingPoints =
_fullRouteCoordinates.sublist(overallClosestIndex); _fullRouteCoordinates.sublist(newClosestIndex);
remainingPoints.insert(0, currentPosition); remainingPoints.insert(0, currentPosition); // ابدأ من الموقع الحالي
// 8. تحديث الخطوط على الخريطة
polylines.removeWhere((p) => p.polylineId.value == 'traveled_route'); polylines.removeWhere((p) => p.polylineId.value == 'traveled_route');
polylines.add(Polyline( polylines.add(Polyline(
polylineId: const PolylineId('traveled_route'), polylineId: const PolylineId('traveled_route'),
@@ -306,33 +385,31 @@ class NavigationController extends GetxController {
)); ));
} }
// دالة مساعدة لحساب الفهرس العام // <<<--- [إلغاء] ---: لم نعد بحاجة لهذه الدالة المعقدة
int _getOverallIndex(int stepIndex, int pointInStepIndex) { // int _getOverallIndex(int stepIndex, int pointInStepIndex) {
int overallIndex = 0; // int overallIndex = 0;
for (int i = 0; i < stepIndex; i++) { // for (int i = 0; i < stepIndex; i++) {
overallIndex += _stepPolylines[i].length; // overallIndex += _stepPolylines[i].length;
} // }
return overallIndex + pointInStepIndex; // return overallIndex + pointInStepIndex;
} // }
// ======================================================================= // =======================================================================
// ٤. دوال مساعدة وتجهيز البيانات // ٤. دوال مساعدة وتجهيز البيانات
// ======================================================================= // =======================================================================
// <<<--- التعديل الأول: تغيير الدالة لتكون async
Future<void> _prepareStepData() async { Future<void> _prepareStepData() async {
_stepBounds.clear(); _stepBounds.clear();
_stepPolylines.clear(); _stepPolylines.clear();
if (routeSteps.isEmpty) return; if (routeSteps.isEmpty) return;
for (final step in routeSteps) { for (final step in routeSteps) {
final pointsString = step['polyline']['points']; final pointsString = step['geometry'];
// <<<--- التعديل الثاني: استخدام compute لفك التشفير في خيط منفصل
// وتصحيح طريقة التعامل مع القائمة المُرجعة
final List<LatLng> polylineCoordinates = await compute( final List<LatLng> polylineCoordinates = await compute(
decodePolylineIsolate as ComputeCallback<dynamic, List<LatLng>>, decodePolylineIsolate as ComputeCallback<dynamic, List<LatLng>>,
pointsString); pointsString);
_stepPolylines.add(polylineCoordinates); // تخزين نقاط الخطوة _stepPolylines.add(polylineCoordinates);
_stepBounds.add(_boundsFromLatLngList(polylineCoordinates)); _stepBounds.add(_boundsFromLatLngList(polylineCoordinates));
} }
} }
@@ -400,26 +477,40 @@ class NavigationController extends GetxController {
} }
} }
// --- (تعديل) ---: تم تعديل الدالة لاستخدام http.get
Future<void> getRoute(LatLng origin, LatLng destination) async { Future<void> getRoute(LatLng origin, LatLng destination) async {
final String key = Env.mapAPIKEY;
final url = final url =
'${AppLink.googleMapsLink}directions/json?language=ar&destination=${destination.latitude},${destination.longitude}&origin=${origin.latitude},${origin.longitude}&key=${key}&mode=driving'; '$_routeApiBaseUrl/route?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&steps=true&overview=full';
var response = await CRUD().getGoogleApi(link: url, payload: {});
// Log.print('response: ${response}');
if (response == null || response['routes'].isEmpty) { try {
final response = await http.get(
Uri.parse(url),
headers: {'X-API-KEY': _routeApiKey},
);
if (response.statusCode != 200) {
print("Error from route API: ${response.statusCode}");
Get.snackbar('خطأ', 'لم يتم العثور على مسار.');
return;
}
final responseData = jsonDecode(response.body);
if (responseData == null || responseData['status'] != 'ok') {
Get.snackbar('خطأ', 'لم يتم العثور على مسار.'); Get.snackbar('خطأ', 'لم يتم العثور على مسار.');
return; return;
} }
polylines.clear(); polylines.clear();
final pointsString = response['routes'][0]['overview_polyline']['points']; final pointsString = responseData['polyline'];
// <<<--- التعديل الثالث: استخدام compute هنا أيضًا للمسار الرئيسي
_fullRouteCoordinates = await compute( _fullRouteCoordinates = await compute(
decodePolylineIsolate as ComputeCallback<dynamic, List<LatLng>>, decodePolylineIsolate as ComputeCallback<dynamic, List<LatLng>>,
pointsString); pointsString);
// <<<--- [تعديل] ---: تصفير الـ index عند بدء مسار جديد
_lastTraveledIndexInFullRoute = 0;
polylines.add( polylines.add(
Polyline( Polyline(
polylineId: const PolylineId('remaining_route'), polylineId: const PolylineId('remaining_route'),
@@ -439,34 +530,44 @@ class NavigationController extends GetxController {
), ),
); );
routeSteps = List<Map<String, dynamic>>.from( routeSteps = List<Map<String, dynamic>>.from(responseData['steps']);
response['routes'][0]['legs'][0]['steps']);
// <<<--- التعديل الرابع: انتظار انتهاء الدالة بعد تحويلها إلى async
await _prepareStepData(); await _prepareStepData();
for (int i = 0; i < routeSteps.length; i++) {
var step = routeSteps[i];
if (i < _stepPolylines.length && _stepPolylines[i].isNotEmpty) {
LatLng endLocation = _stepPolylines[i].last;
step['end_location'] = {
'lat': endLocation.latitude,
'lng': endLocation.longitude
};
} else {
var loc = step['maneuver']['location']; // [lng, lat]
step['end_location'] = {'lat': loc[1], 'lng': loc[0]};
}
step['instruction_text'] = _createInstructionFromManeuver(step);
}
currentStepIndex = 0; currentStepIndex = 0;
_nextInstructionSpoken = false; _nextInstructionSpoken = false;
if (routeSteps.isNotEmpty) { if (routeSteps.isNotEmpty) {
currentInstruction = currentInstruction = routeSteps[0]['instruction_text'];
_parseInstruction(routeSteps[0]['html_instructions']);
nextInstruction = (routeSteps.length > 1) nextInstruction = (routeSteps.length > 1)
? _parseInstruction(routeSteps[1]['html_instructions']) ? routeSteps[1]['instruction_text']
: "الوجهة النهائية"; : "الوجهة النهائية";
Get.find<TextToSpeechController>().speakText(currentInstruction); Get.find<TextToSpeechController>().speakText(currentInstruction);
} }
_adjustUpdateInterval(); // تحديد سرعة التحديث لأول مرة _adjustUpdateInterval();
final boundsData = response['routes'][0]['bounds']; if (_fullRouteCoordinates.isNotEmpty) {
mapController?.animateCamera(CameraUpdate.newLatLngBounds( final bounds = _boundsFromLatLngList(_fullRouteCoordinates);
LatLngBounds( mapController
northeast: LatLng( ?.animateCamera(CameraUpdate.newLatLngBounds(bounds, 100.0));
boundsData['northeast']['lat'], boundsData['northeast']['lng']), }
southwest: LatLng( } catch (e) {
boundsData['southwest']['lat'], boundsData['southwest']['lng']), print("Exception in getRoute: $e");
), Get.snackbar('خطأ', 'حدث خطأ في الشبكة.');
100.0, }
));
} }
Future<void> recalculateRoute() async { Future<void> recalculateRoute() async {
@@ -501,7 +602,10 @@ class NavigationController extends GetxController {
_fullRouteCoordinates.clear(); _fullRouteCoordinates.clear();
_stepPolylines.clear(); _stepPolylines.clear();
_nextInstructionSpoken = false; _nextInstructionSpoken = false;
_locationUpdateTimer?.cancel(); // إيقاف التحديثات عند إلغاء المسار _locationUpdateTimer?.cancel();
// <<<--- [تعديل] ---: تصفير الـ index عند إلغاء المسار
_lastTraveledIndexInFullRoute = 0;
update(); update();
} }
@@ -530,14 +634,77 @@ class NavigationController extends GetxController {
const ImageConfiguration(size: Size(25, 25)), 'assets/images/b.png'); const ImageConfiguration(size: Size(25, 25)), 'assets/images/b.png');
} }
String _parseInstruction(String html) => String _createInstructionFromManeuver(Map<String, dynamic> step) {
html.replaceAll(RegExp(r'<[^>]*>'), ' '); final maneuver = step['maneuver'];
final type = maneuver['type'] ?? 'continue';
final modifier = maneuver['modifier'] ?? 'straight';
final name = step['name'] ?? '';
String instruction = "";
switch (type) {
case 'depart':
instruction = "انطلق";
break;
case 'arrive':
instruction = "لقد وصلت إلى وجهتك";
if (name.isNotEmpty) instruction += "، $name";
return instruction;
case 'turn':
case 'fork':
case 'off ramp':
case 'on ramp':
case 'roundabout':
instruction = _getTurnInstruction(modifier);
break;
case 'continue':
instruction = "استمر";
break;
default:
instruction = "اتجه";
}
if (name.isNotEmpty) {
if (instruction == "استمر") {
instruction += " على $name";
} else {
instruction += " إلى $name";
}
} else if (type == 'continue' && modifier == 'straight') {
instruction = "استمر بشكل مستقيم";
}
return instruction;
}
String _getTurnInstruction(String modifier) {
switch (modifier) {
case 'uturn':
return "قم بالاستدارة والعودة";
case 'sharp right':
return "انعطف يمينًا بحدة";
case 'right':
return "انعطف يمينًا";
case 'slight right':
return "انعطف يمينًا قليلاً";
case 'straight':
return "استمر بشكل مستقيم";
case 'slight left':
return "انعطف يسارًا قليلاً";
case 'left':
return "انعطف يسارًا";
case 'sharp left':
return "انعطف يسارًا بحدة";
default:
return "اتجه";
}
}
// ======================================================================= // =======================================================================
// ٥. دالة البحث عن الأماكن المحدثة والدوال المساعدة لها // ٥. دالة البحث عن الأماكن المحدثة والدوال المساعدة لها
// ======================================================================= // =======================================================================
/// الدالة المحدثة للبحث عن الأماكن // --- (تعديل) ---: تم تعديل الدالة لاستخدام http.post
Future<void> getPlaces() async { Future<void> getPlaces() async {
final q = placeDestinationController.text.trim(); final q = placeDestinationController.text.trim();
if (q.isEmpty || q.length < 3) { if (q.isEmpty || q.length < 3) {
@@ -546,7 +713,6 @@ class NavigationController extends GetxController {
return; return;
} }
// التأكد من أن الموقع الحالي ليس null
if (myLocation == null) { if (myLocation == null) {
print('myLocation is null, cannot search for places.'); print('myLocation is null, cannot search for places.');
return; return;
@@ -554,58 +720,53 @@ class NavigationController extends GetxController {
final lat = myLocation!.latitude; final lat = myLocation!.latitude;
final lng = myLocation!.longitude; final lng = myLocation!.longitude;
// نصف قطر البحث بالكيلومتر
const radiusKm = 200.0; const radiusKm = 200.0;
// حساب النطاق الجغرافي (Bounding Box) لإرساله للسيرفر
final latDelta = _kmToLatDelta(radiusKm); final latDelta = _kmToLatDelta(radiusKm);
final lngDelta = _kmToLngDelta(radiusKm, lat); final lngDelta = _kmToLngDelta(radiusKm, lat);
final latMin = lat - latDelta; final latMin = lat - latDelta;
final latMax = lat + latDelta; final latMax = lat + latDelta;
final lngMin = lng - lngDelta; final lngMin = lng - lngDelta;
final lngMax = lng + lngDelta; final lngMax = lng + lngDelta;
try { // تجهيز البيانات لإرسالها كـ JSON
// استدعاء الـ API final payload = {
final response = await CRUD().post(
link: AppLink.getPlacesSyria,
payload: {
'query': q, 'query': q,
'lat_min': latMin.toString(), 'lat_min': latMin.toString(),
'lat_max': latMax.toString(), 'lat_max': latMax.toString(),
'lng_min': lngMin.toString(), 'lng_min': lngMin.toString(),
'lng_max': lngMax.toString(), 'lng_max': lngMax.toString(),
}, };
);
try {
final response =
await CRUD().post(link: AppLink.getPlacesSyria, payload: payload);
// إرسال البيانات كـ JSON
final responseData = (response);
// معالجة الاستجابة من السيرفر بشكل يوافق {"status":"success", "message":[...]}
List list; List list;
if (response is Map) { if (responseData is Map) {
if (response['status'] == 'success' && response['message'] is List) { if (responseData['status'] == 'success' &&
list = List.from(response['message'] as List); responseData['message'] is List) {
} else if (response['status'] == 'failure') { list = List.from(responseData['message'] as List);
print('Server Error: ${response['message']}'); } else if (responseData['status'] == 'failure') {
print('Server Error: ${responseData['message']}');
return; return;
} else { } else {
print('Unexpected Map shape from server'); print('Unexpected Map shape from server');
return; return;
} }
} else if (response is List) { } else if (responseData is List) {
// للتعامل مع الحالات التي قد يرجع فيها السيرفر قائمة مباشرة list = List.from(responseData);
list = List.from(response);
} else { } else {
print('Unexpected response shape from server'); print('Unexpected response shape from server');
return; return;
} }
// دالة مساعدة لاختيار أفضل اسم متاح
String _bestName(Map p) { String _bestName(Map p) {
return (p['name_ar'] ?? p['name'] ?? p['name_en'] ?? '').toString(); return (p['name_ar'] ?? p['name'] ?? p['name_en'] ?? '').toString();
} }
// حساب المسافة والصلة والنقاط النهائية لكل نتيجة
for (final p in list) { for (final p in list) {
final plat = double.tryParse(p['latitude']?.toString() ?? '0.0') ?? 0.0; final plat = double.tryParse(p['latitude']?.toString() ?? '0.0') ?? 0.0;
final plng = final plng =
@@ -613,8 +774,6 @@ class NavigationController extends GetxController {
final distance = _haversineKm(lat, lng, plat, plng); final distance = _haversineKm(lat, lng, plat, plng);
final relevance = _relevanceScore(_bestName(p), q); final relevance = _relevanceScore(_bestName(p), q);
// معادلة الترتيب: (الأولوية للمسافة الأقرب) * (ثم الصلة الأعلى)
final score = (1.0 / (1.0 + distance)) * (1.0 + relevance); final score = (1.0 / (1.0 + distance)) * (1.0 + relevance);
p['distanceKm'] = distance; p['distanceKm'] = distance;
@@ -622,7 +781,6 @@ class NavigationController extends GetxController {
p['score'] = score; p['score'] = score;
} }
// ترتيب القائمة النهائية حسب النقاط (الأعلى أولاً)
list.sort((a, b) { list.sort((a, b) {
final sa = (a['score'] ?? 0.0) as double; final sa = (a['score'] ?? 0.0) as double;
final sb = (b['score'] ?? 0.0) as double; final sb = (b['score'] ?? 0.0) as double;
@@ -646,9 +804,8 @@ class NavigationController extends GetxController {
// --== دوال مساعدة (محدثة) ==-- // --== دوال مساعدة (محدثة) ==--
// ----------------------------------------------------------------- // -----------------------------------------------------------------
/// تحسب المسافة بين نقطتين بالكيلومتر (معادلة هافرساين)
double _haversineKm(double lat1, double lon1, double lat2, double lon2) { double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
const R = 6371.0; // نصف قطر الأرض بالكيلومتر const R = 6371.0;
final dLat = (lat2 - lat1) * (pi / 180.0); final dLat = (lat2 - lat1) * (pi / 180.0);
final dLon = (lon2 - lon1) * (pi / 180.0); final dLon = (lon2 - lon1) * (pi / 180.0);
final rLat1 = lat1 * (pi / 180.0); final rLat1 = lat1 * (pi / 180.0);
@@ -660,23 +817,20 @@ class NavigationController extends GetxController {
return R * c; return R * c;
} }
/// تحسب درجة تطابق بسيطة بين اسم المكان وكلمة البحث
double _relevanceScore(String placeName, String query) { double _relevanceScore(String placeName, String query) {
if (placeName.isEmpty || query.isEmpty) return 0.0; if (placeName.isEmpty || query.isEmpty) return 0.0;
final pLower = placeName.toLowerCase(); final pLower = placeName.toLowerCase();
final qLower = query.toLowerCase(); final qLower = query.toLowerCase();
if (pLower.startsWith(qLower)) return 1.0; // تطابق كامل في البداية if (pLower.startsWith(qLower)) return 1.0;
if (pLower.contains(qLower)) return 0.5; // تحتوي على الكلمة if (pLower.contains(qLower)) return 0.5;
return 0.0; return 0.0;
} }
/// تحويل كيلومتر إلى فرق درجات لخط العرض
double _kmToLatDelta(double km) { double _kmToLatDelta(double km) {
const kmInDegree = 111.32; const kmInDegree = 111.32;
return km / kmInDegree; return km / kmInDegree;
} }
/// تحويل كيلومتر إلى فرق درجات لخط الطول (يعتمد على خط العرض الحالي)
double _kmToLngDelta(double km, double latitude) { double _kmToLngDelta(double km, double latitude) {
const kmInDegree = 111.32; const kmInDegree = 111.32;
return km / (kmInDegree * cos(latitude * (pi / 180.0))); return km / (kmInDegree * cos(latitude * (pi / 180.0)));

View File

@@ -1,19 +1,22 @@
// lib/views/navigation_view.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'dart:ui'; import 'dart:ui'; // For BackdropFilter
import 'navigation_controller.dart'; // For BackdropFilter import 'navigation_controller.dart';
// استخدام نفس مسار الاستيراد الذي قدمته // ملاحظة: افترضتُ أن لديك لوناً أساسياً في هذا الملف
// import 'package:sefer_driver/constant/colors.dart';
// سأستخدم اللون الأزرق كبديل مؤقت
const Color kPrimaryColor = Color(0xFF0D47A1);
class NavigationView extends StatelessWidget { class NavigationView extends StatelessWidget {
const NavigationView({super.key}); const NavigationView({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// استخدام Get.find() بدلاً من Get.put() لضمان أن الكونترولر مُهيأ مسبقاً
// إذا كانت هذه هي نقطة الدخول، Get.put() صحيح.
final NavigationController controller = Get.put(NavigationController()); final NavigationController controller = Get.put(NavigationController());
return Scaffold( return Scaffold(
@@ -23,7 +26,6 @@ class NavigationView extends StatelessWidget {
// --- الخريطة --- // --- الخريطة ---
GoogleMap( GoogleMap(
onMapCreated: controller.onMapCreated, onMapCreated: controller.onMapCreated,
// --- السطر المضاف والمهم هنا ---
onLongPress: controller.onMapLongPressed, onLongPress: controller.onMapLongPressed,
initialCameraPosition: CameraPosition( initialCameraPosition: CameraPosition(
target: controller.myLocation ?? target: controller.myLocation ??
@@ -37,27 +39,33 @@ class NavigationView extends StatelessWidget {
compassEnabled: false, compassEnabled: false,
zoomControlsEnabled: false, zoomControlsEnabled: false,
// تعديل الـ padding لإعطاء مساحة للعناصر العلوية والسفلية // تعديل الـ padding لإعطاء مساحة للعناصر العلوية والسفلية
// مساحة أكبر في الأعلى للبحث + النتائج، ومساحة أكبر بالأسفل للملاحة
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: controller.currentInstruction.isNotEmpty ? 130 : 0, bottom: controller.currentInstruction.isNotEmpty ? 170 : 0,
top: 140), top: 150,
),
), ),
// --- واجهة البحث ونتائجه --- // --- واجهة البحث (تصميم زجاجي) ---
_buildSearchUI(controller), _buildGlassSearchUI(controller),
// --- إرشادات الملاحة المطورة --- // --- إرشادات الملاحة (تصميم عائم) ---
if (controller.currentInstruction.isNotEmpty) if (controller.currentInstruction.isNotEmpty)
_buildNavigationInstruction(controller), _buildFloatingNavigationUI(controller),
// --- أزرار التحكم على الخريطة --- // --- أزرار التحكم (تصميم عائم) ---
_buildMapControls(controller), _buildFloatingMapControls(controller),
// --- مؤشر التحميل --- // --- مؤشر التحميل ---
if (controller.isLoading) if (controller.isLoading)
Container( Container(
color: Colors.black.withOpacity(0.5), color: Colors.black.withOpacity(0.5),
child: const Center( child: const Center(
child: CircularProgressIndicator(color: Colors.white)), child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
strokeWidth: 3,
),
),
), ),
], ],
), ),
@@ -65,94 +73,75 @@ class NavigationView extends StatelessWidget {
); );
} }
// --- ويدجت خاصة بواجهة البحث --- /// --- 1. واجهة البحث بالتصميم الزجاجي المطور ---
Widget _buildSearchUI(NavigationController controller) { Widget _buildGlassSearchUI(NavigationController controller) {
return Positioned( return Positioned(
top: 0, top: 0,
left: 0, left: 0,
right: 0, right: 0,
child: SafeArea( child: SafeArea(
child: Padding( child: Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
child: Column( child: Column(
children: [ children: [
Container( // --- شريط البحث ---
ClipRRect(
borderRadius: BorderRadius.circular(28.0),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: Container(
height: 56,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white.withOpacity(0.85),
borderRadius: BorderRadius.circular(15.0), borderRadius: BorderRadius.circular(28.0),
border: Border.all(color: Colors.white.withOpacity(0.4)),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.15), color: Colors.black.withOpacity(0.05),
blurRadius: 10, blurRadius: 15,
offset: const Offset(0, 5), offset: const Offset(0, 5),
), ),
], ],
), ),
child: Row(
children: [
const Padding(
padding: EdgeInsets.only(left: 18.0, right: 10.0),
child: Icon(Icons.search,
color: kPrimaryColor, size: 24),
),
Expanded(
child: TextField( child: TextField(
controller: controller.placeDestinationController, controller: controller.placeDestinationController,
onChanged: (val) { onChanged: controller.onSearchChanged,
controller.onSearchChanged(val); textInputAction: TextInputAction.search,
}, style: const TextStyle(
fontSize: 16, color: Colors.black87),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'إلى أين تريد الذهاب؟', hintText: 'إلى أين تريد الذهاب؟',
prefixIcon: const Icon(Icons.search, color: Colors.grey), hintStyle: const TextStyle(
suffixIcon: controller color: Colors.black45, fontSize: 16),
.placeDestinationController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear, color: Colors.grey),
onPressed: () {
controller.placeDestinationController.clear();
controller.placesDestination = [];
controller.update();
},
)
: (controller.polylines.isNotEmpty
? IconButton(
icon:
const Icon(Icons.close, color: Colors.red),
tooltip: 'إلغاء المسار',
onPressed: () => controller.clearRoute(),
)
: null),
border: InputBorder.none, border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.only(bottom: 2),
horizontal: 20, vertical: 15),
), ),
), ),
), ),
const SizedBox(height: 8), // زر المسح أو إلغاء المسار
if (controller
.placeDestinationController.text.isNotEmpty)
_buildClearButton(controller)
else if (controller.polylines.isNotEmpty)
_buildCancelRouteButton(controller),
],
),
),
),
),
const SizedBox(height: 10),
// --- قائمة النتائج ---
if (controller.placesDestination.isNotEmpty) if (controller.placesDestination.isNotEmpty)
ClipRRect( _buildSearchResultsList(controller),
borderRadius: BorderRadius.circular(15.0),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
child: Container(
constraints: const BoxConstraints(maxHeight: 220),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.85),
borderRadius: BorderRadius.circular(15.0),
),
child: ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: controller.placesDestination.length,
itemBuilder: (context, index) {
final place = controller.placesDestination[index];
return ListTile(
title: Text(place['name'] ?? 'اسم غير معروف',
style: const TextStyle(
fontWeight: FontWeight.bold)),
subtitle: Text(place['address'] ?? '',
maxLines: 1, overflow: TextOverflow.ellipsis),
leading: const Icon(Icons.location_on_outlined,
color: Colors.blue),
onTap: () => controller.selectDestination(place),
);
},
),
),
),
),
], ],
), ),
), ),
@@ -160,28 +149,136 @@ class NavigationView extends StatelessWidget {
); );
} }
// --- ويدجت خاصة بأزرار التحكم --- Widget _buildClearButton(NavigationController controller) {
Widget _buildMapControls(NavigationController controller) { return IconButton(
icon: const Icon(Icons.clear, color: Colors.grey, size: 22),
onPressed: () {
controller.placeDestinationController.clear();
controller.placesDestination = [];
controller.update();
},
);
}
Widget _buildCancelRouteButton(NavigationController controller) {
return IconButton(
tooltip: 'إلغاء المسار',
icon: const Icon(Icons.close, color: Colors.redAccent, size: 22),
onPressed: () => controller.clearRoute(),
);
}
Widget _buildSearchResultsList(NavigationController controller) {
return ClipRRect(
borderRadius: BorderRadius.circular(24.0),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: Container(
constraints: const BoxConstraints(maxHeight: 220),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.85),
borderRadius: BorderRadius.circular(24.0),
border: Border.all(color: Colors.white.withOpacity(0.4)),
),
child: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8.0),
itemCount: controller.placesDestination.length,
itemBuilder: (context, index) {
final place = controller.placesDestination[index];
final distance = place['distanceKm'] as double?;
final address = (place['address'] ?? '').toString();
return Material(
color: Colors.transparent,
child: InkWell(
onTap: () => controller.selectDestination(place),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 12.0),
child: Row(
children: [
// أيقونة الموقع
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: kPrimaryColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(Icons.location_on_outlined,
color: kPrimaryColor, size: 20),
),
const SizedBox(width: 14),
// الاسم والعنوان
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
place['name'] ?? 'اسم غير معروف',
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.black87),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (address.isNotEmpty)
Text(
address,
style: const TextStyle(
color: Colors.black54, fontSize: 13),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
const SizedBox(width: 10),
// المسافة
if (distance != null)
Text(
'${distance.toStringAsFixed(1)} كم',
style: const TextStyle(
color: kPrimaryColor,
fontWeight: FontWeight.w500,
fontSize: 13,
),
),
],
),
),
),
);
},
),
),
),
);
}
/// --- 2. أزرار التحكم بالتصميم العائم ---
Widget _buildFloatingMapControls(NavigationController controller) {
return Positioned( return Positioned(
bottom: controller.currentInstruction.isNotEmpty ? 150 : 20, // اجعلها تطفو فوق لوحة الملاحة
right: 12, bottom: controller.currentInstruction.isNotEmpty ? 190 : 24,
right: 16,
child: Column( child: Column(
children: [ children: [
if (controller.polylines.isNotEmpty) ...[ if (controller.polylines.isNotEmpty) ...[
FloatingActionButton( FloatingActionButton(
heroTag: 'rerouteBtn', heroTag: 'rerouteBtn',
mini: true,
backgroundColor: Colors.white, backgroundColor: Colors.white,
tooltip: 'إعادة حساب المسار', elevation: 6,
onPressed: () => controller.recalculateRoute(), onPressed: () => controller.recalculateRoute(),
child: const Icon(Icons.sync_alt, color: Colors.blue), tooltip: 'إعادة حساب المسار',
child: const Icon(Icons.sync_alt, color: kPrimaryColor, size: 24),
), ),
const SizedBox(height: 10), const SizedBox(height: 12),
], ],
FloatingActionButton( FloatingActionButton(
heroTag: 'gpsBtn', heroTag: 'gpsBtn',
mini: true,
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 6,
onPressed: () { onPressed: () {
if (controller.myLocation != null) { if (controller.myLocation != null) {
controller.animateCameraToPosition( controller.animateCameraToPosition(
@@ -191,102 +288,134 @@ class NavigationView extends StatelessWidget {
); );
} }
}, },
child: const Icon(Icons.gps_fixed, color: Colors.black54), child: const Icon(Icons.gps_fixed, color: Colors.black54, size: 24),
), ),
], ],
), ),
); );
} }
// --- ويدجت خاصة بإرشادات الطريق المطورة --- /// --- 3. واجهة الملاحة بالتصميم العائم المطور ---
Widget _buildNavigationInstruction(NavigationController controller) { Widget _buildFloatingNavigationUI(NavigationController controller) {
return Positioned( return Positioned(
bottom: 0, bottom: 16,
left: 0, left: 16,
right: 0, right: 16,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: const LinearGradient(
colors: [Colors.blue.shade900, Colors.blue.shade600], colors: [Color(0xFF1E88E5), Color(0xFF0D47A1)], // أزرق متدرج
begin: Alignment.topLeft, begin: Alignment.topCenter,
end: Alignment.bottomRight, end: Alignment.bottomCenter,
), ),
borderRadius: BorderRadius.circular(28),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.2), color: Colors.black.withOpacity(0.3),
blurRadius: 15, blurRadius: 25,
offset: const Offset(0, -5), offset: const Offset(0, 10),
), ),
], ],
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 20), padding: const EdgeInsets.fromLTRB(22, 20, 22, 22),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// --- الصف العلوي: السرعة والمسافة --- // --- الصف العلوي: الإرشاد والمسافة ---
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
// الأيقونة
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: const Icon(Icons.navigation_rounded,
color: Colors.white, size: 28),
),
const SizedBox(width: 16),
// الإرشاد
Expanded(
child: Text(
controller.currentInstruction,
style: const TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold,
height: 1.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 16),
// المسافة
Text( Text(
controller.distanceToNextStep, controller.distanceToNextStep,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 28, fontSize: 32,
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold,
), ),
),
],
),
// --- فاصل ---
if (controller.nextInstruction.isNotEmpty ||
controller.currentSpeed > 0)
const Padding(
padding: EdgeInsets.symmetric(vertical: 14.0),
child: Divider(color: Colors.white30, height: 1),
),
// --- الصف السفلي: الإرشاد التالي والسرعة ---
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// الإرشاد التالي
Expanded(
child: controller.nextInstruction.isNotEmpty
? Text(
'التالي: ${controller.nextInstruction}',
style: const TextStyle(
color: Colors.white70,
fontSize: 15,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
: const SizedBox(), // يترك مساحة فارغة إذا لم يكن هناك إرشاد تالي
),
// السرعة
Row( Row(
children: [ children: [
Text( Text(
controller.currentSpeed.toStringAsFixed(0), controller.currentSpeed.toStringAsFixed(0),
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 28, fontSize: 22,
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold,
), ),
const SizedBox(width: 4), ),
const SizedBox(width: 6),
const Text( const Text(
"كم/س", 'كم/س',
style: TextStyle(color: Colors.white70, fontSize: 14), style: TextStyle(
color: Colors.white70,
fontSize: 14,
fontWeight: FontWeight.w500,
),
), ),
], ],
), ),
], ],
), ),
const Divider(color: Colors.white38, height: 20, thickness: 0.8),
// --- الصف السفلي: الإرشاد القادم ---
Row(
children: [
const Icon(Icons.navigation_rounded,
color: Colors.white, size: 32),
const SizedBox(width: 15),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("الخطوة التالية",
style:
TextStyle(color: Colors.white70, fontSize: 12)),
Text(
controller.nextInstruction.isNotEmpty
? controller.nextInstruction
: controller.currentInstruction,
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
], ],
), ),
), ),

View File

@@ -0,0 +1,145 @@
// lib/controllers/navigation/route_matcher_worker.dart
import 'dart:async';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'dart:math';
/// Worker entrypoint (spawnUri/spawn).
/// Messages:
/// - init: {'type':'init','coords': Float64List}
/// - match: {'type':'match','id': int, 'lat': double, 'lng': double, 'lastIndex': int, 'window': int}
/// - dispose: {'type':'dispose'}
///
/// Responses are sent back as Map via SendPort:
/// - {'type':'ready'}
/// - {'type':'matchResult','id': id, 'index': overallIndex, 'lat': lat, 'lng': lng, 'dist': meters}
void routeMatcherIsolateEntry(SendPort sendPort) {
final ReceivePort port = ReceivePort();
sendPort.send({'type': 'ready', 'port': port.sendPort});
Float64List? flat; // [lat,lng,lat,lng,...]
int nPoints = 0;
port.listen((dynamic message) {
try {
if (message is Map<String, dynamic>) {
final type = message['type'] as String? ?? '';
if (type == 'init') {
final data = message['coords'] as Float64List?;
if (data != null) {
flat = data;
nPoints = flat!.length ~/ 2;
sendPort.send({'type': 'inited', 'points': nPoints});
} else {
sendPort.send({'type': 'error', 'message': 'init missing coords'});
}
} else if (type == 'match') {
if (flat == null) {
sendPort.send({'type': 'error', 'message': 'not inited'});
return;
}
final int id = message['id'] as int;
final double lat = (message['lat'] as num).toDouble();
final double lng = (message['lng'] as num).toDouble();
final int lastIndex = (message['lastIndex'] as int?) ?? 0;
final int window = (message['window'] as int?) ?? 120;
final result =
_findClosestWindowInternal(flat!, lat, lng, lastIndex, window);
sendPort.send({
'type': 'matchResult',
'id': id,
'index': result['index'],
'lat': result['lat'],
'lng': result['lng'],
'dist': result['dist']
});
} else if (type == 'dispose') {
port.close();
sendPort.send({'type': 'disposed'});
} else {
sendPort.send({'type': 'error', 'message': 'unknown message type'});
}
}
} catch (e, st) {
sendPort.send(
{'type': 'error', 'message': e.toString(), 'stack': st.toString()});
}
});
}
/// Internal helper: projection on segments, windowed search.
/// Returns Map {index, lat, lng, dist}
Map<String, dynamic> _findClosestWindowInternal(
Float64List flat, double lat, double lng, int lastIndex, int window) {
final int n = flat.length ~/ 2;
final int start = max(0, lastIndex - window);
final int end = min(n - 1, lastIndex + window);
double minDist = double.infinity;
int bestIdx = lastIndex;
double bestLat = flat[lastIndex * 2];
double bestLng = flat[lastIndex * 2 + 1];
for (int i = start; i < end; i++) {
final double aLat = flat[i * 2];
final double aLng = flat[i * 2 + 1];
final double bLat = flat[(i + 1) * 2];
final double bLng = flat[(i + 1) * 2 + 1];
final proj = _closestPointOnSegmentLatLng(lat, lng, aLat, aLng, bLat, bLng);
final double d = proj['dist'] as double;
if (d < minDist) {
minDist = d;
bestLat = proj['lat'] as double;
bestLng = proj['lng'] as double;
// choose overall index: i or i+1 depending on t
final double t = proj['t'] as double;
bestIdx = i + (t > 0.5 ? 1 : 0);
}
}
return {'index': bestIdx, 'lat': bestLat, 'lng': bestLng, 'dist': minDist};
}
/// Projection math on geodetic points approximated in degrees (good for short distances).
Map<String, dynamic> _closestPointOnSegmentLatLng(
double px, double py, double ax, double ay, double bx, double by) {
// Here px=lat, py=lng; ax=lat, ay=lng, etc.
final double x0 = px;
final double y0 = py;
final double x1 = ax;
final double y1 = ay;
final double x2 = bx;
final double y2 = by;
final double dx = x2 - x1;
final double dy = y2 - y1;
double t = 0.0;
final double len2 = dx * dx + dy * dy;
if (len2 > 0) {
t = ((x0 - x1) * dx + (y0 - y1) * dy) / len2;
if (t < 0) t = 0;
if (t > 1) t = 1;
}
final double projX = x1 + t * dx;
final double projY = y1 + t * dy;
final double distMeters = _haversineDistanceMeters(x0, y0, projX, projY);
return {'lat': projX, 'lng': projY, 't': t, 'dist': distMeters};
}
/// Haversine distance (meters)
double _haversineDistanceMeters(
double lat1, double lng1, double lat2, double lng2) {
final double R = 6371000.0;
final double dLat = _deg2rad(lat2 - lat1);
final double dLon = _deg2rad(lng2 - lng1);
final double a = sin(dLat / 2) * sin(dLat / 2) +
cos(_deg2rad(lat1)) * cos(_deg2rad(lat2)) * sin(dLon / 2) * sin(dLon / 2);
final double c = 2 * atan2(sqrt(a), sqrt(1 - a));
return R * c;
}
double _deg2rad(double deg) => deg * pi / 180.0;

View File

@@ -307,7 +307,7 @@ class CaptainWalletController extends GetxController {
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'ding', tone: 'ding',
driverList: [], driverList: [], category: 'Transfer',
); );
await addSeferWallet('payout fee', '5'); await addSeferWallet('payout fee', '5');

View File

@@ -10,6 +10,8 @@ import 'package:sefer_driver/views/home/on_boarding_page.dart';
import '../../constant/box_name.dart'; import '../../constant/box_name.dart';
import '../../main.dart'; import '../../main.dart';
import '../../print.dart'; import '../../print.dart';
import '../functions/encrypt_decrypt.dart';
import '../functions/secure_storage.dart';
// Assuming you have a home page to navigate to after successful login. // Assuming you have a home page to navigate to after successful login.
// If not, you might need to adjust the navigation target. // If not, you might need to adjust the navigation target.
@@ -92,33 +94,46 @@ class SplashScreenController extends GetxController
/// is expected to be handled by an internal process (like login). /// is expected to be handled by an internal process (like login).
Future<Widget?> _getNavigationTarget() async { Future<Widget?> _getNavigationTarget() async {
try { try {
final onBoardingShown = box.read(BoxName.onBoarding) != null; // ... (التحقق من OnBoarding)
if (!onBoardingShown) {
return OnBoardingPage();
}
final isDriverDataAvailable = box.read(BoxName.phoneDriver) != null; final isDriverDataAvailable = box.read(BoxName.phoneDriver) != null;
final isPhoneVerified = box.read(BoxName.phoneVerified).toString() == '1'; // final isPhoneVerified = box.read(BoxName.phoneVerified).toString() == '1'; // <-- ⛔️ تم حذف هذا السطر
if (isDriverDataAvailable && isPhoneVerified) {
Log.print('Attempting to log in with stored credentials...');
final loginController = Get.put(LoginDriverController()); final loginController = Get.put(LoginDriverController());
// Assume loginWithGoogleCredential handles its own navigation on success. // ✅ --- (الحل) ---
// تم حذف التحقق من "isPhoneVerified"
// هذا يسمح لـ "loginWithGoogleCredential" بتحديد الحالة والتوجيه الصحيح
// (إلى Home أو DriverVerificationScreen أو PhoneNumberScreen)
if (isDriverDataAvailable) {
Log.print('المستخدم مسجل. جارٍ تهيئة الجلسة...');
// الخطوة 1: ضمان جلب الـ JWT أولاً
// (هذا هو الكود الذي كان في main.dart)
final AppInitializer initializer = AppInitializer();
await initializer.initializeApp();
await EncryptionHelper.initialize();
// انتظر حتى ينتهي جلب الـ JWT
Log.print('تم جلب الـ JWT. جارٍ تسجيل الدخول ببيانات جوجل...');
// الخطوة 2: الآن قم بتسجيل الدخول وأنت متأكد أن الـ JWT موجود
// يجب تعديل "loginWithGoogleCredential" لتعيد "bool" (نجاح/فشل)
await loginController.loginWithGoogleCredential( await loginController.loginWithGoogleCredential(
box.read(BoxName.driverID).toString(), box.read(BoxName.driverID).toString(),
box.read(BoxName.emailDriver).toString(), box.read(BoxName.emailDriver).toString(),
); );
// *** FIX: Return null to signify that navigation has been handled. *** // إذا نجح تسجيل الدخول (سواء لـ Home أو لـ DriverVerification)
return null; // فإن "loginWithGoogleCredential" تقوم بالتوجيه بنفسها
// ونحن نُرجع "null" هنا لمنع "SplashScreen" من التوجيه مرة أخرى.
} else { } else {
Log.print('No valid driver session found. Navigating to login page.'); Log.print('مستخدم غير مسجل. اذهب لصفحة الدخول.');
return LoginCaptin(); return LoginCaptin();
} }
} catch (e) { } catch (e) {
Log.print("Error during navigation logic: $e"); Log.print("Error during navigation logic: $e");
// Fallback to the login page in case of any error. // أي خطأ فادح (مثل خطأ في جلب الـ JWT) سيعيدك للدخول
return LoginCaptin(); return LoginCaptin();
} }
} }

View File

@@ -49,6 +49,10 @@ class MyTranslation extends Translations {
"يرجى البقاء في نقطة الالتقاط المحددة.", "يرجى البقاء في نقطة الالتقاط المحددة.",
"message From Driver": "رسالة من السائق", "message From Driver": "رسالة من السائق",
"Trip is Begin": "بدأت الرحلة", "Trip is Begin": "بدأت الرحلة",
'Rides': 'الرحلات',
'Your Activity': 'نشاطك',
'You have upload Criminal documents': 'لقد قمت بتحميل وثائق جنائية',
'Close': 'إغلاق',
"Cancel Trip from driver": "إلغاء الرحلة من السائق", "Cancel Trip from driver": "إلغاء الرحلة من السائق",
"We will look for a new driver.\nPlease wait.": "We will look for a new driver.\nPlease wait.":
"هنبحث عن سائق جديد.\nمن فضلك انتظر.", "هنبحث عن سائق جديد.\nمن فضلك انتظر.",

View File

@@ -58,11 +58,11 @@ class PassengerNotificationController extends GetxController {
// .sendNotificationToPassengerToken(title, body, 'token', [], 'ding.wav'); // .sendNotificationToPassengerToken(title, body, 'token', [], 'ding.wav');
NotificationService.sendNotification( NotificationService.sendNotification(
target: 'token'.toString(), target: 'token'.toString(),
title: title, title: title.tr,
body: body, body: body,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'ding', tone: 'ding',
driverList: [], driverList: [], category: title,
); );
} }

View File

@@ -81,7 +81,7 @@ class RateController extends GetxController {
body: 'Wallet Added${(remainingFee).toStringAsFixed(0)}'.tr, body: 'Wallet Added${(remainingFee).toStringAsFixed(0)}'.tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'tone2', tone: 'tone2',
driverList: [], driverList: [], category: 'Wallet Added',
); );
walletChecked = 'true'; walletChecked = 'true';
// } // }

2
lib/env/env.dart vendored
View File

@@ -6,6 +6,8 @@ part 'env.g.dart';
abstract class Env { abstract class Env {
@EnviedField(varName: 'basicAuthCredentials', obfuscate: true) @EnviedField(varName: 'basicAuthCredentials', obfuscate: true)
static final String basicAuthCredentials = _Env.basicAuthCredentials; static final String basicAuthCredentials = _Env.basicAuthCredentials;
@EnviedField(varName: 'mapKeyOsm', obfuscate: true)
static final String mapKeyOsm = _Env.mapKeyOsm;
@EnviedField(varName: 'mapAPIKEYIOS', obfuscate: true) @EnviedField(varName: 'mapAPIKEYIOS', obfuscate: true)
static final String mapAPIKEYIOS = _Env.mapAPIKEYIOS; static final String mapAPIKEYIOS = _Env.mapAPIKEYIOS;

24628
lib/env/env.g.dart vendored

File diff suppressed because it is too large Load Diff

View File

@@ -73,11 +73,11 @@ Future<void> backgroundMessageHandler(RemoteMessage message) async {
} }
if (Platform.isAndroid) { if (Platform.isAndroid) {
if (message.notification != null && message.notification!.title != null) { String category = message.data['category'] ?? '';
if (message.notification != null) {
Log.print('message.notification!.title: ${message.notification!.title}'); Log.print('message.notification!.title: ${message.notification!.title}');
if (message.notification?.title == 'Order' || if (category == 'Order' || category == 'OrderSpeed') {
message.notification?.title == 'OrderSpeed') {
final myListString = message.data['DriverList'] ?? '[]'; final myListString = message.data['DriverList'] ?? '[]';
Log.print('myListString: $myListString'); Log.print('myListString: $myListString');
@@ -97,7 +97,7 @@ Future<void> backgroundMessageHandler(RemoteMessage message) async {
enableDrag: true, enableDrag: true,
flag: OverlayFlag.focusPointer, flag: OverlayFlag.focusPointer,
positionGravity: PositionGravity.auto, positionGravity: PositionGravity.auto,
height: 1400, height: WindowSize.matchParent,
width: WindowSize.matchParent, width: WindowSize.matchParent,
startPosition: const OverlayPosition(0, -30), startPosition: const OverlayPosition(0, -30),
); );
@@ -218,10 +218,6 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
Future<void> _initApp() async { Future<void> _initApp() async {
try { try {
final AppInitializer initializer = AppInitializer();
await initializer.initializeApp();
await EncryptionHelper.initialize();
if (!Get.isRegistered<NotificationController>()) { if (!Get.isRegistered<NotificationController>()) {
Get.put(NotificationController()); Get.put(NotificationController());
} }

View File

@@ -440,6 +440,7 @@ class LoginCaptin extends StatelessWidget {
MyElevatedButton( MyElevatedButton(
title: "Allow Location Access".tr, title: "Allow Location Access".tr,
onPressed: () async { onPressed: () async {
Get.put(LoginDriverController());
await getLocationPermission(); // Assumes this function handles the request logic await getLocationPermission(); // Assumes this function handles the request logic
if (await Permission.location.isGranted) { if (await Permission.location.isGranted) {
box.write(BoxName.locationPermission, 'true'); box.write(BoxName.locationPermission, 'true');

View File

@@ -45,6 +45,7 @@ class HomeCaptain extends StatelessWidget {
checkForUpdate(context); checkForUpdate(context);
getPermissionOverlay(); getPermissionOverlay();
showDriverGiftClaim(context); showDriverGiftClaim(context);
checkForAppliedRide(context);
}); });
// The stack is now even simpler. // The stack is now even simpler.
@@ -187,11 +188,38 @@ class _MapView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final locationController = Get.find<LocationController>(); // جلب الكونترولر الرئيسي
return GetBuilder<HomeCaptainController>(builder: (controller) { final homeController = Get.find<HomeCaptainController>();
return controller.isLoading
? const MyCircularProgressIndicator() return GetBuilder<HomeCaptainController>(
: GoogleMap( builder: (controller) {
if (controller.isLoading) {
return const MyCircularProgressIndicator();
}
// --- هذا هو التعديل ---
// هذا الـ Builder يستمع إلى تحديثات الموقع
return GetBuilder<LocationController>(
builder: (locationController) {
// --- هذا هو الكود الجديد ---
// نقوم بتحريك الكاميرا يدوياً عند كل تحديث للموقع
if (homeController.mapHomeCaptainController != null &&
homeController.isActive) {
homeController.mapHomeCaptainController!.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: locationController.myLocation, // الموقع الجديد
zoom: 17.5, // تقريب لمتابعة السائق
tilt: 50.0, // زاوية رؤية 3D
bearing: locationController.heading, // اتجاه السيارة
),
),
);
}
// --- نهاية الكود الجديد ---
// إرجاع الخريطة
return GoogleMap(
padding: const EdgeInsets.only(bottom: 110, top: 300), padding: const EdgeInsets.only(bottom: 110, top: 300),
fortyFiveDegreeImageryEnabled: true, fortyFiveDegreeImageryEnabled: true,
onMapCreated: controller.onMapCreated, onMapCreated: controller.onMapCreated,
@@ -200,19 +228,12 @@ class _MapView extends StatelessWidget {
target: locationController.myLocation, target: locationController.myLocation,
zoom: 15, zoom: 15,
), ),
onCameraMove: (position) { // --- تم حذف onCameraMove الخاطئ ---
CameraPosition(
target: locationController.myLocation,
zoom: 17.5,
tilt: 50.0,
bearing: locationController.heading,
);
},
markers: { markers: {
Marker( Marker(
markerId: MarkerId('MyLocation'.tr), markerId: MarkerId('MyLocation'.tr),
position: locationController.myLocation, position: locationController.myLocation, // يتم تحديثه من هنا
rotation: locationController.heading, rotation: locationController.heading, // يتم تحديثه من هنا
flat: true, flat: true,
anchor: const Offset(0.5, 0.5), anchor: const Offset(0.5, 0.5),
icon: controller.carIcon, icon: controller.carIcon,
@@ -222,12 +243,15 @@ class _MapView extends StatelessWidget {
myLocationButtonEnabled: false, myLocationButtonEnabled: false,
myLocationEnabled: false, myLocationEnabled: false,
trafficEnabled: controller.mapTrafficON, trafficEnabled: controller.mapTrafficON,
buildingsEnabled: true, buildingsEnabled: false,
mapToolbarEnabled: false, mapToolbarEnabled: false,
compassEnabled: true, compassEnabled: false,
zoomControlsEnabled: false, zoomControlsEnabled: false,
); );
}); },
);
},
);
} }
} }
@@ -581,3 +605,7 @@ class FloatingActionButtons extends StatelessWidget {
); );
} }
} }
Future<void> checkForAppliedRide(BuildContext context) async {
checkForPendingOrderFromServer();
}

View File

@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import '../../../../controller/functions/location_controller.dart';
import '../../../../controller/home/captin/home_captain_controller.dart';
// هذه ويدجت بديلة للـ _MapView في الكود الخاص بك
// V3 - MapView Replacement using flutter_map
class OsmMapView extends StatelessWidget {
const OsmMapView({super.key});
@override
Widget build(BuildContext context) {
// افترض أنك تحصل على الموقع والاتجاه بنفس الطريقة من الكونترولر
final LocationController locationController =
Get.find<LocationController>();
final HomeCaptainController homeCaptainController =
Get.find<HomeCaptainController>();
// يمكنك استخدام GetBuilder لمراقبة التغييرات في الموقع
return Obx(() {
final LatLng currentLocation = LatLng(
locationController.myLocation.latitude,
locationController.myLocation.longitude);
final double currentHeading = locationController.heading;
return FlutterMap(
// يمكنك ربط هذا بـ MapController الخاص بـ flutter_map
// mapController: homeCaptainController.flutterMapController,
options: MapOptions(
initialCenter: currentLocation,
initialZoom: 15,
maxZoom: 18,
minZoom: 6,
// تدوير الخريطة (اختياري)
initialRotation: currentHeading,
),
children: [
// 1. طبقة الخريطة الأساسية (Tiles)
// هذا هو الرابط لخرائط OSM الأساسية
TileLayer(
urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c'],
userAgentPackageName:
'com.example.app', // استبدل باسم الباكج الخاص بك
// لاستخدام الخرائط الأوفلاين (بعد إعداد flutter_map_tile_caching)
// tileProvider: CachedTileProvider(),
),
// 2. طبقة العلامات (Markers)
MarkerLayer(
markers: [
Marker(
width: 80.0,
height: 80.0,
point: currentLocation,
child: Transform.rotate(
angle: currentHeading *
(3.1415926535 / 180), // تحويل من درجات إلى راديان
child: Image.asset(
'assets/images/car_icon.png', // تأكد أن لديك أيقونة السيارة
// يمكنك استخدام نفس الـ carIcon من الكونترولر
// icon: homeCaptainController.carIcon, (ملاحظة: flutter_map تستخدم ويدجت)
),
),
),
],
),
// يمكنك إضافة طبقات أخرى هنا (مثل الخطوط Polylines أو المضلعات Polygons)
],
);
});
}
}
// ملاحظة: ستحتاج إلى تعديل بسيط في HomeCaptainController
// لإنشاء MapController الخاص بـ flutter_map بدلاً من GoogleMapController
// import 'package:flutter_map/flutter_map.dart';
// MapController flutterMapController = MapController();

View File

@@ -237,8 +237,10 @@ Future<void> checkForPendingOrderFromServer() async {
// MyDialog().getDialog(orderId.toString(), customerToken, () {}); // MyDialog().getDialog(orderId.toString(), customerToken, () {});
// Now proceed with the UI flow // Now proceed with the UI flow
_sendAcceptanceNotification(customerToken, orderId.toString()); // _sendAcceptanceNotification(customerToken, orderId.toString());
// await _bringAppToForegroundAndNavigate(orderId); // await _bringAppToForegroundAndNavigate(orderId);
Get.to(() => PassengerLocationMapPage(),
arguments: box.read(BoxName.rideArgumentsFromBackground));
} else { } else {
box.write(BoxName.rideArgumentsFromBackground, 'failure'); box.write(BoxName.rideArgumentsFromBackground, 'failure');
} }
@@ -307,8 +309,7 @@ Map<String, dynamic> _transformServerDataToAppArguments(
void _sendAcceptanceNotification(String? customerToken, rideId) { void _sendAcceptanceNotification(String? customerToken, rideId) {
try { try {
if (customerToken == null) return; if (customerToken == null) return;
final FirebaseMessagesController _firebaseMessagesController =
Get.put(FirebaseMessagesController());
List<String> bodyToPassenger = [ List<String> bodyToPassenger = [
box.read(BoxName.driverID).toString(), box.read(BoxName.driverID).toString(),
box.read(BoxName.nameDriver).toString(), box.read(BoxName.nameDriver).toString(),
@@ -319,15 +320,13 @@ void _sendAcceptanceNotification(String? customerToken, rideId) {
// Safely check for customer token // Safely check for customer token
final String? token = customerToken; final String? token = customerToken;
if (token != null && token.isNotEmpty) { if (token != null && token.isNotEmpty) {
// _firebaseMessagesController.sendNotificationToDriverMAP('Accepted Ride',
// 'your ride is applied'.tr, token, bodyToPassenger, 'start.wav');
NotificationService.sendNotification( NotificationService.sendNotification(
target: token.toString(), target: token.toString(),
title: 'Accepted Ride', title: 'Accepted Ride'.tr,
body: 'your ride is Accepted'.tr, body: 'your ride is Accepted'.tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'start', tone: 'start',
driverList: [], driverList: bodyToPassenger, category: 'Accepted Ride',
); );
} else {} } else {}
} catch (e) {} } catch (e) {}

View File

@@ -172,7 +172,7 @@ class PassengerInfoWindow extends StatelessWidget {
body: 'I Arrive at your site'.tr, body: 'I Arrive at your site'.tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'ding', tone: 'ding',
driverList: [], driverList: [], category: 'Hi ,I Arrive your site',
); );
controller.startTimerToShowDriverWaitPassengerDuration(); controller.startTimerToShowDriverWaitPassengerDuration();
controller.isArrivedSend = false; controller.isArrivedSend = false;
@@ -261,12 +261,12 @@ class PassengerInfoWindow extends StatelessWidget {
// 'cancel.wav'); // 'cancel.wav');
NotificationService.sendNotification( NotificationService.sendNotification(
target: controller.tokenPassenger.toString(), target: controller.tokenPassenger.toString(),
title: 'Driver Cancelled Your Trip', title: 'Driver Cancelled Your Trip'.tr,
body: body:
'You will need to pay the cost to the driver, or it will be deducted from your next trip', 'You will need to pay the cost to the driver, or it will be deducted from your next trip',
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'cancel', tone: 'cancel',
driverList: [], driverList: [], category: 'Driver Cancelled Your Trip',
); );
box.write(BoxName.rideStatus, 'Cancel'); box.write(BoxName.rideStatus, 'Cancel');
await controller.addWaitingTimeCostFromPassengerToDriverWallet(); await controller.addWaitingTimeCostFromPassengerToDriverWallet();

View File

@@ -352,7 +352,7 @@ class SosConnect extends StatelessWidget {
body: "Where are you, sir?".tr, body: "Where are you, sir?".tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'ding', tone: 'ding',
driverList: [], driverList: [], category: 'message From Driver',
); );
Get.back(); Get.back();
}), }),
@@ -371,7 +371,7 @@ class SosConnect extends StatelessWidget {
body: "I've been trying to reach you but your phone is off.".tr, body: "I've been trying to reach you but your phone is off.".tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'ding', tone: 'ding',
driverList: [], driverList: [], category: 'message From Driver',
); );
Get.back(); Get.back();
}), }),
@@ -403,7 +403,7 @@ class SosConnect extends StatelessWidget {
body: 'change device'.tr, body: 'change device'.tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'cancel', tone: 'cancel',
driverList: [], driverList: [], category: 'message From Driver',
); );
controller.messageToPassenger.clear(); controller.messageToPassenger.clear();
Get.back(); Get.back();

View File

@@ -225,24 +225,15 @@ class _OrderOverlayState extends State<OrderOverlay>
_getData(8).toString(), _getData(8).toString(),
_getData(9).toString(), _getData(9).toString(),
]; ];
final fmc = Get.isRegistered<FirebaseMessagesController>()
? Get.find<FirebaseMessagesController>()
: Get.put(FirebaseMessagesController());
// fmc.sendNotificationToDriverMAP(
// "Accepted Ride",
// 'your ride is Accepted'.tr,
// _getData(9).toString(),
// bodyToPassenger,
// 'start.wav',
// );
NotificationService.sendNotification( NotificationService.sendNotification(
target: _getData(9).toString(), target: _getData(9).toString(),
title: "Accepted Ride", title: "Accepted Ride".tr,
body: 'your ride is Accepted'.tr, body: 'your ride is Accepted'.tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'start', tone: 'start',
driverList: [], driverList: bodyToPassenger,
category: 'Accepted Ride',
); );
final payload = { final payload = {
// بيانات أساسية // بيانات أساسية
@@ -294,15 +285,6 @@ class _OrderOverlayState extends State<OrderOverlay>
'status': 'Apply' 'status': 'Apply'
}); });
if (AppLink.endPoint != AppLink.seferCairoServer) {
CRUD().post(
link: "${AppLink.endPoint}/ride/driver_order/add.php",
payload: {
'driver_id': driverId,
'order_id': orderData!.orderId,
'status': 'Apply'
});
}
_log("Server update successful. Writing to storage."); _log("Server update successful. Writing to storage.");
notificationController.showNotification( notificationController.showNotification(
"Order Accepted".tr, "Order Accepted".tr,

View File

@@ -184,6 +184,32 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
// Card(
// elevation: 4,
// child: Padding(
// padding: const EdgeInsets.all(16.0),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// children: [
// _InfoTile(
// icon: Icons.timer,
// label:
// '${(double.parse(controller.myList[12]) / 60).toStringAsFixed(0)} ${'min'.tr}',
// ),
// _InfoTile(
// icon: Icons.directions_car,
// label:
// '${(double.parse(controller.myList[11]) / 1000).toStringAsFixed(1)} ${'km'.tr}',
// ),
// _InfoTile(
// icon: Icons.monetization_on,
// label: '${controller.myList[2]}',
// ),
// ],
// ),
// ),
// ),
// استبدل هذا الكود
Card( Card(
elevation: 4, elevation: 4,
child: Padding( child: Padding(
@@ -194,16 +220,21 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
_InfoTile( _InfoTile(
icon: Icons.timer, icon: Icons.timer,
label: label:
'${(double.parse(controller.myList[12]) / 60).toStringAsFixed(0)} ${'min'.tr}', // استخدم الفهرس 13 للوقت (Duration)
'${((double.tryParse(controller.myList[13].toString()) ?? 0.0) / 60).toStringAsFixed(0)} ${'min'.tr}',
), ),
_InfoTile( _InfoTile(
icon: Icons.directions_car, icon: Icons.directions_car,
label: label:
'${(double.parse(controller.myList[11]) / 1000).toStringAsFixed(1)} ${'km'.tr}', // استخدم الفهرس 14 للمسافة (Distance)
// استخدم tryParse للأمان لأن القيمة "" (نص فارغ)
'${((double.tryParse(controller.myList[14].toString()) ?? 0.0) / 1000).toStringAsFixed(1)} ${'km'.tr}',
), ),
_InfoTile( _InfoTile(
icon: Icons.monetization_on, icon: Icons.monetization_on,
label: '${controller.myList[2]}', label:
// السعر أصبح في الفهرس 4
'${controller.myList[4]}',
), ),
], ],
), ),
@@ -230,7 +261,6 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
'status': 'Apply', 'status': 'Apply',
'driver_id': box.read(BoxName.driverID), 'driver_id': box.read(BoxName.driverID),
}); });
if (AppLink.endPoint != AppLink.seferCairoServer) {
CRUD().post( CRUD().post(
link: link:
"${AppLink.endPoint}/ride/rides/updateStausFromSpeed.php", "${AppLink.endPoint}/ride/rides/updateStausFromSpeed.php",
@@ -240,7 +270,6 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
'status': 'Apply', 'status': 'Apply',
'driver_id': box.read(BoxName.driverID), 'driver_id': box.read(BoxName.driverID),
}); });
}
if (res == 'failure') { if (res == 'failure') {
MyDialog().getDialog( MyDialog().getDialog(
"This ride is already applied by another driver." "This ride is already applied by another driver."
@@ -258,43 +287,21 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
(controller.myList[16].toString()), (controller.myList[16].toString()),
'status': 'Apply' 'status': 'Apply'
}); });
if (AppLink.endPoint !=
AppLink.seferCairoServer) {
CRUD().postFromDialogue(
link:
'${AppLink.endPoint}/rides/driver_order/add.php',
payload: {
'driver_id':
(controller.myList[6].toString()),
'order_id':
(controller.myList[16].toString()),
'status': 'Apply'
});
}
List<String> bodyToPassenger = [ List<String> bodyToPassenger = [
controller.myList[6].toString(), controller.myList[6].toString(),
controller.myList[8].toString(), controller.myList[8].toString(),
controller.myList[9].toString(), controller.myList[9].toString(),
]; ];
final fmc =
Get.isRegistered<FirebaseMessagesController>()
? Get.find<FirebaseMessagesController>()
: Get.put(FirebaseMessagesController());
// fmc.sendNotificationToDriverMAP(
// "Accepted Ride",
// 'your ride is Accepted'.tr,
// controller.myList[9].toString(),
// bodyToPassenger,
// 'start',
// );
NotificationService.sendNotification( NotificationService.sendNotification(
target: controller.myList[9].toString(), target: controller.myList[9].toString(),
title: "Accepted Ride", title: "Accepted Ride".tr,
body: 'your ride is Accepted'.tr, body: 'your ride is Accepted'.tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'start', tone: 'start',
driverList: [], driverList: bodyToPassenger,
category: 'Accepted Ride',
); );
Get.back(); Get.back();
box.write(BoxName.rideArguments, { box.write(BoxName.rideArguments, {
@@ -398,7 +405,7 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
'${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}', '${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'start', tone: 'start',
driverList: [], driverList: [], category: 'Order Under Review',
); );
controller.refuseOrder( controller.refuseOrder(

View File

@@ -368,7 +368,7 @@ class OrderSpeedRequest extends StatelessWidget {
AppLink.seferCairoServer) { AppLink.seferCairoServer) {
CRUD().post( CRUD().post(
link: link:
"${AppLink.endPoint}/ride/rides/updateStausFromSpeed.php", "${AppLink.rideServer}/rides/updateStausFromSpeed.php",
payload: { payload: {
'id': rideId, 'id': rideId,
'rideTimeStart': 'rideTimeStart':
@@ -401,12 +401,12 @@ class OrderSpeedRequest extends StatelessWidget {
?[9] ?[9]
?.toString() ?? ?.toString() ??
_getData(9), _getData(9),
title: 'Accepted Ride', title: 'Accepted Ride'.tr,
body: 'your ride is applied'.tr, body: 'your ride is applied'.tr,
isTopic: isTopic:
false, // Important: this is a token false, // Important: this is a token
tone: 'start', tone: 'start',
driverList: [], driverList: [], category: 'Accepted Ride',
); );
// Using rideId (_getData(16)) for order_id consistently // Using rideId (_getData(16)) for order_id consistently
@@ -423,7 +423,7 @@ class OrderSpeedRequest extends StatelessWidget {
AppLink.seferCairoServer) { AppLink.seferCairoServer) {
CRUD().post( CRUD().post(
link: link:
"${AppLink.endPoint}/ride/driver_order/add.php", "${AppLink.rideServer}/driver_order/add.php",
payload: { payload: {
'driver_id': _getData(6), 'driver_id': _getData(6),
'order_id': rideId, 'order_id': rideId,

View File

@@ -139,16 +139,16 @@ class ActionsGrid extends StatelessWidget {
mainAxisSpacing: 16, mainAxisSpacing: 16,
childAspectRatio: 2.5, // للتحكم في ارتفاع الأزرار childAspectRatio: 2.5, // للتحكم في ارتفاع الأزرار
children: [ children: [
_ActionTile( // _ActionTile(
title: 'My Cars'.tr, // title: 'My Cars'.tr,
icon: Icons.directions_car_filled, // icon: Icons.directions_car_filled,
onTap: () => Get.to(() => CaptainsCars()), // onTap: () => Get.to(() => CaptainsCars()),
), // ),
_ActionTile( // _ActionTile(
title: 'Criminal Record'.tr, // title: 'Criminal Record'.tr,
icon: Icons.description, // icon: Icons.description,
onTap: () => Get.to(() => CriminalDocumemtPage()), // onTap: () => Get.to(() => CriminalDocumemtPage()),
), // ),
_ActionTile( _ActionTile(
title: 'Bank Account'.tr, title: 'Bank Account'.tr,
icon: Icons.account_balance, icon: Icons.account_balance,

View File

@@ -347,11 +347,11 @@ class RideAvailableCard extends StatelessWidget {
// 'start.wav'); // 'start.wav');
NotificationService.sendNotification( NotificationService.sendNotification(
target: rideInfo['passengerToken'].toString(), target: rideInfo['passengerToken'].toString(),
title: 'Accepted Ride', title: 'Accepted Ride'.tr,
body: 'your ride is Accepted'.tr, body: 'your ride is Accepted'.tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'start', tone: 'start',
driverList: [], driverList: [], category: 'Accepted Ride',
); );
Get.back(); Get.back();
Get.to(() => PassengerLocationMapPage(), arguments: { Get.to(() => PassengerLocationMapPage(), arguments: {

View File

@@ -8,6 +8,7 @@
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h> #include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
#include <record_linux/record_linux_plugin.h> #include <record_linux/record_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
@@ -18,6 +19,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
g_autoptr(FlPluginRegistrar) objectbox_flutter_libs_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ObjectboxFlutterLibsPlugin");
objectbox_flutter_libs_plugin_register_with_registrar(objectbox_flutter_libs_registrar);
g_autoptr(FlPluginRegistrar) record_linux_registrar = g_autoptr(FlPluginRegistrar) record_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin");
record_linux_plugin_register_with_registrar(record_linux_registrar); record_linux_plugin_register_with_registrar(record_linux_registrar);

View File

@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux file_selector_linux
flutter_secure_storage_linux flutter_secure_storage_linux
objectbox_flutter_libs
record_linux record_linux
url_launcher_linux url_launcher_linux
) )

View File

@@ -24,6 +24,7 @@ import google_sign_in_ios
import just_audio import just_audio
import local_auth_darwin import local_auth_darwin
import location import location
import objectbox_flutter_libs
import package_info_plus import package_info_plus
import path_provider_foundation import path_provider_foundation
import record_darwin import record_darwin
@@ -55,6 +56,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
LocationPlugin.register(with: registry.registrar(forPlugin: "LocationPlugin")) LocationPlugin.register(with: registry.registrar(forPlugin: "LocationPlugin"))
ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin")) RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin"))

View File

@@ -352,6 +352,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.8" version: "1.0.8"
dart_earcut:
dependency: transitive
description:
name: dart_earcut
sha256: e485001bfc05dcbc437d7bfb666316182e3522d4c3f9668048e004d0eb2ce43b
url: "https://pub.dev"
source: hosted
version: "1.2.0"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:
@@ -576,6 +584,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.70.2" version: "0.70.2"
flat_buffers:
dependency: transitive
description:
name: flat_buffers
sha256: "380bdcba5664a718bfd4ea20a45d39e13684f5318fcd8883066a55e21f37f4c3"
url: "https://pub.dev"
source: hosted
version: "23.5.26"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -773,6 +789,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.0.0" version: "8.0.0"
flutter_map:
dependency: "direct main"
description:
name: flutter_map
sha256: "87cc8349b8fa5dccda5af50018c7374b6645334a0d680931c1fe11bce88fa5bb"
url: "https://pub.dev"
source: hosted
version: "6.2.1"
flutter_map_tile_caching:
dependency: "direct main"
description:
name: flutter_map_tile_caching
sha256: "24412e067317ba6f03d8568c024b400ac37bfe364719033f8048d7bca86b80d3"
url: "https://pub.dev"
source: hosted
version: "9.0.1"
flutter_overlay_apps: flutter_overlay_apps:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1414,6 +1446,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
latlong2:
dependency: "direct main"
description:
name: latlong2
sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe"
url: "https://pub.dev"
source: hosted
version: "0.9.1"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -1446,6 +1486,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.1" version: "5.1.1"
lists:
dependency: transitive
description:
name: lists
sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
live_activities: live_activities:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1518,6 +1566,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.1" version: "6.0.1"
logger:
dependency: transitive
description:
name: logger
sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3
url: "https://pub.dev"
source: hosted
version: "2.6.2"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@@ -1558,6 +1614,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.16.0" version: "1.16.0"
mgrs_dart:
dependency: transitive
description:
name: mgrs_dart
sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7
url: "https://pub.dev"
source: hosted
version: "2.0.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@@ -1582,6 +1646,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.5.0"
objectbox:
dependency: transitive
description:
name: objectbox
sha256: "9fb2810156e8f78d82ecf672c36a1aba2c1de16d7903675335e00e374bdc3ba8"
url: "https://pub.dev"
source: hosted
version: "2.5.1"
objectbox_flutter_libs:
dependency: transitive
description:
name: objectbox_flutter_libs
sha256: dca86b2d1074110573b69cbd9afb6b67ab9d2c824704c6ac5187e546418baf9c
url: "https://pub.dev"
source: hosted
version: "2.5.1"
octo_image: octo_image:
dependency: transitive dependency: transitive
description: description:
@@ -1758,6 +1838,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.9.1" version: "3.9.1"
polylabel:
dependency: transitive
description:
name: polylabel
sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@@ -1774,6 +1862,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.3" version: "6.0.3"
proj4dart:
dependency: transitive
description:
name: proj4dart
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
url: "https://pub.dev"
source: hosted
version: "2.1.0"
provider: provider:
dependency: transitive dependency: transitive
description: description:
@@ -2162,6 +2258,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
unicode:
dependency: transitive
description:
name: unicode
sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
upower: upower:
dependency: transitive dependency: transitive
description: description:
@@ -2434,6 +2538,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
wkt_parser:
dependency: transitive
description:
name: wkt_parser
sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View File

@@ -104,6 +104,13 @@ dependencies:
internet_connection_checker: ^3.0.1 internet_connection_checker: ^3.0.1
connectivity_plus: ^6.1.5 connectivity_plus: ^6.1.5
# pip_view: ^0.9.7 # pip_view: ^0.9.7
flutter_map: ^6.1.0 # (استخدم أحدث إصدار دائماً)
# مكتبة التخزين لتحميل الخرائط أوفلاين
flutter_map_tile_caching: ^9.0.0 # (استخدم أحدث إصدار)
latlong2: ^0.9.1
# مكتبة لتحديد النقاط على الخريطة (مثل latLng)
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -16,6 +16,7 @@
#include <flutter_tts/flutter_tts_plugin.h> #include <flutter_tts/flutter_tts_plugin.h>
#include <geolocator_windows/geolocator_windows.h> #include <geolocator_windows/geolocator_windows.h>
#include <local_auth_windows/local_auth_plugin.h> #include <local_auth_windows/local_auth_plugin.h>
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h> #include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <record_windows/record_windows_plugin_c_api.h> #include <record_windows/record_windows_plugin_c_api.h>
#include <share_plus/share_plus_windows_plugin_c_api.h> #include <share_plus/share_plus_windows_plugin_c_api.h>
@@ -42,6 +43,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("GeolocatorWindows")); registry->GetRegistrarForPlugin("GeolocatorWindows"));
LocalAuthPluginRegisterWithRegistrar( LocalAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalAuthPlugin")); registry->GetRegistrarForPlugin("LocalAuthPlugin"));
ObjectboxFlutterLibsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ObjectboxFlutterLibsPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar( PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
RecordWindowsPluginCApiRegisterWithRegistrar( RecordWindowsPluginCApiRegisterWithRegistrar(

View File

@@ -13,6 +13,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_tts flutter_tts
geolocator_windows geolocator_windows
local_auth_windows local_auth_windows
objectbox_flutter_libs
permission_handler_windows permission_handler_windows
record_windows record_windows
share_plus share_plus