diff --git a/backend/ride/location/save_driver_destination.php b/backend/ride/location/save_driver_destination.php index 6cd7f96d..123a7814 100644 --- a/backend/ride/location/save_driver_destination.php +++ b/backend/ride/location/save_driver_destination.php @@ -19,6 +19,37 @@ $destLat = filterRequest('destination_lat') ?? filterRequest('target_latitude') $destLng = filterRequest('destination_lng') ?? filterRequest('target_longitude'); $destName = filterRequest('destination_name') ?? 'Destination'; +function notifySocketServerDestination($userId, $hasDestination, $destLat = '', $destLng = '', $destName = '') { + $url = getenv('LOCATION_SOCKET_URL'); + $internalKey = function_exists('getInternalSocketKey') ? getInternalSocketKey() : ''; + + if (empty($url)) { + error_log("[save_driver_destination] LOCATION_SOCKET_URL env variable not set"); + return; + } + + $postData = [ + 'action' => 'update_driver_destination', + 'driver_id' => (string)$userId, + 'has_destination' => (int)$hasDestination, + 'destination_lat' => (string)$destLat, + 'destination_lng' => (string)$destLng, + 'destination_name' => (string)$destName, + ]; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT_MS, 400); + if ($internalKey) { + curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-internal-key: $internalKey"]); + } + curl_exec($ch); + curl_close($ch); +} + try { if ($action === 'get') { $stmtGet = $con->prepare(" @@ -47,6 +78,10 @@ try { AND is_active = 1 "); $stmtDeactivate->execute([':did' => $user_id]); + + // Sync with Redis on Socket Server + notifySocketServerDestination($user_id, 0); + jsonSuccess(null, "تم إلغاء تفعيل الوجهة الشخصية بنجاح."); exit; } @@ -56,19 +91,34 @@ try { jsonError("Missing required parameters: destination_lat and destination_lng are required."); } - // 3. Enforce Limit: Max 2 times daily + // 3. Enforce Limit: Max 3 times daily + // Check local Redis rate limit first + if (isset($redis)) { + $redisKey = "driver:dest_count:" . $user_id; + $redisCount = intval($redis->get($redisKey)); + if ($redisCount >= 3) { + jsonError("حسناً كابتن، لقد وصلت للحد الأقصى المسموح به لتحديد الوجهة اليوم (3 مرات في اليوم)."); + } + } + + // Check MySQL database limit $stmtCount = $con->prepare(" SELECT COUNT(*) FROM driver_destinations WHERE driver_id = :did - AND usage_date = CURDATE() - AND is_active = 1 + AND usage_date = CURDATE() "); $stmtCount->execute([':did' => $user_id]); $dailyCount = intval($stmtCount->fetchColumn()); - if ($dailyCount >= 2) { - jsonError("حسناً كابتن، لقد وصلت للحد الأقصى المسموح به لتحديد الوجهة اليوم (مرتان في اليوم)."); + if ($dailyCount >= 3) { + // Sync Redis counter just in case + if (isset($redis)) { + $redisKey = "driver:dest_count:" . $user_id; + $redis->set($redisKey, $dailyCount); + $redis->expire($redisKey, 86400); // 24 hours TTL + } + jsonError("حسناً كابتن، لقد وصلت للحد الأقصى المسموح به لتحديد الوجهة اليوم (3 مرات في اليوم)."); } // 4. Deactivate previous active destinations for this driver @@ -93,6 +143,16 @@ try { ':name' => $destName ]); + // Sync with Redis on Socket Server + notifySocketServerDestination($user_id, 1, $destLat, $destLng, $destName); + + // Increment local Redis counter + if (isset($redis)) { + $redisKey = "driver:dest_count:" . $user_id; + $redis->incr($redisKey); + $redis->expire($redisKey, 86400); // 24 hours TTL + } + jsonSuccess(null, "تم حفظ وجهتك كابتن بنجاح! سيتم توجيه الطلبات المطابقة لوجهتك."); } catch (Exception $e) { diff --git a/backend/ride/rides/add_ride.php b/backend/ride/rides/add_ride.php index eb48b32b..8d39c5e9 100644 --- a/backend/ride/rides/add_ride.php +++ b/backend/ride/rides/add_ride.php @@ -19,11 +19,12 @@ try { function broadcastRideToMarket($rideId, $lat, $lng, $payloadData) { $url = getenv('LOCATION_SOCKET_URL'); $INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : ''; - $marketPayload = [ 'id' => (string)$rideId, 'start_lat' => $lat, 'start_lng' => $lng, + 'end_lat' => $payloadData[3], + 'end_lng' => $payloadData[4], 'price' => $payloadData[2], 'carType' => $payloadData[31], 'startName' => $payloadData[29], diff --git a/siro_driver/lib/constant/box_name.dart b/siro_driver/lib/constant/box_name.dart index b6aecb4c..12357ad1 100755 --- a/siro_driver/lib/constant/box_name.dart +++ b/siro_driver/lib/constant/box_name.dart @@ -14,6 +14,7 @@ class BoxName { static const String hmac = "hmac"; static const String ttsEnabled = "ttsEnabled"; static const String deviceFingerprint = "deviceFingerprint"; + static const String deviceFpEncrypted = "deviceFpEncrypted"; static const String security_check = "security_check"; static const String rideType = "rideType"; static const String walletType = "walletType"; diff --git a/siro_driver/lib/controller/auth/captin/login_captin_controller.dart b/siro_driver/lib/controller/auth/captin/login_captin_controller.dart index acaad342..81548cd4 100755 --- a/siro_driver/lib/controller/auth/captin/login_captin_controller.dart +++ b/siro_driver/lib/controller/auth/captin/login_captin_controller.dart @@ -104,7 +104,8 @@ class LoginDriverController extends GetxController { isPhoneVerified() async { // If the phone is already verified locally and we have a driver ID, skip the API check // This prevents asking for OTP again if the user restarts the app during registration. - if (box.read(BoxName.phoneVerified) == '1' && box.read(BoxName.driverID) != null) { + if (box.read(BoxName.phoneVerified) == '1' && + box.read(BoxName.driverID) != null) { Get.offAll(() => RegistrationView()); return; } @@ -177,6 +178,34 @@ class LoginDriverController extends GetxController { getJWT() async { await EncryptionHelper.initialize(); + + // 1. Check secure storage first to avoid redundant API calls + String? secureJwt = await storage.read(key: BoxName.jwt); + if (secureJwt != null && secureJwt.isNotEmpty) { + bool isTokenValid = false; + try { + final parts = secureJwt.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) { + // Check if token is valid with a 30-second buffer + isTokenValid = DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000); + } + } + } catch (_) {} + + if (isTokenValid) { + Log.print('🔑 Valid JWT found in secure storage. Skipping generation.'); + return; + } + } + dev = Platform.isAndroid ? 'android' : 'ios'; Log.print( 'box.read(BoxName.firstTimeLoadKey): ${box.read(BoxName.firstTimeLoadKey)}'); @@ -209,8 +238,8 @@ class LoginDriverController extends GetxController { } if (jwt != null) { - box.write(BoxName.jwt, c(jwt)); - await storage.write(key: BoxName.jwt, value: c(jwt)); + // box.write(BoxName.jwt, c(jwt)); + await storage.write(key: BoxName.jwt, value: jwt); } // ✅ بعد التأكد أن كل المفاتيح موجودة @@ -249,8 +278,8 @@ class LoginDriverController extends GetxController { } if (jwt != null) { - await box.write(BoxName.jwt, c(jwt)); - await storage.write(key: BoxName.jwt, value: c(jwt)); + // await box.write(BoxName.jwt, c(jwt)); + await storage.write(key: BoxName.jwt, value: jwt); } // await AppInitializer().getKey(); @@ -266,32 +295,6 @@ class LoginDriverController extends GetxController { update(); } - String generateUniqueIdFromEmail(String email) { - // Step 1: Extract the local part of the email - String localPart = email.split('@')[0]; - - // Step 2: Replace invalid characters (if any) - String cleanLocalPart = localPart.replaceAll(RegExp(r'[^a-zA-Z0-9]'), ''); - - // Step 3: Ensure it does not exceed 24 characters - if (cleanLocalPart.length > 24) { - cleanLocalPart = cleanLocalPart.substring(0, 24); - } - - // Step 4: Generate a random suffix if needed - String suffix = generateRandomSuffix(24 - cleanLocalPart.length); - - return cleanLocalPart + suffix; - } - - String generateRandomSuffix(int length) { - const String chars = - 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - Random random = Random(); - return List.generate(length, (index) => chars[random.nextInt(chars.length)]) - .join(''); - } - bool isInviteDriverFound = false; Future updateInvitationCodeFromRegister() async { @@ -335,7 +338,8 @@ class LoginDriverController extends GetxController { // await SecurityHelper.performSecurityChecks(); // Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}'); // await getJWT(); - var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID}); + var res = await CRUD().get( + link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID}); Log.print('loginDriver: ${res}'); if (res == 'failure') { await isPhoneVerified(); @@ -391,10 +395,10 @@ class LoginDriverController extends GetxController { // ✅ الحصول على توكن access بدل registration قبل أي طلبات بعد تسجيل الدخول Log.print('🔑 Getting access token after login...'); String fingerPrint = await DeviceHelper.getDeviceFingerprint(); - await storage.write( - key: BoxName.fingerPrint, value: fingerPrint.toString()); - await getJWT(); - Log.print('🔑 Access token obtained.'); + // await storage.write( + // key: BoxName.fingerPrint, value: fingerPrint.toString()); + // await getJWT(); + // Log.print('🔑 Access token obtained.'); // add invitations if (box.read(BoxName.isInstall) == null || @@ -416,10 +420,15 @@ class LoginDriverController extends GetxController { // '963992952235@intaleqapp.com') { if (token != 'failure') { var serverData = jsonDecode(token); - if ((serverData['data'][0]['token'].toString()) != - box.read(BoxName.tokenDriver).toString() || - serverData['data'][0]['fingerPrint'].toString() != - fingerPrint.toString()) { + final serverToken = serverData['data'][0]['token'].toString(); + final storedToken = + box.read(BoxName.tokenDriver)?.toString() ?? ''; + final serverFp = serverData['data'][0]['fingerPrint'].toString(); + final tokenMismatch = + storedToken.isNotEmpty && serverToken != storedToken; + Log.print( + '🔍 [FP_COMPARE] serverFp: $serverFp | localFp: ${fingerPrint.toString()} | tokenMismatch: $tokenMismatch | serverToken: $serverToken | storedToken: $storedToken'); + if (tokenMismatch) { Get.defaultDialog( barrierDismissible: false, title: 'Device Change Detected'.tr, @@ -485,7 +494,8 @@ class LoginDriverController extends GetxController { // await SecurityHelper.performSecurityChecks(); // Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}'); - var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID}); + var res = await CRUD().get( + link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID}); // print('res is $res'); // if (res == 'failure') { @@ -594,7 +604,7 @@ class LoginDriverController extends GetxController { box.write(BoxName.jwt, c(jwt)); await storage.write(key: BoxName.jwt, value: c(jwt)); } - + box.write(BoxName.emailDriver, (d['email'])); box.write(BoxName.driverID, (d['id'])); box.write(BoxName.isTest, '1'); diff --git a/siro_driver/lib/controller/functions/crud.dart b/siro_driver/lib/controller/functions/crud.dart index 7078c322..33a83afa 100755 --- a/siro_driver/lib/controller/functions/crud.dart +++ b/siro_driver/lib/controller/functions/crud.dart @@ -11,6 +11,7 @@ import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:siro_driver/env/env.dart'; import 'package:siro_driver/print.dart'; +import 'package:siro_driver/controller/functions/package_info.dart'; import '../../constant/api_key.dart'; import '../../views/widgets/error_snakbar.dart'; @@ -21,7 +22,6 @@ import 'ssl_pinning.dart'; class CRUD { final NetGuard _netGuard = NetGuard(); final _client = SslPinning.createPinnedClient(); - static bool _isRefreshingJWT = false; static String _lastErrorSignature = ''; static DateTime _lastErrorTimestamp = DateTime(2000); @@ -93,15 +93,15 @@ class CRUD { // نفس القيمة المرسلة عند login وعُملها hash في JWT // السيرفر يتحقق: sha256(X-Device-FP + FP_PEPPER) == JWT.fingerPrint // ───────────────────────────────────────────────────────────── - String _getFpHeader() { - return box.read(BoxName.deviceFingerprint)?.toString() ?? ''; + Future _getFpHeader() async { + return box.read(BoxName.deviceFingerprint)?.toString() ?? await DeviceHelper.getDeviceFingerprint(); } - String _getJwt() { + Future _getJwt() async { try { - final jwt = box.read(BoxName.jwt); + final jwt = await storage.read(key: BoxName.jwt); if (jwt == null || jwt.toString().isEmpty) return ''; - return r(jwt).toString().split(Env.addd)[0]; + return jwt; } catch (_) { return ''; } @@ -172,6 +172,7 @@ class CRUD { final body = response.body; Log.print('📥 [RES-$requestId] [$sc] $link'); + Log.print('payload: $payload'); Log.print('📄 [BODY-$requestId] $body'); // 2xx @@ -217,14 +218,14 @@ class CRUD { required String link, Map? payload, }) async { - String token = _getJwt(); + String token = await _getJwt(); // فحص صلاحية التوكن قبل الإرسال — تجنب طلب مضمون الرفض if (!_isJwtValid(token) && !_isRefreshingJWT) { _isRefreshingJWT = true; try { await Get.put(LoginDriverController()).getJWT(); - token = _getJwt(); + token = await _getJwt(); } finally { _isRefreshingJWT = false; } @@ -233,7 +234,7 @@ class CRUD { final headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer $token', - 'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز + 'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز }; return await _makeRequest(link: link, payload: payload, headers: headers); @@ -249,12 +250,12 @@ class CRUD { }) async { try { // فحص صلاحية التوكن قبل الإرسال - String token = _getJwt(); + String token = await _getJwt(); if (!_isJwtValid(token) && !_isRefreshingJWT) { _isRefreshingJWT = true; try { await Get.put(LoginDriverController()).getJWT(); - token = _getJwt(); + token = await _getJwt(); } finally { _isRefreshingJWT = false; } @@ -267,7 +268,7 @@ class CRUD { headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer $token', - 'X-Device-FP': _getFpHeader(), + 'X-Device-FP': await _getFpHeader(), }, ).timeout(const Duration(seconds: 60)); @@ -321,7 +322,7 @@ class CRUD { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer $jwt', 'X-HMAC-Auth': hmac.toString(), - 'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز + 'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز }; return await _makeRequest(link: link, payload: payload, headers: headers); @@ -346,7 +347,7 @@ class CRUD { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer $s', 'X-HMAC-Auth': hmac.toString(), - 'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز + 'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز }, ).timeout(const Duration(seconds: 60)); @@ -392,7 +393,7 @@ class CRUD { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer $s', 'X-HMAC-Auth': hmac.toString(), - 'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز + 'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز }, ).timeout(const Duration(seconds: 60)); @@ -571,17 +572,17 @@ class CRUD { // ── sendEmail — إصلاح: استخدام r() بدل X.r() القديم ───────── Future sendEmail(String link, Map? payload) async { // r() هي نفس دالة فك التشفير الثلاثي المختصرة - String token = _getJwt(); + String token = await _getJwt(); if (!_isJwtValid(token)) { await LoginDriverController().getJWT(); - token = _getJwt(); + token = await _getJwt(); } final headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer $token', - 'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز + 'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز }; final request = http.Request('POST', Uri.parse(link)); diff --git a/siro_driver/lib/controller/functions/encrypt_decrypt.dart b/siro_driver/lib/controller/functions/encrypt_decrypt.dart index 06b98537..e6c31ff3 100755 --- a/siro_driver/lib/controller/functions/encrypt_decrypt.dart +++ b/siro_driver/lib/controller/functions/encrypt_decrypt.dart @@ -46,6 +46,15 @@ class EncryptionHelper { debugPrint("EncryptionHelper initialized successfully."); } + /// Encrypts a string using AES-256-CBC with constant IV (deterministic) + /// Same input always produces the same output + String encryptDataCbc(String plainText) { + final cbcEncrypter = + encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc)); + final encrypted = cbcEncrypter.encrypt(plainText, iv: iv); + return encrypted.base64; + } + /// ✅ FIX H-04: Encrypts a string using AES-256-GCM with a random IV /// Format: "GCM::" String encryptData(String plainText) { diff --git a/siro_driver/lib/controller/functions/package_info.dart b/siro_driver/lib/controller/functions/package_info.dart index a1acddac..63f7e1f5 100755 --- a/siro_driver/lib/controller/functions/package_info.dart +++ b/siro_driver/lib/controller/functions/package_info.dart @@ -8,6 +8,7 @@ import 'package:siro_driver/constant/box_name.dart'; import 'package:siro_driver/constant/colors.dart'; import 'package:siro_driver/constant/links.dart'; import 'package:siro_driver/controller/functions/crud.dart'; +import 'package:siro_driver/controller/functions/encrypt_decrypt.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -16,7 +17,6 @@ import 'package:url_launcher/url_launcher.dart'; import '../../constant/info.dart'; import '../../main.dart'; import '../../print.dart'; -import 'encrypt_decrypt.dart'; Future checkForUpdate(BuildContext context) async { final packageInfo = await PackageInfo.fromPlatform(); @@ -168,13 +168,6 @@ void showUpdateDialog(BuildContext context) { class DeviceHelper { static Future getDeviceFingerprint() async { - await EncryptionHelper.initialize(); - - final cached = box.read(BoxName.deviceFingerprint); - if (cached != null && cached.toString().isNotEmpty) { - return cached.toString(); - } - final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); var deviceData; @@ -190,19 +183,20 @@ class DeviceHelper { } final String deviceId = Platform.isAndroid - ? deviceData['id'] ?? deviceData['androidId'] ?? deviceData['fingerprint'] ?? 'unknown' + ? deviceData['androidId'] ?? deviceData['fingerprint'] ?? 'unknown' : deviceData['identifierForVendor'] ?? 'unknown'; final String deviceModel = deviceData['model'] ?? 'unknown'; - final String fingerprint = - EncryptionHelper.instance.encryptData('${deviceId}_$deviceModel'); - - box.write(BoxName.deviceFingerprint, fingerprint); - return (fingerprint); + final String fingerprint = '${deviceId}_$deviceModel'; + final String encryptedFp = + EncryptionHelper.instance.encryptDataCbc(fingerprint); + box.write(BoxName.deviceFpEncrypted, encryptedFp); + box.write(BoxName.deviceFingerprint, encryptedFp); + Log.print('fingerprint: $encryptedFp'); + return encryptedFp; } catch (e) { - debugPrint('Error generating device fingerprint: $e'); - throw Exception('Failed to generate device fingerprint: $e'); + throw Exception('Failed to generate device fingerprint'); } } } diff --git a/siro_driver/lib/controller/functions/secure_storage.dart b/siro_driver/lib/controller/functions/secure_storage.dart index 209d388c..f7a783bb 100755 --- a/siro_driver/lib/controller/functions/secure_storage.dart +++ b/siro_driver/lib/controller/functions/secure_storage.dart @@ -38,44 +38,9 @@ class AppInitializer { List> links = []; Future initializeApp() async { - if (box.read(BoxName.jwt) == null) { - String? secureJwt = await storage.read(key: BoxName.jwt); - if (secureJwt != null) { - box.write(BoxName.jwt, secureJwt); - } - } - - if (box.read(BoxName.jwt) == null) { - await LoginDriverController().getJWT(); - } else { - 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(); - } - } - + // getJWT() now internally checks flutter_secure_storage and validates the token + // before making any network requests. + await LoginDriverController().getJWT(); // await getKey(); } diff --git a/siro_driver/lib/controller/home/captin/home_captain_controller.dart b/siro_driver/lib/controller/home/captin/home_captain_controller.dart index adec9059..b3a484dd 100755 --- a/siro_driver/lib/controller/home/captin/home_captain_controller.dart +++ b/siro_driver/lib/controller/home/captin/home_captain_controller.dart @@ -21,6 +21,7 @@ import '../../functions/background_service.dart'; import '../../functions/crud.dart'; import '../../functions/location_background_controller.dart'; import '../../functions/location_controller.dart'; +import '../../functions/package_info.dart'; import '../payment/captain_wallet_controller.dart'; class HomeCaptainController extends GetxController { @@ -507,6 +508,7 @@ class HomeCaptainController extends GetxController { // late SiroMapController mapHomeCaptainController; IntaleqMapController? mapHomeCaptainController; + CameraPosition? currentCameraPosition; LatLng? _lastCameraLoc; // لتتبع آخر موقع حرك الكاميرا // --- FIX 2: Smart Map Creation --- @@ -740,7 +742,7 @@ class HomeCaptainController extends GetxController { } addToken() async { - String? fingerPrint = await storage.read(key: BoxName.fingerPrint); + String? fingerPrint = await DeviceHelper.getDeviceFingerprint(); final payload = { 'token': (box.read(BoxName.tokenDriver)), 'captain_id': (box.read(BoxName.driverID)).toString(), diff --git a/siro_driver/lib/main.dart b/siro_driver/lib/main.dart index 96529a89..1eef6f1e 100755 --- a/siro_driver/lib/main.dart +++ b/siro_driver/lib/main.dart @@ -36,6 +36,7 @@ import 'views/home/Captin/orderCaptin/order_request_page.dart'; import 'views/home/Captin/driver_map_page.dart'; import 'controller/profile/setting_controller.dart'; import 'controller/voice_call_controller.dart'; +import 'controller/functions/tts.dart'; final box = GetStorage(); const storage = FlutterSecureStorage(); @@ -547,6 +548,7 @@ class _MyAppState extends State with WidgetsBindingObserver { Widget build(BuildContext context) { final LocaleController localController = Get.put(LocaleController()); final SettingController settingController = Get.put(SettingController()); + Get.put(TextToSpeechController(), permanent: true); return GetMaterialApp( navigatorKey: navigatorKey, diff --git a/siro_driver/lib/views/home/Captin/home_captain/home_captin.dart b/siro_driver/lib/views/home/Captin/home_captain/home_captin.dart index cec9ab83..4a0555f0 100755 --- a/siro_driver/lib/views/home/Captin/home_captain/home_captin.dart +++ b/siro_driver/lib/views/home/Captin/home_captain/home_captin.dart @@ -155,7 +155,8 @@ class HomeCaptain extends StatelessWidget { shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), onPressed: () async { - final pickerPos = homeCaptainController.mapHomeCaptainController?.cameraPosition?.target; + final pickerPos = homeCaptainController.currentCameraPosition?.target ?? + homeCaptainController.mapHomeCaptainController?.cameraPosition?.target; if (pickerPos != null) { destCtrl.isSelectingDestinationOnMap = false; destCtrl.update(); @@ -403,6 +404,9 @@ class _MapView extends StatelessWidget { myLocationEnabled: false, compassEnabled: false, zoomControlsEnabled: false, + onCameraMove: (CameraPosition position) { + ctrl.currentCameraPosition = position; + }, ), ), ); diff --git a/siro_driver/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart b/siro_driver/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart index 89eb7f91..444440c4 100755 --- a/siro_driver/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart +++ b/siro_driver/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart @@ -77,13 +77,8 @@ GetBuilder leftMainMenuCaptainIcons() { tooltip: 'Active Ride'.tr, onTap: () async { await checkForPendingOrderFromServer(); - if (box.read(BoxName.rideArgumentsFromBackground) != + if (box.read(BoxName.rideArgumentsFromBackground) == 'failure') { - Get.to( - () => PassengerLocationMapPage(), - arguments: box.read(BoxName.rideArgumentsFromBackground), - ); - } else { MyDialog().getDialog( 'Ride info'.tr, 'you dont have accepted ride'.tr, @@ -213,7 +208,7 @@ Future checkForPendingOrderFromServer() async { ); Log.print('response: $response'); - if (response['status'] == 'success') { + if (response != 'failure') { final Map orderInfo = response['message']; final Map rideArgs = _transformServerDataToAppArguments(orderInfo); @@ -234,7 +229,7 @@ Future checkForPendingOrderFromServer() async { box.write(BoxName.rideArgumentsFromBackground, 'failure'); } } catch (_) { - // silent + box.write(BoxName.rideArgumentsFromBackground, 'failure'); } finally { _isCheckingPendingOrder = false; } diff --git a/socket_intaleq/driver_socket.php b/socket_intaleq/driver_socket.php index 92ccebb6..e882aa14 100644 --- a/socket_intaleq/driver_socket.php +++ b/socket_intaleq/driver_socket.php @@ -321,6 +321,8 @@ $io->on('workerStart', function () use ($io, $INTERNAL_KEY) { $rideId = $payload['id'] ?? null; $lat = (float)($payload['start_lat'] ?? 0); $lng = (float)($payload['start_lng'] ?? 0); + $endLat = isset($payload['end_lat']) ? (float)$payload['end_lat'] : null; + $endLng = isset($payload['end_lng']) ? (float)$payload['end_lng'] : null; if (!$redis || !$rideId || $lat == 0 || $lng == 0) { $connection->send('Error: Redis unavailable or invalid coords'); @@ -333,6 +335,20 @@ $io->on('workerStart', function () use ($io, $INTERNAL_KEY) { $count = 0; foreach ($nearbyDrivers as $driverId) { if (isset($connectedDrivers[$driverId])) { + // Check if driver has a destination constraint in Redis + $profileKey = "driver:profile:$driverId"; + $profile = $redis->hgetall($profileKey); + if ($profile && isset($profile['has_destination']) && $profile['has_destination'] == 1 && $endLat !== null && $endLng !== null) { + $driverDestLat = (float)($profile['destination_lat'] ?? 0); + $driverDestLng = (float)($profile['destination_lng'] ?? 0); + + $destDistance = haversineDistance($endLat, $endLng, $driverDestLat, $driverDestLng); + // Filter out driver if destination is > 5km (5000 meters) away + if ($destDistance > 5000.0) { + continue; + } + } + $io->to('driver_' . $driverId)->emit('market_new_ride', $payload); $count++; } @@ -390,6 +406,37 @@ $io->on('workerStart', function () use ($io, $INTERNAL_KEY) { $connection->send('Driver not connected'); } + // ── 6. Update Driver Destination ────────────────────── + } elseif ($action === 'update_driver_destination') { + $driverId = $post['driver_id'] ?? null; + $hasDest = isset($post['has_destination']) ? intval($post['has_destination']) : 0; + + if (!$driverId || !$redis) { + $connection->send('Error: Missing driver_id or Redis unavailable'); + return; + } + + $profileKey = "driver:profile:$driverId"; + if ($hasDest === 1) { + $destLat = $post['destination_lat'] ?? ''; + $destLng = $post['destination_lng'] ?? ''; + $destName = $post['destination_name'] ?? ''; + + $redis->hmset($profileKey, [ + 'has_destination' => 1, + 'destination_lat' => $destLat, + 'destination_lng' => $destLng, + 'destination_name' => $destName + ]); + $redis->expire($profileKey, 86400); // 24 Hours + logMsg("🎯 Destination set for Driver #$driverId: $destName ($destLat, $destLng)"); + } else { + $redis->hmset($profileKey, ['has_destination' => 0]); + $redis->hdel($profileKey, ['destination_lat', 'destination_lng', 'destination_name']); + logMsg("🎯 Destination cleared for Driver #$driverId"); + } + $connection->send('OK'); + } else { $connection->send('Unknown action'); }