new backend 29-04-2026

This commit is contained in:
Hamza-Ayed
2026-04-30 01:42:57 +03:00
parent b92db3bb39
commit 4385ef5a99
20 changed files with 796 additions and 708 deletions

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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') {

View File

@@ -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;
} }

View File

@@ -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;

View File

@@ -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();

View File

@@ -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];
} }

View File

@@ -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

View File

@@ -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();
} }
} }

View File

@@ -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);
})); }
} }
} }

View File

@@ -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();
} }
} }

View File

@@ -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();

View File

@@ -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 {
); );
} }
} }

View File

@@ -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) {

View File

@@ -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);
} }

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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()),
// ),
], ],
), ),
); );

View File

@@ -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,

View File

@@ -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') {
// ✅ نجاح: أنت الفائز بالرحلة // ✅ نجاح: أنت الفائز بالرحلة