From 4385ef5a9919311a1b8c803f6a1af7f62743d220 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Thu, 30 Apr 2026 01:42:57 +0300 Subject: [PATCH] new backend 29-04-2026 --- android/app/build.gradle | 4 +- lib/constant/links.dart | 2 +- .../auth/captin/login_captin_controller.dart | 66 ++- .../auth/captin/opt_token_controller.dart | 21 +- .../auth/syria/registration_controller.dart | 57 +- lib/controller/firebase/firbase_messge.dart | 4 +- lib/controller/functions/crud.dart | 88 ++- .../functions/location_controller.dart | 19 +- lib/controller/functions/secure_storage.dart | 24 +- .../home/captin/duration_controller .dart | 87 +-- .../home/captin/home_captain_controller.dart | 3 +- .../home/captin/map_driver_controller.dart | 526 ++++++++--------- lib/main.dart | 7 +- lib/print.dart | 2 +- lib/views/Rate/ride_calculate_driver.dart | 530 ++++++++---------- lib/views/auth/captin/otp_page.dart | 8 +- lib/views/home/Captin/driver_map_page.dart | 6 +- .../widget/left_menu_map_captain.dart | 38 +- .../google_driver_map_page.dart | 10 +- .../notification/available_rides_page.dart | 2 +- 20 files changed, 796 insertions(+), 708 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 14c7c8c..c403417 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -48,8 +48,8 @@ android { applicationId = "com.intaleq_driver" minSdkVersion = flutter.minSdkVersion targetSdk = 36 - versionCode = 61 - versionName = '1.1.61' + versionCode = 62 + versionName = '1.1.62' multiDexEnabled = true ndk { diff --git a/lib/constant/links.dart b/lib/constant/links.dart index 6d81f38..626a71e 100755 --- a/lib/constant/links.dart +++ b/lib/constant/links.dart @@ -17,7 +17,7 @@ class AppLink { 'https://map-saas.intaleqapp.com/api/geocoding/places'; static const String routeApiBaseUrl = "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 server = endPoint; diff --git a/lib/controller/auth/captin/login_captin_controller.dart b/lib/controller/auth/captin/login_captin_controller.dart index 58730c1..2344122 100755 --- a/lib/controller/auth/captin/login_captin_controller.dart +++ b/lib/controller/auth/captin/login_captin_controller.dart @@ -153,10 +153,13 @@ class LoginDriverController extends GetxController { ); Log.print('response.request: ${response1.request}'); 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']); - return jsonDecode(response1.body)['jwt'].toString(); + await box.write(BoxName.hmac, hmac); + return jwt.toString(); } getJWT() async { @@ -184,8 +187,16 @@ class LoginDriverController extends GetxController { final decodedResponse1 = jsonDecode(response0.body); Log.print('decodedResponse1: ${decodedResponse1}'); - final jwt = decodedResponse1['jwt']; - box.write(BoxName.jwt, c(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)); + } // ✅ بعد التأكد أن كل المفاتيح موجودة await EncryptionHelper.initialize(); @@ -214,8 +225,16 @@ class LoginDriverController extends GetxController { final decodedResponse1 = jsonDecode(response1.body); // Log.print('decodedResponse1: ${decodedResponse1}'); - final jwt = decodedResponse1['jwt']; - await box.write(BoxName.jwt, c(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 AppInitializer().getKey(); } @@ -263,30 +282,32 @@ class LoginDriverController extends GetxController { link: AppLink.updateDriverInvitationDirectly, payload: { "inviterDriverPhone": box.read(BoxName.phoneDriver).toString(), - // "driverId": box.read(BoxName.driverID).toString(), }, ); Log.print('invite: ${res}'); + // حماية من النوع — res قد يكون String ('failure'/'token_expired') بدل Map + if (res is! Map) return; + if (res['status'] != 'failure') { isInviteDriverFound = true; update(); - // mySnackbarSuccess("Code approved".tr); // Localized success message box.write(BoxName.isInstall, '1'); NotificationController().showNotification( "Code approved".tr, "Code approved".tr, 'tone2', ''); - NotificationService.sendNotification( - target: (res)['message'][0]['token'].toString(), - title: 'You have received a gift token!'.tr, - body: 'for '.tr + box.read(BoxName.phoneDriver).toString(), - isTopic: false, // Important: this is a token - tone: 'tone2', - driverList: [], category: 'You have received a gift token!', - ); - } else { - // mySnackeBarError( - // "You dont have invitation code".tr); // Localized error message + try { + NotificationService.sendNotification( + target: (res)['message'][0]['token'].toString(), + title: 'You have received a gift token!'.tr, + body: 'for '.tr + box.read(BoxName.phoneDriver).toString(), + isTopic: false, + tone: 'tone2', + driverList: [], category: 'You have received a gift token!', + ); + } catch (e) { + Log.print('invite notification error: $e'); + } } } @@ -352,6 +373,11 @@ class LoginDriverController extends GetxController { box.write(BoxName.carTypeOfDriver, 'Awfar Car'); } + // ✅ الحصول على توكن access بدل registration قبل أي طلبات بعد تسجيل الدخول + Log.print('🔑 Getting access token after login...'); + await getJWT(); + Log.print('🔑 Access token obtained.'); + // add invitations if (box.read(BoxName.isInstall) == null || box.read(BoxName.isInstall).toString() == '0') { diff --git a/lib/controller/auth/captin/opt_token_controller.dart b/lib/controller/auth/captin/opt_token_controller.dart index 976860e..764eb25 100644 --- a/lib/controller/auth/captin/opt_token_controller.dart +++ b/lib/controller/auth/captin/opt_token_controller.dart @@ -7,6 +7,7 @@ import 'package:sefer_driver/views/home/Captin/home_captain/home_captin.dart'; import '../../../constant/box_name.dart'; import '../../../constant/links.dart'; import '../../../main.dart'; +import '../../../views/widgets/error_snakbar.dart'; import '../../firebase/firbase_messge.dart'; import '../../firebase/notification_service.dart'; import '../../functions/crud.dart'; @@ -75,7 +76,7 @@ class OtpVerificationController extends GetxController { Future verifyOtp(String ptoken) async { isVerifying.value = true; - var finger = await storage.read(key: BoxName.fingerPrint); + var finger = box.read(BoxName.deviceFingerprint); try { final response = await CRUD().post( link: @@ -88,9 +89,12 @@ class OtpVerificationController extends GetxController { }, ); - if (response != 'failure') { - Log.print('response: ${response}'); - // Get.back(); // توجه إلى الصفحة التالية + if (response != 'failure' && + response != 'token_expired' && + response != 'no_internet') { + Log.print('response (already decoded): ${response}'); + + // توجه إلى الصفحة التالية await CRUD().post( link: '${AppLink.paymentServer}/auth/token/update_driver_auth.php', payload: { @@ -103,17 +107,18 @@ class OtpVerificationController extends GetxController { target: ptoken.toString(), title: 'token change'.tr, body: 'token change'.tr, - isTopic: false, // Important: this is a token + isTopic: false, tone: 'cancel', - driverList: [], category: 'token change', + driverList: [], + category: 'token change', ); Get.offAll(() => HomeCaptain()); } else { - Get.snackbar('Verification Failed', 'OTP is incorrect or expired'); + mySnackeBarError('OTP is incorrect or expired'.tr); } } catch (e) { - Get.snackbar('Error', e.toString()); + mySnackeBarError(e.toString()); } finally { isVerifying.value = false; } diff --git a/lib/controller/auth/syria/registration_controller.dart b/lib/controller/auth/syria/registration_controller.dart index 1b362ef..3d310b8 100644 --- a/lib/controller/auth/syria/registration_controller.dart +++ b/lib/controller/auth/syria/registration_controller.dart @@ -220,7 +220,7 @@ class RegistrationController extends GetxController { // // الإرسال للذكاء الاصطناعي // await sendToAI(type, imageFile: outFile); } 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 carFrontUrl = docUrls['car_license_front']; 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; update(); @@ -507,10 +519,18 @@ class RegistrationController extends GetxController { 'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}'; 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); req.headers.addAll({ '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 = {}; @@ -539,13 +559,24 @@ class RegistrationController extends GetxController { 'expiration_date', driverLicenseExpiryController .text); // تأكد من أن هذا تاريخ انتهاء السيارة وليس الرخصة - _addField(fields, 'color', carColorController.text); + _addField( + fields, + 'color', + carColorController.text.isNotEmpty + ? carColorController.text + : 'White'); - if (colorHex != null && colorHex!.isNotEmpty) { - _addField(fields, 'color_hex', colorHex!); - } - _addField(fields, 'owner', - '${firstNameController.text} ${lastNameController.text}'); + _addField(fields, 'color_hex', + (colorHex != null && colorHex!.isNotEmpty) ? colorHex! : '#FFFFFF'); + + _addField( + fields, + 'owner', + '${firstNameController.text} ${lastNameController.text}' + .trim() + .isNotEmpty + ? '${firstNameController.text} ${lastNameController.text}' + : 'Driver Owner'); // ============================================================ // 🔥 التعديل الجديد: إرسال الأرقام (IDs) لتصنيف المركبة والوقود @@ -591,12 +622,12 @@ class RegistrationController extends GetxController { // 4) معالجة الاستجابة Map? json; try { + Log.print('--- Registration Response: ${resp.body} ---'); json = jsonDecode(resp.body) as Map; } catch (_) {} if (resp.statusCode == 200 && json?['status'] == 'success') { - Get.snackbar('Success'.tr, 'Registration completed successfully!'.tr, - backgroundColor: Colors.green, colorText: Colors.white); + mySnackbarSuccess('Registration completed successfully!'.tr); // منطق التوكن والإشعارات وتسجيل الدخول... final email = box.read(BoxName.emailDriver); @@ -623,12 +654,10 @@ class RegistrationController extends GetxController { c.loginWithGoogleCredential(driverID, email); } else { final msg = (json?['message'] ?? 'Registration failed.').toString(); - Get.snackbar('Error'.tr, msg, - backgroundColor: Colors.red, colorText: Colors.white); + mySnackeBarError(msg); } } catch (e) { - Get.snackbar('Error'.tr, 'Error: $e', - backgroundColor: Colors.red, colorText: Colors.white); + mySnackeBarError('Error: $e'); } finally { client.close(); isLoading.value = false; diff --git a/lib/controller/firebase/firbase_messge.dart b/lib/controller/firebase/firbase_messge.dart index 77b448b..af74f2e 100755 --- a/lib/controller/firebase/firbase_messge.dart +++ b/lib/controller/firebase/firbase_messge.dart @@ -177,7 +177,7 @@ class FirebaseMessagesController extends GetxController { notificationController.showNotification(title, body, 'ding', ''); } MyDialog().getDialog(title, body, () { - Get.back(); + // Empty callback, MyDialog already closes itself using pop(). }); break; @@ -202,7 +202,7 @@ class FirebaseMessagesController extends GetxController { style: AppStyle.title, ), () { - Get.back(); + // Navigator.pop(Get.context!); }, ); update(); diff --git a/lib/controller/functions/crud.dart b/lib/controller/functions/crud.dart index edd6eda..10464e0 100755 --- a/lib/controller/functions/crud.dart +++ b/lib/controller/functions/crud.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; 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/network/net_guard.dart'; import 'package:sefer_driver/constant/box_name.dart'; @@ -21,10 +20,34 @@ import 'upload_image.dart'; class CRUD { final NetGuard _netGuard = NetGuard(); + static bool _isRefreshingJWT = false; static String _lastErrorSignature = ''; static DateTime _lastErrorTimestamp = DateTime(2000); 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 addError( String error, String details, String where) async { try { @@ -93,6 +116,10 @@ class CRUD { http.Response? response; 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) { try { @@ -129,7 +156,8 @@ class CRUD { final sc = response.statusCode; final body = response.body; - Log.print('_makeRequest [$sc] $link'); + Log.print('📥 [RES-$requestId] [$sc] $link'); + Log.print('📄 [BODY-$requestId] $body'); // 2xx if (sc >= 200 && sc < 300) { @@ -142,9 +170,18 @@ class CRUD { } } - // 401 → تجديد التوكن + // 401 → تجديد التوكن (مع حماية من الحلقة اللانهائية) if (sc == 401) { - await Get.put(LoginDriverController()).getJWT(); + // تخطي تجديد التوكن لـ endpoints غير حرجة (مثل تسجيل الأخطاء) + final isNonCritical = link.contains('errorApp.php'); + if (!_isRefreshingJWT && !isNonCritical) { + _isRefreshingJWT = true; + try { + await Get.put(LoginDriverController()).getJWT(); + } finally { + _isRefreshingJWT = false; + } + } return 'token_expired'; } @@ -167,6 +204,17 @@ class CRUD { }) async { 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 = { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer $token', @@ -185,15 +233,26 @@ class CRUD { Map? payload, }) async { 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 response = await http.post( url, body: payload, headers: { 'Content-Type': 'application/x-www-form-urlencoded', - 'Authorization': - 'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}', - 'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز + 'Authorization': 'Bearer $token', + 'X-Device-FP': _getFpHeader(), }, ).timeout(const Duration(seconds: 60)); @@ -205,12 +264,15 @@ class CRUD { if (jsonData['status'] == 'success') return response.body; return jsonData['status']; } else if (response.statusCode == 401) { - var jsonData = jsonDecode(response.body); - if (jsonData['error'] == 'Token expired') { - await Get.put(LoginDriverController()).getJWT(); - return 'token_expired'; + if (!_isRefreshingJWT) { + _isRefreshingJWT = true; + try { + await Get.put(LoginDriverController()).getJWT(); + } finally { + _isRefreshingJWT = false; + } } - return 'failure'; + return 'token_expired'; } else { addError('Non-200: ${response.statusCode}', 'crud().get - Other', url.toString()); @@ -505,7 +567,7 @@ class CRUD { // r() هي نفس دالة فك التشفير الثلاثي المختصرة String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; - if (JwtDecoder.isExpired(token)) { + if (!_isJwtValid(token)) { await LoginDriverController().getJWT(); token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; } diff --git a/lib/controller/functions/location_controller.dart b/lib/controller/functions/location_controller.dart index 1833934..c7227dc 100755 --- a/lib/controller/functions/location_controller.dart +++ b/lib/controller/functions/location_controller.dart @@ -412,6 +412,9 @@ class LocationController extends GetxController with WidgetsBindingObserver { void emitLocationToSocket(LatLng pos, double head, double spd) { 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 var payload = { @@ -424,16 +427,14 @@ class LocationController extends GetxController with WidgetsBindingObserver { 'distance': totalDistance, }; - // 🔥 CRITICAL FIX: Inject Passenger ID if a ride is active 🔥 - if (Get.isRegistered()) { - final mapCtrl = Get.find(); + // 🔥 القرار الذكي: حقن بيانات الراكب إذا كان هناك رحلة نشطة في الـ Box 🔥 + bool hasActiveRide = (currentRideStatus == 'Begin' || + currentRideStatus == 'Apply' || + currentRideStatus == 'Arrived'); - // Check if ride is started/active and we have a passenger ID - if (mapCtrl.isRideStarted && mapCtrl.passengerId != null) { - payload['passenger_id'] = - mapCtrl.passengerId; // This triggers the PHP forwarding - payload['ride_id'] = mapCtrl.rideId; // Good for debugging - } + if (hasActiveRide && storedPassengerId != null) { + payload['passenger_id'] = storedPassengerId; + payload['ride_id'] = storedRideId; } // DebugLog.print to verify diff --git a/lib/controller/functions/secure_storage.dart b/lib/controller/functions/secure_storage.dart index 2a383e6..1889049 100755 --- a/lib/controller/functions/secure_storage.dart +++ b/lib/controller/functions/secure_storage.dart @@ -1,7 +1,6 @@ import 'dart:convert'; 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:sefer_driver/controller/auth/captin/login_captin_controller.dart'; import 'package:sefer_driver/controller/functions/encrypt_decrypt.dart'; @@ -42,11 +41,24 @@ class AppInitializer { if (box.read(BoxName.jwt) == null) { await LoginDriverController().getJWT(); } else { - bool isTokenExpired = JwtDecoder.isExpired(X - .r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs) - .toString() - .split(AppInformation.addd)[0]); - if (isTokenExpired) { + String token = r(box.read(BoxName.jwt)).toString().split(AppInformation.addd)[0]; + bool isTokenValid = false; + try { + final parts = token.split('.'); + 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(); } } diff --git a/lib/controller/home/captin/duration_controller .dart b/lib/controller/home/captin/duration_controller .dart index 3945a41..3276e2d 100755 --- a/lib/controller/home/captin/duration_controller .dart +++ b/lib/controller/home/captin/duration_controller .dart @@ -9,6 +9,8 @@ import 'package:sefer_driver/controller/functions/crud.dart'; import 'package:sefer_driver/main.dart'; import 'package:sefer_driver/models/model/driver/rides_summary_model.dart'; +import '../../../views/widgets/error_snakbar.dart'; + class DurationController extends GetxController { final data = DurationData; // late AnimationController animationController; @@ -38,41 +40,56 @@ class DurationController extends GetxController { var res = await CRUD().get( link: AppLink.driverStatistic, payload: {'driverID': box.read(BoxName.driverID)}); - if (res == 'failure') { - monthlyList = []; - isLoading = false; - update(); + + if (res == 'success') { + try { + monthlyList = jsonDecode(res)['message']; + } catch (e) { + monthlyList = []; + } } else { - monthlyList = jsonDecode(res)['message']; - isLoading = false; - update(); + monthlyList = []; } + + isLoading = false; + update(); } Future fetchData() async { isLoading = true; - update(); // Notify the observers about the loading state change + update(); var res = await CRUD().get( link: AppLink.getTotalDriverDuration, payload: {'driver_id': box.read(BoxName.driverID)}, ); - jsonData1 = jsonDecode(res); - var jsonResponse = jsonDecode(res) as Map; - isLoading = false; - final List jsonData = jsonResponse['message']; - rideData = jsonData.map((item) { - return MonthlyDataModel.fromJson(item); - }).toList(); - final List spots = rideData - .map((data) => FlSpot( - data.day.toDouble(), - data.totalDuration.toDouble(), - )) - .toList(); - chartData = spots; - update(); // Notify the observers about the data and loading state change + if (res == 'success') { + try { + jsonData1 = jsonDecode(res); + final List jsonData = jsonData1['message']; + rideData = jsonData.map((item) { + return MonthlyDataModel.fromJson(item); + }).toList(); + + final List spots = rideData + .map((data) => FlSpot( + data.day.toDouble(), + data.totalDuration.toDouble(), + )) + .toList(); + chartData = spots; + } catch (e) { + jsonData1 = {}; + chartData = []; + } + } else { + jsonData1 = {}; + chartData = []; + } + + isLoading = false; + update(); } Future fetchRideDriver() async { @@ -83,9 +100,9 @@ class DurationController extends GetxController { link: AppLink.getRidesDriverByDay, payload: {'driver_id': box.read(BoxName.driverID)}, ); - if (res != 'failure') { + if (res != 'failure' && res != 'no_internet' && res != 'token_expired') { jsonData2 = jsonDecode(res); - var jsonResponse = jsonDecode(res) as Map; + var jsonResponse = jsonData2 as Map; isLoading = false; final List jsonData = jsonResponse['message']; rideCountData = jsonData.map((item) { @@ -110,17 +127,17 @@ class DurationController extends GetxController { .toList(); chartRidePriceDriver = spotsDriverPrices; - update(); // Notify the observers about the data and loading state change + update(); } else { - Get.defaultDialog( - title: 'No data yet!'.tr, - middleText: '', - confirm: MyElevatedButton( - title: 'OK'.tr, - onPressed: () { - Get.back(); - Get.back(); - })); + isLoading = false; + jsonData2 = {}; + chartRideCount = []; + chartRidePriceDriver = []; + update(); + + if (res == 'no_internet') { + mySnackeBarError('No internet connection'.tr); + } } } diff --git a/lib/controller/home/captin/home_captain_controller.dart b/lib/controller/home/captin/home_captain_controller.dart index 31f8962..c639f54 100755 --- a/lib/controller/home/captin/home_captain_controller.dart +++ b/lib/controller/home/captin/home_captain_controller.dart @@ -98,7 +98,7 @@ class HomeCaptainController extends GetxController { print("🚀 [Heatmap] Fetching live data..."); // استخدم الرابط المباشر لملف JSON لسرعة قصوى final String jsonUrl = - "https://api.intaleq.xyz/intaleq/ride/rides/heatmap_live.json"; + "https://ride.intaleq.xyz/intaleq/ride/heatmap_data.json"; try { // نستخدم timestamp لمنع الكاش من الموبايل نفسه @@ -758,5 +758,4 @@ class HomeCaptainController extends GetxController { update(); } - } diff --git a/lib/controller/home/captin/map_driver_controller.dart b/lib/controller/home/captin/map_driver_controller.dart index e60fc8d..fbf3b7c 100755 --- a/lib/controller/home/captin/map_driver_controller.dart +++ b/lib/controller/home/captin/map_driver_controller.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui'; import 'dart:convert'; import 'dart:io'; 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:bubble_head/bubble.dart'; import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart' as geo; import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:intaleq_maps/intaleq_maps.dart'; @@ -20,6 +22,7 @@ import '../../../constant/colors.dart'; import '../../../constant/country_polygons.dart'; import '../../../constant/links.dart'; import '../../../constant/table_names.dart'; +import '../../../env/env.dart'; import '../../../main.dart'; import '../../../print.dart'; import '../../../views/Rate/rate_passenger.dart'; @@ -31,7 +34,8 @@ import '../../functions/location_controller.dart'; import '../../functions/tts.dart'; import 'behavior_controller.dart'; -class MapDriverController extends GetxController { +class MapDriverController extends GetxController + with GetSingleTickerProviderStateMixin { bool isLoading = true; final formKey1 = GlobalKey(); final formKey2 = GlobalKey(); @@ -97,6 +101,26 @@ class MapDriverController extends GetxController { int remainingTimeToShowPassengerInfoWindowFromDriver = 25; int remainingTimeToPassenger = 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> routeSteps = []; + int currentStepIndex = 0; + String currentInstruction = ""; + String nextInstruction = ""; + String distanceToNextStep = ""; + int currentManeuverModifier = 0; + bool _nextInstructionSpoken = false; + bool isTtsEnabled = true; + StreamSubscription? _locationSubscription; + // ───────────────────────────────────────────────────────────────────── bool isDriverNearPassengerStart = false; IntaleqMapController? mapController; late LatLng myLocation; @@ -111,10 +135,7 @@ class MapDriverController extends GetxController { LatLng latLngPassengerLocation = LatLng(0, 0); late LatLng latLngPassengerDestination = LatLng(0, 0); - List> routeSteps = []; - String currentInstruction = ""; - int currentStepIndex = 0; - bool isTtsEnabled = false; + // في MapDriverController @@ -152,7 +173,9 @@ class MapDriverController extends GetxController { _posSub?.cancel(); _posSub = null; - // mapController?.dispose(); + _animController?.dispose(); + _locationSubscription?.cancel(); + super.onClose(); } @@ -224,7 +247,7 @@ class MapDriverController extends GetxController { if (isCameraLocked && mapController != null) { double bearing = (speedKmh > 5) ? heading : 0.0; // ملاحظة: يمكنك تخزين آخر bearing معروف واستخدامه عند التوقف لتحسين التجربة - _animateCameraToNavigationMode(newLoc, heading); + _animateCameraToNavigationMode(newLoc, bearing); } // 4. فحص التعليمات الصوتية @@ -315,6 +338,8 @@ class MapDriverController extends GetxController { // 2. تنظيف الحالة box.write(BoxName.rideStatus, 'Canceled'); // أو الحالة الافتراضية box.remove(BoxName.rideArguments); + box.remove(BoxName.passengerID); + box.remove(BoxName.rideId); // 3. عرض رسالة للسائق if (Get.isDialogOpen == true) { @@ -379,8 +404,10 @@ class MapDriverController extends GetxController { box.write(BoxName.statusDriverLocation, 'blocked'); // عرض رسالة العقوبة - Get.snackbar("Your account is temporarily restricted ⛔".tr, - "Due to excessive cancellations (3 times), receiving orders has been suspended for 4 hours.".tr, + Get.snackbar( + "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), backgroundColor: Colors.red, colorText: Colors.white, @@ -396,6 +423,8 @@ class MapDriverController extends GetxController { // تنظيف البيانات box.remove(BoxName.rideArgumentsFromBackground); box.remove(BoxName.rideArguments); + box.remove(BoxName.passengerID); + box.remove(BoxName.rideId); box.write(BoxName.rideStatus, 'Cancel'); // تسجيل محلي (اختياري) @@ -726,10 +755,10 @@ class MapDriverController extends GetxController { // إغلاق مؤشر التحميل لأننا حصلنا على النتيجة if (Get.isDialogOpen == true) { - navigatorKey.currentState?.pop(); + Get.back(); } - if (distanceToPassenger < 100) { + if (distanceToPassenger < 150) { // زدت المسافة قليلاً لمرونة أكبر (150م) // --- أ) تحديث الحالة المحلية (Optimistic Update) --- @@ -772,16 +801,26 @@ class MapDriverController extends GetxController { }); } else { // --- حالة الرفض (بعيد جداً) --- - MyDialog().getDialog('You are far from passenger location'.tr, - 'Please go closer to the passenger location (less than 150m)'.tr, - () { - // الديالوج يغلق نفسه الآن تلقائياً - }); + showDialog( + 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) { // تنظيف اللودينج في حال حدوث خطأ غير متوقع if (Get.isDialogOpen == true) { - navigatorKey.currentState?.pop(); + Get.back(); } Log.print("Error starting ride: $e"); Get.snackbar("Error", "Could not start ride. Please check internet."); @@ -1066,6 +1105,8 @@ class MapDriverController extends GetxController { isPriceWindow = false; box.write(BoxName.rideStatus, 'Finished'); box.write(BoxName.statusDriverLocation, 'off'); + box.remove(BoxName.passengerID); + box.remove(BoxName.rideId); // 4. حساب التكلفة النهائية (Logic) _calculateFinalTotalCost(); @@ -1382,7 +1423,7 @@ class MapDriverController extends GetxController { // 🟢 2. المنطق المتغير const double longTripPerMin = 600.0; - const double mediumDistThresholdKm = 25.0; + const double longDistThresholdKm = 35.0; // نسبة التخفيض @@ -1588,9 +1629,26 @@ class MapDriverController extends GetxController { if (mapController == null) return; try { - // 1. طلب المسار من الباكيج - final response = - await mapController!.getDirections(origin, destination, steps: true); + // 1. طلب المسار من السيرفر الموحد (SaaS) لضمان الدقة وتفادي الـ 401 + final saasUrl = Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: { + '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 المباشر (الذي أرسله المستخدم) // إذا كان الـ response يحتوي على الحقول مباشرة في الجذر @@ -1628,41 +1686,36 @@ class MapDriverController extends GetxController { color: routeColor, )); - // د) معالجة الخطوات (Legs & Steps) إذا توفرت في الـ JSON - List legs = response['legs'] ?? []; - if (legs.isNotEmpty) { - final stepsList = - List>.from(legs[0]['steps'] ?? []); + // د) معالجة الخطوات (Instructions) للسيرفر الموحد + final List instructions = response['instructions'] ?? []; + if (instructions.isNotEmpty) { + routeSteps = List>.from(instructions.map((e) { + int endIdx = (e['interval'] as List)[1]; + // التأكد من أن الـ index لا يتجاوز طول المسار + if (endIdx >= fullRoute.length) endIdx = fullRoute.length - 1; - for (var step in stepsList) { - step['html_instructions'] = _createInstructionFromManeuverSmart(step); - - 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; + return { + 'html_instructions': e['text'] ?? "", + 'sign': e['sign'] ?? 0, + 'end_location': { + 'lat': fullRoute[endIdx].latitude, + 'lng': fullRoute[endIdx].longitude, } - } - } + }; + })); - routeSteps = stepsList; currentStepIndex = 0; + currentInstruction = routeSteps[0]['html_instructions']; + currentManeuverModifier = routeSteps[0]['sign']; + _nextInstructionSpoken = false; - // نطق أول تعليمة - if (routeSteps.isNotEmpty) { - currentInstruction = routeSteps[0]['html_instructions']; - if (Get.isRegistered()) { - Get.find().speakText(currentInstruction); - } + if (Get.isRegistered() && isTtsEnabled) { + Get.find().speakText(currentInstruction); } } else { - // في حال عدم وجود steps، نقوم بتصفيرها routeSteps = []; currentInstruction = ""; + currentManeuverModifier = 0; } // هـ) تحريك الكاميرا لتشمل المسار @@ -1678,119 +1731,7 @@ class MapDriverController extends GetxController { } } - // 🔥 دالة الترجمة المحسنة (من NavigationController) - String _createInstructionFromManeuverSmart(Map 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 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 list) { assert(list.isNotEmpty); double? x0, x1, y0, y1; @@ -1809,66 +1750,7 @@ class MapDriverController extends GetxController { 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() - ? Get.find().speakText(currentInstruction) - : Get.put(TextToSpeechController()).speakText(currentInstruction); - - // -->> هنا يتم تحديث لون المسار <<-- - _updateTraveledPath(); - - _fitToBounds(_stepBounds[currentStepIndex], - padding: 80); // تقريب الكاميرا على الخطوة التالية - update(); - } // داخل MapDriverController Future markDriverAsArrived() async { @@ -1918,53 +1800,7 @@ class MapDriverController extends GetxController { } } - void _updateTraveledPath() { - // استخراج كل النقاط للخطوات التي تم اجتيازها - List pointsForTraveledSteps = []; - for (int i = 0; i < currentStepIndex; i++) { - final stepPolyline = activeRouteSteps[i]['polyline']['points']; - pointsForTraveledSteps.addAll(decodePolylineToLatLng(stepPolyline)); - } - traveledPathPoints.assignAll(pointsForTraveledSteps); - } - void _prepareStepData(List> 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 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 decodePolylineToLatLng(String polylineString) { - // 1. Decode the string into a list of number lists (e.g., [[lat, lng], ...]) - List decodedPoints = PolylineUtils.decode(polylineString); - - return decodedPoints; - } // ================================================================= // 4. منطق الأداء الذكي (Smart Performance Logic) @@ -1981,7 +1817,8 @@ class MapDriverController extends GetxController { void _suggestOptimization() { Get.snackbar( "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), mainButton: TextButton( child: Text("Yes, optimize".tr), @@ -1998,13 +1835,7 @@ class MapDriverController extends GetxController { // ================================================================= // 5. دوال مساعدة (Helper Functions) // ================================================================= - void _resetRouteState() { - activeRouteSteps.clear(); - traveledPathPoints.clear(); - upcomingPathPoints.clear(); - _allPointsForActiveRoute.clear(); - currentStepIndex = 0; - } + String _parseInstruction(String html) => 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) { // هافرساين مبسطة @@ -2188,6 +2010,10 @@ class MapDriverController extends GetxController { durationOfRideValue = Get.arguments['durationOfRideValue']; paymentAmount = Get.arguments['paymentAmount']; paymentMethod = Get.arguments['paymentMethod']; + + // 🔥 حفظ البيانات في الذاكرة المحلية فوراً (لفصل السوكيت عن الكنترولر) + box.write(BoxName.passengerID, passengerId.toString()); + box.write(BoxName.rideId, rideId.toString()); isHaveSteps = Get.arguments['isHaveSteps']; step0 = Get.arguments['step0']; step1 = Get.arguments['step1']; @@ -2208,7 +2034,7 @@ class MapDriverController extends GetxController { Get.find().myLocation.latitude.toString(); String lng = Get.find().myLocation.longitude.toString(); - String origin = '$lat,$lng'; + // Set the origin and destination coordinates for the Google Maps directions request. Future.delayed(const Duration(seconds: 1)); getRoute( @@ -2222,7 +2048,7 @@ class MapDriverController extends GetxController { } } - latlng(String passengerLocation, passengerDestination) { + void latlng(String passengerLocation, String passengerDestination) { double latPassengerLocation = double.parse(passengerLocation.toString().split(',')[0]); double lngPassengerLocation = @@ -2257,7 +2083,6 @@ class MapDriverController extends GetxController { @override void onInit() async { mapAPIKEY = await storage.read(key: BoxName.mapAPIKEY); - // Get the passenger location from the arguments. await argumentLoading(); Get.put(FirebaseMessagesController()); runGoogleMapDirectly(); @@ -2265,24 +2090,141 @@ class MapDriverController extends GetxController { addCustomPassengerIcon(); addCustomStartIcon(); addCustomEndIcon(); + if (!Get.isRegistered()) { 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(); - // durationToAdd = Duration(seconds: int.parse(duration)); durationToAdd = Duration(seconds: parseDurationToInt(duration)); hours = durationToAdd.inHours; minutes = (durationToAdd.inMinutes % 60).round(); calculateConsumptionFuel(); - // updateLocation();// for now to test it - // cancelCheckRidefromPassenger(); - // checkIsDriverNearPassenger(); 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().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) { if (value == null) return 0; String text = value.toString(); diff --git a/lib/main.dart b/lib/main.dart index 3006e39..ce296f2 100755 --- a/lib/main.dart +++ b/lib/main.dart @@ -523,7 +523,7 @@ class _MyAppState extends State with WidgetsBindingObserver { Widget build(BuildContext context) { final LocaleController localController = Get.put(LocaleController()); final SettingController settingController = Get.put(SettingController()); - + return GetMaterialApp( navigatorKey: navigatorKey, title: AppInformation.appName, @@ -532,8 +532,8 @@ class _MyAppState extends State with WidgetsBindingObserver { locale: localController.language, theme: localController.lightTheme, darkTheme: localController.darkTheme, - themeMode: settingController.isDarkMode ? ThemeMode.dark : ThemeMode.light, - + themeMode: + settingController.isDarkMode ? ThemeMode.dark : ThemeMode.light, initialRoute: '/', getPages: [ GetPage(name: '/', page: () => SplashScreen()), @@ -545,4 +545,3 @@ class _MyAppState extends State with WidgetsBindingObserver { ); } } - diff --git a/lib/print.dart b/lib/print.dart index 63efb2d..a3d59f6 100755 --- a/lib/print.dart +++ b/lib/print.dart @@ -4,7 +4,7 @@ class Log { Log._(); 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) { diff --git a/lib/views/Rate/ride_calculate_driver.dart b/lib/views/Rate/ride_calculate_driver.dart index 2986eca..462ccd3 100755 --- a/lib/views/Rate/ride_calculate_driver.dart +++ b/lib/views/Rate/ride_calculate_driver.dart @@ -19,295 +19,259 @@ class RideCalculateDriver extends StatelessWidget { return MyScafolld( title: 'Ride Summaries'.tr, body: [ - Center( - child: GetBuilder( - builder: (durationController) => durationController.isLoading - ? const Center(child: MyCircularProgressIndicator()) - : durationController.jsonData1.isEmpty || - durationController.jsonData2.isEmpty - ? Center( - child: Text('No data yet!'.tr), - ) - : ListView( - // mainAxisAlignment: MainAxisAlignment.start, - // crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - '${'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, - textAlign: TextAlign.center, - ), - Padding( - padding: const EdgeInsets.all(6), - child: Container( - decoration: AppStyle.boxDecoration1, - height: Get.height * .4, - child: LineChart( - duration: const Duration(milliseconds: 150), - curve: Curves.ease, - LineChartData( - lineBarsData: [ - LineChartBarData( - isStepLineChart: true, - spots: durationController.chartData, - isCurved: true, - color: Colors - .deepPurpleAccent, // Custom color - barWidth: 3, // Thinner line - dotData: const FlDotData( - show: - true), // Show dots on each point - belowBarData: BarAreaData( - // Add gradient fill below the line - show: true, - color: AppColor.deepPurpleAccent, - ), - isStrokeJoinRound: true, - shadow: const BoxShadow( - color: AppColor.yellowColor, - blurRadius: 4, - offset: Offset(2, 2), - ), - ), - ], - showingTooltipIndicators: const [], - titlesData: FlTitlesData( - show: true, - topTitles: AxisTitles( - axisNameWidget: Text( - 'Days'.tr, - style: AppStyle.title, - ), - axisNameSize: 30, - ), - bottomTitles: AxisTitles( - axisNameWidget: Text( - 'Total Hours on month'.tr, - style: AppStyle.title, - ), - axisNameSize: 30, - sideTitles: const SideTitles( - reservedSize: 30, - showTitles: true)), - leftTitles: AxisTitles( - axisNameWidget: Text( - 'Counts of Hours on days'.tr, - style: AppStyle.title, - ), - axisNameSize: 30, - sideTitles: const SideTitles( - reservedSize: 30, - showTitles: true)), - ), - gridData: const FlGridData( - show: true, - ), - borderData: FlBorderData( - show: true, - border: const Border( - bottom: BorderSide( - color: AppColor.accentColor), - left: BorderSide( - color: AppColor.accentColor), - ), - ), + GetBuilder( + builder: (durationController) { + if (durationController.isLoading) { + return const Center(child: MyCircularProgressIndicator()); + } + + 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), + ); + } + + return ListView( + children: [ + if (hasDurations) ...[ + Text( + '${'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, + textAlign: TextAlign.center, + ), + Padding( + padding: const EdgeInsets.all(6), + child: Container( + decoration: AppStyle.boxDecoration1, + height: Get.height * .4, + child: LineChart( + duration: const Duration(milliseconds: 150), + curve: Curves.ease, + LineChartData( + lineBarsData: [ + LineChartBarData( + isStepLineChart: true, + spots: durationController.chartData, + isCurved: true, + color: Colors.deepPurpleAccent, + barWidth: 3, + dotData: const FlDotData(show: true), + belowBarData: BarAreaData( + show: true, + color: AppColor.deepPurpleAccent, ), + isStrokeJoinRound: true, + shadow: const BoxShadow( + color: AppColor.yellowColor, + blurRadius: 4, + offset: Offset(2, 2), + ), + ), + ], + showingTooltipIndicators: const [], + titlesData: FlTitlesData( + show: true, + topTitles: AxisTitles( + axisNameWidget: + Text('Days'.tr, style: AppStyle.title), + axisNameSize: 30, + ), + bottomTitles: AxisTitles( + axisNameWidget: Text( + 'Total Hours on month'.tr, + style: AppStyle.title), + axisNameSize: 30, + sideTitles: const SideTitles( + reservedSize: 30, showTitles: true)), + leftTitles: AxisTitles( + axisNameWidget: Text( + 'Counts of Hours on days'.tr, + style: AppStyle.title), + axisNameSize: 30, + sideTitles: const SideTitles( + reservedSize: 30, showTitles: true)), + ), + gridData: const FlGridData(show: true), + borderData: FlBorderData( + show: true, + border: const Border( + bottom: BorderSide(color: AppColor.accentColor), + left: BorderSide(color: AppColor.accentColor), ), ), ), - const SizedBox( - height: 5, - ), - Padding( - padding: const EdgeInsets.all(6), - child: Container( - decoration: AppStyle.boxDecoration1, - height: Get.height * .4, - child: LineChart( - duration: const Duration(milliseconds: 150), - curve: Curves.ease, - LineChartData( - lineBarsData: [ - LineChartBarData( - spots: durationController.chartRideCount, - // isCurved: true, - color: Colors - .deepPurpleAccent, // Custom color - barWidth: 3, // Thinner line - dotData: const FlDotData( - show: - true), // Show dots on each point - belowBarData: BarAreaData( - // Add gradient fill below the line - show: true, - color: AppColor.deepPurpleAccent, - ), - isStrokeJoinRound: true, - shadow: const BoxShadow( - color: AppColor.yellowColor, - blurRadius: 4, - offset: Offset(2, 2), - ), - ), - ], - showingTooltipIndicators: const [], - titlesData: FlTitlesData( - show: true, - topTitles: AxisTitles( - axisNameWidget: Text( - 'Days'.tr, - style: AppStyle.title, - ), - axisNameSize: 30, - // sideTitles: const SideTitles( - // reservedSize: 30, showTitles: true), - ), - bottomTitles: AxisTitles( - axisNameWidget: Text( - '${"Total rides on month".tr} = ${durationController.jsonData2['message'][0]['totalCount'].toString()}' - .tr, - style: AppStyle.title, - ), - axisNameSize: 30, - sideTitles: const SideTitles( - reservedSize: 30, - showTitles: true)), - leftTitles: AxisTitles( - axisNameWidget: Text( - 'Counts of rides on days'.tr, - style: AppStyle.title, - ), - axisNameSize: 30, - sideTitles: const SideTitles( - reservedSize: 30, - showTitles: true)), - ), - gridData: const FlGridData( - show: true, - ), - borderData: FlBorderData( - show: true, - border: const Border( - bottom: BorderSide( - color: AppColor.accentColor), - left: BorderSide( - color: AppColor.accentColor), - ), - ), - ), - ), - ), - ), - const SizedBox( - height: 5, - ), - Padding( - padding: const EdgeInsets.all(6), - child: Container( - decoration: AppStyle.boxDecoration1, - height: Get.height * .4, - child: LineChart( - duration: const Duration(milliseconds: 150), - curve: Curves.ease, - LineChartData( - lineBarsData: [ - LineChartBarData( - isStepLineChart: true, - spots: durationController - .chartRidePriceDriver, - isCurved: true, - isStrokeCapRound: true, - preventCurveOverShooting: true, - color: Colors - .deepPurpleAccent, // Custom color - barWidth: 3, // Thinner line - dotData: const FlDotData( - show: - true), // Show dots on each point - belowBarData: BarAreaData( - // Add gradient fill below the line - show: true, - color: AppColor.deepPurpleAccent, - ), - isStrokeJoinRound: true, - shadow: const BoxShadow( - color: AppColor.yellowColor, - blurRadius: 4, - offset: Offset(2, 2), - ), - ), - ], - showingTooltipIndicators: const [], - titlesData: FlTitlesData( - show: true, - topTitles: AxisTitles( - axisNameWidget: Text( - 'Days'.tr, - style: AppStyle.title, - ), - axisNameSize: 30, - // sideTitles: const SideTitles( - // reservedSize: 30, showTitles: true), - ), - bottomTitles: AxisTitles( - axisNameWidget: Text( - '${"Total budgets on month".tr} = ${durationController.jsonData2['message'][0]['totalPrice'].toString()}' - .tr, - style: AppStyle.title, - ), - axisNameSize: 30, - sideTitles: const SideTitles( - reservedSize: 30, - showTitles: true)), - leftTitles: AxisTitles( - axisNameWidget: Text( - 'Counts of budgets on days'.tr, - style: AppStyle.title, - ), - axisNameSize: 30, - sideTitles: const SideTitles( - reservedSize: 30, - showTitles: true)), - ), - gridData: const FlGridData( - show: true, - ), - borderData: FlBorderData( - show: true, - border: const Border( - bottom: BorderSide( - color: AppColor.accentColor), - left: BorderSide( - color: AppColor.accentColor), - ), - ), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - decoration: AppStyle.boxDecoration1, - child: durationController.monthlyList.isEmpty - ? SizedBox( - height: Get.height * .2, - child: Center( - child: Text( - "No data yet".tr, - style: AppStyle.title, - ), - ), - ) - : DriverStatsTable( - monthlyList: - durationController.monthlyList, - ))) - ], + ), ), + ), + ], + if (hasRides) ...[ + const SizedBox(height: 5), + Padding( + padding: const EdgeInsets.all(6), + child: Container( + decoration: AppStyle.boxDecoration1, + height: Get.height * .4, + child: LineChart( + duration: const Duration(milliseconds: 150), + curve: Curves.ease, + LineChartData( + lineBarsData: [ + LineChartBarData( + spots: durationController.chartRideCount, + color: Colors.deepPurpleAccent, + barWidth: 3, + dotData: const FlDotData(show: true), + belowBarData: BarAreaData( + show: true, + color: AppColor.deepPurpleAccent, + ), + isStrokeJoinRound: true, + shadow: const BoxShadow( + color: AppColor.yellowColor, + blurRadius: 4, + offset: Offset(2, 2), + ), + ), + ], + showingTooltipIndicators: const [], + titlesData: FlTitlesData( + show: true, + topTitles: AxisTitles( + axisNameWidget: + Text('Days'.tr, style: AppStyle.title), + axisNameSize: 30, + ), + bottomTitles: AxisTitles( + axisNameWidget: Text( + '${"Total rides on month".tr} = ${durationController.jsonData2['message'][0]['totalCount'] ?? 0}' + .tr, + style: AppStyle.title, + ), + axisNameSize: 30, + sideTitles: const SideTitles( + reservedSize: 30, showTitles: true)), + leftTitles: AxisTitles( + axisNameWidget: Text( + 'Counts of rides on days'.tr, + style: AppStyle.title), + axisNameSize: 30, + sideTitles: const SideTitles( + reservedSize: 30, showTitles: true)), + ), + gridData: const FlGridData(show: true), + borderData: FlBorderData( + show: true, + border: const Border( + bottom: BorderSide(color: AppColor.accentColor), + left: BorderSide(color: AppColor.accentColor), + ), + ), + ), + ), + ), + ), + const SizedBox(height: 5), + Padding( + padding: const EdgeInsets.all(6), + child: Container( + decoration: AppStyle.boxDecoration1, + height: Get.height * .4, + child: LineChart( + duration: const Duration(milliseconds: 150), + curve: Curves.ease, + LineChartData( + lineBarsData: [ + LineChartBarData( + isStepLineChart: true, + spots: durationController.chartRidePriceDriver, + isCurved: true, + isStrokeCapRound: true, + preventCurveOverShooting: true, + color: Colors.deepPurpleAccent, + barWidth: 3, + dotData: const FlDotData(show: true), + belowBarData: BarAreaData( + show: true, + color: AppColor.deepPurpleAccent, + ), + isStrokeJoinRound: true, + shadow: const BoxShadow( + color: AppColor.yellowColor, + blurRadius: 4, + offset: Offset(2, 2), + ), + ), + ], + showingTooltipIndicators: const [], + titlesData: FlTitlesData( + show: true, + topTitles: AxisTitles( + axisNameWidget: + Text('Days'.tr, style: AppStyle.title), + axisNameSize: 30, + ), + bottomTitles: AxisTitles( + axisNameWidget: Text( + '${"Total budgets on month".tr} = ${durationController.jsonData2['message'][0]['totalPrice'] ?? 0}' + .tr, + style: AppStyle.title, + ), + axisNameSize: 30, + sideTitles: const SideTitles( + reservedSize: 30, showTitles: true)), + leftTitles: AxisTitles( + axisNameWidget: Text( + 'Counts of budgets on days'.tr, + style: AppStyle.title), + axisNameSize: 30, + sideTitles: const SideTitles( + reservedSize: 30, showTitles: true)), + ), + gridData: const FlGridData(show: true), + borderData: FlBorderData( + show: true, + border: const Border( + bottom: BorderSide(color: AppColor.accentColor), + left: BorderSide(color: AppColor.accentColor), + ), + ), + ), + ), + ), + ), + ], + Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + decoration: AppStyle.boxDecoration1, + child: !hasStats + ? SizedBox( + height: Get.height * .2, + child: Center( + child: Text( + "No statistics yet".tr, + style: AppStyle.title, + ), + ), + ) + : DriverStatsTable( + monthlyList: durationController.monthlyList, + ))) + ], + ); + }, ) - // BarChartWidget(), - ), + // BarChartWidget(), ], isleading: true); } diff --git a/lib/views/auth/captin/otp_page.dart b/lib/views/auth/captin/otp_page.dart index 8ab90d2..c00a639 100644 --- a/lib/views/auth/captin/otp_page.dart +++ b/lib/views/auth/captin/otp_page.dart @@ -296,17 +296,17 @@ class PhoneNumberScreen extends StatefulWidget { } class _PhoneNumberScreenState extends State { - final _phoneController = TextEditingController(); final _formKey = GlobalKey(); bool _isLoading = false; + String _completePhone = ''; void _submit() async { if (_formKey.currentState!.validate()) { 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); if (success && mounted) { - // Get.to(() => OtpVerificationScreen(phoneNumber: rawPhone)); await PhoneAuthHelper.verifyOtp(rawPhone); } if (mounted) setState(() => _isLoading = false); @@ -351,7 +351,7 @@ class _PhoneNumberScreenState extends State { ), initialCountryCode: 'SY', onChanged: (phone) { - _phoneController.text = phone.completeNumber; + _completePhone = phone.completeNumber; }, validator: (phone) { if (phone == null || phone.number.isEmpty) { diff --git a/lib/views/home/Captin/driver_map_page.dart b/lib/views/home/Captin/driver_map_page.dart index a859a74..abc817c 100755 --- a/lib/views/home/Captin/driver_map_page.dart +++ b/lib/views/home/Captin/driver_map_page.dart @@ -184,7 +184,7 @@ class InstructionsOfRoads extends StatelessWidget { color: AppColor.primaryColor, shape: BoxShape.circle, ), - child: const Icon(Icons.turn_right_rounded, + child: Icon(controller.currentManeuverIcon, color: Colors.white, size: 24), ), const SizedBox(width: 14), @@ -193,8 +193,8 @@ class InstructionsOfRoads extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "NEXT STEP".tr, + Text( + "${"NEXT STEP".tr} (${controller.distanceToNextStep})", style: theme.textTheme.labelSmall?.copyWith( color: theme.hintColor, fontWeight: FontWeight.bold, diff --git a/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart b/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart index 42146ba..3ac7335 100755 --- a/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart +++ b/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart @@ -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/controller/firebase/local_notification.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/orderCaptin/vip_order_page.dart'; +import 'package:sefer_driver/views/auth/syria/registration_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_font_icons/flutter_font_icons.dart'; import 'package:get/get.dart'; import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart'; import 'package:sefer_driver/views/widgets/mydialoug.dart'; -import '../../../../../constant/colors.dart'; import '../../../../../constant/links.dart'; import '../../../../../controller/firebase/notification_service.dart'; import '../../../../../controller/functions/crud.dart'; import '../../../../../controller/home/captin/order_request_controller.dart'; -import '../../../../../controller/home/navigation/navigation_view.dart'; import '../../../../../print.dart'; import '../../../../Rate/ride_calculate_driver.dart'; @@ -78,7 +76,8 @@ GetBuilder leftMainMenuCaptainIcons() { tooltip: 'Active Ride'.tr, onTap: () async { await checkForPendingOrderFromServer(); - if (box.read(BoxName.rideArgumentsFromBackground) != 'failure') { + if (box.read(BoxName.rideArgumentsFromBackground) != + 'failure') { Get.to( () => PassengerLocationMapPage(), arguments: box.read(BoxName.rideArgumentsFromBackground), @@ -91,6 +90,13 @@ GetBuilder 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), @@ -102,11 +108,21 @@ GetBuilder leftMainMenuCaptainIcons() { tooltip: 'Earnings'.tr, onTap: () { 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 || now.difference(lastTime).inMinutes >= 2) { - box.write(BoxName.lastTimeStaticThrottle, now); + box.write( + BoxName.lastTimeStaticThrottle, now.toIso8601String()); Get.to(() => RideCalculateDriver()); } else { final left = 2 - now.difference(lastTime).inMinutes; @@ -133,6 +149,16 @@ GetBuilder leftMainMenuCaptainIcons() { 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()), + // ), ], ), ); diff --git a/lib/views/home/Captin/mapDriverWidgets/google_driver_map_page.dart b/lib/views/home/Captin/mapDriverWidgets/google_driver_map_page.dart index 96290f9..c1acf13 100755 --- a/lib/views/home/Captin/mapDriverWidgets/google_driver_map_page.dart +++ b/lib/views/home/Captin/mapDriverWidgets/google_driver_map_page.dart @@ -5,6 +5,7 @@ import 'package:sefer_driver/constant/api_key.dart'; import '../../../../controller/functions/location_controller.dart'; import '../../../../controller/home/captin/map_driver_controller.dart'; +import '../../../../controller/profile/setting_controller.dart'; class GoogleDriverMap extends StatelessWidget { const GoogleDriverMap({ @@ -27,6 +28,11 @@ class GoogleDriverMap extends StatelessWidget { onMapCreated: (mapController) { controller.onMapCreated(mapController); }, + mapType: Get.isRegistered() + ? (Get.find().isMapDarkMode + ? IntaleqMapType.normal + : IntaleqMapType.light) + : IntaleqMapType.light, zoomControlsEnabled: false, initialCameraPosition: CameraPosition( target: locationController.myLocation, @@ -43,8 +49,8 @@ class GoogleDriverMap extends StatelessWidget { markers: { Marker( markerId: MarkerId('MyLocation'.tr), - position: locationController.myLocation, - rotation: locationController.heading, + position: controller.smoothedLocation ?? locationController.myLocation, + rotation: controller.smoothedHeading, flat: true, anchor: const Offset(0.5, 0.5), icon: controller.carIcon, diff --git a/lib/views/notification/available_rides_page.dart b/lib/views/notification/available_rides_page.dart index 920e6c8..4545d33 100755 --- a/lib/views/notification/available_rides_page.dart +++ b/lib/views/notification/available_rides_page.dart @@ -281,7 +281,7 @@ class RideAvailableCard extends StatelessWidget { Get.back(); // 3. تحليل الرد - var jsonResponse = jsonDecode(response); + var jsonResponse = response; if (jsonResponse['status'] == 'success') { // ✅ نجاح: أنت الفائز بالرحلة