25-10-5/1

This commit is contained in:
Hamza-Ayed
2025-10-05 14:12:23 +03:00
parent 729378c507
commit f5dfe2c0fe
19 changed files with 1332 additions and 970 deletions

View File

@@ -6,6 +6,7 @@ import 'dart:math' as math;
import 'dart:ui';
import 'dart:convert';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:Intaleq/constant/univeries_polygon.dart';
@@ -22,7 +23,7 @@ import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
// import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
import 'package:intl/intl.dart';
import 'package:location/location.dart';
import 'package:Intaleq/constant/colors.dart';
@@ -52,6 +53,7 @@ import '../functions/launch.dart';
import '../functions/package_info.dart';
import '../functions/secure_storage.dart';
import '../payment/payment_controller.dart';
import 'decode_polyline_isolate.dart';
import 'deep_link_controller.dart';
import 'device_tier.dart';
import 'vip_waitting_page.dart';
@@ -1035,7 +1037,7 @@ class MapPassengerController extends GetxController {
if (isBeginRideFromDriverRunning) return; // Prevent duplicate streams
isBeginRideFromDriverRunning = true;
Timer.periodic(const Duration(seconds: 1), (timer) async {
Timer.periodic(const Duration(seconds: 2), (timer) async {
try {
var res = await CRUD().get(
link: AppLink.getRideStatusBegin, payload: {'ride_id': rideId});
@@ -1138,13 +1140,14 @@ class MapPassengerController extends GetxController {
Map<String, dynamic> tripData =
box.read(BoxName.tripData) as Map<String, dynamic>;
final points = decodePolyline(
tripData["routes"][0]["overview_polyline"]["points"]);
String pointsString =
(tripData["routes"][0]["overview_polyline"]["points"]);
for (int i = 0; i < points.length; i++) {
double lat = points[i][0].toDouble();
double lng = points[i][1].toDouble();
polylineCoordinates.add(LatLng(lat, lng));
List<LatLng> decodedPoints =
await compute(decodePolylineIsolate, pointsString);
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
for (int i = 0; i < decodedPoints.length; i++) {
polylineCoordinates.add(decodedPoints[i]);
}
var polyline = Polyline(
polylineId: const PolylineId('begin trip'),
@@ -1819,6 +1822,7 @@ class MapPassengerController extends GetxController {
await getCarsLocationByPassengerAndReloadMarker(
box.read(BoxName.carType), reloadDuration);
// await getNearestDriverByPassengerLocation();
currentTimeSearchingCaptainWindow = 0;
driversStatusForSearchWindow = 'We are search for nearst driver'.tr;
if (isDriversDataValid()) {
driversFound = true;
@@ -2142,8 +2146,9 @@ class MapPassengerController extends GetxController {
} else if (rideStatusDelayed == 'Apply' ||
rideStatusDelayed == 'Applied') {
isApplied = true;
rideAppliedFromDriver(isApplied);
timer.cancel();
rideAppliedFromDriver(isApplied);
// timer.cancel();
// Close stream after applying
} else if (attemptCounter >= maxAttempts ||
rideStatusDelayed == 'waiting') {
@@ -2186,7 +2191,7 @@ class MapPassengerController extends GetxController {
rideAppliedFromDriver(bool isApplied) async {
await getUpdatedRideForDriverApply(rideId);
NotificationController().showNotification(
'Accepted Ride'.tr,
'Accepted Ride',
'$driverName ${'accepted your order at price'.tr} ${totalPassenger.toStringAsFixed(1)} ${'with type'.tr} ${box.read(BoxName.carType)}',
'ding');
if (box.read(BoxName.carType) == 'Speed' ||
@@ -2306,7 +2311,7 @@ class MapPassengerController extends GetxController {
// driversToken.remove(driverToken);
// for (var i = 1; i < driversToken.length; i++) {
firebaseMessagesController.sendNotificationToDriverMAP(
'Order Accepted'.tr,
'Order Accepted',
'$driverName${'Accepted your order'.tr}',
driverToken.toString(),
[],
@@ -3514,57 +3519,71 @@ class MapPassengerController extends GetxController {
/// تحويل نصف قطر بالكيلومتر إلى دلتا درجات عرض
double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
const R = 6371.0; // km
final dLat = (lat2 - lat1) * math.pi / 180.0;
final dLon = (lon2 - lon1) * math.pi / 180.0;
final a = math.sin(dLat / 2) * math.sin(dLat / 2) +
math.cos(lat1 * math.pi / 180.0) *
math.cos(lat2 * math.pi / 180.0) *
math.sin(dLon / 2) *
math.sin(dLon / 2);
final c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a));
return R * c;
}
// double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
// const R = 6371.0; // km
// final dLat = (lat2 - lat1) * math.pi / 180.0;
// final dLon = (lon2 - lon1) * math.pi / 180.0;
// final a = math.sin(dLat / 2) * math.sin(dLat / 2) +
// math.cos(lat1 * math.pi / 180.0) *
// math.cos(lat2 * math.pi / 180.0) *
// math.sin(dLon / 2) *
// math.sin(dLon / 2);
// final c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a));
// return R * c;
// }
/// تحويل نصف قطر بالكيلومتر إلى دلتا درجات عرض
double _kmToLatDelta(double km) => km / 111.0;
// double _kmToLatDelta(double km) => km / 111.0;
/// تحويل نصف قطر بالكيلومتر إلى دلتا درجات طول (تعتمد على خط العرض)
double _kmToLngDelta(double km, double atLat) =>
km / (111.320 * math.cos(atLat * math.pi / 180.0)).abs().clamp(1e-6, 1e9);
// /// تحويل نصف قطر بالكيلومتر إلى دلتا درجات طول (تعتمد على خط العرض)
// double _kmToLngDelta(double km, double atLat) =>
// km / (111.320 * math.cos(atLat * math.pi / 180.0)).abs().clamp(1e-6, 1e9);
/// حساب درجة التطابق النصي (كل كلمة تبدأ بها الاسم = 2 نقاط، يحتويها = 1 نقطة)
double _relevanceScore(String name, String query) {
final n = name.toLowerCase();
final parts =
query.toLowerCase().split(RegExp(r'\s+')).where((p) => p.length >= 2);
double s = 0.0;
for (final p in parts) {
if (n.startsWith(p)) {
s += 2.0;
} else if (n.contains(p)) {
s += 1.0;
}
}
return s;
}
// double _relevanceScore(String name, String query) {
// final n = name.toLowerCase();
// final parts =
// query.toLowerCase().split(RegExp(r'\s+')).where((p) => p.length >= 2);
// double s = 0.0;
// for (final p in parts) {
// if (n.startsWith(p)) {
// s += 2.0;
// } else if (n.contains(p)) {
// s += 1.0;
// }
// }
// return s;
// }
// الدالة الرئيسية لجلب الأماكن من السيرفر وترتيبها
// انسخ هذه الدوال والصقها داخل كلاس الكنترولر الخاص بك
// -----------------------------------------------------------------
// --== الدالة الرئيسية للبحث ==--
// -----------------------------------------------------------------
/// الدالة الرئيسية لجلب الأماكن من السيرفر وترتيبها
// انسخ هذه الدوال والصقها داخل كلاس الكنترولر الخاص بك
// -----------------------------------------------------------------
// --== الدالة الرئيسية للبحث ==--
// -----------------------------------------------------------------
/// الدالة الرئيسية لجلب الأماكن من السيرفر وترتيبها
Future<void> getPlaces() async {
// افترض وجود `placeDestinationController` و `passengerLocation` و `CRUD()` معرفة في الكنترولر
final q = placeDestinationController.text.trim();
if (q.isEmpty) {
if (q.isEmpty || q.length < 3) {
// يفضل عدم البحث قبل 3 أحرف
placesDestination = [];
update();
update(); // افترض أنك تستخدم GetX أو أي State Management آخر
return;
}
final lat = passengerLocation.latitude;
final lng = passengerLocation.longitude;
// نصف قطر البحث بالكيلومتر (عدّل حسب رغبتك)
// نصف قطر البحث بالكيلومتر
const radiusKm = 200.0;
// حساب الباوند الصحيح (درجات، وليس 2.2 درجة ثابتة)
// حساب النطاق الجغرافي (Bounding Box) لإرساله للسيرفر
final latDelta = _kmToLatDelta(radiusKm);
final lngDelta = _kmToLngDelta(radiusKm, lat);
@@ -3574,6 +3593,7 @@ class MapPassengerController extends GetxController {
final lngMax = lng + lngDelta;
try {
// استدعاء الـ API (تأكد من أن AppLink.getPlacesSyria يشير للسكريبت الجديد)
final response = await CRUD().post(
link: AppLink.getPlacesSyria,
payload: {
@@ -3585,59 +3605,106 @@ class MapPassengerController extends GetxController {
},
);
// يدعم شكلي استجابة: إما {"...","message":[...]} أو قائمة مباشرة [...]
// --- [تم الإصلاح هنا] ---
// معالجة الاستجابة من السيرفر بشكل يوافق {"status":"success", "message":[...]}
List list;
if (response is Map && response['message'] is List) {
list = List.from(response['message'] as List);
if (response is Map) {
if (response['status'] == 'success' && response['message'] is List) {
list = List.from(response['message'] as List);
} else if (response['status'] == 'failure') {
print('Server Error: ${response['message']}');
return;
} else {
print('Unexpected Map shape from server');
return;
}
} else if (response is List) {
// للتعامل مع الحالات التي قد يرجع فيها السيرفر قائمة مباشرة
list = List.from(response);
} else {
print('Unexpected response shape');
print('Unexpected response shape from server');
return;
}
// جهّز الحقول المحتملة للأسماء
// --- هنا يبدأ عمل فلاتر: الترتيب النهائي الدقيق ---
// دالة مساعدة لاختيار أفضل اسم متاح
String _bestName(Map p) {
return (p['name'] ?? p['name_ar'] ?? p['name_en'] ?? '').toString();
return (p['name_ar'] ?? p['name'] ?? p['name_en'] ?? '').toString();
}
// احسب المسافة ودرجة التطابق والنقاط
// حساب المسافة والصلة والنقاط النهائية لكل نتيجة
for (final p in list) {
final plat = double.tryParse(p['latitude']?.toString() ?? '') ?? 0.0;
final plng = double.tryParse(p['longitude']?.toString() ?? '') ?? 0.0;
final plat = double.tryParse(p['latitude']?.toString() ?? '0.0') ?? 0.0;
final plng =
double.tryParse(p['longitude']?.toString() ?? '0.0') ?? 0.0;
final d = _haversineKm(lat, lng, plat, plng);
final rel = _relevanceScore(_bestName(p), q);
final distance = _haversineKm(lat, lng, plat, plng);
final relevance = _relevanceScore(_bestName(p), q);
// معادلة ترتيب ذكية: مسافة أقل + تطابق أعلى = نقاط أعلى
// تضيف +1 لضمان عدم وصول الوزن للصفر عند عدم وجود تطابق
final score = (1.0 / (1.0 + d)) * (1.0 + rel);
// معادلة الترتيب: (الأولوية للمسافة الأقرب) * (ثم الصلة الأعلى)
final score = (1.0 / (1.0 + distance)) * (1.0 + relevance);
p['distanceKm'] = d;
p['relevance'] = rel;
p['distanceKm'] = distance;
p['relevance'] = relevance;
p['score'] = score;
}
// رتّب حسب score تنازليًا، ثم المسافة تصاعديًا كحسم
// ترتيب القائمة النهائية حسب النقاط (الأعلى أولاً)
list.sort((a, b) {
final sa = (a['score'] ?? 0.0) as double;
final sb = (b['score'] ?? 0.0) as double;
final cmp = sb.compareTo(sa);
if (cmp != 0) return cmp;
final da = (a['distanceKm'] ?? 1e9) as double;
final db = (b['distanceKm'] ?? 1e9) as double;
return da.compareTo(db);
return sb.compareTo(sa);
});
// خذ أول 1015 للعرض (اختياري)، أو اعرض الكل
placesDestination = list.take(15).toList();
Log.print('placesDestination: $placesDestination');
placesDestination = list;
print('Updated places: $placesDestination');
update();
} catch (e) {
print('Exception in getPlaces: $e');
}
}
// -----------------------------------------------------------------
// --== دوال مساعدة ==--
// -----------------------------------------------------------------
/// تحسب المسافة بين نقطتين بالكيلومتر (معادلة هافرساين)
double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
const R = 6371.0; // نصف قطر الأرض بالكيلومتر
final dLat = (lat2 - lat1) * (pi / 180.0);
final dLon = (lon2 - lon1) * (pi / 180.0);
final rLat1 = lat1 * (pi / 180.0);
final rLat2 = lat2 * (pi / 180.0);
final a = sin(dLat / 2) * sin(dLat / 2) +
cos(rLat1) * cos(rLat2) * sin(dLon / 2) * sin(dLon / 2);
final c = 2 * atan2(sqrt(a), sqrt(1 - a));
return R * c;
}
/// تحسب درجة تطابق بسيطة بين اسم المكان وكلمة البحث
double _relevanceScore(String placeName, String query) {
if (placeName.isEmpty || query.isEmpty) return 0.0;
final pLower = placeName.toLowerCase();
final qLower = query.toLowerCase();
if (pLower.startsWith(qLower)) return 1.0; // تطابق كامل في البداية
if (pLower.contains(qLower)) return 0.5; // تحتوي على الكلمة
return 0.0;
}
/// تحويل كيلومتر إلى فرق درجات لخط العرض
double _kmToLatDelta(double km) {
const kmInDegree = 111.32;
return km / kmInDegree;
}
/// تحويل كيلومتر إلى فرق درجات لخط الطول (يعتمد على خط العرض الحالي)
double _kmToLngDelta(double km, double latitude) {
const kmInDegree = 111.32;
return km / (kmInDegree * cos(latitude * (pi / 180.0)));
}
// var languageCode;
// // تحديد اللغة حسب الإدخال
@@ -3786,27 +3853,102 @@ class MapPassengerController extends GetxController {
// update();
// }
Future getPlacesStart() async {
var languageCode = wayPoint0Controller.text;
// Regular expression to check for English alphabet characters
final englishRegex = RegExp(r'[a-zA-Z]');
// Check if text contains English characters
if (englishRegex.hasMatch(languageCode)) {
languageCode = 'en';
} else {
languageCode = 'ar';
Future<void> getPlacesStart() async {
// افترض وجود `placeDestinationController` و `passengerLocation` و `CRUD()` معرفة في الكنترولر
final q = placeStartController.text.trim();
if (q.isEmpty || q.length < 3) {
// يفضل عدم البحث قبل 3 أحرف
placesStart = [];
update(); // افترض أنك تستخدم GetX أو أي State Management آخر
return;
}
var url =
// '${AppLink.googleMapsLink}place/nearbysearch/json?location=${mylocation.longitude}&radius=25000&language=ar&keyword=&key=${placeController.text}${AK.mapAPIKEY}';
'${AppLink.googleMapsLink}place/nearbysearch/json?keyword=${placeStartController.text}&location=${passengerLocation.latitude},${passengerLocation.longitude}&radius=250000&language=$languageCode&key=${AK.mapAPIKEY.toString()}';
final lat = passengerLocation.latitude;
final lng = passengerLocation.longitude;
var response = await CRUD().getGoogleApi(link: url, payload: {});
// نصف قطر البحث بالكيلومتر
const radiusKm = 200.0;
placesStart = response['results'];
update();
// حساب النطاق الجغرافي (Bounding Box) لإرساله للسيرفر
final latDelta = _kmToLatDelta(radiusKm);
final lngDelta = _kmToLngDelta(radiusKm, lat);
final latMin = lat - latDelta;
final latMax = lat + latDelta;
final lngMin = lng - lngDelta;
final lngMax = lng + lngDelta;
try {
// استدعاء الـ API (تأكد من أن AppLink.getPlacesSyria يشير للسكريبت الجديد)
final response = await CRUD().post(
link: AppLink.getPlacesSyria,
payload: {
'query': q,
'lat_min': latMin.toString(),
'lat_max': latMax.toString(),
'lng_min': lngMin.toString(),
'lng_max': lngMax.toString(),
},
);
// --- [تم الإصلاح هنا] ---
// معالجة الاستجابة من السيرفر بشكل يوافق {"status":"success", "message":[...]}
List list;
if (response is Map) {
if (response['status'] == 'success' && response['message'] is List) {
list = List.from(response['message'] as List);
} else if (response['status'] == 'failure') {
print('Server Error: ${response['message']}');
return;
} else {
print('Unexpected Map shape from server');
return;
}
} else if (response is List) {
// للتعامل مع الحالات التي قد يرجع فيها السيرفر قائمة مباشرة
list = List.from(response);
} else {
print('Unexpected response shape from server');
return;
}
// --- هنا يبدأ عمل فلاتر: الترتيب النهائي الدقيق ---
// دالة مساعدة لاختيار أفضل اسم متاح
String _bestName(Map p) {
return (p['name_ar'] ?? p['name'] ?? p['name_en'] ?? '').toString();
}
// حساب المسافة والصلة والنقاط النهائية لكل نتيجة
for (final p in list) {
final plat = double.tryParse(p['latitude']?.toString() ?? '0.0') ?? 0.0;
final plng =
double.tryParse(p['longitude']?.toString() ?? '0.0') ?? 0.0;
final distance = _haversineKm(lat, lng, plat, plng);
final relevance = _relevanceScore(_bestName(p), q);
// معادلة الترتيب: (الأولوية للمسافة الأقرب) * (ثم الصلة الأعلى)
final score = (1.0 / (1.0 + distance)) * (1.0 + relevance);
p['distanceKm'] = distance;
p['relevance'] = relevance;
p['score'] = score;
}
// ترتيب القائمة النهائية حسب النقاط (الأعلى أولاً)
list.sort((a, b) {
final sa = (a['score'] ?? 0.0) as double;
final sb = (b['score'] ?? 0.0) as double;
return sb.compareTo(sa);
});
placesStart = list;
print('Updated places: $placesDestination');
update();
} catch (e) {
print('Exception in getPlaces: $e');
}
}
Future getPlacesListsWayPoint(int index) async {
@@ -4557,12 +4699,15 @@ class MapPassengerController extends GetxController {
data[0]["start_location"]['lat'], data[0]["start_location"]['lng']);
durationToRide = data[0]['duration']['value'];
final points =
decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
for (int i = 0; i < points.length; i++) {
double lat = points[i][0].toDouble();
double lng = points[i][1].toDouble();
polylineCoordinates.add(LatLng(lat, lng));
final String pointsString =
response['routes'][0]["overview_polyline"]["points"];
List<LatLng> decodedPoints =
await compute(decodePolylineIsolate, pointsString);
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
for (int i = 0; i < decodedPoints.length; i++) {
// double lat = decodedPoints[i][0].toDouble();
// double lng = decodedPoints[i][1].toDouble();
polylineCoordinates.add(decodedPoints[i]);
}
// Define the northeast and southwest coordinates
@@ -4609,18 +4754,18 @@ class MapPassengerController extends GetxController {
position: LatLng(
data[0]["end_location"]['lat'], data[0]["end_location"]['lng']),
icon: endIcon,
// infoWindow: InfoWindow(
// title: endNameAddress,
// snippet:
// '$distance ${'KM'.tr} ⌛ ${hours > 0 ? '${'Your Ride Duration is '.tr}$hours ${'H and'.tr} $minutes ${'m'.tr}' : '${'Your Ride Duration is '.tr} $minutes ${'m'.tr}'}'),
infoWindow: InfoWindow(
title: endNameAddress,
snippet:
'$distance ${'KM'.tr}${hours > 0 ? '${'Your Ride Duration is '.tr}$hours ${'H and'.tr} $minutes ${'m'.tr}' : '${'Your Ride Duration is '.tr} $minutes ${'m'.tr}'}'),
),
);
// // Show info windows automatically
// Future.delayed(const Duration(milliseconds: 500), () {
// mapController?.showMarkerInfoWindow(const MarkerId('start'));
// });
// Future.delayed(const Duration(milliseconds: 500), () {
// mapController?.showMarkerInfoWindow(const MarkerId('end'));
// mapController?.showMarkerInfoWindow(const MarkerId('start'));
// });
// Future.delayed(const Duration(milliseconds: 500), () {
// mapController?.showMarkerInfoWindow(const MarkerId('end'));
// });
// update();
@@ -4632,11 +4777,23 @@ class MapPassengerController extends GetxController {
false; // الأفضل أن يكون الافتراضي هو الجودة العالية
// نمرر عدد النقاط المناسب هنا
animatePolylineLayered(
polylineCoordinates,
maxPoints:
lowEndMode ? 30 : 150, // 30 نقطة لوضع الأداء، 150 للوضع العادي
);
if (Platform.isIOS) {
animatePolylineLayered(
polylineCoordinates,
maxPoints:
lowEndMode ? 30 : 150, // 30 نقطة لوضع الأداء، 150 للوضع العادي
);
} else {
polyLines.add(Polyline(
polylineId: const PolylineId('route'),
points: polylineCoordinates,
width: 6,
color: AppColor.primaryColor,
endCap: Cap.roundCap,
startCap: Cap.roundCap,
jointType: JointType.round,
));
}
rideConfirm = false;
isMarkersShown = true;
@@ -4672,7 +4829,7 @@ class MapPassengerController extends GetxController {
// 2) رسم متدرّج بطبقات متراكبة (بدون حذف)، برونزي ↔ أخضر، مع zIndex وعرض مختلف
Future<void> animatePolylineLayered(List<LatLng> coordinates,
{int layersCount = 1, int stepDelayMs = 10, int maxPoints = 150}) async {
{int layersCount = 8, int stepDelayMs = 10, int maxPoints = 160}) async {
// امسح أي طبقات قديمة فقط الخاصة بالطريق
polyLines.removeWhere((p) => p.polylineId.value.startsWith('route_layer_'));
update();
@@ -4682,8 +4839,8 @@ class MapPassengerController extends GetxController {
if (coords.length < 2) return;
// ألوان مع شفافية خفيفة للتمييز
Color bronze([int alpha = 220]) => const Color(0xFFCD7F32).withAlpha(alpha);
Color green([int alpha = 220]) => Colors.green.withAlpha(alpha);
Color bronze([int alpha = 220]) => AppColor.gold;
Color green([int alpha = 220]) => AppColor.primaryColor;
Color _layerColor(int layer) => (layer % 2 == 0) ? bronze() : green();
@@ -4818,14 +4975,16 @@ class MapPassengerController extends GetxController {
distance = distanceOfDestination + (data[0]['distance']['value']) / 1000;
update();
final points =
decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
for (int i = 0; i < points.length; i++) {
if (points[i][0].toString() != '') {
double lat = points[i][0].toDouble();
double lng = points[i][1].toDouble();
polylineCoordinatesPointsAll[index].add(LatLng(lat, lng));
}
// final points =
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
final String pointsString =
response['routes'][0]["overview_polyline"]["points"];
List<LatLng> decodedPoints =
await compute(decodePolylineIsolate, pointsString);
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
for (int i = 0; i < decodedPoints.length; i++) {
polylineCoordinates.add(decodedPoints[i]);
}
// Define the northeast and southwest coordinates