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

@@ -0,0 +1,62 @@
import 'package:google_maps_flutter/google_maps_flutter.dart';
List<LatLng> decodePolylineIsolate(String encoded) {
List<LatLng> points = [];
int index = 0, len = encoded.length;
int lat = 0, lng = 0;
while (index < len) {
int b, shift = 0, result = 0;
do {
b = encoded.codeUnitAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lat += dlat;
shift = 0;
result = 0;
do {
b = encoded.codeUnitAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lng += dlng;
points.add(LatLng(lat / 1E5, lng / 1E5));
}
return points;
}
// Helper method for decoding polyline (if not already defined)
// List<LatLng> decodePolyline(String encoded) {
// List<LatLng> points = [];
// int index = 0, len = encoded.length;
// int lat = 0, lng = 0;
// while (index < len) {
// int b, shift = 0, result = 0;
// do {
// b = encoded.codeUnitAt(index++) - 63;
// result |= (b & 0x1f) << shift;
// shift += 5;
// } while (b >= 0x20);
// int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
// lat += dlat;
// shift = 0;
// result = 0;
// do {
// b = encoded.codeUnitAt(index++) - 63;
// result |= (b & 0x1f) << shift;
// shift += 5;
// } while (b >= 0x20);
// int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
// lng += dlng;
// points.add(LatLng(lat / 1E5, lng / 1E5));
// }
// return points;
// }

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

View File

@@ -1,4 +1,6 @@
import 'dart:async';
import 'dart:math';
import 'package:Intaleq/controller/functions/crud.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
@@ -7,22 +9,21 @@ import 'package:Intaleq/onbording_page.dart';
import 'package:Intaleq/views/auth/login_page.dart';
import 'package:Intaleq/controller/auth/login_controller.dart';
import 'package:Intaleq/controller/functions/secure_storage.dart';
import 'package:Intaleq/views/widgets/my_scafold.dart';
import 'package:Intaleq/constant/style.dart';
import 'package:quick_actions/quick_actions.dart';
import '../../../constant/notification.dart';
import '../../../main.dart';
import '../firebase/firbase_messge.dart';
import '../firebase/local_notification.dart';
import '../functions/encrypt_decrypt.dart';
import '../../main.dart';
// كنترولر مع منطق تحميل محسن ينتظر العمليات غير المتزامنة
class SplashScreenController extends GetxController
with GetTickerProviderStateMixin {
late AnimationController _animationController;
// الحركات الخاصة بكل عنصر من عناصر الواجهة
late Animation<double> titleFadeAnimation;
late Animation<double> titleScaleAnimation;
late Animation<double> taglineFadeAnimation;
late Animation<double> titleFadeAnimation,
titleScaleAnimation,
taglineFadeAnimation,
footerFadeAnimation;
late Animation<Offset> taglineSlideAnimation;
late Animation<double> footerFadeAnimation;
final progress = 0.0.obs;
Timer? _progressTimer;
@@ -30,111 +31,160 @@ class SplashScreenController extends GetxController
@override
void onInit() {
super.onInit();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 2000),
);
vsync: this, duration: const Duration(milliseconds: 2000));
// --- تعريف الحركات المتتالية ---
// Animation definitions
titleFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
),
);
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.0, 0.5, curve: Curves.easeOut)));
titleScaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
),
);
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.0, 0.5, curve: Curves.easeOut)));
taglineFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.3, 0.8, curve: Curves.easeOut),
),
);
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.3, 0.8, curve: Curves.easeOut)));
taglineSlideAnimation =
Tween<Offset>(begin: const Offset(0, 0.5), end: Offset.zero).animate(
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.3, 0.8, curve: Curves.easeOut),
),
);
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.3, 0.8, curve: Curves.easeOut)));
footerFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.5, 1.0, curve: Curves.easeOut),
),
);
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.5, 1.0, curve: Curves.easeOut)));
_animationController.forward();
// بدء عملية التهيئة والتحميل
_initializeApp();
}
/// تهيئة التطبيق وانتظار انتهاء التحميل قبل الانتقال
Future<void> _initializeApp() async {
// تشغيل مؤقت شريط التقدم ليعطي انطباعاً مرئياً فقط
_startProgressAnimation();
// تعريف مهمتين: منطق التحميل، والحد الأدنى لوقت عرض الشاشة
// Run navigation logic and background services in parallel.
final logicFuture = _performNavigationLogic();
final backgroundServicesFuture = _initializeBackgroundServices();
// Ensure the splash screen is visible for a minimum duration.
final minTimeFuture = Future.delayed(const Duration(seconds: 3));
// الانتظار حتى انتهاء المهمتين معاً
await Future.wait([logicFuture, minTimeFuture]);
// بعد انتهاء الانتظار، سيتم الانتقال تلقائياً من داخل a_performNavigationLogic
// Wait for all tasks to complete.
await Future.wait([logicFuture, backgroundServicesFuture, minTimeFuture]);
}
/// تشغيل حركة شريط التقدم بشكل مرئي ومنفصل عن منطق التحميل
void _startProgressAnimation() {
const totalTime = 2800; // مدة ملء الشريط
const interval = 50;
// Visual timer for the progress bar.
const totalTime = 2800; // ms
const interval = 50; // ms
int elapsed = 0;
_progressTimer =
Timer.periodic(const Duration(milliseconds: interval), (timer) {
elapsed += interval;
progress.value = (elapsed / totalTime).clamp(0.0, 1.0);
if (elapsed >= totalTime) {
timer.cancel();
}
if (elapsed >= totalTime) timer.cancel();
});
}
/// تنفيذ منطق التحميل الفعلي وتسجيل الدخول وتحديد وجهة الانتقال
/// Initializes all heavy background services while the splash animation is running.
Future<void> _initializeBackgroundServices() async {
try {
await EncryptionHelper.initialize();
// --- [LAZY LOADING IN ACTION] ---
// This `Get.find()` call will create the NotificationController instance
// for the first time because it was defined with `lazyPut`.
final notificationController = Get.find<NotificationController>();
await notificationController.initNotifications();
// The same happens for FirebaseMessagesController.
final fcm = Get.find<FirebaseMessagesController>();
await fcm.requestFirebaseMessagingPermission();
_scheduleDailyNotifications(notificationController);
_initializeQuickActions();
} catch (e, st) {
CRUD.addError('background_init_error: $e', st.toString(), 'SplashScreen');
}
}
/// Determines the next screen based on user's login state.
Future<void> _performNavigationLogic() async {
// تنفيذ المهام الأولية
await _getPackageInfo();
SecureStorage().saveData('iss', 'mobile-app:');
// تحديد الشاشة التالية
if (box.read(BoxName.onBoarding) == null) {
Get.off(() => OnBoardingPage());
} else if (box.read(BoxName.email) != null &&
box.read(BoxName.phone) != null &&
box.read(BoxName.isVerified) == '1') {
// -- النقطة الأهم --
// هنا ننتظر انتهاء عملية تسجيل الدخول قبل الانتقال
await Get.put(LoginController()).loginUsingCredentials(
// `Get.find()` creates the LoginController instance here.
final loginController = Get.find<LoginController>();
// The loginController itself will handle navigation via Get.offAll() upon success.
await loginController.loginUsingCredentials(
box.read(BoxName.passengerID).toString(),
box.read(BoxName.email).toString(),
);
// بعد هذه العملية، سيتولى LoginController بنفسه الانتقال للصفحة الرئيسية أو صفحة الدخول
} else {
Get.off(() => LoginPage());
}
}
/// جلب معلومات الحزمة لعرض إصدار التطبيق
Future<void> _getPackageInfo() async {
final info = await PackageInfo.fromPlatform();
box.write(BoxName.packagInfo, info.version);
update();
try {
final info = await PackageInfo.fromPlatform();
box.write(BoxName.packagInfo, info.version);
update();
} catch (e) {
print("Could not get package info: $e");
}
}
void _scheduleDailyNotifications(NotificationController controller) {
try {
final List<String> msgs = passengerMessages ?? const [];
if (msgs.isEmpty) {
controller.scheduleNotificationsForSevenDays(
'Intaleq', 'مرحباً بك! تابع رحلاتك بأمان مع انطلق.', "tone1");
} else {
final rnd = Random();
final raw = msgs[rnd.nextInt(msgs.length)];
final parts = raw.split(':');
final title = parts.isNotEmpty ? parts.first.trim() : 'Intaleq';
final body = parts.length > 1
? parts.sublist(1).join(':').trim()
: 'مرحباً بك! تابع رحلاتك بأمان مع انطلق.';
controller.scheduleNotificationsForSevenDays(
title.isEmpty ? 'Intaleq' : title,
body.isEmpty ? 'مرحباً بك! تابع رحلاتك بأمان مع انطلق.' : body,
"tone1");
}
} catch (e, st) {
CRUD.addError('notif_init: $e', st.toString(), 'SplashScreen');
}
}
void _initializeQuickActions() {
final QuickActions quickActions = QuickActions();
quickActions.initialize((String shortcutType) {
Get.toNamed('/$shortcutType');
});
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(
type: 'shareApp', localizedTitle: 'Share App'.tr, icon: 'icon_share'),
ShortcutItem(
type: 'wallet', localizedTitle: 'Wallet'.tr, icon: 'icon_wallet'),
ShortcutItem(
type: 'profile', localizedTitle: 'Profile'.tr, icon: 'icon_user'),
ShortcutItem(
type: 'contactSupport',
localizedTitle: 'Contact Support'.tr,
icon: 'icon_support'),
]);
}
@override
@@ -144,40 +194,3 @@ class SplashScreenController extends GetxController
super.onClose();
}
}
// يمكن الإبقاء على هذه الفئة لواجهة المستخدم المتعلقة بالأمان كما في الملف الأصلي
class SecurityPage extends StatelessWidget {
const SecurityPage({
super.key,
});
@override
Widget build(BuildContext context) {
return MyScafolld(
title: "security_warning".tr,
body: [
Center(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Text(
"security_message".tr,
style: AppStyle.headTitle2,
),
TextButton(
onPressed: () async {
// await SecurityHelper.clearAllData();
},
child: Text(
"security_warning".tr,
),
),
],
),
),
)
],
isleading: false);
}
}