import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:intaleq_maps/intaleq_maps.dart'; import 'package:http/http.dart' as http; import 'package:sefer_driver/constant/box_name.dart'; import 'dart:async'; import '../../../constant/links.dart'; import '../../../constant/style.dart'; import '../../../constant/table_names.dart'; import '../../../main.dart'; import '../../../print.dart'; import '../../../views/home/my_wallet/walet_captain.dart'; import '../../../views/widgets/elevated_btn.dart'; import '../../firebase/firbase_messge.dart'; import '../../functions/background_service.dart'; import '../../functions/crud.dart'; import '../../functions/location_background_controller.dart'; import '../../functions/location_controller.dart'; import '../payment/captain_wallet_controller.dart'; class HomeCaptainController extends GetxController { bool isActive = false; DateTime? activeStartTime; Duration activeDuration = Duration.zero; Timer? activeTimer; Map data = {}; bool isHomeMapActive = true; InlqBitmap carIcon = InlqBitmap.defaultMarker; bool isMapReadyForCommands = false; bool isLoading = true; late double kazan = 0; double latePrice = 0; double heavyPrice = 0; double comfortPrice = 0, speedPrice = 0, deliveryPrice = 0, mashwariPrice = 0, familyPrice = 0, fuelPrice = 0; double naturePrice = 0; bool isCallOn = false; String totalMoneyToday = '0'; double? rating = 5; String rideId = '0'; String countRideToday = '0'; String totalMoneyInSEFER = '0'; String totalDurationToday = '0'; Timer? timer; Timer? _cameraFollowTimer; LatLng myLocation = const LatLng(33.5138, 36.2765); String totalPoints = '0'; String countRefuse = '0'; bool mapType = false; bool mapTrafficON = false; double widthMapTypeAndTraffic = 50; // === متغيرات الهيت ماب الجديدة === bool isHeatmapVisible = false; Set heatmapPolygons = {}; // سنستخدم Polygon لرسم المربعات على جوجل مابس // Inject the LocationController class // final locationController = Get.put(LocationController()); // الكود الصحيح final locationController = Get.find(); // final locationBackController = Get.put(LocationBackgroundController()); String formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, "0"); String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); return "${duration.inHours}:$twoDigitMinutes:$twoDigitSeconds"; } // دالة لتغيير حالة الهيت ماب (عرض/إخفاء) void toggleHeatmap() async { isHeatmapVisible = !isHeatmapVisible; print("🔥 [Heatmap] Visibility toggled to: $isHeatmapVisible"); if (isHeatmapVisible) { startHeatmapCycle(); } else { _heatmapTimer?.cancel(); heatmapPolygons.clear(); print("🧹 [Heatmap] Polygons cleared."); } update(); // تحديث الواجهة } // داخل MapDriverController // متغير لتخزين المربعات // Set heatmapPolygons = {}; // دالة جلب البيانات ورسم الخريطة Future fetchAndDrawHeatmap() async { print("🚀 [Heatmap] Fetching live data..."); // استخدم الرابط المباشر لملف JSON لسرعة قصوى final String jsonUrl = "https://ride.intaleq.xyz/intaleq/ride/heatmap_data.json"; try { // نستخدم timestamp لمنع الكاش من الموبايل نفسه final response = await http.get( Uri.parse("$jsonUrl?t=${DateTime.now().millisecondsSinceEpoch}")); if (response.statusCode == 200) { final List data = json.decode(response.body); print("✅ [Heatmap] Data received. Points count: ${data.length}"); _generatePolygons(data); } else { print("⚠️ [Heatmap] Server error: ${response.statusCode}"); } } catch (e) { print("❌ [Heatmap] Error: $e"); } } void _generatePolygons(List data) { print("🎨 [Heatmap] Processing polygons..."); Set tempPolygons = {}; // الأوفست لرسم المربع (نصف حجم الشبكة) // الشبكة دقتها 0.01 درجة، لذا نصفها 0.005 double offset = 0.005; int highCount = 0, medCount = 0, lowCount = 0; for (var point in data) { double lat = double.parse(point['lat'].toString()); double lng = double.parse(point['lng'].toString()); String intensity = point['intensity'] ?? 'low'; int count = int.parse(point['count'].toString()); // ✅ جلب العدد Color color; Color strokeColor; // 🧠 منطق الألوان: ندمج الذكاء (Intensity) مع العدد (Count) if (intensity == 'high' || count >= 5) { highCount++; // منطقة مشتعلة (أحمر) // إما فيها طلبات ضائعة (Timeout) أو فيها عدد كبير من الطلبات color = Colors.red.withValues(alpha: 0.35); strokeColor = Colors.red.withValues(alpha: 0.8); } else if (intensity == 'medium' || count >= 3) { medCount++; // منطقة متوسطة (برتقالي) color = Colors.orange.withValues(alpha: 0.35); strokeColor = Colors.orange.withValues(alpha: 0.8); } else { lowCount++; // منطقة خفيفة (أصفر) color = Colors.yellow.withValues(alpha: 0.3); strokeColor = Colors.yellow.withValues(alpha: 0.6); } // رسم المربع tempPolygons.add(Polygon( polygonId: PolygonId("$lat-$lng"), // consumeTapEvents: true, // للسماح بالضغط عليه مستقبلاً points: [ LatLng(lat - offset, lng - offset), LatLng(lat + offset, lng - offset), LatLng(lat + offset, lng + offset), LatLng(lat - offset, lng + offset), ], fillColor: color, strokeColor: strokeColor, strokeWidth: 2, )); } heatmapPolygons = tempPolygons; print( "✨ [Heatmap] Rendering Done. (🔥 High: $highCount, 🟠 Med: $medCount, 🟡 Low: $lowCount)"); print("📍 [Heatmap] Total Polygons on Map: ${heatmapPolygons.length}"); update(); // تحديث الخريطة } Timer? _heatmapTimer; // دالة لتشغيل الخريطة الحرارية كل فترة (كل 5 دقائق) لضمان نشاط البيانات void startHeatmapCycle() { _heatmapTimer?.cancel(); fetchAndDrawHeatmap(); _heatmapTimer = Timer.periodic(const Duration(minutes: 5), (timer) { if (isHeatmapVisible) { print("🔄 [Heatmap] Periodic refresh started..."); fetchAndDrawHeatmap(); } else { timer.cancel(); } }); } void goToWalletFromConnect() { Get.back(); Get.back(); Get.to(() => WalletCaptainRefactored()); } void changeRideId() { rideId = 'rideId'; update(); } void addCustomCarIcon() { carIcon = InlqBitmap.fromAsset('assets/images/car.png'); update(); } String stringActiveDuration = ''; // ========================================== // ====== 🛡️ Fatigue Monitoring System ====== // ========================================== void _checkFatigueBeforeOnline() { int totalSecondsToday = box.read('fatigue_total_seconds') ?? 0; String? lastOfflineStr = box.read('fatigue_last_offline'); if (lastOfflineStr != null) { DateTime lastOffline = DateTime.parse(lastOfflineStr); // If offline for more than 6 continuous hours, reset the fatigue counter if (DateTime.now().difference(lastOffline).inHours >= 6) { totalSecondsToday = 0; box.write('fatigue_total_seconds', 0); } } if (totalSecondsToday >= 12 * 3600) { // 12 Hours _forceOfflineDueToFatigue(); throw Exception('Fatigue Limit Exceeded'); } } void _forceOfflineDueToFatigue() { if (isActive) { isActive = false; locationController.stopLocationUpdates(); activeStartTime = null; activeTimer?.cancel(); update(); } Get.defaultDialog( title: 'Safety First 🛑'.tr, middleText: 'You have been driving for 12 hours. For your safety and compliance, please take a 6-hour break.'.tr, barrierDismissible: false, titleStyle: const TextStyle(color: Colors.red, fontWeight: FontWeight.bold), confirm: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () => Get.back(), child: Text('OK'.tr, style: const TextStyle(color: Colors.white)), ), ); } void onButtonSelected() { if (!Get.isRegistered()) { Get.put(CaptainWalletController()); } totalPoints = Get.find().totalPoints; // Toggle Active State isActive = !isActive; if (isActive) { try { _checkFatigueBeforeOnline(); // Throws exception if tired if (double.parse(totalPoints) > -200) { locationController.startLocationUpdates(); HapticFeedback.heavyImpact(); activeStartTime = DateTime.now(); activeTimer = Timer.periodic(const Duration(seconds: 1), (timer) { activeDuration = DateTime.now().difference(activeStartTime!); stringActiveDuration = formatDuration(activeDuration); // Increment Fatigue Counter int totalSeconds = box.read('fatigue_total_seconds') ?? 0; totalSeconds += 1; box.write('fatigue_total_seconds', totalSeconds); if (totalSeconds >= 12 * 3600) { // 12 hours _forceOfflineDueToFatigue(); } update(); }); } else { locationController.stopLocationUpdates(); activeStartTime = null; activeTimer?.cancel(); savePeriod(activeDuration); activeDuration = Duration.zero; box.write('fatigue_last_offline', DateTime.now().toIso8601String()); update(); } } catch (e) { // Driver is fatigued, revert state isActive = false; update(); } } else { locationController.stopLocationUpdates(); activeStartTime = null; activeTimer?.cancel(); savePeriod(activeDuration); activeDuration = Duration.zero; // Save offline time for Fatigue Monitoring reset box.write('fatigue_last_offline', DateTime.now().toIso8601String()); update(); } } // متغيرات العداد للحظر RxString remainingBlockTimeStr = "".obs; Timer? _blockTimer; /// دالة الفحص والدايلوج void checkAndShowBlockDialog() { String? blockStr = box.read(BoxName.blockUntilDate); if (blockStr == null || blockStr.isEmpty) return; DateTime blockExpiry = DateTime.parse(blockStr); DateTime now = DateTime.now(); if (now.isBefore(blockExpiry)) { // 1. إجبار السائق على وضع الأوفلاين box.write(BoxName.statusDriverLocation, 'blocked'); update(); // 2. بدء العداد _startBlockCountdown(blockExpiry); // 3. إظهار الديالوج المانع Get.defaultDialog( title: "Your account is temporarily restricted ⛔".tr, titleStyle: const TextStyle(color: Colors.red, fontWeight: FontWeight.bold), barrierDismissible: false, // 🚫 ممنوع الإغلاق بالضغط خارجاً onWillPop: () async => false, // 🚫 ممنوع زر الرجوع في الأندرويد content: Obx(() => Column( children: [ const Icon(Icons.timer_off_outlined, size: 50, color: Colors.orange), const SizedBox(height: 15), Text( "You have exceeded the allowed cancellation limit (3 times).\nYou cannot work until the penalty expires." .tr, textAlign: TextAlign.center, ), const SizedBox(height: 20), Text( remainingBlockTimeStr.value, // 🔥 الوقت يتحدث هنا style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87), ), const SizedBox(height: 20), ], )), confirm: Obx(() { // الزر يكون مفعلاً فقط عندما ينتهي الوقت bool isFinished = remainingBlockTimeStr.value == "00:00:00" || remainingBlockTimeStr.value == "Done"; return ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: isFinished ? Colors.green : Colors.grey, ), onPressed: isFinished ? () { Get.back(); // إغلاق الديالوج box.remove(BoxName.blockUntilDate); // إزالة الحظر Get.snackbar("Welcome".tr, "You can now receive orders".tr, backgroundColor: Colors.green); } : null, // زر معطل child: Text(isFinished ? "Go Online".tr : "Wait for timer".tr), ); }), ); } else { // الوقت انتهى أصلاً -> تنظيف box.remove(BoxName.blockUntilDate); } } /// دالة العداد التنازلي void _startBlockCountdown(DateTime expiry) { _blockTimer?.cancel(); _blockTimer = Timer.periodic(const Duration(seconds: 1), (timer) { DateTime now = DateTime.now(); if (now.isAfter(expiry)) { // انتهى الوقت remainingBlockTimeStr.value = "Done"; timer.cancel(); } else { // حساب الفرق وتنسيقه Duration diff = expiry.difference(now); String twoDigits(int n) => n.toString().padLeft(2, "0"); String hours = twoDigits(diff.inHours); String minutes = twoDigits(diff.inMinutes.remainder(60)); String seconds = twoDigits(diff.inSeconds.remainder(60)); remainingBlockTimeStr.value = "$hours:$minutes:$seconds"; } }); } @override void onClose() { print("🔥 [HomeCaptain] onClose called. Tearing down map resources..."); _blockTimer?.cancel(); activeTimer?.cancel(); _cameraFollowTimer?.cancel(); _heatmapTimer?.cancel(); stopTimer(); mapHomeCaptainController = null; super.onClose(); } void getRefusedOrderByCaptain() async { DateTime today = DateTime.now(); int todayDay = today.day; String driverId = box.read(BoxName.driverID).toString(); String customQuery = ''' SELECT COUNT(*) AS count FROM ${TableName.driverOrdersRefuse} WHERE driver_id = '$driverId' AND created_at LIKE '%$todayDay%' '''; try { List> results = await sql.getCustomQuery(customQuery); countRefuse = results[0]['count'].toString(); update(); if (double.parse(totalPoints) <= -200) { // if (int.parse(countRefuse) > 3 || double.parse(totalPoints) <= -200) { locationController.stopLocationUpdates(); activeStartTime = null; activeTimer?.cancel(); savePeriod(activeDuration); activeDuration = Duration.zero; update(); Get.defaultDialog( // backgroundColor: CupertinoColors.destructiveRed, barrierDismissible: false, title: 'You Are Stopped For this Day !'.tr, content: Text( 'You Refused 3 Rides this Day that is the reason \nSee you Tomorrow!' .tr, style: AppStyle.title, ), confirm: MyElevatedButton( title: 'Ok , See you Tomorrow'.tr, onPressed: () { // إغلاق الديالوج والعودة قسرياً navigatorKey.currentState?.pop(); Get.back(); })); } } catch (e) {} } void changeMapType() { mapType = !mapType; // heightButtomSheetShown = isButtomSheetShown == true ? 240 : 0; update(); } void changeMapTraffic() { mapTrafficON = !mapTrafficON; update(); } // late IntaleqMapController mapHomeCaptainController; IntaleqMapController? mapHomeCaptainController; // --- FIX 2: Smart Map Creation --- void onMapCreated(IntaleqMapController controller) { print("🔥 [HomeCaptain] onMapCreated started"); mapHomeCaptainController = controller; // We delay the first move to ensure the native side is fully ready Future.delayed(const Duration(milliseconds: 800), () { if (isClosed || mapHomeCaptainController == null) return; try { var currentLoc = locationController.myLocation; if (currentLoc.latitude != 0 && currentLoc.latitude != null && !currentLoc.latitude.isNaN) { print( "🔥 [HomeCaptain] Safely moving camera to: ${currentLoc.latitude}"); mapHomeCaptainController!.moveCamera( CameraUpdate.newLatLngZoom(currentLoc, 15), ); } else { print("🔥 [HomeCaptain] Safely moving to default Damascus"); mapHomeCaptainController!.moveCamera( CameraUpdate.newLatLngZoom(myLocation, 12), ); } // Mark as ready for regular listener updates isMapReadyForCommands = true; } catch (e) { print("❌ [HomeCaptain] Map move failed: $e"); } }); } void savePeriod(Duration period) { final periods = box.read>(BoxName.periods) ?? []; periods.add(period.inSeconds); box.write(BoxName.periods, periods); } Duration calculateTotalDuration() { final periods = box.read>(BoxName.periods) ?? []; Duration totalDuration = Duration.zero; for (dynamic periodInSeconds in periods) { final periodDuration = Duration(seconds: periodInSeconds); totalDuration += periodDuration; } return totalDuration; } Timer? _localDurationTimer; RxString totalDurationDisplay = "00:00:00".obs; // لعرض الوقت في الواجهة Duration _currentDuration = Duration.zero; // لتخزين الوقت ككائن Duration void startPeriodicExecution() async { await getCaptainDurationOnToday(); String? initialDurationStr = totalDurationToday; if (initialDurationStr != '0') { // تحويل النص (01:20:30) إلى كائن Duration List parts = initialDurationStr.split(':'); _currentDuration = Duration( hours: int.parse(parts[0]), minutes: int.parse(parts[1]), seconds: int.parse(parts[2]), ); // بدء العداد المحلي _startLocalClock(); } // Timer.periodic(const Duration(seconds: 30), (timer) async { // await getCaptainDurationOnToday(); // }); } void _startLocalClock() { _localDurationTimer?.cancel(); _localDurationTimer = Timer.periodic(const Duration(seconds: 1), (timer) { // زيادة ثانية واحدة محلياً _currentDuration += const Duration(seconds: 1); // تحديث النص المعروض في الواجهة (Formatting) totalDurationDisplay.value = _formatDuration(_currentDuration); // اختيارياً: كل 5 دقائق فقط، قم بتحديث القيمة من السيرفر للتأكد من المزامنة if (timer.tick % 300 == 0) { getCaptainDurationOnToday(); } }); } String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, "0"); String hours = twoDigits(duration.inHours); String minutes = twoDigits(duration.inMinutes.remainder(60)); String seconds = twoDigits(duration.inSeconds.remainder(60)); return "$hours:$minutes:$seconds"; } void stopTimer() { _localDurationTimer?.cancel(); } getlocation() async { isLoading = true; update(); try { // ننتظر جلب الموقع مع مهلة 10 ثوانٍ لتجنب التعليق var locData = await locationController.getLocation().timeout( const Duration(seconds: 10), onTimeout: () => null, ); if (locData != null && locData.latitude != null) { myLocation = LatLng(locData.latitude!, locData.longitude!); print( "📍 [HomeCaptain] Location updated: ${myLocation.latitude}, ${myLocation.longitude}"); } else { print( "⚠️ [HomeCaptain] Could not get current location, using default."); } } catch (e) { print("❌ Error in getlocation: $e"); } finally { isLoading = false; update(); } } Map walletDriverPointsDate = {}; Future getCaptainWalletFromBuyPoints() async { // isLoading = true; update(); var res = await CRUD().getWallet( link: AppLink.getDriverPaymentPoints, payload: {'driverID': box.read(BoxName.driverID).toString()}, ); isLoading = false; // update(); if (res != 'failure') { walletDriverPointsDate = jsonDecode(res); double totalPointsDouble = double.parse( walletDriverPointsDate['message'][0]['total_amount'].toString()); totalPoints = totalPointsDouble.toStringAsFixed(0); update(); } else { totalPoints = '0'; } } // 3. دالة نستدعيها عند قبول الطلب void pauseHomeMapUpdates() { isHomeMapActive = false; update(); } // 4. دالة نستدعيها عند العودة للصفحة الرئيسية void resumeHomeMapUpdates() { isHomeMapActive = true; // تم حذف استدعاء onMapCreated المتكرر لمنع قفز الخريطة عند العودة update(); } @override void onInit() async { // ✅ تم إرجاعه كتعليق لمنع الديالوج عند التشغيل (كما كان في الكود الأصلي) // bool permissionsGranted = await PermissionsHelper.requestAllPermissions(); // if (permissionsGranted) { // await BackgroundServiceHelper.startService(); // } Get.put(FirebaseMessagesController()); addToken(); await getlocation(); onButtonSelected(); getDriverRate(); addCustomCarIcon(); getKazanPercent(); getPaymentToday(); getCountRideToday(); getAllPayment(); startPeriodicExecution(); getCaptainWalletFromBuyPoints(); // onMapCreated(mapHomeCaptainController!); // totalPoints = Get.find().totalPoints.toString(); // getRefusedOrderByCaptain(); // 🔥 الفحص عند تشغيل التطبيق checkAndShowBlockDialog(); box.write(BoxName.statusDriverLocation, 'off'); // 2. عدل الليسنر ليصبح مشروطاً // 2. مؤقت التتبع التلقائي (كل 5 ثوانٍ كما في الكود السابق) _cameraFollowTimer = Timer.periodic(const Duration(seconds: 5), (timer) { if (isClosed || !isHomeMapActive || mapHomeCaptainController == null || !isActive) return; var loc = locationController.myLocation; if (loc.latitude != 0 && loc.latitude != null && !loc.latitude.isNaN) { try { // 🔥 Safety double-check before animating if (mapHomeCaptainController != null) { print("🔥 [HomeCaptain] Safely moving camera to: ${loc.latitude}"); mapHomeCaptainController?.animateCamera( CameraUpdate.newLatLngZoom(loc, 17.5), ); } } catch (e) { print("❌ [HomeCaptain] Camera movement failed: $e"); } } }); // LocationController().getLocation(); super.onInit(); } addToken() async { String? fingerPrint = await storage.read(key: BoxName.fingerPrint); final payload = { 'token': (box.read(BoxName.tokenDriver)), 'captain_id': (box.read(BoxName.driverID)).toString(), 'fingerPrint': (fingerPrint).toString() }; // Log.print('payload: ${payload}'); CRUD().post(link: AppLink.addTokensDriver, payload: payload); } getPaymentToday() async { var res = await CRUD().getWallet( link: AppLink.getDriverPaymentToday, payload: {'driverID': box.read(BoxName.driverID).toString()}); if (res != 'failure') { data = jsonDecode(res); totalMoneyToday = data['message'][0]['todayAmount'].toString(); update(); } else {} } getKazanPercent() async { var res = await CRUD().get( link: AppLink.getKazanPercent, payload: {'country': box.read(BoxName.countryCode).toString()}, ); if (res != 'failure') { var json = jsonDecode(res); kazan = double.parse(json['message'][0]['kazan']); naturePrice = double.parse(json['message'][0]['naturePrice']); heavyPrice = double.parse(json['message'][0]['heavyPrice']); latePrice = double.parse(json['message'][0]['latePrice']); comfortPrice = double.parse(json['message'][0]['comfortPrice']); speedPrice = double.parse(json['message'][0]['speedPrice']); deliveryPrice = double.parse(json['message'][0]['deliveryPrice']); mashwariPrice = double.parse(json['message'][0]['freePrice']); familyPrice = double.parse(json['message'][0]['familyPrice']); fuelPrice = double.parse(json['message'][0]['fuelPrice']); } update(); } double mpg = 0; calculateConsumptionFuel() { mpg = fuelPrice / 12; //todo in register car add mpg in box } getCountRideToday() async { var res = await CRUD().get( link: AppLink.getCountRide, payload: {'driver_id': box.read(BoxName.driverID).toString()}); data = jsonDecode(res); countRideToday = data['message'][0]['count'].toString(); update(); } getDriverRate() async { var res = await CRUD().get( link: AppLink.getDriverRate, payload: {'driver_id': box.read(BoxName.driverID).toString()}); if (res != 'failure') { var decod = jsonDecode(res); if (decod['message'][0]['rating'] != null) { rating = double.parse(decod['message'][0]['rating'].toString()); } else { rating = 5.0; // Set a default value (e.g., 5.0 for full rating) } } else { rating = 5; } } getAllPayment() async { var res = await CRUD().getWallet( link: AppLink.getAllPaymentFromRide, payload: {'driverID': box.read(BoxName.driverID).toString()}); if (res == 'failure') { totalMoneyInSEFER = '0'; } else { data = jsonDecode(res); totalMoneyInSEFER = data['message'][0]['total_amount']; } update(); } void changeToAppliedRide(String status) { box.write(BoxName.rideStatus, status); Log.print('rideStatus from homcaptain : ${box.read(BoxName.rideStatus)}'); update(); } Future getCaptainDurationOnToday() async { try { var res = await CRUD().get( link: AppLink.getTotalDriverDurationToday, payload: {'driver_id': box.read(BoxName.driverID).toString()}, ); if (res == null || res == 'failure') { totalDurationToday = '0'; update(); return; } var data = jsonDecode(res); totalDurationToday = data['message']?[0]?['total_duration'] ?? '0'; } catch (e) { print('Error in getCaptainDurationOnToday: $e'); totalDurationToday = '0'; } update(); } }