import 'dart:async'; import 'dart:convert'; import 'dart:math' show Random, atan2, cos, pi, pow, sin, sqrt; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:intaleq_maps/intaleq_maps.dart'; import '../../../constant/links.dart'; import '../../../constant/api_key.dart'; import '../../../print.dart'; import '../../functions/crud.dart'; import 'map_engine_controller.dart'; import 'location_search_controller.dart'; import 'ride_lifecycle_controller.dart'; import '../../../models/model/locations.dart'; import 'car_location.dart'; import 'package:device_info_plus/device_info_plus.dart'; class NearbyDriversController extends GetxController { List carsLocationByPassenger = []; List driverCarsLocationToPassengerAfterApplied = []; List carLocationsModels = []; String? currentDriverMarkerId; bool lowPerf = false; dynamic dataCarsLocationByPassenger; bool noCarString = false; final double minMovementThreshold = 2.0; final Map _animationTimers = {}; final List> fakeCarData = []; Future getCarsLocationByPassengerAndReloadMarker() async { carsLocationByPassenger = []; final locSearch = Get.find(); if (locSearch.passengerLocation.latitude == 0 && locSearch.passengerLocation.longitude == 0) { return false; } var res = await CRUD().get( link: AppLink.getCarsLocationByPassenger, payload: { 'lat': locSearch.passengerLocation.latitude.toString(), 'lng': locSearch.passengerLocation.longitude.toString(), 'radius': '5', 'limit': '50', }, ); if (res == 'failure') { noCarString = true; dataCarsLocationByPassenger = 'failure'; update(); return false; } noCarString = false; var responseData = jsonDecode(res); dataCarsLocationByPassenger = responseData; List driversList = []; if (responseData['status'] == true && responseData['data'] != null) { driversList = responseData['data']; } else if (responseData['message'] != null) { driversList = responseData['message']; } final mapEngine = Get.find(); if (driversList.isEmpty) { carsLocationByPassenger.clear(); mapEngine.update(); return false; } carsLocationByPassenger.clear(); for (var i = 0; i < driversList.length; i++) { var carData = driversList[i]; double lat = double.tryParse(carData['latitude'].toString()) ?? 0.0; double lng = double.tryParse(carData['longitude'].toString()) ?? 0.0; double heading = double.tryParse(carData['heading'].toString()) ?? 0.0; if (lat == 0.0 || lng == 0.0) continue; String driverId = (carData['driver_id'] ?? carData['id'] ?? '').toString(); if (driverId.isEmpty || driverId == 'null') continue; _updateOrCreateMarker( driverId, LatLng(lat, lng), heading, _getIconForCar(carData), ); } mapEngine.update(); return true; } void _addFakeCarMarkers(LatLng center, int count) { if (fakeCarData.isEmpty) { Random random = Random(); double radiusInKm = 2.5; for (int i = 0; i < count; i++) { double angle = random.nextDouble() * 2 * pi; double distance = sqrt(random.nextDouble()) * radiusInKm; double latOffset = (distance / 111.32); double lonOffset = (distance / (111.32 * cos(center.latitude * pi / 180.0))); double lat = center.latitude + (latOffset * cos(angle)); double lon = center.longitude + (lonOffset * sin(angle)); double heading = random.nextDouble() * 360; fakeCarData.add({ 'id': 'fake_$i', 'latitude': lat, 'longitude': lon, 'heading': heading, 'gender': 'Male', }); } } for (var carData in fakeCarData) { _updateOrCreateMarker( carData['id'].toString(), LatLng(carData['latitude'], carData['longitude']), carData['heading'], _getIconForCar(carData), ); } } void addFakeCarMarkers(LatLng center, int count) { _addFakeCarMarkers(center, count); } Future getNearestDriverByPassengerLocation() async { final rideLife = Get.find(); final locSearch = Get.find(); if (!rideLife.rideConfirm) { if (dataCarsLocationByPassenger != 'failure' && dataCarsLocationByPassenger != null && dataCarsLocationByPassenger.containsKey('message') && dataCarsLocationByPassenger['message'] != null && dataCarsLocationByPassenger['message'].length > 0) { double nearestDistance = double.infinity; CarLocation? nearestCar; for (var i = 0; i < dataCarsLocationByPassenger['message'].length; i++) { var carLocation = dataCarsLocationByPassenger['message'][i]; try { final distance = Geolocator.distanceBetween( locSearch.passengerLocation.latitude, locSearch.passengerLocation.longitude, double.parse(carLocation['latitude']), double.parse(carLocation['longitude']), ); int durationToPassenger = (distance / 1000 / 25 * 3600).round(); update(); if (distance < nearestDistance) { nearestDistance = distance; nearestCar = CarLocation( distance: distance, duration: durationToPassenger.toDouble(), id: carLocation['driver_id'], latitude: double.parse(carLocation['latitude']), longitude: double.parse(carLocation['longitude']), ); update(); } } catch (e) { Log.print('Error calculating distance/duration: $e'); } } return nearestCar; } } return null; } Future getNearestDriverByPassengerLocationAPIGOOGLE() async { final rideLife = Get.find(); final mapEngine = Get.find(); final locSearch = Get.find(); if (mapEngine.polyLines.isEmpty || rideLife.totalCostPassenger == 0) { return null; } if (!rideLife.rideConfirm) { double nearestDistance = double.infinity; if (dataCarsLocationByPassenger != 'failure' && dataCarsLocationByPassenger != null && dataCarsLocationByPassenger.containsKey('message') && dataCarsLocationByPassenger['message'] != null) { if (dataCarsLocationByPassenger['message'].length > 0) { CarLocation? nearestCar; for (var i = 0; i < dataCarsLocationByPassenger['message'].length; i++) { var carLocation = dataCarsLocationByPassenger['message'][i]; update(); String apiUrl = '${AppLink.googleMapsLink}distancematrix/json?destinations=${carLocation['latitude']},${carLocation['longitude']}&origins=${locSearch.passengerLocation.latitude},${locSearch.passengerLocation.longitude}&units=metric&key=${AK.mapAPIKEY}'; var response = await CRUD().getGoogleApi(link: apiUrl, payload: {}); if (response != null && response['status'] == "OK") { var data = response; int distance1 = data['rows'][0]['elements'][0]['distance']['value']; rideLife.distanceByPassenger = data['rows'][0]['elements'][0]['distance']['text']; rideLife.durationToPassenger = data['rows'][0]['elements'][0]['duration']['value']; Duration durationFromDriverToPassenger = Duration(seconds: rideLife.durationToPassenger.toInt()); rideLife.stringRemainingTimeToPassenger = data['rows'][0]['elements'][0]['duration']['text']; update(); if (distance1 < nearestDistance) { nearestDistance = distance1.toDouble(); nearestCar = CarLocation( distance: distance1.toDouble(), duration: rideLife.durationToPassenger.toDouble(), id: carLocation['driver_id'], latitude: double.parse(carLocation['latitude']), longitude: double.parse(carLocation['longitude']), ); update(); } } else { Log.print('${response?['status']}: error Google distance matrix'); } } return nearestCar; } } } return null; } Future getCarForFirstConfirm(String carType) async { bool foundCars = false; int attempt = 0; Timer.periodic(const Duration(seconds: 4), (Timer t) async { foundCars = await getCarsLocationByPassengerAndReloadMarker(); Log.print('foundCars: $foundCars'); if (foundCars) { t.cancel(); } else if (attempt >= 4) { t.cancel(); if (!foundCars) { noCarString = true; dataCarsLocationByPassenger = 'failure'; } update(); } attempt++; }); } void startCarLocationSearch(String carType) { int searchInterval = 5; Log.print('searchInterval: $searchInterval'); int boundIncreaseStep = 2500; Log.print('boundIncreaseStep: $boundIncreaseStep'); int maxAttempts = 3; int maxBoundIncreaseStep = 6000; int attempt = 0; Log.print('initial attempt: $attempt'); Timer.periodic(Duration(seconds: searchInterval), (Timer timer) async { Log.print('Current attempt: $attempt'); bool foundCars = false; final mapEngine = Get.find(); if (attempt >= maxAttempts) { timer.cancel(); if (foundCars == false) { noCarString = true; update(); } } else if (mapEngine.reloadStartApp == true) { Log.print('reloadStartApp: ${mapEngine.reloadStartApp}'); foundCars = await getCarsLocationByPassengerAndReloadMarker(); Log.print('foundCars: $foundCars'); if (foundCars) { timer.cancel(); } else { attempt++; Log.print('Incrementing attempt to: $attempt'); if (boundIncreaseStep < maxBoundIncreaseStep) { boundIncreaseStep += 1500; if (boundIncreaseStep > maxBoundIncreaseStep) { boundIncreaseStep = maxBoundIncreaseStep; } Log.print('New boundIncreaseStep: $boundIncreaseStep'); } } } }); } String _getIconForCar(Map carData) { final mapEngine = Get.find(); if (carData['model'].toString().contains('دراجة')) { return mapEngine.motoIcon; } else if (carData['gender'] == 'Female') { return mapEngine.ladyIcon; } else { return mapEngine.carIcon; } } void _updateOrCreateMarker( String markerId, LatLng newPosition, double newHeading, String icon) { final mapEngine = Get.find(); if (!mapEngine.isIconsLoaded) { Log.print("⚠️ Skipping drawing marker $markerId because map icons are not fully loaded yet."); return; } final mId = MarkerId(markerId); final existingMarker = mapEngine.markers.cast().firstWhere( (m) => m?.markerId == mId, orElse: () => null, ); if (existingMarker == null) { mapEngine.markers = { ...mapEngine.markers, Marker( markerId: mId, position: newPosition, rotation: newHeading, icon: InlqBitmap.fromStyleImage(icon), anchor: const Offset(0.5, 0.5), ), }; mapEngine.update(); } else { double distance = Geolocator.distanceBetween( existingMarker.position.latitude, existingMarker.position.longitude, newPosition.latitude, newPosition.longitude); if (distance >= minMovementThreshold) { _smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon); } } } void _smoothlyUpdateMarker( Marker oldMarker, LatLng newPosition, double newHeading, String icon) { final mapEngine = Get.find(); final MarkerId markerIdKey = oldMarker.markerId; _animationTimers[markerIdKey.value]?.cancel(); int ticks = 0; const int totalSteps = 20; const int stepDuration = 50; double latStep = (newPosition.latitude - oldMarker.position.latitude) / totalSteps; double lngStep = (newPosition.longitude - oldMarker.position.longitude) / totalSteps; double headingStep = (newHeading - oldMarker.rotation) / totalSteps; LatLng currentPos = oldMarker.position; double currentHeading = oldMarker.rotation; _animationTimers[markerIdKey.value] = Timer.periodic(const Duration(milliseconds: stepDuration), (timer) { ticks++; currentPos = LatLng(currentPos.latitude + latStep, currentPos.longitude + lngStep); currentHeading += headingStep; final updatedMarker = oldMarker.copyWith( position: currentPos, rotation: currentHeading, icon: InlqBitmap.fromStyleImage(icon), ); mapEngine.markers = { ...mapEngine.markers.where((m) => m.markerId != markerIdKey), updatedMarker, }; if (mapEngine.mapController != null) { mapEngine.mapController!.animateCamera(CameraUpdate.newLatLng(currentPos)); } mapEngine.update(); if (ticks >= totalSteps) { timer.cancel(); _animationTimers.remove(markerIdKey.value); } }); } double calculateBearing(double lat1, double lon1, double lat2, double lon2) { double deltaLon = lon2 - lon1; double y = sin(deltaLon) * cos(lat2); double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(deltaLon); double bearing = atan2(y, x); return (bearing * 180 / pi + 360) % 360; } void analyzeBehavior(Position currentPosition, List routePoints) { double actualBearing = currentPosition.heading; double expectedBearing = calculateBearing( routePoints[0].latitude, routePoints[0].longitude, routePoints[1].latitude, routePoints[1].longitude, ); double bearingDifference = (expectedBearing - actualBearing).abs(); if (bearingDifference > 30) { Log.print("⚠️ السائق انحرف عن المسار!"); } } void detectStops(Position currentPosition) { if (currentPosition.speed < 0.5) { Log.print("🚦 السائق توقف في موقع غير متوقع!"); } } Future detectPerfMode() async { try { if (GetPlatform.isAndroid) { final info = await DeviceInfoPlugin().androidInfo; final sdk = info.version.sdkInt; final ram = info.availableRamSize; lowPerf = (sdk < 28) || (ram > 0 && ram < 3 * 1024 * 1024 * 1024); } else { lowPerf = false; } } catch (_) { lowPerf = false; } update(); } @override void onClose() { _animationTimers.forEach((key, timer) => timer.cancel()); _animationTimers.clear(); super.onClose(); } }