diff --git a/backend/ride/rides/getRideStatusFromStartApp.php b/backend/ride/rides/getRideStatusFromStartApp.php index cf6b124..f88f7af 100644 --- a/backend/ride/rides/getRideStatusFromStartApp.php +++ b/backend/ride/rides/getRideStatusFromStartApp.php @@ -14,7 +14,7 @@ try { status, start_location, end_location, - carType, + car_type AS carType, driver_id,distance, price, created_at @@ -90,6 +90,6 @@ try { } catch (Exception $e) { error_log("[getRideStatusFromStartApp] Error: " . $e->getMessage()); - echo json_encode(["status" => "failure", "message" => "An internal error occurred."]); + echo json_encode(["status" => "failure", "message" => $e->getMessage()]); } ?> \ No newline at end of file diff --git a/backend/temp_schema.php b/backend/temp_schema.php new file mode 100644 index 0000000..6560348 --- /dev/null +++ b/backend/temp_schema.php @@ -0,0 +1,10 @@ +query("DESCRIBE ride"); + $columns = $stmt->fetchAll(PDO::FETCH_ASSOC); + print_r($columns); +} catch (Exception $e) { + echo "Error: " . $e->getMessage(); +} diff --git a/intaleq_driver/lib/constant/box_name.dart b/intaleq_driver/lib/constant/box_name.dart index 6a6d25c..eb2227b 100755 --- a/intaleq_driver/lib/constant/box_name.dart +++ b/intaleq_driver/lib/constant/box_name.dart @@ -129,4 +129,5 @@ class BoxName { static const String isBusMode = 'isBusMode'; static const String busModeTripId = 'busModeTripId'; static const String busModeRouteId = 'busModeRouteId'; + static const String isGmsAvailable = 'isGmsAvailable'; } diff --git a/intaleq_driver/lib/controller/functions/location_controller.dart b/intaleq_driver/lib/controller/functions/location_controller.dart index c77776d..319219b 100755 --- a/intaleq_driver/lib/controller/functions/location_controller.dart +++ b/intaleq_driver/lib/controller/functions/location_controller.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:geolocator/geolocator.dart' as geo; import 'package:intaleq_maps/intaleq_maps.dart'; @@ -25,6 +26,37 @@ import 'background_service.dart'; import 'crud.dart'; import '../transit/transit_driver_controller.dart'; +/// إعدادات تتبّع الموقع لحالة معيّنة من حالات السائق. +/// الهدف: عدم تشغيل الـ GPS بأقصى طاقته إلا أثناء الرحلة الفعلية. +class _LocationProfile { + final String name; + final LocationAccuracy accuracy; + final int interval; // ميلي ثانية + final double distanceFilter; // متر + + const _LocationProfile( + this.name, this.accuracy, this.interval, this.distanceFilter); + + _LocationProfile copyWith( + {String? name, + LocationAccuracy? accuracy, + int? interval, + double? distanceFilter}) => + _LocationProfile( + name ?? this.name, + accuracy ?? this.accuracy, + interval ?? this.interval, + distanceFilter ?? this.distanceFilter, + ); + + /// بصمة تُستخدم لتفادي استدعاء changeSettings بلا داعٍ. + String get signature => '$accuracy|$interval|$distanceFilter'; + + @override + String toString() => + '$name(acc: ${accuracy.name}, ${interval}ms, ${distanceFilter}m)'; +} + class LocationController extends GetxController with WidgetsBindingObserver { // =================================================================== // ====== Tunables ====== @@ -38,6 +70,29 @@ class LocationController extends GetxController with WidgetsBindingObserver { static const int powerSaveTriggerLevel = 20; static const int powerSaveExitLevel = 25; + // =================================================================== + // ====== ملفات التتبّع التكيّفية (أ) ====== + // =================================================================== + /// السائق غير متاح (off/blocked): أرخص إعداد ممكن مع إبقاء آخر موقع معروف. + static const _profileIdle = + _LocationProfile('idle', LocationAccuracy.balanced, 30000, 100); + + /// متصل وينتظر طلباً: لا حاجة لدقة الملاحة — يكفي أن يعرفه السيرفر بالحي. + static const _profileWaiting = + _LocationProfile('waiting', LocationAccuracy.high, 15000, 50); + + /// قَبِل الطلب وفي طريقه للراكب: الراكب يراقب السهم، نحتاج تحديثاً معقولاً. + static const _profileEnRoute = + _LocationProfile('enRoute', LocationAccuracy.high, 5000, 15); + + /// رحلة جارية (أو وضع الباص): أعلى دقة — هنا تُحتسب المسافة والأجرة. + static const _profileOnTrip = + _LocationProfile('onTrip', LocationAccuracy.navigation, 4000, 10); + + /// بعد هذه المدة من السكون نضاعف الفترة (سائق واقف في مرآب/إشارة طويلة). + static const Duration stationaryGrace = Duration(seconds: 90); + static const double stationarySpeedThreshold = 1.0; // م/ث ≈ 3.6 كم/س + // =================================================================== // ====== Services & Variables ====== // =================================================================== @@ -84,6 +139,17 @@ class LocationController extends GetxController with WidgetsBindingObserver { bool _isReady = false; bool _isPowerSavingMode = false; + /// هل خدمات جوجل متوفرة على الجهاز؟ (ج) + /// null = لم يُفحص بعد. على أجهزة هواوي بلا GMS يسقط الباكيج إلى + /// LocationManager الخام: دقة أقل واستهلاك بطارية أعلى ⇒ نخفّف الإعدادات. + bool? _gmsAvailable; + static const MethodChannel _deviceServicesChannel = + MethodChannel('com.siro.siro_driver/device_services'); + + /// آخر إعداد طُبّق فعلياً — لتفادي استدعاء changeSettings بلا تغيير. + String? _appliedProfileSignature; + DateTime? _stationarySince; + final List> _trackBuffer = []; final List> _behaviorBuffer = []; @@ -130,6 +196,7 @@ class LocationController extends GetxController with WidgetsBindingObserver { _isReady = true; initSocket(); + await _detectGmsAvailability(); await _initLocationSettings(); _listenToBatteryChanges(); @@ -163,10 +230,15 @@ class LocationController extends GetxController with WidgetsBindingObserver { // إيقاف خدمة الخلفية BackgroundServiceHelper.stopService(); - if (socket == null || (!socket!.connected && !_isInitializingSocket)) { - Log.print("🔄 Initializing Socket on resume..."); - initSocket(); + // 🔥 Fix: iOS silently kills WebSockets in background. + // The socket_io_client might still report 'connected' as true for ~20s. + // We MUST force a disconnect so initSocket() properly creates a fresh connection + // and immediately emits 'get_pending_orders' to catch missed FCM payloads. + if (socket != null) { + socket!.disconnect(); } + initSocket(); + } else if (state == AppLifecycleState.paused || state == AppLifecycleState.detached) { Log.print("📱 Lifecycle: App is in BACKGROUND"); @@ -555,13 +627,9 @@ class LocationController extends GetxController with WidgetsBindingObserver { Future _subscribeLocationStream() async { _locSub?.cancel(); - int interval = _isPowerSavingMode ? 10000 : 5000; await location.enableBackgroundMode(enable: true); - location.changeSettings( - accuracy: LocationAccuracy.navigation, - interval: interval, - distanceFilter: _isPowerSavingMode ? 20 : 10, - ); + // مصدر الحقيقة الوحيد لإعدادات الموقع: _applyProfile. + await _applyProfile(force: true); _locSub = location.onLocationChanged.listen((LocationData loc) async { if (loc.latitude == null || loc.longitude == null) return; @@ -577,6 +645,11 @@ class LocationController extends GetxController with WidgetsBindingObserver { speed = loc.speed ?? 0.0; heading = loc.heading ?? 0.0; + // إعادة تقييم ملف التتبّع مع كل قراءة — لا يستدعي changeSettings + // إلا إذا تغيّرت الحالة فعلاً (حالة السائق/الرحلة/السكون/البطارية). + _trackStationary(); + await _applyProfile(); + box.write('last_lat', pos.latitude); box.write('last_lng', pos.longitude); box.write('last_heading', heading); @@ -635,6 +708,8 @@ class LocationController extends GetxController with WidgetsBindingObserver { _locSub?.cancel(); _locSub = null; + _appliedProfileSignature = null; + _stationarySince = null; _recordTimer?.cancel(); _uploadBatchTimer?.cancel(); _socketHeartbeat?.cancel(); @@ -746,24 +821,123 @@ class LocationController extends GetxController with WidgetsBindingObserver { if (level >= powerSaveExitLevel) _isPowerSavingMode = false; if (previousMode != _isPowerSavingMode) { _startBatchTimers(); - _updateLocationSettings(); + _applyProfile(); } }); } - Future _updateLocationSettings() async { - if (_locSub == null) return; - int interval = _isPowerSavingMode ? 10000 : 5000; + // =================================================================== + // ====== محرّك الملفات التكيّفية (أ + ج) ====== + // =================================================================== + + /// (ج) فحص توفّر Google Play Services مرة واحدة عند الإقلاع. + /// النتيجة تُحفظ في الصندوق ليقرأها الـ background isolate أيضاً، + /// وتُبَث مع بيانات السائق لمعرفة حجم شريحة الأجهزة بلا GMS. + Future _detectGmsAvailability() async { + if (!Platform.isAndroid) { + _gmsAvailable = true; // iOS: CoreLocation دائماً متاح + return; + } + try { + final bool available = + await _deviceServicesChannel.invokeMethod('isGmsAvailable') ?? + true; + _gmsAvailable = available; + box.write(BoxName.isGmsAvailable, available); + Log.print(available + ? "✅ GMS available — FusedLocationProvider in use." + : "⚠️ No GMS (Huawei/AOSP) — falling back to raw LocationManager, " + "relaxing location profile to protect battery."); + } catch (e) { + // القناة غير مسجّلة (نسخة قديمة) — نفترض التوفّر ولا نغيّر السلوك. + _gmsAvailable = true; + Log.print("⚠️ GMS check failed, assuming available: $e"); + } + } + + /// تتبّع السكون: سائق واقف لا يحتاج قراءات متلاحقة. + void _trackStationary() { + if (speed >= stationarySpeedThreshold) { + _stationarySince = null; + } else { + _stationarySince ??= DateTime.now(); + } + } + + bool get _isStationary => + _stationarySince != null && + DateTime.now().difference(_stationarySince!) >= stationaryGrace; + + /// يختار الملف المناسب من حالة السائق والرحلة، ثم يطبّق عليه + /// مُعدِّلات البطارية والسكون وغياب GMS. + _LocationProfile _resolveProfile() { + final String driverStatus = + box.read(BoxName.statusDriverLocation) ?? 'off'; + final String rideStatus = (box.read(BoxName.rideStatus) ?? '').toString(); + + _LocationProfile p; + if (isBusMode) { + p = _profileOnTrip; + } else if (driverStatus == 'off' || driverStatus == 'blocked') { + p = _profileIdle; + } else if (rideStatus == 'Begin') { + p = _profileOnTrip; + } else if (rideStatus == 'Apply' || rideStatus == 'Arrived') { + p = _profileEnRoute; + } else { + p = _profileWaiting; + } + + // وضع توفير الطاقة: ضاعف الفترة والمسافة، وانزل عن دقة الملاحة. + if (_isPowerSavingMode) { + p = p.copyWith( + name: '${p.name}+save', + accuracy: p.accuracy == LocationAccuracy.navigation + ? LocationAccuracy.high + : p.accuracy, + interval: p.interval * 2, + distanceFilter: p.distanceFilter * 2, + ); + } + + // (ج) بلا GMS: كل قراءة تُشعل شريحة الـ GPS منفردة ⇒ خفّف التردد. + if (_gmsAvailable == false) { + p = p.copyWith( + name: '${p.name}+nogms', + interval: (p.interval * 1.5).round(), + distanceFilter: p.distanceFilter * 2, + ); + } + + // السكون المطوّل: ضاعف الفترة ثلاثاً — إلا في رحلة جارية (عدّاد الانتظار). + if (_isStationary && p.accuracy != LocationAccuracy.navigation) { + p = p.copyWith( + name: '${p.name}+idle', + interval: p.interval * 3, + ); + } + + return p; + } + + /// يطبّق الملف الحالي. لا يستدعي changeSettings إلا عند تغيّر فعلي، + /// لأن كل استدعاء يعيد تشغيل مزوّد الموقع في الطبقة الأصلية. + Future _applyProfile({bool force = false}) async { + if (_locSub == null && !force) return; + + final p = _resolveProfile(); + if (!force && p.signature == _appliedProfileSignature) return; + try { await location.changeSettings( - accuracy: LocationAccuracy.navigation, - interval: interval, - distanceFilter: _isPowerSavingMode ? 20 : 10, + accuracy: p.accuracy, + interval: p.interval, + distanceFilter: p.distanceFilter, ); - Log.print( - "🔋 Location settings updated. Power Save: $_isPowerSavingMode"); + _appliedProfileSignature = p.signature; + Log.print("📍 Location profile → $p"); } catch (e) { - Log.print("❌ Failed to update location settings: $e"); + Log.print("❌ Failed to apply location profile $p: $e"); } } @@ -832,10 +1006,9 @@ class LocationController extends GetxController with WidgetsBindingObserver { if (await _ensureServiceAndPermission()) { try { await location.enableBackgroundMode(enable: true); - location.changeSettings( - accuracy: LocationAccuracy.navigation, - interval: 1000, - distanceFilter: 10); + // (ب) لا نضبط الإعدادات هنا: كانت interval: 1000 تتعارض مع إعدادات + // _subscribeLocationStream وتُبقي الـ GPS مشتعلاً كل ثانية. + // مصدر الحقيقة الوحيد الآن هو _applyProfile. } catch (e) { Log.print("Warning: $e"); } diff --git a/intaleq_driver/lib/controller/home/captin/home_captain_controller.dart b/intaleq_driver/lib/controller/home/captin/home_captain_controller.dart index 280d2a6..7cda3ae 100755 --- a/intaleq_driver/lib/controller/home/captin/home_captain_controller.dart +++ b/intaleq_driver/lib/controller/home/captin/home_captain_controller.dart @@ -32,8 +32,7 @@ class HomeCaptainController extends GetxController { Timer? activeTimer; Map data = {}; bool isHomeMapActive = true; - InlqBitmap carIcon = - InlqBitmap.fromStyleImage('car_icon', size: 2.3); + InlqBitmap carIcon = InlqBitmap.fromStyleImage('car_icon', size: 2.3); bool isMapReadyForCommands = false; bool isLoading = true; late double kazan = 0; @@ -388,6 +387,7 @@ class HomeCaptainController extends GetxController { _checkFatigueBeforeOnline(); // Throws exception if tired if (double.parse(totalPoints) > minPointsThreshold) { + box.write(BoxName.statusDriverLocation, 'on'); locationController.startLocationUpdates(); HapticFeedback.heavyImpact(); activeStartTime = DateTime.now(); @@ -412,6 +412,7 @@ class HomeCaptainController extends GetxController { update(); }); } else { + box.write(BoxName.statusDriverLocation, 'off'); locationController.stopLocationUpdates(); activeStartTime = null; activeTimer?.cancel(); @@ -426,6 +427,7 @@ class HomeCaptainController extends GetxController { update(); } } else { + box.write(BoxName.statusDriverLocation, 'off'); locationController.stopLocationUpdates(); activeStartTime = null; activeTimer?.cancel(); diff --git a/intaleq_driver/pubspec.yaml b/intaleq_driver/pubspec.yaml index ed8f272..ef73049 100644 --- a/intaleq_driver/pubspec.yaml +++ b/intaleq_driver/pubspec.yaml @@ -2,7 +2,7 @@ name: intaleq_driver description: "A new Flutter project." publish_to: "none" # Remove this line if you wish to publish to pub.dev -version: 1.0.1+6 +version: 2.0.0+66 environment: sdk: ">=3.0.5 <4.0.0" @@ -18,7 +18,7 @@ dependencies: path: ./packages/get get_storage: path: ./packages/get_storage - intaleq_maps: ^2.2.1 + intaleq_maps: ^2.3.0 secure_string_operations: path: ./secure_string_operations trip_overlay_plugin: diff --git a/intaleq_rider/pubspec.yaml b/intaleq_rider/pubspec.yaml index 7bb00cf..cf92a26 100644 --- a/intaleq_rider/pubspec.yaml +++ b/intaleq_rider/pubspec.yaml @@ -2,7 +2,7 @@ name: intaleq_rider description: "A new Flutter project." publish_to: "none" # Remove this line if you wish to publish to pub.dev -version: 1.0.6+6 +version: 2.0.0+66 environment: sdk: ">=3.0.5 <4.0.0" @@ -79,7 +79,7 @@ dependencies: internet_connection_checker: ^3.0.1 connectivity_plus: ^6.1.5 app_links: ^7.0.0 - intaleq_maps: ^2.2.1 + intaleq_maps: ^2.3.0 socket_io_client: 1.0.2 # home_widget: ^0.7.0+1