1053 lines
34 KiB
Dart
1053 lines
34 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'dart:math' show cos, pi, max, min, atan2, sin, sqrt;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:geolocator/geolocator.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:intaleq_maps/intaleq_maps.dart';
|
|
import 'package:location/location.dart';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import '../../../constant/box_name.dart';
|
|
import '../../../constant/links.dart';
|
|
import '../../../constant/colors.dart';
|
|
import '../../../constant/style.dart';
|
|
import '../../../constant/table_names.dart';
|
|
import '../../../main.dart'; // contains global 'box', 'sql'
|
|
import '../../../print.dart';
|
|
import '../../../services/offline_map_service.dart';
|
|
import '../../functions/crud.dart';
|
|
import '../points_for_rider_controller.dart';
|
|
import '../../../views/home/map_widget.dart/form_serch_multiy_point.dart';
|
|
import '../../../views/widgets/error_snakbar.dart';
|
|
import 'map_engine_controller.dart';
|
|
import '../deep_link_controller.dart';
|
|
import 'ride_lifecycle_controller.dart';
|
|
import 'ride_state.dart';
|
|
import '../../../constant/country_polygons.dart';
|
|
import '../../../constant/univeries_polygon.dart';
|
|
|
|
class LocationSearchController extends GetxController {
|
|
List<Map<String, dynamic>> waypoints = [];
|
|
String passengerLocationStringUnvirsity = 'Not in University';
|
|
bool isInUniversity = false;
|
|
List<String> coordinatesWithoutEmpty = [];
|
|
LatLng? latestPosition;
|
|
|
|
List<LatLng> polylineCoordinates0 = [];
|
|
List<LatLng> polylineCoordinates1 = [];
|
|
List<LatLng> polylineCoordinates2 = [];
|
|
List<LatLng> polylineCoordinates3 = [];
|
|
List<LatLng> polylineCoordinates4 = [];
|
|
late List<List<LatLng>> polylineCoordinatesPointsAll = [
|
|
polylineCoordinates0,
|
|
polylineCoordinates1,
|
|
polylineCoordinates2,
|
|
polylineCoordinates3,
|
|
polylineCoordinates4,
|
|
];
|
|
|
|
bool isLoading = true;
|
|
TextEditingController placeDestinationController = TextEditingController();
|
|
TextEditingController placeStartController = TextEditingController();
|
|
TextEditingController wayPoint0Controller = TextEditingController();
|
|
TextEditingController wayPoint1Controller = TextEditingController();
|
|
TextEditingController wayPoint2Controller = TextEditingController();
|
|
TextEditingController wayPoint3Controller = TextEditingController();
|
|
TextEditingController wayPoint4Controller = TextEditingController();
|
|
TextEditingController whatsAppLocationText = TextEditingController();
|
|
final sosFormKey = GlobalKey<FormState>();
|
|
|
|
List<LatLng> bounds = [];
|
|
List placesStart = [];
|
|
List<TextEditingController> allTextEditingPlaces = [];
|
|
List placesDestination = [];
|
|
List wayPoint0 = [];
|
|
List wayPoint1 = [];
|
|
List wayPoint2 = [];
|
|
List wayPoint3 = [];
|
|
List wayPoint4 = [];
|
|
List<List<dynamic>> placeListResponseAll = [];
|
|
|
|
List<Widget> placeListResponse = [];
|
|
|
|
PermissionStatus? permissionGranted;
|
|
final location = Location();
|
|
double speed = 0;
|
|
|
|
late LatLng passengerLocation = const LatLng(32, 34);
|
|
late LatLng newMyLocation = const LatLng(32.115295, 36.064773);
|
|
late LatLng newStartPointLocation = const LatLng(32.115295, 36.064773);
|
|
late LatLng newPointLocation0 = const LatLng(32.115295, 36.064773);
|
|
late LatLng newPointLocation1 = const LatLng(32.115295, 36.064773);
|
|
late LatLng newPointLocation2 = const LatLng(32.115295, 36.064773);
|
|
late LatLng newPointLocation3 = const LatLng(32.115295, 36.064773);
|
|
late LatLng newPointLocation4 = const LatLng(32.115295, 36.064773);
|
|
late LatLng myDestination = const LatLng(32, 34);
|
|
|
|
bool startLocationFromMap = false;
|
|
bool passengerStartLocationFromMap = false;
|
|
bool workLocationFromMap = false;
|
|
bool homeLocationFromMap = false;
|
|
bool isPassengerRideLocationWidget = false;
|
|
bool startLocationFromMap0 = false;
|
|
bool startLocationFromMap1 = false;
|
|
bool startLocationFromMap2 = false;
|
|
bool startLocationFromMap3 = false;
|
|
bool startLocationFromMap4 = false;
|
|
List startLocationFromMapAll = [];
|
|
|
|
bool currentLocationToFormPlaces = false;
|
|
bool currentLocationToFormPlaces0 = false;
|
|
bool currentLocationToFormPlaces1 = false;
|
|
bool currentLocationToFormPlaces2 = false;
|
|
bool currentLocationToFormPlaces3 = false;
|
|
bool currentLocationToFormPlaces4 = false;
|
|
List currentLocationToFormPlacesAll = [];
|
|
|
|
// Multi-Waypoint (max 2 stops)
|
|
List<LatLng?> menuWaypoints = [null, null];
|
|
List<String> menuWaypointNames = ['', ''];
|
|
int activeMenuWaypointCount = 0;
|
|
bool isPickingWaypoint = false;
|
|
int pickingWaypointIndex = -1;
|
|
|
|
int wayPointIndex = 0;
|
|
String hintTextStartPoint = 'Search for your Start point'.tr;
|
|
String hintTextwayPoint0 = 'Search for waypoint'.tr;
|
|
String hintTextwayPoint1 = 'Search for waypoint'.tr;
|
|
String hintTextwayPoint2 = 'Search for waypoint'.tr;
|
|
String hintTextwayPoint3 = 'Search for waypoint'.tr;
|
|
String hintTextwayPoint4 = 'Search for waypoint'.tr;
|
|
String currentLocationString = 'Current Location'.tr;
|
|
String currentLocationString0 = 'Current Location'.tr;
|
|
String currentLocationString1 = 'Add Location 1'.tr;
|
|
String currentLocationString2 = 'Add Location 2'.tr;
|
|
String currentLocationString3 = 'Add Location 3'.tr;
|
|
String currentLocationString4 = 'Add Location 4'.tr;
|
|
String placesCoordinate0 = ''.tr;
|
|
String placesCoordinate1 = ''.tr;
|
|
String placesCoordinate2 = ''.tr;
|
|
String placesCoordinate3 = ''.tr;
|
|
String placesCoordinate4 = ''.tr;
|
|
List<String> currentLocationStringAll = [];
|
|
List<String> hintTextwayPointStringAll = [];
|
|
var placesCoordinate = <String>[];
|
|
String hintTextDestinationPoint = 'Select your destination'.tr;
|
|
|
|
late String startNameAddress = '';
|
|
late String endNameAddress = '';
|
|
List<Map<String, dynamic>> stopPoints = [];
|
|
|
|
double latitudeWhatsApp = 0;
|
|
double longitudeWhatsApp = 0;
|
|
late List recentPlaces = [];
|
|
|
|
Timer? _camThrottle;
|
|
|
|
final DeepLinkController _deepLinkController =
|
|
Get.isRegistered<DeepLinkController>()
|
|
? Get.find<DeepLinkController>()
|
|
: Get.put(DeepLinkController());
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
placeListResponse = [
|
|
formSearchPlaces(0),
|
|
formSearchPlaces(1),
|
|
formSearchPlaces(2),
|
|
formSearchPlaces(3),
|
|
];
|
|
readyWayPoints();
|
|
getLocation();
|
|
}
|
|
|
|
void readyWayPoints() {
|
|
hintTextwayPointStringAll = [
|
|
hintTextwayPoint0,
|
|
hintTextwayPoint1,
|
|
hintTextwayPoint2,
|
|
hintTextwayPoint3,
|
|
hintTextwayPoint4,
|
|
];
|
|
allTextEditingPlaces = [
|
|
wayPoint0Controller,
|
|
wayPoint1Controller,
|
|
wayPoint2Controller,
|
|
wayPoint3Controller,
|
|
wayPoint4Controller,
|
|
];
|
|
currentLocationToFormPlacesAll = [
|
|
currentLocationToFormPlaces0,
|
|
currentLocationToFormPlaces1,
|
|
currentLocationToFormPlaces2,
|
|
currentLocationToFormPlaces3,
|
|
currentLocationToFormPlaces4,
|
|
];
|
|
placeListResponseAll = [
|
|
wayPoint0,
|
|
wayPoint1,
|
|
wayPoint2,
|
|
wayPoint3,
|
|
wayPoint4
|
|
];
|
|
startLocationFromMapAll = [
|
|
startLocationFromMap0,
|
|
startLocationFromMap1,
|
|
startLocationFromMap2,
|
|
startLocationFromMap3,
|
|
startLocationFromMap4,
|
|
];
|
|
currentLocationStringAll = [
|
|
currentLocationString0,
|
|
currentLocationString1,
|
|
currentLocationString2,
|
|
currentLocationString3,
|
|
currentLocationString4,
|
|
];
|
|
placesCoordinate = [
|
|
placesCoordinate0,
|
|
placesCoordinate1,
|
|
placesCoordinate2,
|
|
placesCoordinate3,
|
|
placesCoordinate4,
|
|
];
|
|
update();
|
|
}
|
|
|
|
void removeStop(Map<String, dynamic> stop) {
|
|
stopPoints.remove(stop);
|
|
update();
|
|
}
|
|
|
|
Future<void> savePlaceToServer(
|
|
String latitude, String longitude, String name, String rate) async {
|
|
var data = {
|
|
'latitude': latitude,
|
|
'longitude': longitude,
|
|
'name': name,
|
|
'rate': rate,
|
|
};
|
|
try {
|
|
CRUD().post(
|
|
link: AppLink.savePlacesServer,
|
|
payload: data,
|
|
);
|
|
} catch (e) {
|
|
Log.print('Error: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> getLocation() async {
|
|
Log.print('🛰️ getLocation() called');
|
|
permissionGranted = await location.hasPermission();
|
|
if (permissionGranted == PermissionStatus.denied) {
|
|
permissionGranted = await location.requestPermission();
|
|
if (permissionGranted != PermissionStatus.granted) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Pre-populate with last known position for instant loading
|
|
try {
|
|
Position? lastPosition = await Geolocator.getLastKnownPosition();
|
|
if (lastPosition != null) {
|
|
passengerLocation =
|
|
LatLng(lastPosition.latitude, lastPosition.longitude);
|
|
newStartPointLocation = passengerLocation;
|
|
newMyLocation = passengerLocation;
|
|
Log.print(
|
|
'📍 Pre-populated location from last known: $passengerLocation');
|
|
}
|
|
} catch (e) {
|
|
Log.print('⚠️ Error getting last known position: $e');
|
|
}
|
|
|
|
LocationData? _locationData;
|
|
try {
|
|
_locationData = await location.getLocation().timeout(
|
|
const Duration(seconds: 5),
|
|
onTimeout: () {
|
|
Log.print("⚠️ Location fetch timed out after 5s.");
|
|
return LocationData.fromMap({
|
|
"latitude": passengerLocation.latitude,
|
|
"longitude": passengerLocation.longitude,
|
|
"speed": 0.0
|
|
});
|
|
},
|
|
);
|
|
} catch (e) {
|
|
Log.print("⚠️ Error fetching location: $e");
|
|
}
|
|
|
|
if (_locationData == null) {
|
|
isLoading = false;
|
|
update();
|
|
return;
|
|
}
|
|
passengerLocation =
|
|
(_locationData.latitude != null && _locationData.longitude != null
|
|
? LatLng(_locationData.latitude!, _locationData.longitude!)
|
|
: const LatLng(32, 34));
|
|
|
|
newStartPointLocation = passengerLocation;
|
|
newMyLocation = passengerLocation;
|
|
|
|
if (Get.isRegistered<RideLifecycleController>()) {
|
|
final rideLifecycle = Get.find<RideLifecycleController>();
|
|
rideLifecycle.getLocationArea(
|
|
passengerLocation.latitude, passengerLocation.longitude);
|
|
rideLifecycle.resetNoRideSearch();
|
|
}
|
|
|
|
try {
|
|
getReverseGeocoding(passengerLocation).then((address) {
|
|
currentLocationString = address;
|
|
update();
|
|
});
|
|
} catch (e) {
|
|
Log.print('Error resolving current location: $e');
|
|
}
|
|
|
|
OfflineMapService.instance
|
|
.downloadRegion(passengerLocation, radiusKm: 10.0);
|
|
|
|
try {
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
if (mapEngine.mapController != null) {
|
|
mapEngine.mapController!.animateCamera(
|
|
CameraUpdate.newLatLng(passengerLocation),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
Log.print('Error animating camera to passenger location: $e');
|
|
}
|
|
|
|
speed = _locationData.speed ?? 0.0;
|
|
isLoading = false;
|
|
update();
|
|
}
|
|
|
|
void getCurrentLocationFormString() async {
|
|
currentLocationToFormPlaces = true;
|
|
currentLocationString = 'Waiting for your location'.tr;
|
|
await getLocation();
|
|
currentLocationString = passengerLocation.toString();
|
|
newStartPointLocation = passengerLocation;
|
|
update();
|
|
}
|
|
|
|
Future<void> getPlaces() async {
|
|
final q = placeDestinationController.text.trim();
|
|
if (q.isEmpty || q.length < 3) {
|
|
placesDestination = [];
|
|
update();
|
|
return;
|
|
}
|
|
|
|
final lat = passengerLocation.latitude;
|
|
final lng = passengerLocation.longitude;
|
|
final country = CountryPolygons.getCountryName(passengerLocation);
|
|
|
|
try {
|
|
final url =
|
|
'${AppLink.searchGeocoding}?q=${Uri.encodeComponent(q)}&lat=$lat&lng=$lng&radius=15000&country=$country';
|
|
final response = await CRUD().getMapSaas(link: url);
|
|
|
|
if (response != null && response['results'] is List) {
|
|
List results = List.from(response['results']);
|
|
final List filteredResults = [];
|
|
final Set<String> seenPlaces = {};
|
|
|
|
for (final p in results) {
|
|
final name = p['name_ar'] ?? p['name'] ?? '';
|
|
final district = p['district'] ?? '';
|
|
final plat = p['latitude']?.toString() ?? '0';
|
|
final plng = p['longitude']?.toString() ?? '0';
|
|
|
|
final dedupeKey =
|
|
"${name.trim().toLowerCase()}_${district.trim().toLowerCase()}";
|
|
|
|
if (!seenPlaces.contains(dedupeKey)) {
|
|
seenPlaces.add(dedupeKey);
|
|
|
|
p['distanceKm'] = (p['distance'] as num).toDouble() / 1000.0;
|
|
p['latitude'] = plat;
|
|
p['longitude'] = plng;
|
|
p['name'] = name;
|
|
p['address'] = p['full_address'] ??
|
|
(district.isNotEmpty
|
|
? "$district، ${p['governorate'] ?? ''}"
|
|
: (p['governorate'] ?? ''));
|
|
|
|
filteredResults.add(p);
|
|
}
|
|
}
|
|
|
|
placesDestination = filteredResults;
|
|
update();
|
|
}
|
|
} catch (e) {
|
|
Log.print('Exception in getPlaces: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> getPlacesStart() async {
|
|
final q = placeStartController.text.trim();
|
|
if (q.isEmpty || q.length < 3) {
|
|
placesStart = [];
|
|
update();
|
|
return;
|
|
}
|
|
|
|
final lat = passengerLocation.latitude;
|
|
final lng = passengerLocation.longitude;
|
|
final country = CountryPolygons.getCountryName(passengerLocation);
|
|
|
|
try {
|
|
final url =
|
|
'${AppLink.searchGeocoding}?q=${Uri.encodeComponent(q)}&lat=$lat&lng=$lng&radius=15000&country=$country';
|
|
final response = await CRUD().getMapSaas(link: url);
|
|
|
|
if (response != null && response['results'] is List) {
|
|
List list = List.from(response['results']);
|
|
for (final p in list) {
|
|
p['distanceKm'] = (p['distance'] as num).toDouble() / 1000.0;
|
|
p['latitude'] = p['latitude'].toString();
|
|
p['longitude'] = p['longitude'].toString();
|
|
p['name'] = p['name_ar'] ?? p['name'] ?? '';
|
|
p['address'] = p['full_address'] ??
|
|
(p['district'] != null
|
|
? "${p['district']}، ${p['governorate'] ?? ''}"
|
|
: (p['governorate'] ?? ''));
|
|
}
|
|
placesStart = list;
|
|
update();
|
|
}
|
|
} catch (e) {
|
|
Log.print('Exception in getPlacesStart: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> getPlacesListsWayPoint(int index) async {
|
|
final q = wayPoint0Controller.text.trim();
|
|
if (q.length < 3) return;
|
|
|
|
final lat = passengerLocation.latitude;
|
|
final lng = passengerLocation.longitude;
|
|
final country = CountryPolygons.getCountryName(passengerLocation);
|
|
|
|
try {
|
|
final url =
|
|
'${AppLink.searchGeocoding}?q=${Uri.encodeComponent(q)}&lat=$lat&lng=$lng&radius=15000&country=$country';
|
|
final response = await CRUD().getMapSaas(link: url);
|
|
|
|
if (response != null && response['results'] is List) {
|
|
List list = List.from(response['results']);
|
|
for (final p in list) {
|
|
p['distanceKm'] = (p['distance'] as num).toDouble() / 1000.0;
|
|
p['latitude'] = p['latitude'].toString();
|
|
p['longitude'] = p['longitude'].toString();
|
|
p['name'] = p['name_ar'] ?? p['name'] ?? '';
|
|
p['address'] = p['full_address'] ??
|
|
(p['district'] != null
|
|
? "${p['district']}، ${p['governorate'] ?? ''}"
|
|
: (p['governorate'] ?? ''));
|
|
}
|
|
wayPoint0 = list;
|
|
placeListResponseAll[index] = list;
|
|
update();
|
|
}
|
|
} catch (e) {
|
|
Log.print('Error fetching places in WayPoint: $e');
|
|
}
|
|
}
|
|
|
|
Future<String> getReverseGeocoding(LatLng location) async {
|
|
final lat = location.latitude;
|
|
final lng = location.longitude;
|
|
final url = '${AppLink.reverseGeocoding}?lat=$lat&lng=$lng';
|
|
|
|
try {
|
|
final response = await CRUD().getMapSaas(link: url);
|
|
|
|
if (response != null && response is List && response.isNotEmpty) {
|
|
final data = response[0];
|
|
String name = data['name_ar'] ?? data['name'] ?? 'Unknown Location'.tr;
|
|
return name;
|
|
}
|
|
return 'Unknown Location'.tr;
|
|
} catch (e) {
|
|
Log.print('ReverseGeocoding Exception: $e');
|
|
return 'Unknown Location'.tr;
|
|
}
|
|
}
|
|
|
|
void updateCurrentLocationFromCamera(LatLng target) {
|
|
Log.print('📍 updateCurrentLocationFromCamera: $target');
|
|
newMyLocation = target;
|
|
|
|
if (startLocationFromMap == true) {
|
|
Log.print('📍 Updating startLocationFromMap to $target');
|
|
newStartPointLocation = target;
|
|
} else if (passengerStartLocationFromMap == true) {
|
|
Log.print('📍 Updating passengerStartLocationFromMap to $target');
|
|
newStartPointLocation = target;
|
|
}
|
|
|
|
int waypointsLength = Get.find<WayPointController>().wayPoints.length;
|
|
if (waypointsLength > 0 &&
|
|
wayPointIndex >= 0 &&
|
|
wayPointIndex < placesCoordinate.length) {
|
|
Log.print('📍 Updating wayPointIndex $wayPointIndex to $target');
|
|
placesCoordinate[wayPointIndex] =
|
|
'${target.latitude},${target.longitude}';
|
|
}
|
|
if (Get.isRegistered<RideLifecycleController>()) {
|
|
final rideLifecycle = Get.find<RideLifecycleController>();
|
|
rideLifecycle.getLocationArea(target.latitude, target.longitude);
|
|
rideLifecycle.resetNoRideSearch();
|
|
}
|
|
update();
|
|
}
|
|
|
|
void onCameraMoveThrottled(CameraPosition pos) {
|
|
_camThrottle?.cancel();
|
|
_camThrottle = Timer(const Duration(milliseconds: 160), () {
|
|
Log.print('📸 onCameraMoveThrottled: ${pos.target}');
|
|
int waypointsLength = Get.find<WayPointController>().wayPoints.length;
|
|
int index = wayPointIndex;
|
|
if (waypointsLength > 0 && index < placesCoordinate.length) {
|
|
placesCoordinate[index] =
|
|
'${pos.target.latitude},${pos.target.longitude}';
|
|
}
|
|
newMyLocation = pos.target;
|
|
});
|
|
}
|
|
|
|
void convertHintTextPlaces(int index, var res) {
|
|
if (placeListResponseAll[index].isEmpty) {
|
|
placeListResponseAll[index] = res;
|
|
hintTextwayPointStringAll[index] = 'Search for your Start point'.tr;
|
|
update();
|
|
} else {
|
|
hintTextwayPointStringAll[index] = res['name'];
|
|
currentLocationStringAll[index] = res['name'];
|
|
placesCoordinate[index] =
|
|
'${res['geometry']['location']['lat']},${res['geometry']['location']['lng']}';
|
|
placeListResponseAll[index] = [];
|
|
allTextEditingPlaces[index].clear();
|
|
update();
|
|
Get.back();
|
|
}
|
|
}
|
|
|
|
void convertHintTextDestinationNewPlaces(int index) {
|
|
if (placesDestination.isEmpty) {
|
|
hintTextDestinationPoint = 'Search for your destination'.tr;
|
|
update();
|
|
} else {
|
|
var res = placesDestination[index];
|
|
hintTextDestinationPoint = res['displayName']?['text'] ??
|
|
res['formattedAddress'] ??
|
|
res['name'] ??
|
|
'Unknown Place';
|
|
double? lat = res['location']?['latitude'] ??
|
|
double.tryParse(res['latitude']?.toString() ?? '');
|
|
double? lng = res['location']?['longitude'] ??
|
|
double.tryParse(res['longitude']?.toString() ?? '');
|
|
|
|
if (lat != null && lng != null) {
|
|
newMyLocation = LatLng(lat, lng);
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
mapEngine.mapController
|
|
?.animateCamera(CameraUpdate.newLatLngZoom(newMyLocation, 16));
|
|
}
|
|
update();
|
|
}
|
|
}
|
|
|
|
void convertHintTextDestinationNewPlacesFromRecent(
|
|
List recentLocations, int index) {
|
|
hintTextDestinationPoint = recentLocations[index]['name'];
|
|
double lat = recentLocations[index]['latitude'];
|
|
double lng = recentLocations[index]['longitude'];
|
|
newMyLocation = LatLng(lat, lng);
|
|
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
mapEngine.mapController
|
|
?.animateCamera(CameraUpdate.newLatLngZoom(newMyLocation, 16));
|
|
update();
|
|
}
|
|
|
|
void clearPlacesDestination() {
|
|
placesDestination = [];
|
|
hintTextDestinationPoint = 'Search for your destination'.tr;
|
|
update();
|
|
}
|
|
|
|
void clearPlacesStart() {
|
|
placesStart = [];
|
|
hintTextStartPoint = 'Search for your Start point'.tr;
|
|
update();
|
|
}
|
|
|
|
void clearPlaces(int index) {
|
|
placeListResponseAll[index] = [];
|
|
hintTextwayPointStringAll[index] = 'Search for waypoint'.tr;
|
|
update();
|
|
}
|
|
|
|
Future<Map<String, double>?> extractCoordinatesFromLinkAsync(
|
|
String link) async {
|
|
try {
|
|
if (link.startsWith('geo:') || link.startsWith('google.navigation:')) {
|
|
RegExp regex = RegExp(r'(-?\d+\.\d+)[,/~=](-?\d+\.\d+)');
|
|
var match = regex.firstMatch(link);
|
|
if (match != null) {
|
|
double lat = double.parse(match.group(1)!);
|
|
double lng = double.parse(match.group(2)!);
|
|
if (lat > 40 && lat > lng) {
|
|
double temp = lat;
|
|
lat = lng;
|
|
lng = temp;
|
|
}
|
|
return {'latitude': lat, 'longitude': lng};
|
|
}
|
|
}
|
|
|
|
int urlStartIndex = link.indexOf(RegExp(r'https?://'));
|
|
if (urlStartIndex == -1) return null;
|
|
String cleanLink = link.substring(urlStartIndex).trim();
|
|
|
|
Uri uri = Uri.parse(cleanLink);
|
|
String finalUrl = cleanLink;
|
|
|
|
if (cleanLink.contains('goo.gl') ||
|
|
cleanLink.contains('maps.google.com')) {
|
|
try {
|
|
var response =
|
|
await http.get(uri).timeout(const Duration(seconds: 5));
|
|
finalUrl = response.request?.url.toString() ?? cleanLink;
|
|
} catch (e) {
|
|
Log.print('Redirect logic failed, using original: $e');
|
|
}
|
|
}
|
|
|
|
RegExp regex = RegExp(r'(-?\d+\.\d+)[,/~](-?\d+\.\d+)');
|
|
var match = regex.firstMatch(finalUrl);
|
|
|
|
if (match != null) {
|
|
double lat = double.parse(match.group(1)!);
|
|
double lng = double.parse(match.group(2)!);
|
|
|
|
if (lat > 40 && lat > lng) {
|
|
Log.print("⚠️ Detected Swapped Coordinates in Link. Correcting...");
|
|
double temp = lat;
|
|
lat = lng;
|
|
lng = temp;
|
|
}
|
|
|
|
return {
|
|
'latitude': lat,
|
|
'longitude': lng,
|
|
};
|
|
}
|
|
} catch (e) {
|
|
Log.print('Error parsing location link: $e');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void handleWhatsAppLink(String link) async {
|
|
Map<String, double>? coordinates =
|
|
await extractCoordinatesFromLinkAsync(link);
|
|
|
|
if (coordinates != null) {
|
|
latitudeWhatsApp = coordinates['latitude']!;
|
|
longitudeWhatsApp = coordinates['longitude']!;
|
|
Log.print(
|
|
'Extracted coordinates: Lat: $latitudeWhatsApp, Long: $longitudeWhatsApp');
|
|
} else {
|
|
Log.print('Failed to extract coordinates from the link');
|
|
}
|
|
}
|
|
|
|
void goToWhatappLocation() async {
|
|
if (sosFormKey.currentState!.validate()) {
|
|
Map<String, double>? coordinates =
|
|
await extractCoordinatesFromLinkAsync(whatsAppLocationText.text);
|
|
|
|
if (coordinates != null) {
|
|
latitudeWhatsApp = coordinates['latitude']!;
|
|
longitudeWhatsApp = coordinates['longitude']!;
|
|
|
|
Log.print(
|
|
'📍 Final Coordinates for OSM: Lat: $latitudeWhatsApp, Lng: $longitudeWhatsApp');
|
|
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
mapEngine.changeIsWhatsAppOrder(true);
|
|
Get.back();
|
|
|
|
myDestination = LatLng(latitudeWhatsApp, longitudeWhatsApp);
|
|
|
|
if (passengerLocation != null) {
|
|
await mapEngine.mapController?.animateCamera(CameraUpdate.newLatLng(
|
|
LatLng(passengerLocation.latitude, passengerLocation.longitude)));
|
|
}
|
|
|
|
mapEngine.changeMainBottomMenuMap();
|
|
passengerStartLocationFromMap = true;
|
|
mapEngine.isPickerShown = true;
|
|
update();
|
|
} else {
|
|
mySnackbarWarning('لم نتمكن من استخراج الموقع من الرابط');
|
|
}
|
|
}
|
|
}
|
|
|
|
void addMenuWaypoint() {
|
|
if (activeMenuWaypointCount >= 2) return;
|
|
activeMenuWaypointCount++;
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
mapEngine.mainBottomMenuMapHeight =
|
|
Get.height * .6 + (activeMenuWaypointCount * 56);
|
|
update();
|
|
}
|
|
|
|
void removeMenuWaypoint(int index) {
|
|
if (index < 0 || index >= 2) return;
|
|
if (index == 0 && activeMenuWaypointCount == 2) {
|
|
menuWaypoints[0] = menuWaypoints[1];
|
|
menuWaypointNames[0] = menuWaypointNames[1];
|
|
}
|
|
menuWaypoints[activeMenuWaypointCount - 1] = null;
|
|
menuWaypointNames[activeMenuWaypointCount - 1] = '';
|
|
activeMenuWaypointCount--;
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
mapEngine.mainBottomMenuMapHeight =
|
|
Get.height * .6 + (activeMenuWaypointCount * 56);
|
|
update();
|
|
}
|
|
|
|
void clearAllMenuWaypoints() {
|
|
menuWaypoints = [null, null];
|
|
menuWaypointNames = ['', ''];
|
|
activeMenuWaypointCount = 0;
|
|
isPickingWaypoint = false;
|
|
pickingWaypointIndex = -1;
|
|
update();
|
|
}
|
|
|
|
void startPickingWaypointOnMap(int index) {
|
|
pickingWaypointIndex = index;
|
|
isPickingWaypoint = true;
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
mapEngine.isPickerShown = true;
|
|
mapEngine.heightPickerContainer = 150;
|
|
mapEngine.isMainBottomMenuMap = true;
|
|
mapEngine.mainBottomMenuMapHeight = Get.height * .22;
|
|
update();
|
|
}
|
|
|
|
void setMenuWaypointFromMap(int index, LatLng position) {
|
|
Log.print('📍 setMenuWaypointFromMap called: index=$index, pos=$position');
|
|
if (index < 0 || index >= 2) return;
|
|
menuWaypoints[index] = position;
|
|
menuWaypointNames[index] =
|
|
'${position.latitude.toStringAsFixed(4)}, ${position.longitude.toStringAsFixed(4)}';
|
|
isPickingWaypoint = false;
|
|
pickingWaypointIndex = -1;
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
mapEngine.isPickerShown = false;
|
|
mapEngine.isMainBottomMenuMap = false;
|
|
mapEngine.mainBottomMenuMapHeight =
|
|
Get.height * .6 + (activeMenuWaypointCount * 56);
|
|
update();
|
|
}
|
|
|
|
void setMenuWaypointFromSearch(int index, LatLng pos, String name) {
|
|
if (index < 0 || index >= 2) return;
|
|
menuWaypoints[index] = pos;
|
|
menuWaypointNames[index] = name;
|
|
update();
|
|
}
|
|
|
|
String buildOsrmWaypointCoords() {
|
|
String coords = '';
|
|
for (int i = 0; i < activeMenuWaypointCount; i++) {
|
|
final wp = menuWaypoints[i];
|
|
if (wp != null) {
|
|
coords += ';${wp.longitude},${wp.latitude}';
|
|
}
|
|
}
|
|
return coords;
|
|
}
|
|
|
|
void changeHeightPointsPageForRider() {
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
mapEngine.isPointsPageForRider = !mapEngine.isPointsPageForRider;
|
|
mapEngine.heightPointsPageForRider =
|
|
mapEngine.isPointsPageForRider == true ? Get.height : 0;
|
|
update();
|
|
}
|
|
|
|
getCoordinateFromMapWayPoints(int index) {
|
|
placesCoordinate[index] = newStartPointLocation.toString();
|
|
update();
|
|
}
|
|
|
|
void addWaypoint(Map<String, dynamic> placeDetails) {
|
|
waypoints.add(placeDetails);
|
|
update();
|
|
}
|
|
|
|
void removeWaypoint(int index) {
|
|
if (index >= 0 && index < waypoints.length) {
|
|
waypoints.removeAt(index);
|
|
update();
|
|
}
|
|
}
|
|
|
|
getFavioratePlaces() async {
|
|
recentPlaces = await sql.getCustomQuery(
|
|
'SELECT * FROM ${TableName.recentLocations} ORDER BY createdAt DESC');
|
|
update();
|
|
}
|
|
|
|
void _listenForDeepLink() {
|
|
ever(_deepLinkController.rawDeepLink, (String? link) async {
|
|
if (link != null && link.isNotEmpty) {
|
|
Log.print('📍 MapPassengerController processing link: $link');
|
|
|
|
Map<String, double>? coordinates =
|
|
await extractCoordinatesFromLinkAsync(link);
|
|
|
|
if (coordinates != null) {
|
|
double destLat = coordinates['latitude']!;
|
|
double destLng = coordinates['longitude']!;
|
|
myDestination = LatLng(destLat, destLng);
|
|
|
|
if (passengerLocation == null ||
|
|
(passengerLocation.latitude == 0 &&
|
|
passengerLocation.longitude == 0)) {
|
|
Log.print('⏳ Waiting for current location to calculate route...');
|
|
await getLocation();
|
|
}
|
|
|
|
if (passengerLocation != null) {
|
|
String originStr =
|
|
'${passengerLocation.latitude},${passengerLocation.longitude}';
|
|
String destStr = '$destLat,$destLng';
|
|
|
|
Log.print(
|
|
'🚀 Drawing route from Deep Link: $originStr to $destStr');
|
|
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
mapEngine.clearPolyline();
|
|
waypoints.clear();
|
|
clearAllMenuWaypoints();
|
|
|
|
final rideLife = Get.find<RideLifecycleController>();
|
|
await rideLife.getDirectionMap(originStr, destStr);
|
|
|
|
mapEngine.isBottomSheetShown = true;
|
|
mapEngine.heightBottomSheetShown = 250;
|
|
update();
|
|
|
|
Get.snackbar(
|
|
'Location Received'.tr,
|
|
'Route and prices have been calculated successfully!'.tr,
|
|
backgroundColor: AppColor.greenColor,
|
|
colorText: Colors.white,
|
|
);
|
|
}
|
|
} else {
|
|
Log.print('⚠️ Could not extract valid coordinates from link: $link');
|
|
}
|
|
|
|
_deepLinkController.rawDeepLink.value = null;
|
|
}
|
|
});
|
|
|
|
if (_deepLinkController.rawDeepLink.value != null &&
|
|
_deepLinkController.rawDeepLink.value!.isNotEmpty) {
|
|
String link = _deepLinkController.rawDeepLink.value!;
|
|
_deepLinkController.rawDeepLink.value = null;
|
|
|
|
Future.delayed(const Duration(milliseconds: 500), () async {
|
|
Log.print(
|
|
'📍 MapPassengerController processing link (Cold Start): $link');
|
|
|
|
Map<String, double>? coordinates =
|
|
await extractCoordinatesFromLinkAsync(link);
|
|
|
|
if (coordinates != null) {
|
|
double destLat = coordinates['latitude']!;
|
|
double destLng = coordinates['longitude']!;
|
|
myDestination = LatLng(destLat, destLng);
|
|
|
|
if (passengerLocation == null ||
|
|
(passengerLocation.latitude == 0 &&
|
|
passengerLocation.longitude == 0)) {
|
|
await getLocation();
|
|
}
|
|
|
|
if (passengerLocation != null) {
|
|
String originStr =
|
|
'${passengerLocation.latitude},${passengerLocation.longitude}';
|
|
String destStr = '$destLat,$destLng';
|
|
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
mapEngine.clearPolyline();
|
|
waypoints.clear();
|
|
clearAllMenuWaypoints();
|
|
|
|
final rideLife = Get.find<RideLifecycleController>();
|
|
await rideLife.getDirectionMap(originStr, destStr);
|
|
|
|
mapEngine.isBottomSheetShown = true;
|
|
mapEngine.heightBottomSheetShown = 250;
|
|
update();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// --- Polygon Math and University Check Methods ---
|
|
bool isPointInPolygon(LatLng point, List<LatLng> polygon) {
|
|
int intersections = 0;
|
|
for (int i = 0; i < polygon.length; i++) {
|
|
LatLng vertex1 = polygon[i];
|
|
LatLng vertex2 = polygon[(i + 1) % polygon.length];
|
|
|
|
if (_rayIntersectsSegment(point, vertex1, vertex2)) {
|
|
intersections++;
|
|
}
|
|
}
|
|
return intersections % 2 != 0;
|
|
}
|
|
|
|
bool _rayIntersectsSegment(LatLng point, LatLng vertex1, LatLng vertex2) {
|
|
double px = point.longitude;
|
|
double py = point.latitude;
|
|
|
|
double v1x = vertex1.longitude;
|
|
double v1y = vertex1.latitude;
|
|
double v2x = vertex2.longitude;
|
|
double v2y = vertex2.latitude;
|
|
|
|
if ((py < v1y && py < v2y) || (py > v1y && py > v2y)) {
|
|
return false;
|
|
}
|
|
|
|
double intersectX = v1x + (py - v1y) * (v2x - v1x) / (v2y - v1y);
|
|
return intersectX > px;
|
|
}
|
|
|
|
String checkPassengerLocation(LatLng passengerLocation,
|
|
List<List<LatLng>> universityPolygons, List<String> universityNames) {
|
|
for (int i = 0; i < universityPolygons.length; i++) {
|
|
if (isPointInPolygon(passengerLocation, universityPolygons[i])) {
|
|
isInUniversity = true;
|
|
return "Passenger is in ${universityNames[i]}";
|
|
}
|
|
}
|
|
return "Passenger is not in any university";
|
|
}
|
|
|
|
void getPassengerLocationUniversity() {
|
|
passengerLocationStringUnvirsity = checkPassengerLocation(
|
|
passengerLocation,
|
|
UniversitiesPolygons.universityPolygons,
|
|
UniversitiesPolygons.universityNames,
|
|
);
|
|
Log.print(passengerLocationStringUnvirsity);
|
|
}
|
|
|
|
void getMapPointsForAllMethods() async {
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
final rideLife = Get.find<RideLifecycleController>();
|
|
|
|
mapEngine.clearPolyline();
|
|
mapEngine.isMarkersShown = false;
|
|
mapEngine.isWayPointStopsSheetUtilGetMap = false;
|
|
mapEngine.isWayPointSheet = false;
|
|
rideLife.durationToRide = 0;
|
|
rideLife.distanceOfDestination = 0;
|
|
mapEngine.wayPointSheetHeight = 0;
|
|
rideLife.remainingTime = 25;
|
|
rideLife.haveSteps = true;
|
|
|
|
// Filter out empty value
|
|
coordinatesWithoutEmpty =
|
|
placesCoordinate.where((coord) => coord.isNotEmpty).toList();
|
|
latestPosition = LatLng(
|
|
double.parse(coordinatesWithoutEmpty.last.split(',')[0]),
|
|
double.parse(coordinatesWithoutEmpty.last.split(',')[1]));
|
|
for (var i = 0; i < coordinatesWithoutEmpty.length; i++) {
|
|
if ((i + 1) < coordinatesWithoutEmpty.length) {
|
|
await rideLife.getMapPoints(
|
|
coordinatesWithoutEmpty[i].toString(),
|
|
coordinatesWithoutEmpty[i + 1].toString(),
|
|
i,
|
|
);
|
|
if (i == 0) {
|
|
startNameAddress = rideLife.data[0]['start_address'];
|
|
}
|
|
if (i == coordinatesWithoutEmpty.length - 1) {
|
|
endNameAddress = rideLife.data[0]['end_address'];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rideLife.haveSteps) {
|
|
String latestWaypoint =
|
|
placesCoordinate.lastWhere((coord) => coord.isNotEmpty);
|
|
update();
|
|
}
|
|
}
|
|
|
|
double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
|
|
const double r = 6371.0;
|
|
double dLat = (lat2 - lat1) * pi / 180.0;
|
|
double dLon = (lon2 - lon1) * pi / 180.0;
|
|
double a = sin(dLat / 2) * sin(dLat / 2) +
|
|
cos(lat1 * pi / 180.0) *
|
|
cos(lat2 * pi / 180.0) *
|
|
sin(dLon / 2) *
|
|
sin(dLon / 2);
|
|
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
|
|
return r * c;
|
|
}
|
|
|
|
double _kmToLatDelta(double km) {
|
|
return km / 111.0;
|
|
}
|
|
|
|
double _kmToLngDelta(double km, double lat) {
|
|
return km / (111.0 * cos(lat * pi / 180.0));
|
|
}
|
|
|
|
String _buildOsrmWaypointCoords() {
|
|
String coords = '';
|
|
for (int i = 0; i < activeMenuWaypointCount; i++) {
|
|
final wp = menuWaypoints[i];
|
|
if (wp != null) {
|
|
coords += ';${wp.longitude},${wp.latitude}';
|
|
}
|
|
}
|
|
return coords;
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
_camThrottle?.cancel();
|
|
super.onClose();
|
|
}
|
|
}
|