Fix payment server: remove global conRide to prevent crashing non-ride DB queries
This commit is contained in:
@@ -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<Map<String, dynamic>> _trackBuffer = [];
|
||||
final List<Map<String, dynamic>> _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<void> _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<void> _updateLocationSettings() async {
|
||||
if (_locSub == null) return;
|
||||
int interval = _isPowerSavingMode ? 10000 : 5000;
|
||||
// ===================================================================
|
||||
// ====== محرّك الملفات التكيّفية (أ + ج) ======
|
||||
// ===================================================================
|
||||
|
||||
/// (ج) فحص توفّر Google Play Services مرة واحدة عند الإقلاع.
|
||||
/// النتيجة تُحفظ في الصندوق ليقرأها الـ background isolate أيضاً،
|
||||
/// وتُبَث مع بيانات السائق لمعرفة حجم شريحة الأجهزة بلا GMS.
|
||||
Future<void> _detectGmsAvailability() async {
|
||||
if (!Platform.isAndroid) {
|
||||
_gmsAvailable = true; // iOS: CoreLocation دائماً متاح
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final bool available =
|
||||
await _deviceServicesChannel.invokeMethod<bool>('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<void> _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");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user