Fixes & Updates - 2026-06-01: Integrate Back-End v3 updates, fix call/connection issues across apps
This commit is contained in:
475
lib/controller/home/map/nearby_drivers_controller.dart
Normal file
475
lib/controller/home/map/nearby_drivers_controller.dart
Normal file
@@ -0,0 +1,475 @@
|
||||
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<LatLng> driverCarsLocationToPassengerAfterApplied = [];
|
||||
List<CarLocationModel> carLocationsModels = [];
|
||||
String? currentDriverMarkerId;
|
||||
bool lowPerf = false;
|
||||
|
||||
dynamic dataCarsLocationByPassenger;
|
||||
bool noCarString = false;
|
||||
final double minMovementThreshold = 2.0;
|
||||
final Map<String, Timer> _animationTimers = {};
|
||||
|
||||
final List<Map<String, dynamic>> fakeCarData = [];
|
||||
|
||||
Future<bool> getCarsLocationByPassengerAndReloadMarker() async {
|
||||
carsLocationByPassenger = [];
|
||||
final locSearch = Get.find<LocationSearchController>();
|
||||
|
||||
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<MapEngineController>();
|
||||
|
||||
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<CarLocation?> getNearestDriverByPassengerLocation() async {
|
||||
final rideLife = Get.find<RideLifecycleController>();
|
||||
final locSearch = Get.find<LocationSearchController>();
|
||||
|
||||
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<CarLocation?> getNearestDriverByPassengerLocationAPIGOOGLE() async {
|
||||
final rideLife = Get.find<RideLifecycleController>();
|
||||
final mapEngine = Get.find<MapEngineController>();
|
||||
final locSearch = Get.find<LocationSearchController>();
|
||||
|
||||
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<MapEngineController>();
|
||||
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<String, dynamic> carData) {
|
||||
final mapEngine = Get.find<MapEngineController>();
|
||||
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<MapEngineController>();
|
||||
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<Marker?>().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<MapEngineController>();
|
||||
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<LatLng> 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<void> 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user