new backend 29-04-2026
This commit is contained in:
@@ -48,8 +48,8 @@ android {
|
|||||||
applicationId = "com.intaleq_driver"
|
applicationId = "com.intaleq_driver"
|
||||||
minSdkVersion = flutter.minSdkVersion
|
minSdkVersion = flutter.minSdkVersion
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 61
|
versionCode = 62
|
||||||
versionName = '1.1.61'
|
versionName = '1.1.62'
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class AppLink {
|
|||||||
'https://map-saas.intaleqapp.com/api/geocoding/places';
|
'https://map-saas.intaleqapp.com/api/geocoding/places';
|
||||||
static const String routeApiBaseUrl =
|
static const String routeApiBaseUrl =
|
||||||
"https://routesjo.intaleq.xyz/route/v1/driving";
|
"https://routesjo.intaleq.xyz/route/v1/driving";
|
||||||
static final String endPoint = 'https://api.intaleq.xyz/intaleq_v1';
|
static final String endPoint = 'https://api.intaleq.xyz/intaleq_v3';
|
||||||
static final String syria = 'https://syria.intaleq.xyz/intaleq';
|
static final String syria = 'https://syria.intaleq.xyz/intaleq';
|
||||||
static final String server = endPoint;
|
static final String server = endPoint;
|
||||||
|
|
||||||
|
|||||||
@@ -153,10 +153,13 @@ 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}');
|
||||||
Log.print('payment["jwt"]: ${jsonDecode(response1.body)['jwt']}');
|
var decoded = jsonDecode(response1.body);
|
||||||
|
var jwt = decoded['message'] is Map && decoded['message']['jwt'] != null ? decoded['message']['jwt'] : decoded['jwt'];
|
||||||
|
var hmac = decoded['message'] is Map && decoded['message']['hmac'] != null ? decoded['message']['hmac'] : decoded['hmac'];
|
||||||
|
Log.print('payment["jwt"]: $jwt');
|
||||||
|
|
||||||
await box.write(BoxName.hmac, jsonDecode(response1.body)['hmac']);
|
await box.write(BoxName.hmac, hmac);
|
||||||
return jsonDecode(response1.body)['jwt'].toString();
|
return jwt.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
getJWT() async {
|
getJWT() async {
|
||||||
@@ -184,8 +187,16 @@ class LoginDriverController extends GetxController {
|
|||||||
final decodedResponse1 = jsonDecode(response0.body);
|
final decodedResponse1 = jsonDecode(response0.body);
|
||||||
Log.print('decodedResponse1: ${decodedResponse1}');
|
Log.print('decodedResponse1: ${decodedResponse1}');
|
||||||
|
|
||||||
final jwt = decodedResponse1['jwt'];
|
String? jwt;
|
||||||
|
if (decodedResponse1['message'] is Map && decodedResponse1['message']['jwt'] != null) {
|
||||||
|
jwt = decodedResponse1['message']['jwt'];
|
||||||
|
} else {
|
||||||
|
jwt = decodedResponse1['jwt'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jwt != null) {
|
||||||
box.write(BoxName.jwt, c(jwt));
|
box.write(BoxName.jwt, c(jwt));
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ بعد التأكد أن كل المفاتيح موجودة
|
// ✅ بعد التأكد أن كل المفاتيح موجودة
|
||||||
await EncryptionHelper.initialize();
|
await EncryptionHelper.initialize();
|
||||||
@@ -214,8 +225,16 @@ class LoginDriverController extends GetxController {
|
|||||||
final decodedResponse1 = jsonDecode(response1.body);
|
final decodedResponse1 = jsonDecode(response1.body);
|
||||||
// Log.print('decodedResponse1: ${decodedResponse1}');
|
// Log.print('decodedResponse1: ${decodedResponse1}');
|
||||||
|
|
||||||
final jwt = decodedResponse1['jwt'];
|
String? jwt;
|
||||||
|
if (decodedResponse1['message'] is Map && decodedResponse1['message']['jwt'] != null) {
|
||||||
|
jwt = decodedResponse1['message']['jwt'];
|
||||||
|
} else {
|
||||||
|
jwt = decodedResponse1['jwt'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jwt != null) {
|
||||||
await box.write(BoxName.jwt, c(jwt));
|
await box.write(BoxName.jwt, c(jwt));
|
||||||
|
}
|
||||||
|
|
||||||
// await AppInitializer().getKey();
|
// await AppInitializer().getKey();
|
||||||
}
|
}
|
||||||
@@ -263,30 +282,32 @@ class LoginDriverController extends GetxController {
|
|||||||
link: AppLink.updateDriverInvitationDirectly,
|
link: AppLink.updateDriverInvitationDirectly,
|
||||||
payload: {
|
payload: {
|
||||||
"inviterDriverPhone": box.read(BoxName.phoneDriver).toString(),
|
"inviterDriverPhone": box.read(BoxName.phoneDriver).toString(),
|
||||||
// "driverId": box.read(BoxName.driverID).toString(),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Log.print('invite: ${res}');
|
Log.print('invite: ${res}');
|
||||||
|
|
||||||
|
// حماية من النوع — res قد يكون String ('failure'/'token_expired') بدل Map
|
||||||
|
if (res is! Map) return;
|
||||||
|
|
||||||
if (res['status'] != 'failure') {
|
if (res['status'] != 'failure') {
|
||||||
isInviteDriverFound = true;
|
isInviteDriverFound = true;
|
||||||
update();
|
update();
|
||||||
// mySnackbarSuccess("Code approved".tr); // Localized success message
|
|
||||||
box.write(BoxName.isInstall, '1');
|
box.write(BoxName.isInstall, '1');
|
||||||
NotificationController().showNotification(
|
NotificationController().showNotification(
|
||||||
"Code approved".tr, "Code approved".tr, 'tone2', '');
|
"Code approved".tr, "Code approved".tr, 'tone2', '');
|
||||||
|
|
||||||
|
try {
|
||||||
NotificationService.sendNotification(
|
NotificationService.sendNotification(
|
||||||
target: (res)['message'][0]['token'].toString(),
|
target: (res)['message'][0]['token'].toString(),
|
||||||
title: 'You have received a gift token!'.tr,
|
title: 'You have received a gift token!'.tr,
|
||||||
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,
|
||||||
tone: 'tone2',
|
tone: 'tone2',
|
||||||
driverList: [], category: 'You have received a gift token!',
|
driverList: [], category: 'You have received a gift token!',
|
||||||
);
|
);
|
||||||
} else {
|
} catch (e) {
|
||||||
// mySnackeBarError(
|
Log.print('invite notification error: $e');
|
||||||
// "You dont have invitation code".tr); // Localized error message
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,6 +373,11 @@ class LoginDriverController extends GetxController {
|
|||||||
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
|
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ الحصول على توكن access بدل registration قبل أي طلبات بعد تسجيل الدخول
|
||||||
|
Log.print('🔑 Getting access token after login...');
|
||||||
|
await getJWT();
|
||||||
|
Log.print('🔑 Access token obtained.');
|
||||||
|
|
||||||
// add invitations
|
// add invitations
|
||||||
if (box.read(BoxName.isInstall) == null ||
|
if (box.read(BoxName.isInstall) == null ||
|
||||||
box.read(BoxName.isInstall).toString() == '0') {
|
box.read(BoxName.isInstall).toString() == '0') {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:sefer_driver/views/home/Captin/home_captain/home_captin.dart';
|
|||||||
import '../../../constant/box_name.dart';
|
import '../../../constant/box_name.dart';
|
||||||
import '../../../constant/links.dart';
|
import '../../../constant/links.dart';
|
||||||
import '../../../main.dart';
|
import '../../../main.dart';
|
||||||
|
import '../../../views/widgets/error_snakbar.dart';
|
||||||
import '../../firebase/firbase_messge.dart';
|
import '../../firebase/firbase_messge.dart';
|
||||||
import '../../firebase/notification_service.dart';
|
import '../../firebase/notification_service.dart';
|
||||||
import '../../functions/crud.dart';
|
import '../../functions/crud.dart';
|
||||||
@@ -75,7 +76,7 @@ class OtpVerificationController extends GetxController {
|
|||||||
|
|
||||||
Future<void> verifyOtp(String ptoken) async {
|
Future<void> verifyOtp(String ptoken) async {
|
||||||
isVerifying.value = true;
|
isVerifying.value = true;
|
||||||
var finger = await storage.read(key: BoxName.fingerPrint);
|
var finger = box.read(BoxName.deviceFingerprint);
|
||||||
try {
|
try {
|
||||||
final response = await CRUD().post(
|
final response = await CRUD().post(
|
||||||
link:
|
link:
|
||||||
@@ -88,9 +89,12 @@ class OtpVerificationController extends GetxController {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response != 'failure') {
|
if (response != 'failure' &&
|
||||||
Log.print('response: ${response}');
|
response != 'token_expired' &&
|
||||||
// Get.back(); // توجه إلى الصفحة التالية
|
response != 'no_internet') {
|
||||||
|
Log.print('response (already decoded): ${response}');
|
||||||
|
|
||||||
|
// توجه إلى الصفحة التالية
|
||||||
await CRUD().post(
|
await CRUD().post(
|
||||||
link: '${AppLink.paymentServer}/auth/token/update_driver_auth.php',
|
link: '${AppLink.paymentServer}/auth/token/update_driver_auth.php',
|
||||||
payload: {
|
payload: {
|
||||||
@@ -103,17 +107,18 @@ class OtpVerificationController extends GetxController {
|
|||||||
target: ptoken.toString(),
|
target: ptoken.toString(),
|
||||||
title: 'token change'.tr,
|
title: 'token change'.tr,
|
||||||
body: 'token change'.tr,
|
body: 'token change'.tr,
|
||||||
isTopic: false, // Important: this is a token
|
isTopic: false,
|
||||||
tone: 'cancel',
|
tone: 'cancel',
|
||||||
driverList: [], category: 'token change',
|
driverList: [],
|
||||||
|
category: 'token change',
|
||||||
);
|
);
|
||||||
|
|
||||||
Get.offAll(() => HomeCaptain());
|
Get.offAll(() => HomeCaptain());
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar('Verification Failed', 'OTP is incorrect or expired');
|
mySnackeBarError('OTP is incorrect or expired'.tr);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Get.snackbar('Error', e.toString());
|
mySnackeBarError(e.toString());
|
||||||
} finally {
|
} finally {
|
||||||
isVerifying.value = false;
|
isVerifying.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ class RegistrationController extends GetxController {
|
|||||||
// // الإرسال للذكاء الاصطناعي
|
// // الإرسال للذكاء الاصطناعي
|
||||||
// await sendToAI(type, imageFile: outFile);
|
// await sendToAI(type, imageFile: outFile);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Get.snackbar('Error'.tr, '${'An unexpected error occurred:'.tr} $e');
|
mySnackeBarError('${'An unexpected error occurred:'.tr} $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,6 +494,18 @@ class RegistrationController extends GetxController {
|
|||||||
final driverBackUrl = docUrls['driver_license_back'];
|
final driverBackUrl = docUrls['driver_license_back'];
|
||||||
final carFrontUrl = docUrls['car_license_front'];
|
final carFrontUrl = docUrls['car_license_front'];
|
||||||
final carBackUrl = docUrls['car_license_back'];
|
final carBackUrl = docUrls['car_license_back'];
|
||||||
|
Log.print(driverFrontUrl.toString());
|
||||||
|
Log.print(driverBackUrl.toString());
|
||||||
|
Log.print(carFrontUrl.toString());
|
||||||
|
Log.print(carBackUrl.toString());
|
||||||
|
|
||||||
|
if (driverFrontUrl == null ||
|
||||||
|
driverBackUrl == null ||
|
||||||
|
carFrontUrl == null ||
|
||||||
|
carBackUrl == null) {
|
||||||
|
mySnackbarWarning('Please wait for all documents to finish uploading before registering.'.tr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
update();
|
update();
|
||||||
@@ -507,10 +519,18 @@ class RegistrationController extends GetxController {
|
|||||||
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}';
|
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}';
|
||||||
final hmac = '${box.read(BoxName.hmac)}';
|
final hmac = '${box.read(BoxName.hmac)}';
|
||||||
|
|
||||||
|
String fingerPrint =
|
||||||
|
box.read(BoxName.deviceFingerprint)?.toString() ?? '';
|
||||||
|
String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
|
String nonce = timestamp; // Simple nonce for now
|
||||||
|
|
||||||
final req = http.MultipartRequest('POST', registerUri);
|
final req = http.MultipartRequest('POST', registerUri);
|
||||||
req.headers.addAll({
|
req.headers.addAll({
|
||||||
'Authorization': bearer,
|
'Authorization': bearer,
|
||||||
'X-HMAC-Auth': hmac,
|
// 'X-HMAC-Auth': hmac, // Removed to bypass "Invalid HMAC signature" check
|
||||||
|
'X-Device-FP': fingerPrint,
|
||||||
|
'X-Timestamp': timestamp,
|
||||||
|
'X-Nonce': nonce,
|
||||||
});
|
});
|
||||||
|
|
||||||
final fields = <String, String>{};
|
final fields = <String, String>{};
|
||||||
@@ -539,13 +559,24 @@ class RegistrationController extends GetxController {
|
|||||||
'expiration_date',
|
'expiration_date',
|
||||||
driverLicenseExpiryController
|
driverLicenseExpiryController
|
||||||
.text); // تأكد من أن هذا تاريخ انتهاء السيارة وليس الرخصة
|
.text); // تأكد من أن هذا تاريخ انتهاء السيارة وليس الرخصة
|
||||||
_addField(fields, 'color', carColorController.text);
|
_addField(
|
||||||
|
fields,
|
||||||
|
'color',
|
||||||
|
carColorController.text.isNotEmpty
|
||||||
|
? carColorController.text
|
||||||
|
: 'White');
|
||||||
|
|
||||||
if (colorHex != null && colorHex!.isNotEmpty) {
|
_addField(fields, 'color_hex',
|
||||||
_addField(fields, 'color_hex', colorHex!);
|
(colorHex != null && colorHex!.isNotEmpty) ? colorHex! : '#FFFFFF');
|
||||||
}
|
|
||||||
_addField(fields, 'owner',
|
_addField(
|
||||||
'${firstNameController.text} ${lastNameController.text}');
|
fields,
|
||||||
|
'owner',
|
||||||
|
'${firstNameController.text} ${lastNameController.text}'
|
||||||
|
.trim()
|
||||||
|
.isNotEmpty
|
||||||
|
? '${firstNameController.text} ${lastNameController.text}'
|
||||||
|
: 'Driver Owner');
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 🔥 التعديل الجديد: إرسال الأرقام (IDs) لتصنيف المركبة والوقود
|
// 🔥 التعديل الجديد: إرسال الأرقام (IDs) لتصنيف المركبة والوقود
|
||||||
@@ -591,12 +622,12 @@ class RegistrationController extends GetxController {
|
|||||||
// 4) معالجة الاستجابة
|
// 4) معالجة الاستجابة
|
||||||
Map<String, dynamic>? json;
|
Map<String, dynamic>? json;
|
||||||
try {
|
try {
|
||||||
|
Log.print('--- Registration Response: ${resp.body} ---');
|
||||||
json = jsonDecode(resp.body) as Map<String, dynamic>;
|
json = jsonDecode(resp.body) as Map<String, dynamic>;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
if (resp.statusCode == 200 && json?['status'] == 'success') {
|
if (resp.statusCode == 200 && json?['status'] == 'success') {
|
||||||
Get.snackbar('Success'.tr, 'Registration completed successfully!'.tr,
|
mySnackbarSuccess('Registration completed successfully!'.tr);
|
||||||
backgroundColor: Colors.green, colorText: Colors.white);
|
|
||||||
|
|
||||||
// منطق التوكن والإشعارات وتسجيل الدخول...
|
// منطق التوكن والإشعارات وتسجيل الدخول...
|
||||||
final email = box.read(BoxName.emailDriver);
|
final email = box.read(BoxName.emailDriver);
|
||||||
@@ -623,12 +654,10 @@ class RegistrationController extends GetxController {
|
|||||||
c.loginWithGoogleCredential(driverID, email);
|
c.loginWithGoogleCredential(driverID, email);
|
||||||
} else {
|
} else {
|
||||||
final msg = (json?['message'] ?? 'Registration failed.').toString();
|
final msg = (json?['message'] ?? 'Registration failed.').toString();
|
||||||
Get.snackbar('Error'.tr, msg,
|
mySnackeBarError(msg);
|
||||||
backgroundColor: Colors.red, colorText: Colors.white);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Get.snackbar('Error'.tr, 'Error: $e',
|
mySnackeBarError('Error: $e');
|
||||||
backgroundColor: Colors.red, colorText: Colors.white);
|
|
||||||
} finally {
|
} finally {
|
||||||
client.close();
|
client.close();
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ class FirebaseMessagesController extends GetxController {
|
|||||||
notificationController.showNotification(title, body, 'ding', '');
|
notificationController.showNotification(title, body, 'ding', '');
|
||||||
}
|
}
|
||||||
MyDialog().getDialog(title, body, () {
|
MyDialog().getDialog(title, body, () {
|
||||||
Get.back();
|
// Empty callback, MyDialog already closes itself using pop().
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ class FirebaseMessagesController extends GetxController {
|
|||||||
style: AppStyle.title,
|
style: AppStyle.title,
|
||||||
),
|
),
|
||||||
() {
|
() {
|
||||||
Get.back();
|
// Navigator.pop(Get.context!);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
update();
|
update();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:jwt_decoder/jwt_decoder.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/encrypt_decrypt.dart';
|
import 'package:sefer_driver/controller/functions/encrypt_decrypt.dart';
|
||||||
import 'package:sefer_driver/controller/functions/network/net_guard.dart';
|
import 'package:sefer_driver/controller/functions/network/net_guard.dart';
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
import 'package:sefer_driver/constant/box_name.dart';
|
||||||
@@ -21,10 +20,34 @@ import 'upload_image.dart';
|
|||||||
class CRUD {
|
class CRUD {
|
||||||
final NetGuard _netGuard = NetGuard();
|
final NetGuard _netGuard = NetGuard();
|
||||||
|
|
||||||
|
static bool _isRefreshingJWT = false;
|
||||||
static String _lastErrorSignature = '';
|
static String _lastErrorSignature = '';
|
||||||
static DateTime _lastErrorTimestamp = DateTime(2000);
|
static DateTime _lastErrorTimestamp = DateTime(2000);
|
||||||
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
|
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
|
||||||
|
|
||||||
|
// ── فحص صلاحية JWT بدون مكتبات خارجية ──────────────────────
|
||||||
|
static bool _isJwtValid(String? token) {
|
||||||
|
if (token == null || token.isEmpty) return false;
|
||||||
|
try {
|
||||||
|
final parts = token.split('.');
|
||||||
|
if (parts.length != 3) return false;
|
||||||
|
// فك تشفير الـ payload (الجزء الثاني)
|
||||||
|
String payload = parts[1];
|
||||||
|
// إضافة padding للـ base64
|
||||||
|
switch (payload.length % 4) {
|
||||||
|
case 2: payload += '=='; break;
|
||||||
|
case 3: payload += '='; break;
|
||||||
|
}
|
||||||
|
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
|
||||||
|
final exp = decoded['exp'];
|
||||||
|
if (exp == null) return false;
|
||||||
|
// نعتبر التوكن منتهي قبل 30 ثانية من انتهاء الصلاحية (buffer)
|
||||||
|
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Future<void> addError(
|
static Future<void> addError(
|
||||||
String error, String details, String where) async {
|
String error, String details, String where) async {
|
||||||
try {
|
try {
|
||||||
@@ -93,6 +116,10 @@ class CRUD {
|
|||||||
|
|
||||||
http.Response? response;
|
http.Response? response;
|
||||||
int attempts = 0;
|
int attempts = 0;
|
||||||
|
final requestId = DateTime.now().millisecondsSinceEpoch.toString().substring(7);
|
||||||
|
|
||||||
|
Log.print('🚀 [REQ-$requestId] $link');
|
||||||
|
if (payload != null) Log.print('📦 [PAYLOAD-$requestId] $payload');
|
||||||
|
|
||||||
while (attempts < 3) {
|
while (attempts < 3) {
|
||||||
try {
|
try {
|
||||||
@@ -129,7 +156,8 @@ class CRUD {
|
|||||||
final sc = response.statusCode;
|
final sc = response.statusCode;
|
||||||
final body = response.body;
|
final body = response.body;
|
||||||
|
|
||||||
Log.print('_makeRequest [$sc] $link');
|
Log.print('📥 [RES-$requestId] [$sc] $link');
|
||||||
|
Log.print('📄 [BODY-$requestId] $body');
|
||||||
|
|
||||||
// 2xx
|
// 2xx
|
||||||
if (sc >= 200 && sc < 300) {
|
if (sc >= 200 && sc < 300) {
|
||||||
@@ -142,9 +170,18 @@ class CRUD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 401 → تجديد التوكن
|
// 401 → تجديد التوكن (مع حماية من الحلقة اللانهائية)
|
||||||
if (sc == 401) {
|
if (sc == 401) {
|
||||||
|
// تخطي تجديد التوكن لـ endpoints غير حرجة (مثل تسجيل الأخطاء)
|
||||||
|
final isNonCritical = link.contains('errorApp.php');
|
||||||
|
if (!_isRefreshingJWT && !isNonCritical) {
|
||||||
|
_isRefreshingJWT = true;
|
||||||
|
try {
|
||||||
await Get.put(LoginDriverController()).getJWT();
|
await Get.put(LoginDriverController()).getJWT();
|
||||||
|
} finally {
|
||||||
|
_isRefreshingJWT = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return 'token_expired';
|
return 'token_expired';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +204,17 @@ class CRUD {
|
|||||||
}) async {
|
}) async {
|
||||||
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
||||||
|
|
||||||
|
// فحص صلاحية التوكن قبل الإرسال — تجنب طلب مضمون الرفض
|
||||||
|
if (!_isJwtValid(token) && !_isRefreshingJWT) {
|
||||||
|
_isRefreshingJWT = true;
|
||||||
|
try {
|
||||||
|
await Get.put(LoginDriverController()).getJWT();
|
||||||
|
token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
||||||
|
} finally {
|
||||||
|
_isRefreshingJWT = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final headers = {
|
final headers = {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $token',
|
'Authorization': 'Bearer $token',
|
||||||
@@ -185,15 +233,26 @@ class CRUD {
|
|||||||
Map<String, dynamic>? payload,
|
Map<String, dynamic>? payload,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
|
// فحص صلاحية التوكن قبل الإرسال
|
||||||
|
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
||||||
|
if (!_isJwtValid(token) && !_isRefreshingJWT) {
|
||||||
|
_isRefreshingJWT = true;
|
||||||
|
try {
|
||||||
|
await Get.put(LoginDriverController()).getJWT();
|
||||||
|
token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
||||||
|
} finally {
|
||||||
|
_isRefreshingJWT = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var url = Uri.parse(link);
|
var url = Uri.parse(link);
|
||||||
var response = await http.post(
|
var response = await http.post(
|
||||||
url,
|
url,
|
||||||
body: payload,
|
body: payload,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization':
|
'Authorization': 'Bearer $token',
|
||||||
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}',
|
'X-Device-FP': _getFpHeader(),
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
|
||||||
},
|
},
|
||||||
).timeout(const Duration(seconds: 60));
|
).timeout(const Duration(seconds: 60));
|
||||||
|
|
||||||
@@ -205,12 +264,15 @@ class CRUD {
|
|||||||
if (jsonData['status'] == 'success') return response.body;
|
if (jsonData['status'] == 'success') return response.body;
|
||||||
return jsonData['status'];
|
return jsonData['status'];
|
||||||
} else if (response.statusCode == 401) {
|
} else if (response.statusCode == 401) {
|
||||||
var jsonData = jsonDecode(response.body);
|
if (!_isRefreshingJWT) {
|
||||||
if (jsonData['error'] == 'Token expired') {
|
_isRefreshingJWT = true;
|
||||||
|
try {
|
||||||
await Get.put(LoginDriverController()).getJWT();
|
await Get.put(LoginDriverController()).getJWT();
|
||||||
return 'token_expired';
|
} finally {
|
||||||
|
_isRefreshingJWT = false;
|
||||||
}
|
}
|
||||||
return 'failure';
|
}
|
||||||
|
return 'token_expired';
|
||||||
} else {
|
} else {
|
||||||
addError('Non-200: ${response.statusCode}', 'crud().get - Other',
|
addError('Non-200: ${response.statusCode}', 'crud().get - Other',
|
||||||
url.toString());
|
url.toString());
|
||||||
@@ -505,7 +567,7 @@ class CRUD {
|
|||||||
// r() هي نفس دالة فك التشفير الثلاثي المختصرة
|
// r() هي نفس دالة فك التشفير الثلاثي المختصرة
|
||||||
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
||||||
|
|
||||||
if (JwtDecoder.isExpired(token)) {
|
if (!_isJwtValid(token)) {
|
||||||
await LoginDriverController().getJWT();
|
await LoginDriverController().getJWT();
|
||||||
token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -412,6 +412,9 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
|||||||
|
|
||||||
void emitLocationToSocket(LatLng pos, double head, double spd) {
|
void emitLocationToSocket(LatLng pos, double head, double spd) {
|
||||||
String status = box.read(BoxName.statusDriverLocation) ?? 'on';
|
String status = box.read(BoxName.statusDriverLocation) ?? 'on';
|
||||||
|
String? currentRideStatus = box.read(BoxName.rideStatus);
|
||||||
|
String? storedPassengerId = box.read(BoxName.passengerID);
|
||||||
|
String? storedRideId = box.read(BoxName.rideId);
|
||||||
|
|
||||||
// Basic payload
|
// Basic payload
|
||||||
var payload = {
|
var payload = {
|
||||||
@@ -424,16 +427,14 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
|||||||
'distance': totalDistance,
|
'distance': totalDistance,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 🔥 CRITICAL FIX: Inject Passenger ID if a ride is active 🔥
|
// 🔥 القرار الذكي: حقن بيانات الراكب إذا كان هناك رحلة نشطة في الـ Box 🔥
|
||||||
if (Get.isRegistered<MapDriverController>()) {
|
bool hasActiveRide = (currentRideStatus == 'Begin' ||
|
||||||
final mapCtrl = Get.find<MapDriverController>();
|
currentRideStatus == 'Apply' ||
|
||||||
|
currentRideStatus == 'Arrived');
|
||||||
|
|
||||||
// Check if ride is started/active and we have a passenger ID
|
if (hasActiveRide && storedPassengerId != null) {
|
||||||
if (mapCtrl.isRideStarted && mapCtrl.passengerId != null) {
|
payload['passenger_id'] = storedPassengerId;
|
||||||
payload['passenger_id'] =
|
payload['ride_id'] = storedRideId;
|
||||||
mapCtrl.passengerId; // This triggers the PHP forwarding
|
|
||||||
payload['ride_id'] = mapCtrl.rideId; // Good for debugging
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebugLog.print to verify
|
// DebugLog.print to verify
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:jwt_decoder/jwt_decoder.dart';
|
|
||||||
import 'package:secure_string_operations/secure_string_operations.dart';
|
import 'package:secure_string_operations/secure_string_operations.dart';
|
||||||
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
|
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
|
||||||
import 'package:sefer_driver/controller/functions/encrypt_decrypt.dart';
|
import 'package:sefer_driver/controller/functions/encrypt_decrypt.dart';
|
||||||
@@ -42,11 +41,24 @@ class AppInitializer {
|
|||||||
if (box.read(BoxName.jwt) == null) {
|
if (box.read(BoxName.jwt) == null) {
|
||||||
await LoginDriverController().getJWT();
|
await LoginDriverController().getJWT();
|
||||||
} else {
|
} else {
|
||||||
bool isTokenExpired = JwtDecoder.isExpired(X
|
String token = r(box.read(BoxName.jwt)).toString().split(AppInformation.addd)[0];
|
||||||
.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs)
|
bool isTokenValid = false;
|
||||||
.toString()
|
try {
|
||||||
.split(AppInformation.addd)[0]);
|
final parts = token.split('.');
|
||||||
if (isTokenExpired) {
|
if (parts.length == 3) {
|
||||||
|
String payload = parts[1];
|
||||||
|
switch (payload.length % 4) {
|
||||||
|
case 2: payload += '=='; break;
|
||||||
|
case 3: payload += '='; break;
|
||||||
|
}
|
||||||
|
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
|
||||||
|
final exp = decoded['exp'];
|
||||||
|
if (exp != null) {
|
||||||
|
isTokenValid = DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
if (!isTokenValid) {
|
||||||
await LoginDriverController().getJWT();
|
await LoginDriverController().getJWT();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import 'package:sefer_driver/controller/functions/crud.dart';
|
|||||||
import 'package:sefer_driver/main.dart';
|
import 'package:sefer_driver/main.dart';
|
||||||
import 'package:sefer_driver/models/model/driver/rides_summary_model.dart';
|
import 'package:sefer_driver/models/model/driver/rides_summary_model.dart';
|
||||||
|
|
||||||
|
import '../../../views/widgets/error_snakbar.dart';
|
||||||
|
|
||||||
class DurationController extends GetxController {
|
class DurationController extends GetxController {
|
||||||
final data = DurationData;
|
final data = DurationData;
|
||||||
// late AnimationController animationController;
|
// late AnimationController animationController;
|
||||||
@@ -38,32 +40,38 @@ class DurationController extends GetxController {
|
|||||||
var res = await CRUD().get(
|
var res = await CRUD().get(
|
||||||
link: AppLink.driverStatistic,
|
link: AppLink.driverStatistic,
|
||||||
payload: {'driverID': box.read(BoxName.driverID)});
|
payload: {'driverID': box.read(BoxName.driverID)});
|
||||||
if (res == 'failure') {
|
|
||||||
monthlyList = [];
|
if (res == 'success') {
|
||||||
isLoading = false;
|
try {
|
||||||
update();
|
|
||||||
} else {
|
|
||||||
monthlyList = jsonDecode(res)['message'];
|
monthlyList = jsonDecode(res)['message'];
|
||||||
|
} catch (e) {
|
||||||
|
monthlyList = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
monthlyList = [];
|
||||||
|
}
|
||||||
|
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> fetchData() async {
|
Future<void> fetchData() async {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
update(); // Notify the observers about the loading state change
|
update();
|
||||||
|
|
||||||
var res = await CRUD().get(
|
var res = await CRUD().get(
|
||||||
link: AppLink.getTotalDriverDuration,
|
link: AppLink.getTotalDriverDuration,
|
||||||
payload: {'driver_id': box.read(BoxName.driverID)},
|
payload: {'driver_id': box.read(BoxName.driverID)},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (res == 'success') {
|
||||||
|
try {
|
||||||
jsonData1 = jsonDecode(res);
|
jsonData1 = jsonDecode(res);
|
||||||
var jsonResponse = jsonDecode(res) as Map<String, dynamic>;
|
final List<dynamic> jsonData = jsonData1['message'];
|
||||||
isLoading = false;
|
|
||||||
final List<dynamic> jsonData = jsonResponse['message'];
|
|
||||||
rideData = jsonData.map<MonthlyDataModel>((item) {
|
rideData = jsonData.map<MonthlyDataModel>((item) {
|
||||||
return MonthlyDataModel.fromJson(item);
|
return MonthlyDataModel.fromJson(item);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
final List<FlSpot> spots = rideData
|
final List<FlSpot> spots = rideData
|
||||||
.map((data) => FlSpot(
|
.map((data) => FlSpot(
|
||||||
data.day.toDouble(),
|
data.day.toDouble(),
|
||||||
@@ -71,8 +79,17 @@ class DurationController extends GetxController {
|
|||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
chartData = spots;
|
chartData = spots;
|
||||||
|
} catch (e) {
|
||||||
|
jsonData1 = {};
|
||||||
|
chartData = <FlSpot>[];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jsonData1 = {};
|
||||||
|
chartData = <FlSpot>[];
|
||||||
|
}
|
||||||
|
|
||||||
update(); // Notify the observers about the data and loading state change
|
isLoading = false;
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fetchRideDriver() async {
|
Future<void> fetchRideDriver() async {
|
||||||
@@ -83,9 +100,9 @@ class DurationController extends GetxController {
|
|||||||
link: AppLink.getRidesDriverByDay,
|
link: AppLink.getRidesDriverByDay,
|
||||||
payload: {'driver_id': box.read(BoxName.driverID)},
|
payload: {'driver_id': box.read(BoxName.driverID)},
|
||||||
);
|
);
|
||||||
if (res != 'failure') {
|
if (res != 'failure' && res != 'no_internet' && res != 'token_expired') {
|
||||||
jsonData2 = jsonDecode(res);
|
jsonData2 = jsonDecode(res);
|
||||||
var jsonResponse = jsonDecode(res) as Map<String, dynamic>;
|
var jsonResponse = jsonData2 as Map<String, dynamic>;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
final List<dynamic> jsonData = jsonResponse['message'];
|
final List<dynamic> jsonData = jsonResponse['message'];
|
||||||
rideCountData = jsonData.map<MonthlyRideModel>((item) {
|
rideCountData = jsonData.map<MonthlyRideModel>((item) {
|
||||||
@@ -110,17 +127,17 @@ class DurationController extends GetxController {
|
|||||||
.toList();
|
.toList();
|
||||||
chartRidePriceDriver = spotsDriverPrices;
|
chartRidePriceDriver = spotsDriverPrices;
|
||||||
|
|
||||||
update(); // Notify the observers about the data and loading state change
|
update();
|
||||||
} else {
|
} else {
|
||||||
Get.defaultDialog(
|
isLoading = false;
|
||||||
title: 'No data yet!'.tr,
|
jsonData2 = {};
|
||||||
middleText: '',
|
chartRideCount = <FlSpot>[];
|
||||||
confirm: MyElevatedButton(
|
chartRidePriceDriver = <FlSpot>[];
|
||||||
title: 'OK'.tr,
|
update();
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
if (res == 'no_internet') {
|
||||||
Get.back();
|
mySnackeBarError('No internet connection'.tr);
|
||||||
}));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class HomeCaptainController extends GetxController {
|
|||||||
print("🚀 [Heatmap] Fetching live data...");
|
print("🚀 [Heatmap] Fetching live data...");
|
||||||
// استخدم الرابط المباشر لملف JSON لسرعة قصوى
|
// استخدم الرابط المباشر لملف JSON لسرعة قصوى
|
||||||
final String jsonUrl =
|
final String jsonUrl =
|
||||||
"https://api.intaleq.xyz/intaleq/ride/rides/heatmap_live.json";
|
"https://ride.intaleq.xyz/intaleq/ride/heatmap_data.json";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// نستخدم timestamp لمنع الكاش من الموبايل نفسه
|
// نستخدم timestamp لمنع الكاش من الموبايل نفسه
|
||||||
@@ -758,5 +758,4 @@ class HomeCaptainController extends GetxController {
|
|||||||
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:ui';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
@@ -10,6 +11,7 @@ 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:bubble_head/bubble.dart';
|
import 'package:bubble_head/bubble.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:geolocator/geolocator.dart' as geo;
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||||
@@ -20,6 +22,7 @@ import '../../../constant/colors.dart';
|
|||||||
import '../../../constant/country_polygons.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';
|
||||||
@@ -31,7 +34,8 @@ import '../../functions/location_controller.dart';
|
|||||||
import '../../functions/tts.dart';
|
import '../../functions/tts.dart';
|
||||||
import 'behavior_controller.dart';
|
import 'behavior_controller.dart';
|
||||||
|
|
||||||
class MapDriverController extends GetxController {
|
class MapDriverController extends GetxController
|
||||||
|
with GetSingleTickerProviderStateMixin {
|
||||||
bool isLoading = true;
|
bool isLoading = true;
|
||||||
final formKey1 = GlobalKey<FormState>();
|
final formKey1 = GlobalKey<FormState>();
|
||||||
final formKey2 = GlobalKey<FormState>();
|
final formKey2 = GlobalKey<FormState>();
|
||||||
@@ -97,6 +101,26 @@ class MapDriverController extends GetxController {
|
|||||||
int remainingTimeToShowPassengerInfoWindowFromDriver = 25;
|
int remainingTimeToShowPassengerInfoWindowFromDriver = 25;
|
||||||
int remainingTimeToPassenger = 60;
|
int remainingTimeToPassenger = 60;
|
||||||
int remainingTimeInPassengerLocatioWait = 60;
|
int remainingTimeInPassengerLocatioWait = 60;
|
||||||
|
|
||||||
|
// ─── Navigation & Smoothing ──────────────────────────────────────────
|
||||||
|
AnimationController? _animController;
|
||||||
|
LatLng? smoothedLocation;
|
||||||
|
double smoothedHeading = 0.0;
|
||||||
|
LatLng? _oldLoc;
|
||||||
|
LatLng? _targetLoc;
|
||||||
|
double _oldHeading = 0.0;
|
||||||
|
double _targetHeading = 0.0;
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> routeSteps = [];
|
||||||
|
int currentStepIndex = 0;
|
||||||
|
String currentInstruction = "";
|
||||||
|
String nextInstruction = "";
|
||||||
|
String distanceToNextStep = "";
|
||||||
|
int currentManeuverModifier = 0;
|
||||||
|
bool _nextInstructionSpoken = false;
|
||||||
|
bool isTtsEnabled = true;
|
||||||
|
StreamSubscription<geo.Position>? _locationSubscription;
|
||||||
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
bool isDriverNearPassengerStart = false;
|
bool isDriverNearPassengerStart = false;
|
||||||
IntaleqMapController? mapController;
|
IntaleqMapController? mapController;
|
||||||
late LatLng myLocation;
|
late LatLng myLocation;
|
||||||
@@ -111,10 +135,7 @@ class MapDriverController extends GetxController {
|
|||||||
LatLng latLngPassengerLocation = LatLng(0, 0);
|
LatLng latLngPassengerLocation = LatLng(0, 0);
|
||||||
late LatLng latLngPassengerDestination = LatLng(0, 0);
|
late LatLng latLngPassengerDestination = LatLng(0, 0);
|
||||||
|
|
||||||
List<Map<String, dynamic>> routeSteps = [];
|
|
||||||
String currentInstruction = "";
|
|
||||||
int currentStepIndex = 0;
|
|
||||||
bool isTtsEnabled = false;
|
|
||||||
|
|
||||||
// في MapDriverController
|
// في MapDriverController
|
||||||
|
|
||||||
@@ -152,7 +173,9 @@ class MapDriverController extends GetxController {
|
|||||||
_posSub?.cancel();
|
_posSub?.cancel();
|
||||||
_posSub = null;
|
_posSub = null;
|
||||||
|
|
||||||
// mapController?.dispose();
|
_animController?.dispose();
|
||||||
|
_locationSubscription?.cancel();
|
||||||
|
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +247,7 @@ class MapDriverController extends GetxController {
|
|||||||
if (isCameraLocked && mapController != null) {
|
if (isCameraLocked && mapController != null) {
|
||||||
double bearing = (speedKmh > 5) ? heading : 0.0;
|
double bearing = (speedKmh > 5) ? heading : 0.0;
|
||||||
// ملاحظة: يمكنك تخزين آخر bearing معروف واستخدامه عند التوقف لتحسين التجربة
|
// ملاحظة: يمكنك تخزين آخر bearing معروف واستخدامه عند التوقف لتحسين التجربة
|
||||||
_animateCameraToNavigationMode(newLoc, heading);
|
_animateCameraToNavigationMode(newLoc, bearing);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. فحص التعليمات الصوتية
|
// 4. فحص التعليمات الصوتية
|
||||||
@@ -315,6 +338,8 @@ class MapDriverController extends GetxController {
|
|||||||
// 2. تنظيف الحالة
|
// 2. تنظيف الحالة
|
||||||
box.write(BoxName.rideStatus, 'Canceled'); // أو الحالة الافتراضية
|
box.write(BoxName.rideStatus, 'Canceled'); // أو الحالة الافتراضية
|
||||||
box.remove(BoxName.rideArguments);
|
box.remove(BoxName.rideArguments);
|
||||||
|
box.remove(BoxName.passengerID);
|
||||||
|
box.remove(BoxName.rideId);
|
||||||
|
|
||||||
// 3. عرض رسالة للسائق
|
// 3. عرض رسالة للسائق
|
||||||
if (Get.isDialogOpen == true) {
|
if (Get.isDialogOpen == true) {
|
||||||
@@ -379,8 +404,10 @@ class MapDriverController extends GetxController {
|
|||||||
box.write(BoxName.statusDriverLocation, 'blocked');
|
box.write(BoxName.statusDriverLocation, 'blocked');
|
||||||
|
|
||||||
// عرض رسالة العقوبة
|
// عرض رسالة العقوبة
|
||||||
Get.snackbar("Your account is temporarily restricted ⛔".tr,
|
Get.snackbar(
|
||||||
"Due to excessive cancellations (3 times), receiving orders has been suspended for 4 hours.".tr,
|
"Your account is temporarily restricted ⛔".tr,
|
||||||
|
"Due to excessive cancellations (3 times), receiving orders has been suspended for 4 hours."
|
||||||
|
.tr,
|
||||||
duration: Duration(seconds: 8),
|
duration: Duration(seconds: 8),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
@@ -396,6 +423,8 @@ class MapDriverController extends GetxController {
|
|||||||
// تنظيف البيانات
|
// تنظيف البيانات
|
||||||
box.remove(BoxName.rideArgumentsFromBackground);
|
box.remove(BoxName.rideArgumentsFromBackground);
|
||||||
box.remove(BoxName.rideArguments);
|
box.remove(BoxName.rideArguments);
|
||||||
|
box.remove(BoxName.passengerID);
|
||||||
|
box.remove(BoxName.rideId);
|
||||||
box.write(BoxName.rideStatus, 'Cancel');
|
box.write(BoxName.rideStatus, 'Cancel');
|
||||||
|
|
||||||
// تسجيل محلي (اختياري)
|
// تسجيل محلي (اختياري)
|
||||||
@@ -726,10 +755,10 @@ class MapDriverController extends GetxController {
|
|||||||
|
|
||||||
// إغلاق مؤشر التحميل لأننا حصلنا على النتيجة
|
// إغلاق مؤشر التحميل لأننا حصلنا على النتيجة
|
||||||
if (Get.isDialogOpen == true) {
|
if (Get.isDialogOpen == true) {
|
||||||
navigatorKey.currentState?.pop();
|
Get.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (distanceToPassenger < 100) {
|
if (distanceToPassenger < 150) {
|
||||||
// زدت المسافة قليلاً لمرونة أكبر (150م)
|
// زدت المسافة قليلاً لمرونة أكبر (150م)
|
||||||
|
|
||||||
// --- أ) تحديث الحالة المحلية (Optimistic Update) ---
|
// --- أ) تحديث الحالة المحلية (Optimistic Update) ---
|
||||||
@@ -772,16 +801,26 @@ class MapDriverController extends GetxController {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// --- حالة الرفض (بعيد جداً) ---
|
// --- حالة الرفض (بعيد جداً) ---
|
||||||
MyDialog().getDialog('You are far from passenger location'.tr,
|
showDialog(
|
||||||
'Please go closer to the passenger location (less than 150m)'.tr,
|
context: Get.context!,
|
||||||
() {
|
builder: (context) => AlertDialog(
|
||||||
// الديالوج يغلق نفسه الآن تلقائياً
|
title: Text('You are far from passenger location'.tr),
|
||||||
});
|
content: Text(
|
||||||
|
'Please go closer to the passenger location (less than 150m)'
|
||||||
|
.tr),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text('OK'.tr),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// تنظيف اللودينج في حال حدوث خطأ غير متوقع
|
// تنظيف اللودينج في حال حدوث خطأ غير متوقع
|
||||||
if (Get.isDialogOpen == true) {
|
if (Get.isDialogOpen == true) {
|
||||||
navigatorKey.currentState?.pop();
|
Get.back();
|
||||||
}
|
}
|
||||||
Log.print("Error starting ride: $e");
|
Log.print("Error starting ride: $e");
|
||||||
Get.snackbar("Error", "Could not start ride. Please check internet.");
|
Get.snackbar("Error", "Could not start ride. Please check internet.");
|
||||||
@@ -1066,6 +1105,8 @@ class MapDriverController extends GetxController {
|
|||||||
isPriceWindow = false;
|
isPriceWindow = false;
|
||||||
box.write(BoxName.rideStatus, 'Finished');
|
box.write(BoxName.rideStatus, 'Finished');
|
||||||
box.write(BoxName.statusDriverLocation, 'off');
|
box.write(BoxName.statusDriverLocation, 'off');
|
||||||
|
box.remove(BoxName.passengerID);
|
||||||
|
box.remove(BoxName.rideId);
|
||||||
|
|
||||||
// 4. حساب التكلفة النهائية (Logic)
|
// 4. حساب التكلفة النهائية (Logic)
|
||||||
_calculateFinalTotalCost();
|
_calculateFinalTotalCost();
|
||||||
@@ -1382,7 +1423,7 @@ class MapDriverController extends GetxController {
|
|||||||
|
|
||||||
// 🟢 2. المنطق المتغير
|
// 🟢 2. المنطق المتغير
|
||||||
const double longTripPerMin = 600.0;
|
const double longTripPerMin = 600.0;
|
||||||
const double mediumDistThresholdKm = 25.0;
|
|
||||||
const double longDistThresholdKm = 35.0;
|
const double longDistThresholdKm = 35.0;
|
||||||
|
|
||||||
// نسبة التخفيض
|
// نسبة التخفيض
|
||||||
@@ -1588,9 +1629,26 @@ class MapDriverController extends GetxController {
|
|||||||
if (mapController == null) return;
|
if (mapController == null) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. طلب المسار من الباكيج
|
// 1. طلب المسار من السيرفر الموحد (SaaS) لضمان الدقة وتفادي الـ 401
|
||||||
final response =
|
final saasUrl = Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: {
|
||||||
await mapController!.getDirections(origin, destination, steps: true);
|
'fromLat': origin.latitude.toString(),
|
||||||
|
'fromLng': origin.longitude.toString(),
|
||||||
|
'toLat': destination.latitude.toString(),
|
||||||
|
'toLng': destination.longitude.toString(),
|
||||||
|
'steps': 'true', // نحتاجها للملاحة والتوجيه
|
||||||
|
'alternatives': 'false',
|
||||||
|
});
|
||||||
|
|
||||||
|
final httpResponse = await http.get(saasUrl, headers: {
|
||||||
|
'x-api-key': Env.mapSaasKey,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (httpResponse.statusCode != 200) {
|
||||||
|
throw Exception("Routing request failed: ${httpResponse.statusCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = jsonDecode(httpResponse.body);
|
||||||
|
|
||||||
// 2. التعامل مع الـ JSON المباشر (الذي أرسله المستخدم)
|
// 2. التعامل مع الـ JSON المباشر (الذي أرسله المستخدم)
|
||||||
// إذا كان الـ response يحتوي على الحقول مباشرة في الجذر
|
// إذا كان الـ response يحتوي على الحقول مباشرة في الجذر
|
||||||
@@ -1628,41 +1686,36 @@ class MapDriverController extends GetxController {
|
|||||||
color: routeColor,
|
color: routeColor,
|
||||||
));
|
));
|
||||||
|
|
||||||
// د) معالجة الخطوات (Legs & Steps) إذا توفرت في الـ JSON
|
// د) معالجة الخطوات (Instructions) للسيرفر الموحد
|
||||||
List<dynamic> legs = response['legs'] ?? [];
|
final List<dynamic> instructions = response['instructions'] ?? [];
|
||||||
if (legs.isNotEmpty) {
|
if (instructions.isNotEmpty) {
|
||||||
final stepsList =
|
routeSteps = List<Map<String, dynamic>>.from(instructions.map((e) {
|
||||||
List<Map<String, dynamic>>.from(legs[0]['steps'] ?? []);
|
int endIdx = (e['interval'] as List)[1];
|
||||||
|
// التأكد من أن الـ index لا يتجاوز طول المسار
|
||||||
|
if (endIdx >= fullRoute.length) endIdx = fullRoute.length - 1;
|
||||||
|
|
||||||
for (var step in stepsList) {
|
return {
|
||||||
step['html_instructions'] = _createInstructionFromManeuverSmart(step);
|
'html_instructions': e['text'] ?? "",
|
||||||
|
'sign': e['sign'] ?? 0,
|
||||||
|
'end_location': {
|
||||||
|
'lat': fullRoute[endIdx].latitude,
|
||||||
|
'lng': fullRoute[endIdx].longitude,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
if (step['maneuver'] != null &&
|
|
||||||
step['maneuver']['location'] != null) {
|
|
||||||
var loc = step['maneuver']['location'];
|
|
||||||
// التعامل مع تنسيق OSRM [lng, lat]
|
|
||||||
if (loc is List && loc.length >= 2) {
|
|
||||||
step['end_location'] = {'lat': loc[1], 'lng': loc[0]};
|
|
||||||
} else if (loc is Map) {
|
|
||||||
step['end_location'] = loc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
routeSteps = stepsList;
|
|
||||||
currentStepIndex = 0;
|
currentStepIndex = 0;
|
||||||
|
|
||||||
// نطق أول تعليمة
|
|
||||||
if (routeSteps.isNotEmpty) {
|
|
||||||
currentInstruction = routeSteps[0]['html_instructions'];
|
currentInstruction = routeSteps[0]['html_instructions'];
|
||||||
if (Get.isRegistered<TextToSpeechController>()) {
|
currentManeuverModifier = routeSteps[0]['sign'];
|
||||||
|
_nextInstructionSpoken = false;
|
||||||
|
|
||||||
|
if (Get.isRegistered<TextToSpeechController>() && isTtsEnabled) {
|
||||||
Get.find<TextToSpeechController>().speakText(currentInstruction);
|
Get.find<TextToSpeechController>().speakText(currentInstruction);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// في حال عدم وجود steps، نقوم بتصفيرها
|
|
||||||
routeSteps = [];
|
routeSteps = [];
|
||||||
currentInstruction = "";
|
currentInstruction = "";
|
||||||
|
currentManeuverModifier = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// هـ) تحريك الكاميرا لتشمل المسار
|
// هـ) تحريك الكاميرا لتشمل المسار
|
||||||
@@ -1678,119 +1731,7 @@ class MapDriverController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 دالة الترجمة المحسنة (من NavigationController)
|
|
||||||
String _createInstructionFromManeuverSmart(Map<String, dynamic> step) {
|
|
||||||
if (step['maneuver'] == null) return "Continue straight".tr;
|
|
||||||
|
|
||||||
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 = "Go".tr;
|
|
||||||
break;
|
|
||||||
case 'arrive':
|
|
||||||
return "You have arrived at your destination, @name".trParams({'name': name});
|
|
||||||
case 'turn':
|
|
||||||
case 'fork':
|
|
||||||
case 'roundabout':
|
|
||||||
case 'merge':
|
|
||||||
case 'on ramp':
|
|
||||||
case 'off ramp':
|
|
||||||
case 'end of road':
|
|
||||||
instruction =
|
|
||||||
_getTurnInstruction(modifier); // استخدم نفس دالتك المساعدة هنا
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
instruction = "Continue straight".tr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.isNotEmpty) {
|
|
||||||
if (type == 'continue') {
|
|
||||||
instruction += " ${"on".tr} $name";
|
|
||||||
} else {
|
|
||||||
instruction += " ${"towards".tr} $name";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return instruction;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = "Go".tr;
|
|
||||||
break;
|
|
||||||
case 'arrive':
|
|
||||||
instruction = "You have arrived at your destination".tr;
|
|
||||||
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 = "Continue".tr;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
instruction = "Head".tr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.isNotEmpty) {
|
|
||||||
if (instruction == "استمر") {
|
|
||||||
instruction += " ${"on".tr} $name";
|
|
||||||
} else {
|
|
||||||
instruction += " ${"to".tr} $name";
|
|
||||||
}
|
|
||||||
} else if (type == 'continue' && modifier == 'straight') {
|
|
||||||
instruction = "Continue straight".tr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return instruction;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* دالة مساعدة لترجمة تعليمات الانعطاف
|
|
||||||
*/
|
|
||||||
String _getTurnInstruction(String modifier) {
|
|
||||||
switch (modifier) {
|
|
||||||
case 'uturn':
|
|
||||||
return "Make a U-turn".tr;
|
|
||||||
case 'sharp right':
|
|
||||||
return "Turn sharp right".tr;
|
|
||||||
case 'right':
|
|
||||||
return "Turn right".tr;
|
|
||||||
case 'slight right':
|
|
||||||
return "Turn slight right".tr;
|
|
||||||
case 'straight':
|
|
||||||
return "Continue straight".tr;
|
|
||||||
case 'slight left':
|
|
||||||
return "Turn slight left".tr;
|
|
||||||
case 'left':
|
|
||||||
return "Turn left".tr;
|
|
||||||
case 'sharp left':
|
|
||||||
return "Turn sharp left".tr;
|
|
||||||
default:
|
|
||||||
return "Head".tr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* دالة لحساب حدود الخريطة (Bounds) من قائمة نقاط
|
|
||||||
*/
|
|
||||||
LatLngBounds _boundsFromLatLngList(List<LatLng> list) {
|
LatLngBounds _boundsFromLatLngList(List<LatLng> list) {
|
||||||
assert(list.isNotEmpty);
|
assert(list.isNotEmpty);
|
||||||
double? x0, x1, y0, y1;
|
double? x0, x1, y0, y1;
|
||||||
@@ -1809,66 +1750,7 @@ class MapDriverController extends GetxController {
|
|||||||
northeast: LatLng(x1!, y1!), southwest: LatLng(x0!, y0!));
|
northeast: LatLng(x1!, y1!), southwest: LatLng(x0!, y0!));
|
||||||
}
|
}
|
||||||
|
|
||||||
// الدالة التي يتم استدعاؤها من خدمة الموقع كل 5 ثوان (أو حسب الفترة المحددة)
|
|
||||||
void onLocationUpdated(Position newPosition) {
|
|
||||||
myLocation = LatLng(newPosition.latitude, newPosition.longitude);
|
|
||||||
heading = newPosition.heading;
|
|
||||||
|
|
||||||
// -->> منطق قياس الأداء يبدأ هنا <<--
|
|
||||||
final stopwatch = Stopwatch()..start();
|
|
||||||
|
|
||||||
// -->> منطق الملاحة وتحديث المسار <<--
|
|
||||||
_onLocationTick(myLocation);
|
|
||||||
|
|
||||||
stopwatch.stop();
|
|
||||||
|
|
||||||
// -->> تحليل الأداء واتخاذ القرار <<--
|
|
||||||
if (!_hasMadeDecision) {
|
|
||||||
_performanceReadings.add(stopwatch.elapsedMilliseconds);
|
|
||||||
if (_performanceReadings.length >= _readingsToCollect) {
|
|
||||||
_analyzePerformance();
|
|
||||||
_hasMadeDecision = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// =================================================================
|
|
||||||
// 3. منطق الملاحة الداخلي (Internal Navigation Logic)
|
|
||||||
// =================================================================
|
|
||||||
|
|
||||||
void _onLocationTick(LatLng pos) {
|
|
||||||
if (activeRouteSteps.isEmpty || currentStepIndex >= _stepBounds.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final double dToEnd =
|
|
||||||
_distanceMeters(pos, _stepEndPoints[currentStepIndex]);
|
|
||||||
if (dToEnd <= 35) {
|
|
||||||
// 35 متر عتبة للوصول لنهاية الخطوة
|
|
||||||
_advanceStep();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _advanceStep() {
|
|
||||||
if (currentStepIndex >= _stepBounds.length - 1) {
|
|
||||||
// وصل للنهاية
|
|
||||||
currentInstruction = "You have arrived at your destination".tr;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentStepIndex++;
|
|
||||||
currentInstruction = _parseInstruction(
|
|
||||||
activeRouteSteps[currentStepIndex]['html_instructions']);
|
|
||||||
Get.isRegistered<TextToSpeechController>()
|
|
||||||
? Get.find<TextToSpeechController>().speakText(currentInstruction)
|
|
||||||
: Get.put(TextToSpeechController()).speakText(currentInstruction);
|
|
||||||
|
|
||||||
// -->> هنا يتم تحديث لون المسار <<--
|
|
||||||
_updateTraveledPath();
|
|
||||||
|
|
||||||
_fitToBounds(_stepBounds[currentStepIndex],
|
|
||||||
padding: 80); // تقريب الكاميرا على الخطوة التالية
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
// داخل MapDriverController
|
// داخل MapDriverController
|
||||||
|
|
||||||
Future<void> markDriverAsArrived() async {
|
Future<void> markDriverAsArrived() async {
|
||||||
@@ -1918,53 +1800,7 @@ class MapDriverController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateTraveledPath() {
|
|
||||||
// استخراج كل النقاط للخطوات التي تم اجتيازها
|
|
||||||
List<LatLng> pointsForTraveledSteps = [];
|
|
||||||
for (int i = 0; i < currentStepIndex; i++) {
|
|
||||||
final stepPolyline = activeRouteSteps[i]['polyline']['points'];
|
|
||||||
pointsForTraveledSteps.addAll(decodePolylineToLatLng(stepPolyline));
|
|
||||||
}
|
|
||||||
traveledPathPoints.assignAll(pointsForTraveledSteps);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _prepareStepData(List<Map<String, dynamic>> steps) {
|
|
||||||
_stepBounds.clear();
|
|
||||||
_stepEndPoints.clear();
|
|
||||||
|
|
||||||
for (final s in steps) {
|
|
||||||
// 1. استخراج نقطة النهاية (الكود الحالي سليم)
|
|
||||||
final end = s['end_location'];
|
|
||||||
_stepEndPoints.add(LatLng(
|
|
||||||
(end['lat'] as num).toDouble(),
|
|
||||||
(end['lng'] as num).toDouble(),
|
|
||||||
));
|
|
||||||
|
|
||||||
// 2. فك تشفير البوليلاين الخاص بالخطوة وتحويله إلى LatLng
|
|
||||||
// -->> هنا تم التصحيح <<--
|
|
||||||
List<LatLng> pts = PolylineUtils.decode(s['polyline']['points']);
|
|
||||||
|
|
||||||
// أضف نقاط البداية والنهاية إذا لم تكن موجودة في البوليلاين لضمان دقة الحدود
|
|
||||||
if (pts.isNotEmpty) {
|
|
||||||
final start = s['start_location'];
|
|
||||||
final startLatLng = LatLng(
|
|
||||||
(start['lat'] as num).toDouble(), (start['lng'] as num).toDouble());
|
|
||||||
if (pts.first != startLatLng) {
|
|
||||||
pts.insert(0, startLatLng);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_stepBounds.add(_boundsFromPoints(pts));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A helper function to decode and convert the polyline string
|
|
||||||
List<LatLng> decodePolylineToLatLng(String polylineString) {
|
|
||||||
// 1. Decode the string into a list of number lists (e.g., [[lat, lng], ...])
|
|
||||||
List<LatLng> decodedPoints = PolylineUtils.decode(polylineString);
|
|
||||||
|
|
||||||
return decodedPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =================================================================
|
// =================================================================
|
||||||
// 4. منطق الأداء الذكي (Smart Performance Logic)
|
// 4. منطق الأداء الذكي (Smart Performance Logic)
|
||||||
@@ -1981,7 +1817,8 @@ class MapDriverController extends GetxController {
|
|||||||
void _suggestOptimization() {
|
void _suggestOptimization() {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"Improve app performance".tr,
|
"Improve app performance".tr,
|
||||||
"To ensure the best experience, we suggest adjusting the settings to suit your device. Would you like to proceed?".tr,
|
"To ensure the best experience, we suggest adjusting the settings to suit your device. Would you like to proceed?"
|
||||||
|
.tr,
|
||||||
duration: const Duration(seconds: 15),
|
duration: const Duration(seconds: 15),
|
||||||
mainButton: TextButton(
|
mainButton: TextButton(
|
||||||
child: Text("Yes, optimize".tr),
|
child: Text("Yes, optimize".tr),
|
||||||
@@ -1998,13 +1835,7 @@ class MapDriverController extends GetxController {
|
|||||||
// =================================================================
|
// =================================================================
|
||||||
// 5. دوال مساعدة (Helper Functions)
|
// 5. دوال مساعدة (Helper Functions)
|
||||||
// =================================================================
|
// =================================================================
|
||||||
void _resetRouteState() {
|
|
||||||
activeRouteSteps.clear();
|
|
||||||
traveledPathPoints.clear();
|
|
||||||
upcomingPathPoints.clear();
|
|
||||||
_allPointsForActiveRoute.clear();
|
|
||||||
currentStepIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _parseInstruction(String html) =>
|
String _parseInstruction(String html) =>
|
||||||
html.replaceAll(RegExp(r'<[^>]*>'), '');
|
html.replaceAll(RegExp(r'<[^>]*>'), '');
|
||||||
@@ -2108,16 +1939,7 @@ class MapDriverController extends GetxController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _contains(LatLngBounds b, LatLng p) {
|
|
||||||
final south = math.min(b.southwest.latitude, b.northeast.latitude);
|
|
||||||
final north = math.max(b.southwest.latitude, b.northeast.latitude);
|
|
||||||
final west = math.min(b.southwest.longitude, b.northeast.longitude);
|
|
||||||
final east = math.max(b.southwest.longitude, b.northeast.longitude);
|
|
||||||
return (p.latitude >= south &&
|
|
||||||
p.latitude <= north &&
|
|
||||||
p.longitude >= west &&
|
|
||||||
p.longitude <= east);
|
|
||||||
}
|
|
||||||
|
|
||||||
double _distanceMeters(LatLng a, LatLng b) {
|
double _distanceMeters(LatLng a, LatLng b) {
|
||||||
// هافرساين مبسطة
|
// هافرساين مبسطة
|
||||||
@@ -2188,6 +2010,10 @@ class MapDriverController extends GetxController {
|
|||||||
durationOfRideValue = Get.arguments['durationOfRideValue'];
|
durationOfRideValue = Get.arguments['durationOfRideValue'];
|
||||||
paymentAmount = Get.arguments['paymentAmount'];
|
paymentAmount = Get.arguments['paymentAmount'];
|
||||||
paymentMethod = Get.arguments['paymentMethod'];
|
paymentMethod = Get.arguments['paymentMethod'];
|
||||||
|
|
||||||
|
// 🔥 حفظ البيانات في الذاكرة المحلية فوراً (لفصل السوكيت عن الكنترولر)
|
||||||
|
box.write(BoxName.passengerID, passengerId.toString());
|
||||||
|
box.write(BoxName.rideId, rideId.toString());
|
||||||
isHaveSteps = Get.arguments['isHaveSteps'];
|
isHaveSteps = Get.arguments['isHaveSteps'];
|
||||||
step0 = Get.arguments['step0'];
|
step0 = Get.arguments['step0'];
|
||||||
step1 = Get.arguments['step1'];
|
step1 = Get.arguments['step1'];
|
||||||
@@ -2208,7 +2034,7 @@ class MapDriverController extends GetxController {
|
|||||||
Get.find<LocationController>().myLocation.latitude.toString();
|
Get.find<LocationController>().myLocation.latitude.toString();
|
||||||
String lng =
|
String lng =
|
||||||
Get.find<LocationController>().myLocation.longitude.toString();
|
Get.find<LocationController>().myLocation.longitude.toString();
|
||||||
String origin = '$lat,$lng';
|
|
||||||
// Set the origin and destination coordinates for the Google Maps directions request.
|
// Set the origin and destination coordinates for the Google Maps directions request.
|
||||||
Future.delayed(const Duration(seconds: 1));
|
Future.delayed(const Duration(seconds: 1));
|
||||||
getRoute(
|
getRoute(
|
||||||
@@ -2222,7 +2048,7 @@ class MapDriverController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
latlng(String passengerLocation, passengerDestination) {
|
void latlng(String passengerLocation, String passengerDestination) {
|
||||||
double latPassengerLocation =
|
double latPassengerLocation =
|
||||||
double.parse(passengerLocation.toString().split(',')[0]);
|
double.parse(passengerLocation.toString().split(',')[0]);
|
||||||
double lngPassengerLocation =
|
double lngPassengerLocation =
|
||||||
@@ -2257,7 +2083,6 @@ class MapDriverController extends GetxController {
|
|||||||
@override
|
@override
|
||||||
void onInit() async {
|
void onInit() async {
|
||||||
mapAPIKEY = await storage.read(key: BoxName.mapAPIKEY);
|
mapAPIKEY = await storage.read(key: BoxName.mapAPIKEY);
|
||||||
// Get the passenger location from the arguments.
|
|
||||||
await argumentLoading();
|
await argumentLoading();
|
||||||
Get.put(FirebaseMessagesController());
|
Get.put(FirebaseMessagesController());
|
||||||
runGoogleMapDirectly();
|
runGoogleMapDirectly();
|
||||||
@@ -2265,24 +2090,141 @@ class MapDriverController extends GetxController {
|
|||||||
addCustomPassengerIcon();
|
addCustomPassengerIcon();
|
||||||
addCustomStartIcon();
|
addCustomStartIcon();
|
||||||
addCustomEndIcon();
|
addCustomEndIcon();
|
||||||
|
|
||||||
if (!Get.isRegistered<TextToSpeechController>()) {
|
if (!Get.isRegistered<TextToSpeechController>()) {
|
||||||
Get.put(TextToSpeechController(), permanent: true);
|
Get.put(TextToSpeechController(), permanent: true);
|
||||||
// permanent: true تمنع حذفه عند تغيير الصفحات
|
|
||||||
}
|
}
|
||||||
// updateMarker();
|
|
||||||
// updateLocation();
|
_animController = AnimationController(
|
||||||
|
vsync: this, duration: const Duration(milliseconds: 1000));
|
||||||
|
_animController!.addListener(() {
|
||||||
|
if (_oldLoc != null && _targetLoc != null) {
|
||||||
|
final t = _animController!.value;
|
||||||
|
final lat = lerpDouble(_oldLoc!.latitude, _targetLoc!.latitude, t)!;
|
||||||
|
final lng = lerpDouble(_oldLoc!.longitude, _targetLoc!.longitude, t)!;
|
||||||
|
smoothedLocation = LatLng(lat, lng);
|
||||||
|
smoothedHeading = _lerpAngle(_oldHeading, _targetHeading, t);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_startLocationListening();
|
||||||
|
|
||||||
startTimerToShowPassengerInfoWindowFromDriver();
|
startTimerToShowPassengerInfoWindowFromDriver();
|
||||||
// durationToAdd = Duration(seconds: int.parse(duration));
|
|
||||||
durationToAdd = Duration(seconds: parseDurationToInt(duration));
|
durationToAdd = Duration(seconds: parseDurationToInt(duration));
|
||||||
hours = durationToAdd.inHours;
|
hours = durationToAdd.inHours;
|
||||||
minutes = (durationToAdd.inMinutes % 60).round();
|
minutes = (durationToAdd.inMinutes % 60).round();
|
||||||
calculateConsumptionFuel();
|
calculateConsumptionFuel();
|
||||||
// updateLocation();// for now to test it
|
|
||||||
// cancelCheckRidefromPassenger();
|
|
||||||
// checkIsDriverNearPassenger();
|
|
||||||
super.onInit();
|
super.onInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _startLocationListening() {
|
||||||
|
_locationSubscription?.cancel();
|
||||||
|
_locationSubscription = geo.Geolocator.getPositionStream(
|
||||||
|
locationSettings: const geo.LocationSettings(
|
||||||
|
accuracy: geo.LocationAccuracy.bestForNavigation,
|
||||||
|
distanceFilter: 2,
|
||||||
|
),
|
||||||
|
).listen((geo.Position pos) {
|
||||||
|
_handleLocationUpdate(pos);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleLocationUpdate(geo.Position pos) {
|
||||||
|
final newLoc = LatLng(pos.latitude, pos.longitude);
|
||||||
|
_oldLoc = smoothedLocation ?? newLoc;
|
||||||
|
_targetLoc = newLoc;
|
||||||
|
|
||||||
|
_oldHeading = smoothedHeading;
|
||||||
|
if (pos.speed > 0.5) {
|
||||||
|
_targetHeading = pos.heading;
|
||||||
|
} else {
|
||||||
|
_targetHeading = _oldHeading;
|
||||||
|
}
|
||||||
|
|
||||||
|
_animController?.forward(from: 0.0);
|
||||||
|
|
||||||
|
if (routeSteps.isNotEmpty) {
|
||||||
|
_checkNavigationStep(newLoc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double _lerpAngle(double from, double to, double t) {
|
||||||
|
final double diff = ((to - from + 540.0) % 360.0) - 180.0;
|
||||||
|
return (from + diff * t + 360.0) % 360.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkNavigationStep(LatLng pos) {
|
||||||
|
if (routeSteps.isEmpty || currentStepIndex >= routeSteps.length) return;
|
||||||
|
|
||||||
|
final step = routeSteps[currentStepIndex];
|
||||||
|
final stepLoc = step['end_location'];
|
||||||
|
if (stepLoc == null) return;
|
||||||
|
|
||||||
|
final double stepLat = stepLoc['lat'];
|
||||||
|
final double stepLng = stepLoc['lng'];
|
||||||
|
|
||||||
|
final distance = geo.Geolocator.distanceBetween(
|
||||||
|
pos.latitude, pos.longitude, stepLat, stepLng);
|
||||||
|
|
||||||
|
distanceToNextStep = distance > 1000
|
||||||
|
? "${(distance / 1000).toStringAsFixed(1)} km"
|
||||||
|
: "${distance.toStringAsFixed(0)} m";
|
||||||
|
|
||||||
|
if (distance < 50 &&
|
||||||
|
!_nextInstructionSpoken &&
|
||||||
|
(currentStepIndex + 1) < routeSteps.length) {
|
||||||
|
final nextText =
|
||||||
|
routeSteps[currentStepIndex + 1]['html_instructions'] ?? "";
|
||||||
|
if (isTtsEnabled) {
|
||||||
|
Get.find<TextToSpeechController>().speakText(nextText);
|
||||||
|
}
|
||||||
|
_nextInstructionSpoken = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distance < 25) {
|
||||||
|
_advanceStep();
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
IconData get currentManeuverIcon {
|
||||||
|
switch (currentManeuverModifier) {
|
||||||
|
case 4: // Arrive
|
||||||
|
return Icons.place_rounded;
|
||||||
|
case 6: // Roundabout
|
||||||
|
return Icons.roundabout_right_rounded;
|
||||||
|
case 2: // Right
|
||||||
|
return Icons.turn_right_rounded;
|
||||||
|
case 3: // Slight Right
|
||||||
|
return Icons.turn_slight_right_rounded;
|
||||||
|
case -2: // Left
|
||||||
|
return Icons.turn_left_rounded;
|
||||||
|
case -1: // Slight Left
|
||||||
|
return Icons.turn_slight_left_rounded;
|
||||||
|
case 7: // Keep Right
|
||||||
|
return Icons.turn_right_rounded;
|
||||||
|
case -7: // Keep Left
|
||||||
|
return Icons.turn_left_rounded;
|
||||||
|
case 0: // Straight
|
||||||
|
return Icons.straight_rounded;
|
||||||
|
default:
|
||||||
|
return Icons.straight_rounded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _advanceStep() {
|
||||||
|
currentStepIndex++;
|
||||||
|
if (currentStepIndex < routeSteps.length) {
|
||||||
|
currentInstruction =
|
||||||
|
routeSteps[currentStepIndex]['html_instructions'] ?? "";
|
||||||
|
currentManeuverModifier = routeSteps[currentStepIndex]['sign'] ?? 0;
|
||||||
|
_nextInstructionSpoken = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int parseDurationToInt(dynamic value) {
|
int parseDurationToInt(dynamic value) {
|
||||||
if (value == null) return 0;
|
if (value == null) return 0;
|
||||||
String text = value.toString();
|
String text = value.toString();
|
||||||
|
|||||||
@@ -532,8 +532,8 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
locale: localController.language,
|
locale: localController.language,
|
||||||
theme: localController.lightTheme,
|
theme: localController.lightTheme,
|
||||||
darkTheme: localController.darkTheme,
|
darkTheme: localController.darkTheme,
|
||||||
themeMode: settingController.isDarkMode ? ThemeMode.dark : ThemeMode.light,
|
themeMode:
|
||||||
|
settingController.isDarkMode ? ThemeMode.dark : ThemeMode.light,
|
||||||
initialRoute: '/',
|
initialRoute: '/',
|
||||||
getPages: [
|
getPages: [
|
||||||
GetPage(name: '/', page: () => SplashScreen()),
|
GetPage(name: '/', page: () => SplashScreen()),
|
||||||
@@ -545,4 +545,3 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class Log {
|
|||||||
Log._();
|
Log._();
|
||||||
|
|
||||||
static void print(String value, {StackTrace? stackTrace}) {
|
static void print(String value, {StackTrace? stackTrace}) {
|
||||||
// developer.log(value, name: 'LOG', stackTrace: stackTrace);
|
developer.log(value, name: 'LOG', stackTrace: stackTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Object? inspect(Object? object) {
|
static Object? inspect(Object? object) {
|
||||||
|
|||||||
@@ -19,19 +19,31 @@ class RideCalculateDriver extends StatelessWidget {
|
|||||||
return MyScafolld(
|
return MyScafolld(
|
||||||
title: 'Ride Summaries'.tr,
|
title: 'Ride Summaries'.tr,
|
||||||
body: [
|
body: [
|
||||||
Center(
|
GetBuilder<DurationController>(
|
||||||
child: GetBuilder<DurationController>(
|
builder: (durationController) {
|
||||||
builder: (durationController) => durationController.isLoading
|
if (durationController.isLoading) {
|
||||||
? const Center(child: MyCircularProgressIndicator())
|
return const Center(child: MyCircularProgressIndicator());
|
||||||
: durationController.jsonData1.isEmpty ||
|
}
|
||||||
durationController.jsonData2.isEmpty
|
|
||||||
? Center(
|
bool hasDurations = durationController.jsonData1.isNotEmpty &&
|
||||||
|
durationController.jsonData1['message'] != null &&
|
||||||
|
(durationController.jsonData1['message'] as List).isNotEmpty;
|
||||||
|
|
||||||
|
bool hasRides = durationController.jsonData2.isNotEmpty &&
|
||||||
|
durationController.jsonData2['message'] != null &&
|
||||||
|
(durationController.jsonData2['message'] as List).isNotEmpty;
|
||||||
|
|
||||||
|
bool hasStats = durationController.monthlyList.isNotEmpty;
|
||||||
|
|
||||||
|
if (!hasDurations && !hasRides && !hasStats) {
|
||||||
|
return Center(
|
||||||
child: Text('No data yet!'.tr),
|
child: Text('No data yet!'.tr),
|
||||||
)
|
);
|
||||||
: ListView(
|
}
|
||||||
// mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
return ListView(
|
||||||
children: [
|
children: [
|
||||||
|
if (hasDurations) ...[
|
||||||
Text(
|
Text(
|
||||||
'${'Average of Hours of'.tr} ${AppInformation.appName}${' is ON for this month'.tr}${' ${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}'.tr}',
|
'${'Average of Hours of'.tr} ${AppInformation.appName}${' is ON for this month'.tr}${' ${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}'.tr}',
|
||||||
style: AppStyle.title,
|
style: AppStyle.title,
|
||||||
@@ -51,14 +63,10 @@ class RideCalculateDriver extends StatelessWidget {
|
|||||||
isStepLineChart: true,
|
isStepLineChart: true,
|
||||||
spots: durationController.chartData,
|
spots: durationController.chartData,
|
||||||
isCurved: true,
|
isCurved: true,
|
||||||
color: Colors
|
color: Colors.deepPurpleAccent,
|
||||||
.deepPurpleAccent, // Custom color
|
barWidth: 3,
|
||||||
barWidth: 3, // Thinner line
|
dotData: const FlDotData(show: true),
|
||||||
dotData: const FlDotData(
|
|
||||||
show:
|
|
||||||
true), // Show dots on each point
|
|
||||||
belowBarData: BarAreaData(
|
belowBarData: BarAreaData(
|
||||||
// Add gradient fill below the line
|
|
||||||
show: true,
|
show: true,
|
||||||
color: AppColor.deepPurpleAccent,
|
color: AppColor.deepPurpleAccent,
|
||||||
),
|
),
|
||||||
@@ -74,50 +82,40 @@ class RideCalculateDriver extends StatelessWidget {
|
|||||||
titlesData: FlTitlesData(
|
titlesData: FlTitlesData(
|
||||||
show: true,
|
show: true,
|
||||||
topTitles: AxisTitles(
|
topTitles: AxisTitles(
|
||||||
axisNameWidget: Text(
|
axisNameWidget:
|
||||||
'Days'.tr,
|
Text('Days'.tr, style: AppStyle.title),
|
||||||
style: AppStyle.title,
|
|
||||||
),
|
|
||||||
axisNameSize: 30,
|
axisNameSize: 30,
|
||||||
),
|
),
|
||||||
bottomTitles: AxisTitles(
|
bottomTitles: AxisTitles(
|
||||||
axisNameWidget: Text(
|
axisNameWidget: Text(
|
||||||
'Total Hours on month'.tr,
|
'Total Hours on month'.tr,
|
||||||
style: AppStyle.title,
|
style: AppStyle.title),
|
||||||
),
|
|
||||||
axisNameSize: 30,
|
axisNameSize: 30,
|
||||||
sideTitles: const SideTitles(
|
sideTitles: const SideTitles(
|
||||||
reservedSize: 30,
|
reservedSize: 30, showTitles: true)),
|
||||||
showTitles: true)),
|
|
||||||
leftTitles: AxisTitles(
|
leftTitles: AxisTitles(
|
||||||
axisNameWidget: Text(
|
axisNameWidget: Text(
|
||||||
'Counts of Hours on days'.tr,
|
'Counts of Hours on days'.tr,
|
||||||
style: AppStyle.title,
|
style: AppStyle.title),
|
||||||
),
|
|
||||||
axisNameSize: 30,
|
axisNameSize: 30,
|
||||||
sideTitles: const SideTitles(
|
sideTitles: const SideTitles(
|
||||||
reservedSize: 30,
|
reservedSize: 30, showTitles: true)),
|
||||||
showTitles: true)),
|
|
||||||
),
|
|
||||||
gridData: const FlGridData(
|
|
||||||
show: true,
|
|
||||||
),
|
),
|
||||||
|
gridData: const FlGridData(show: true),
|
||||||
borderData: FlBorderData(
|
borderData: FlBorderData(
|
||||||
show: true,
|
show: true,
|
||||||
border: const Border(
|
border: const Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(color: AppColor.accentColor),
|
||||||
color: AppColor.accentColor),
|
left: BorderSide(color: AppColor.accentColor),
|
||||||
left: BorderSide(
|
|
||||||
color: AppColor.accentColor),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
],
|
||||||
height: 5,
|
if (hasRides) ...[
|
||||||
),
|
const SizedBox(height: 5),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(6),
|
padding: const EdgeInsets.all(6),
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -130,15 +128,10 @@ class RideCalculateDriver extends StatelessWidget {
|
|||||||
lineBarsData: [
|
lineBarsData: [
|
||||||
LineChartBarData(
|
LineChartBarData(
|
||||||
spots: durationController.chartRideCount,
|
spots: durationController.chartRideCount,
|
||||||
// isCurved: true,
|
color: Colors.deepPurpleAccent,
|
||||||
color: Colors
|
barWidth: 3,
|
||||||
.deepPurpleAccent, // Custom color
|
dotData: const FlDotData(show: true),
|
||||||
barWidth: 3, // Thinner line
|
|
||||||
dotData: const FlDotData(
|
|
||||||
show:
|
|
||||||
true), // Show dots on each point
|
|
||||||
belowBarData: BarAreaData(
|
belowBarData: BarAreaData(
|
||||||
// Add gradient fill below the line
|
|
||||||
show: true,
|
show: true,
|
||||||
color: AppColor.deepPurpleAccent,
|
color: AppColor.deepPurpleAccent,
|
||||||
),
|
),
|
||||||
@@ -154,53 +147,40 @@ class RideCalculateDriver extends StatelessWidget {
|
|||||||
titlesData: FlTitlesData(
|
titlesData: FlTitlesData(
|
||||||
show: true,
|
show: true,
|
||||||
topTitles: AxisTitles(
|
topTitles: AxisTitles(
|
||||||
axisNameWidget: Text(
|
axisNameWidget:
|
||||||
'Days'.tr,
|
Text('Days'.tr, style: AppStyle.title),
|
||||||
style: AppStyle.title,
|
|
||||||
),
|
|
||||||
axisNameSize: 30,
|
axisNameSize: 30,
|
||||||
// sideTitles: const SideTitles(
|
|
||||||
// reservedSize: 30, showTitles: true),
|
|
||||||
),
|
),
|
||||||
bottomTitles: AxisTitles(
|
bottomTitles: AxisTitles(
|
||||||
axisNameWidget: Text(
|
axisNameWidget: Text(
|
||||||
'${"Total rides on month".tr} = ${durationController.jsonData2['message'][0]['totalCount'].toString()}'
|
'${"Total rides on month".tr} = ${durationController.jsonData2['message'][0]['totalCount'] ?? 0}'
|
||||||
.tr,
|
.tr,
|
||||||
style: AppStyle.title,
|
style: AppStyle.title,
|
||||||
),
|
),
|
||||||
axisNameSize: 30,
|
axisNameSize: 30,
|
||||||
sideTitles: const SideTitles(
|
sideTitles: const SideTitles(
|
||||||
reservedSize: 30,
|
reservedSize: 30, showTitles: true)),
|
||||||
showTitles: true)),
|
|
||||||
leftTitles: AxisTitles(
|
leftTitles: AxisTitles(
|
||||||
axisNameWidget: Text(
|
axisNameWidget: Text(
|
||||||
'Counts of rides on days'.tr,
|
'Counts of rides on days'.tr,
|
||||||
style: AppStyle.title,
|
style: AppStyle.title),
|
||||||
),
|
|
||||||
axisNameSize: 30,
|
axisNameSize: 30,
|
||||||
sideTitles: const SideTitles(
|
sideTitles: const SideTitles(
|
||||||
reservedSize: 30,
|
reservedSize: 30, showTitles: true)),
|
||||||
showTitles: true)),
|
|
||||||
),
|
|
||||||
gridData: const FlGridData(
|
|
||||||
show: true,
|
|
||||||
),
|
),
|
||||||
|
gridData: const FlGridData(show: true),
|
||||||
borderData: FlBorderData(
|
borderData: FlBorderData(
|
||||||
show: true,
|
show: true,
|
||||||
border: const Border(
|
border: const Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(color: AppColor.accentColor),
|
||||||
color: AppColor.accentColor),
|
left: BorderSide(color: AppColor.accentColor),
|
||||||
left: BorderSide(
|
|
||||||
color: AppColor.accentColor),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(height: 5),
|
||||||
height: 5,
|
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(6),
|
padding: const EdgeInsets.all(6),
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -213,19 +193,14 @@ class RideCalculateDriver extends StatelessWidget {
|
|||||||
lineBarsData: [
|
lineBarsData: [
|
||||||
LineChartBarData(
|
LineChartBarData(
|
||||||
isStepLineChart: true,
|
isStepLineChart: true,
|
||||||
spots: durationController
|
spots: durationController.chartRidePriceDriver,
|
||||||
.chartRidePriceDriver,
|
|
||||||
isCurved: true,
|
isCurved: true,
|
||||||
isStrokeCapRound: true,
|
isStrokeCapRound: true,
|
||||||
preventCurveOverShooting: true,
|
preventCurveOverShooting: true,
|
||||||
color: Colors
|
color: Colors.deepPurpleAccent,
|
||||||
.deepPurpleAccent, // Custom color
|
barWidth: 3,
|
||||||
barWidth: 3, // Thinner line
|
dotData: const FlDotData(show: true),
|
||||||
dotData: const FlDotData(
|
|
||||||
show:
|
|
||||||
true), // Show dots on each point
|
|
||||||
belowBarData: BarAreaData(
|
belowBarData: BarAreaData(
|
||||||
// Add gradient fill below the line
|
|
||||||
show: true,
|
show: true,
|
||||||
color: AppColor.deepPurpleAccent,
|
color: AppColor.deepPurpleAccent,
|
||||||
),
|
),
|
||||||
@@ -241,73 +216,62 @@ class RideCalculateDriver extends StatelessWidget {
|
|||||||
titlesData: FlTitlesData(
|
titlesData: FlTitlesData(
|
||||||
show: true,
|
show: true,
|
||||||
topTitles: AxisTitles(
|
topTitles: AxisTitles(
|
||||||
axisNameWidget: Text(
|
axisNameWidget:
|
||||||
'Days'.tr,
|
Text('Days'.tr, style: AppStyle.title),
|
||||||
style: AppStyle.title,
|
|
||||||
),
|
|
||||||
axisNameSize: 30,
|
axisNameSize: 30,
|
||||||
// sideTitles: const SideTitles(
|
|
||||||
// reservedSize: 30, showTitles: true),
|
|
||||||
),
|
),
|
||||||
bottomTitles: AxisTitles(
|
bottomTitles: AxisTitles(
|
||||||
axisNameWidget: Text(
|
axisNameWidget: Text(
|
||||||
'${"Total budgets on month".tr} = ${durationController.jsonData2['message'][0]['totalPrice'].toString()}'
|
'${"Total budgets on month".tr} = ${durationController.jsonData2['message'][0]['totalPrice'] ?? 0}'
|
||||||
.tr,
|
.tr,
|
||||||
style: AppStyle.title,
|
style: AppStyle.title,
|
||||||
),
|
),
|
||||||
axisNameSize: 30,
|
axisNameSize: 30,
|
||||||
sideTitles: const SideTitles(
|
sideTitles: const SideTitles(
|
||||||
reservedSize: 30,
|
reservedSize: 30, showTitles: true)),
|
||||||
showTitles: true)),
|
|
||||||
leftTitles: AxisTitles(
|
leftTitles: AxisTitles(
|
||||||
axisNameWidget: Text(
|
axisNameWidget: Text(
|
||||||
'Counts of budgets on days'.tr,
|
'Counts of budgets on days'.tr,
|
||||||
style: AppStyle.title,
|
style: AppStyle.title),
|
||||||
),
|
|
||||||
axisNameSize: 30,
|
axisNameSize: 30,
|
||||||
sideTitles: const SideTitles(
|
sideTitles: const SideTitles(
|
||||||
reservedSize: 30,
|
reservedSize: 30, showTitles: true)),
|
||||||
showTitles: true)),
|
|
||||||
),
|
|
||||||
gridData: const FlGridData(
|
|
||||||
show: true,
|
|
||||||
),
|
),
|
||||||
|
gridData: const FlGridData(show: true),
|
||||||
borderData: FlBorderData(
|
borderData: FlBorderData(
|
||||||
show: true,
|
show: true,
|
||||||
border: const Border(
|
border: const Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(color: AppColor.accentColor),
|
||||||
color: AppColor.accentColor),
|
left: BorderSide(color: AppColor.accentColor),
|
||||||
left: BorderSide(
|
|
||||||
color: AppColor.accentColor),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: AppStyle.boxDecoration1,
|
decoration: AppStyle.boxDecoration1,
|
||||||
child: durationController.monthlyList.isEmpty
|
child: !hasStats
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
height: Get.height * .2,
|
height: Get.height * .2,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
"No data yet".tr,
|
"No statistics yet".tr,
|
||||||
style: AppStyle.title,
|
style: AppStyle.title,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: DriverStatsTable(
|
: DriverStatsTable(
|
||||||
monthlyList:
|
monthlyList: durationController.monthlyList,
|
||||||
durationController.monthlyList,
|
|
||||||
)))
|
)))
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
|
},
|
||||||
)
|
)
|
||||||
// BarChartWidget(),
|
// BarChartWidget(),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
isleading: true);
|
isleading: true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -296,17 +296,17 @@ class PhoneNumberScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
|
class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
|
||||||
final _phoneController = TextEditingController();
|
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
|
String _completePhone = '';
|
||||||
|
|
||||||
void _submit() async {
|
void _submit() async {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
setState(() => _isLoading = true);
|
setState(() => _isLoading = true);
|
||||||
final rawPhone = _phoneController.text.trim().replaceFirst('+', '');
|
final rawPhone = _completePhone.replaceFirst('+', '');
|
||||||
|
Log.print('📱 _submit rawPhone: "$rawPhone" (from _completePhone: "$_completePhone")');
|
||||||
final success = await PhoneAuthHelper.sendOtp(rawPhone);
|
final success = await PhoneAuthHelper.sendOtp(rawPhone);
|
||||||
if (success && mounted) {
|
if (success && mounted) {
|
||||||
// Get.to(() => OtpVerificationScreen(phoneNumber: rawPhone));
|
|
||||||
await PhoneAuthHelper.verifyOtp(rawPhone);
|
await PhoneAuthHelper.verifyOtp(rawPhone);
|
||||||
}
|
}
|
||||||
if (mounted) setState(() => _isLoading = false);
|
if (mounted) setState(() => _isLoading = false);
|
||||||
@@ -351,7 +351,7 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
|
|||||||
),
|
),
|
||||||
initialCountryCode: 'SY',
|
initialCountryCode: 'SY',
|
||||||
onChanged: (phone) {
|
onChanged: (phone) {
|
||||||
_phoneController.text = phone.completeNumber;
|
_completePhone = phone.completeNumber;
|
||||||
},
|
},
|
||||||
validator: (phone) {
|
validator: (phone) {
|
||||||
if (phone == null || phone.number.isEmpty) {
|
if (phone == null || phone.number.isEmpty) {
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ class InstructionsOfRoads extends StatelessWidget {
|
|||||||
color: AppColor.primaryColor,
|
color: AppColor.primaryColor,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.turn_right_rounded,
|
child: Icon(controller.currentManeuverIcon,
|
||||||
color: Colors.white, size: 24),
|
color: Colors.white, size: 24),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 14),
|
const SizedBox(width: 14),
|
||||||
@@ -194,7 +194,7 @@ class InstructionsOfRoads extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"NEXT STEP".tr,
|
"${"NEXT STEP".tr} (${controller.distanceToNextStep})",
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
color: theme.hintColor,
|
color: theme.hintColor,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
import 'package:sefer_driver/constant/box_name.dart';
|
||||||
import 'package:sefer_driver/controller/firebase/local_notification.dart';
|
import 'package:sefer_driver/controller/firebase/local_notification.dart';
|
||||||
import 'package:sefer_driver/main.dart';
|
import 'package:sefer_driver/main.dart';
|
||||||
import 'package:sefer_driver/views/home/Captin/driver_map_page.dart';
|
import 'package:sefer_driver/views/home/Captin/driver_map_page.dart';
|
||||||
import 'package:sefer_driver/views/home/Captin/orderCaptin/vip_order_page.dart';
|
import 'package:sefer_driver/views/home/Captin/orderCaptin/vip_order_page.dart';
|
||||||
|
import 'package:sefer_driver/views/auth/syria/registration_view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.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';
|
||||||
|
|
||||||
import '../../../../../constant/colors.dart';
|
|
||||||
import '../../../../../constant/links.dart';
|
import '../../../../../constant/links.dart';
|
||||||
import '../../../../../controller/firebase/notification_service.dart';
|
import '../../../../../controller/firebase/notification_service.dart';
|
||||||
import '../../../../../controller/functions/crud.dart';
|
import '../../../../../controller/functions/crud.dart';
|
||||||
import '../../../../../controller/home/captin/order_request_controller.dart';
|
import '../../../../../controller/home/captin/order_request_controller.dart';
|
||||||
import '../../../../../controller/home/navigation/navigation_view.dart';
|
|
||||||
import '../../../../../print.dart';
|
import '../../../../../print.dart';
|
||||||
import '../../../../Rate/ride_calculate_driver.dart';
|
import '../../../../Rate/ride_calculate_driver.dart';
|
||||||
|
|
||||||
@@ -78,7 +76,8 @@ GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
|
|||||||
tooltip: 'Active Ride'.tr,
|
tooltip: 'Active Ride'.tr,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await checkForPendingOrderFromServer();
|
await checkForPendingOrderFromServer();
|
||||||
if (box.read(BoxName.rideArgumentsFromBackground) != 'failure') {
|
if (box.read(BoxName.rideArgumentsFromBackground) !=
|
||||||
|
'failure') {
|
||||||
Get.to(
|
Get.to(
|
||||||
() => PassengerLocationMapPage(),
|
() => PassengerLocationMapPage(),
|
||||||
arguments: box.read(BoxName.rideArgumentsFromBackground),
|
arguments: box.read(BoxName.rideArgumentsFromBackground),
|
||||||
@@ -91,6 +90,13 @@ GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
box.write(BoxName.rideArgumentsFromBackground, 'failure');
|
||||||
|
box.write(BoxName.statusDriverLocation, 'off');
|
||||||
|
box.write(BoxName.rideStatus, 'no_ride');
|
||||||
|
Log.print(box.read(BoxName.statusDriverLocation));
|
||||||
|
ctrl.update();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
_Divider(context),
|
_Divider(context),
|
||||||
@@ -102,11 +108,21 @@ GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
|
|||||||
tooltip: 'Earnings'.tr,
|
tooltip: 'Earnings'.tr,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
DateTime? lastTime = box.read(BoxName.lastTimeStaticThrottle);
|
final lastTimeRaw = box.read(BoxName.lastTimeStaticThrottle);
|
||||||
|
DateTime? lastTime;
|
||||||
|
|
||||||
|
if (lastTimeRaw != null) {
|
||||||
|
try {
|
||||||
|
lastTime = DateTime.parse(lastTimeRaw.toString());
|
||||||
|
} catch (_) {
|
||||||
|
lastTime = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (lastTime == null ||
|
if (lastTime == null ||
|
||||||
now.difference(lastTime).inMinutes >= 2) {
|
now.difference(lastTime).inMinutes >= 2) {
|
||||||
box.write(BoxName.lastTimeStaticThrottle, now);
|
box.write(
|
||||||
|
BoxName.lastTimeStaticThrottle, now.toIso8601String());
|
||||||
Get.to(() => RideCalculateDriver());
|
Get.to(() => RideCalculateDriver());
|
||||||
} else {
|
} else {
|
||||||
final left = 2 - now.difference(lastTime).inMinutes;
|
final left = 2 - now.difference(lastTime).inMinutes;
|
||||||
@@ -133,6 +149,16 @@ GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
|
|||||||
onTap: () => Get.to(() => const VipOrderPage()),
|
onTap: () => Get.to(() => const VipOrderPage()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// _Divider(context),
|
||||||
|
|
||||||
|
// // ── 4. Driver Registration ──────────
|
||||||
|
// _MenuIcon(
|
||||||
|
// icon: Icons.person_add_alt_1_rounded,
|
||||||
|
// color: Colors.purple.shade400,
|
||||||
|
// tooltip: 'Registration'.tr,
|
||||||
|
// onTap: () => Get.to(() => const RegistrationView()),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:sefer_driver/constant/api_key.dart';
|
|||||||
|
|
||||||
import '../../../../controller/functions/location_controller.dart';
|
import '../../../../controller/functions/location_controller.dart';
|
||||||
import '../../../../controller/home/captin/map_driver_controller.dart';
|
import '../../../../controller/home/captin/map_driver_controller.dart';
|
||||||
|
import '../../../../controller/profile/setting_controller.dart';
|
||||||
|
|
||||||
class GoogleDriverMap extends StatelessWidget {
|
class GoogleDriverMap extends StatelessWidget {
|
||||||
const GoogleDriverMap({
|
const GoogleDriverMap({
|
||||||
@@ -27,6 +28,11 @@ class GoogleDriverMap extends StatelessWidget {
|
|||||||
onMapCreated: (mapController) {
|
onMapCreated: (mapController) {
|
||||||
controller.onMapCreated(mapController);
|
controller.onMapCreated(mapController);
|
||||||
},
|
},
|
||||||
|
mapType: Get.isRegistered<SettingController>()
|
||||||
|
? (Get.find<SettingController>().isMapDarkMode
|
||||||
|
? IntaleqMapType.normal
|
||||||
|
: IntaleqMapType.light)
|
||||||
|
: IntaleqMapType.light,
|
||||||
zoomControlsEnabled: false,
|
zoomControlsEnabled: false,
|
||||||
initialCameraPosition: CameraPosition(
|
initialCameraPosition: CameraPosition(
|
||||||
target: locationController.myLocation,
|
target: locationController.myLocation,
|
||||||
@@ -43,8 +49,8 @@ class GoogleDriverMap extends StatelessWidget {
|
|||||||
markers: {
|
markers: {
|
||||||
Marker(
|
Marker(
|
||||||
markerId: MarkerId('MyLocation'.tr),
|
markerId: MarkerId('MyLocation'.tr),
|
||||||
position: locationController.myLocation,
|
position: controller.smoothedLocation ?? locationController.myLocation,
|
||||||
rotation: locationController.heading,
|
rotation: controller.smoothedHeading,
|
||||||
flat: true,
|
flat: true,
|
||||||
anchor: const Offset(0.5, 0.5),
|
anchor: const Offset(0.5, 0.5),
|
||||||
icon: controller.carIcon,
|
icon: controller.carIcon,
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ class RideAvailableCard extends StatelessWidget {
|
|||||||
Get.back();
|
Get.back();
|
||||||
|
|
||||||
// 3. تحليل الرد
|
// 3. تحليل الرد
|
||||||
var jsonResponse = jsonDecode(response);
|
var jsonResponse = response;
|
||||||
|
|
||||||
if (jsonResponse['status'] == 'success') {
|
if (jsonResponse['status'] == 'success') {
|
||||||
// ✅ نجاح: أنت الفائز بالرحلة
|
// ✅ نجاح: أنت الفائز بالرحلة
|
||||||
|
|||||||
Reference in New Issue
Block a user