25-10-11/1
This commit is contained in:
1
.env
1
.env
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
107
lib/constant/country_polygons.dart
Normal file
107
lib/constant/country_polygons.dart
Normal 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 =
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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']);
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
);
|
);
|
||||||
// يمكن إضافة أي إجراء آخر هنا عند الاقتراب من الوجهة
|
// يمكن إضافة أي إجراء آخر هنا عند الاقتراب من الوجهة
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
|||||||
@@ -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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
145
lib/controller/home/navigation/route_matcher_worker.dart
Normal file
145
lib/controller/home/navigation/route_matcher_worker.dart
Normal 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;
|
||||||
@@ -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');
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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من فضلك انتظر.",
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
2
lib/env/env.dart
vendored
@@ -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
24628
lib/env/env.g.dart
vendored
File diff suppressed because it is too large
Load Diff
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|||||||
83
lib/views/home/Captin/home_captain/osm_view_map.dart
Normal file
83
lib/views/home/Captin/home_captain/osm_view_map.dart
Normal 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();
|
||||||
@@ -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) {}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
|||||||
112
pubspec.lock
112
pubspec.lock
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user