25-09-22/1
This commit is contained in:
63
lib/controller/home/deep_link_controller.dart
Normal file
63
lib/controller/home/deep_link_controller.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'dart:async';
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
|
||||
class DeepLinkController extends GetxController {
|
||||
// استخدم AppLinks للتعامل مع الروابط
|
||||
final _appLinks = AppLinks();
|
||||
StreamSubscription<Uri>? _linkSubscription;
|
||||
|
||||
// متغير لتخزين الإحداثيات القادمة من الرابط
|
||||
final Rx<LatLng?> deepLinkLatLng = Rx<LatLng?>(null);
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// ابدأ بالاستماع للروابط عند تشغيل التطبيق
|
||||
initDeepLinks();
|
||||
}
|
||||
|
||||
Future<void> initDeepLinks() async {
|
||||
// الاستماع إلى الروابط القادمة
|
||||
_linkSubscription = _appLinks.uriLinkStream.listen((uri) {
|
||||
print('Received deep link: $uri');
|
||||
_handleLink(uri);
|
||||
});
|
||||
|
||||
// جلب الرابط الأولي الذي قد يكون فتح التطبيق
|
||||
final initialUri = await _appLinks.getInitialLink();
|
||||
if (initialUri != null) {
|
||||
print('Received initial deep link: $initialUri');
|
||||
_handleLink(initialUri);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleLink(Uri uri) {
|
||||
// تحقق من أن الرابط يتبع النمط المتوقع (مثال: intaleq://route?lat=xx&lng=yy)
|
||||
if (uri.scheme == 'intaleq' && uri.host == 'route') {
|
||||
// استخراج خطوط الطول والعرض من الرابط
|
||||
final latString = uri.queryParameters['lat'];
|
||||
final lngString = uri.queryParameters['lng'];
|
||||
|
||||
if (latString != null && lngString != null) {
|
||||
final double? lat = double.tryParse(latString);
|
||||
final double? lng = double.tryParse(lngString);
|
||||
|
||||
if (lat != null && lng != null) {
|
||||
// إذا كانت الإحداثيات صالحة، قم بتحديث المتغير
|
||||
// ستستمع وحدة التحكم في الخريطة لهذا التغيير
|
||||
deepLinkLatLng.value = LatLng(lat, lng);
|
||||
print('Parsed LatLng from deep link: ${deepLinkLatLng.value}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
// تأكد من إلغاء الاشتراك عند إغلاق وحدة التحكم
|
||||
_linkSubscription?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
import 'dart:convert';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:Intaleq/constant/univeries_polygon.dart';
|
||||
@@ -14,7 +13,6 @@ import 'package:Intaleq/controller/firebase/local_notification.dart';
|
||||
import 'package:Intaleq/controller/functions/encrypt_decrypt.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_confetti/flutter_confetti.dart';
|
||||
import 'package:uni_links/uni_links.dart';
|
||||
import 'package:vector_math/vector_math.dart' show radians, degrees;
|
||||
|
||||
import 'package:Intaleq/controller/functions/tts.dart';
|
||||
@@ -54,14 +52,11 @@ import '../functions/launch.dart';
|
||||
import '../functions/package_info.dart';
|
||||
import '../functions/secure_storage.dart';
|
||||
import '../payment/payment_controller.dart';
|
||||
import 'deep_link_controller.dart';
|
||||
import 'device_tier.dart';
|
||||
import 'vip_waitting_page.dart';
|
||||
|
||||
class MapPassengerController extends GetxController {
|
||||
// --- START: DEEP LINKING ADDITIONS ---
|
||||
StreamSubscription? _linkSubscription;
|
||||
// --- END: DEEP LINKING ADDITIONS ---
|
||||
|
||||
bool isLoading = true;
|
||||
TextEditingController placeDestinationController = TextEditingController();
|
||||
TextEditingController increasFeeFromPassenger = TextEditingController();
|
||||
@@ -276,6 +271,11 @@ class MapPassengerController extends GetxController {
|
||||
late DateTime newTime = DateTime.now();
|
||||
int hours = 0;
|
||||
int minutes = 0;
|
||||
|
||||
// --- إضافة جديدة: للوصول إلى وحدة التحكم بالروابط ---
|
||||
final DeepLinkController _deepLinkController = Get.find();
|
||||
// ------------------------------------------------
|
||||
|
||||
void onChangedPassengerCount(int newValue) {
|
||||
selectedPassengerCount = newValue;
|
||||
update();
|
||||
@@ -286,92 +286,6 @@ class MapPassengerController extends GetxController {
|
||||
update();
|
||||
}
|
||||
|
||||
/// Initializes the deep link listener.
|
||||
/// It checks for the initial link when the app starts and then listens for subsequent links.
|
||||
Future<void> _initUniLinks() async {
|
||||
try {
|
||||
// Get the initial link that opened the app
|
||||
final initialLink = await getInitialUri();
|
||||
if (initialLink != null) {
|
||||
handleDeepLink(initialLink);
|
||||
}
|
||||
} on PlatformException {
|
||||
print('Failed to get initial deep link.');
|
||||
} on FormatException {
|
||||
print('Invalid initial deep link format.');
|
||||
}
|
||||
|
||||
// Listen for incoming links while the app is running
|
||||
_linkSubscription = uriLinkStream.listen((Uri? link) {
|
||||
handleDeepLink(link);
|
||||
}, onError: (err) {
|
||||
print('Error listening to deep links: $err');
|
||||
});
|
||||
}
|
||||
|
||||
/// Parses the incoming deep link and triggers the route initiation.
|
||||
void handleDeepLink(Uri? link) {
|
||||
if (link == null) return;
|
||||
|
||||
// Check if the link matches your app's scheme and path
|
||||
// e.g., intaleq://map?lat=31.9539&lng=35.9106
|
||||
if (link.scheme == 'intaleq' && link.host == 'map') {
|
||||
final latString = link.queryParameters['lat'];
|
||||
final lngString = link.queryParameters['lng'];
|
||||
|
||||
if (latString != null && lngString != null) {
|
||||
final double? lat = double.tryParse(latString);
|
||||
final double? lng = double.tryParse(lngString);
|
||||
|
||||
if (lat != null && lng != null) {
|
||||
final destination = LatLng(lat, lng);
|
||||
print('Deep link received. Destination: $destination');
|
||||
initiateRouteFromDeepLink(destination);
|
||||
} else {
|
||||
print('Failed to parse lat/lng from deep link.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the destination from the deep link and updates the UI to show the map.
|
||||
void initiateRouteFromDeepLink(LatLng destination) async {
|
||||
// Wait for map controller to be ready
|
||||
if (mapController == null) {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (mapController == null) {
|
||||
print("Map controller is not available to handle deep link.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
myDestination = destination;
|
||||
|
||||
// Animate camera to user's current location to show the starting point
|
||||
await mapController?.animateCamera(CameraUpdate.newLatLng(
|
||||
LatLng(passengerLocation.latitude, passengerLocation.longitude)));
|
||||
|
||||
// Ensure the main menu is visible to start the booking process
|
||||
if (isMainBottomMenuMap) {
|
||||
changeMainBottomMenuMap();
|
||||
}
|
||||
|
||||
passengerStartLocationFromMap = true;
|
||||
isPickerShown = true;
|
||||
hintTextDestinationPoint = "Destination from external link".tr;
|
||||
update();
|
||||
|
||||
// The user can now see the destination and proceed to get the route and price.
|
||||
Get.snackbar(
|
||||
"Location Received".tr,
|
||||
"The destination has been set from the link.".tr,
|
||||
backgroundColor: AppColor.greenColor,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
|
||||
// --- END: DEEP LINKING METHODS ---
|
||||
|
||||
void getCurrentLocationFormString() async {
|
||||
currentLocationToFormPlaces = true;
|
||||
currentLocationString = 'Waiting for your location'.tr;
|
||||
@@ -3282,8 +3196,6 @@ class MapPassengerController extends GetxController {
|
||||
print(
|
||||
"--- MapPassengerController: Closing and cleaning up all resources. ---");
|
||||
|
||||
_linkSubscription?.cancel();
|
||||
|
||||
// 1. إلغاء المؤقتات الفردية
|
||||
// Using ?.cancel() is safe even if the timer is null
|
||||
markerReloadingTimer.cancel();
|
||||
@@ -3616,6 +3528,8 @@ class MapPassengerController extends GetxController {
|
||||
update();
|
||||
}
|
||||
|
||||
/// تحويل نصف قطر بالكيلومتر إلى دلتا درجات عرض
|
||||
|
||||
double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
|
||||
const R = 6371.0; // km
|
||||
final dLat = (lat2 - lat1) * math.pi / 180.0;
|
||||
@@ -3629,6 +3543,29 @@ class MapPassengerController extends GetxController {
|
||||
return R * c;
|
||||
}
|
||||
|
||||
/// تحويل نصف قطر بالكيلومتر إلى دلتا درجات عرض
|
||||
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);
|
||||
|
||||
/// حساب درجة التطابق النصي (كل كلمة تبدأ بها الاسم = 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;
|
||||
}
|
||||
|
||||
Future<void> getPlaces() async {
|
||||
final q = placeDestinationController.text.trim();
|
||||
if (q.isEmpty) {
|
||||
@@ -3640,11 +3577,17 @@ class MapPassengerController extends GetxController {
|
||||
final lat = passengerLocation.latitude;
|
||||
final lng = passengerLocation.longitude;
|
||||
|
||||
const range = 2.2;
|
||||
final latMin = lat - range;
|
||||
final latMax = lat + range;
|
||||
final lngMin = lng - range;
|
||||
final lngMax = lng + range;
|
||||
// نصف قطر البحث بالكيلومتر (عدّل حسب رغبتك)
|
||||
const radiusKm = 200.0;
|
||||
|
||||
// حساب الباوند الصحيح (درجات، وليس 2.2 درجة ثابتة)
|
||||
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 {
|
||||
final response = await CRUD().post(
|
||||
@@ -3658,43 +3601,59 @@ class MapPassengerController extends GetxController {
|
||||
},
|
||||
);
|
||||
|
||||
if (response != 'failure') {
|
||||
final list = (response['message'] as List?) ?? [];
|
||||
|
||||
// احسب المسافة وألصقها بكل عنصر
|
||||
for (final p in list) {
|
||||
final plat = double.tryParse(p['latitude']?.toString() ?? '') ?? 0.0;
|
||||
final plng = double.tryParse(p['longitude']?.toString() ?? '') ?? 0.0;
|
||||
p['distanceKm'] = _haversineKm(lat, lng, plat, plng);
|
||||
}
|
||||
|
||||
// رتّب حسب الأقرب
|
||||
list.sort((a, b) {
|
||||
final da = (a['distanceKm'] ?? 1e9) as double;
|
||||
final db = (b['distanceKm'] ?? 1e9) as double;
|
||||
final cmp = da.compareTo(db);
|
||||
if (cmp != 0) return cmp;
|
||||
|
||||
// تعادل؟ فضّل من يطابق الاسم
|
||||
final nameA =
|
||||
(a['name'] ?? a['name_ar'] ?? a['name_en'] ?? '').toString();
|
||||
final nameB =
|
||||
(b['name'] ?? b['name_ar'] ?? b['name_en'] ?? '').toString();
|
||||
final qLower = q.toLowerCase();
|
||||
final hitA = nameA.toLowerCase().contains(qLower) ? 0 : 1;
|
||||
final hitB = nameB.toLowerCase().contains(qLower) ? 0 : 1;
|
||||
return hitA.compareTo(hitB);
|
||||
});
|
||||
|
||||
placesDestination = list;
|
||||
update();
|
||||
// يدعم شكلي استجابة: إما {"...","message":[...]} أو قائمة مباشرة [...]
|
||||
List list;
|
||||
if (response is Map && response['message'] is List) {
|
||||
list = List.from(response['message'] as List);
|
||||
} else if (response is List) {
|
||||
list = List.from(response);
|
||||
} else {
|
||||
print('Server error');
|
||||
print('Unexpected response shape');
|
||||
return;
|
||||
}
|
||||
|
||||
// جهّز الحقول المحتملة للأسماء
|
||||
String _bestName(Map p) {
|
||||
return (p['name'] ?? p['name_ar'] ?? 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 d = _haversineKm(lat, lng, plat, plng);
|
||||
final rel = _relevanceScore(_bestName(p), q);
|
||||
|
||||
// معادلة ترتيب ذكية: مسافة أقل + تطابق أعلى = نقاط أعلى
|
||||
// تضيف +1 لضمان عدم وصول الوزن للصفر عند عدم وجود تطابق
|
||||
final score = (1.0 / (1.0 + d)) * (1.0 + rel);
|
||||
|
||||
p['distanceKm'] = d;
|
||||
p['relevance'] = rel;
|
||||
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);
|
||||
});
|
||||
|
||||
// خذ أول 10–15 للعرض (اختياري)، أو اعرض الكل
|
||||
placesDestination = list.take(15).toList();
|
||||
Log.print('placesDestination: $placesDestination');
|
||||
update();
|
||||
} catch (e) {
|
||||
print('Exception: $e');
|
||||
print('Exception in getPlaces: $e');
|
||||
}
|
||||
} // Future getPlaces() async {
|
||||
}
|
||||
|
||||
// var languageCode;
|
||||
|
||||
// // تحديد اللغة حسب الإدخال
|
||||
@@ -5804,16 +5763,45 @@ class MapPassengerController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
// --- دالة جديدة للاستماع ومعالجة الرابط ---
|
||||
void _listenForDeepLink() {
|
||||
// استمع إلى أي تغيير في الإحداثيات القادمة من الرابط
|
||||
ever(_deepLinkController.deepLinkLatLng, (LatLng? latLng) {
|
||||
if (latLng != null) {
|
||||
print('MapPassengerController detected deep link LatLng: $latLng');
|
||||
// عندما يتم استلام إحداثيات جديدة، عينها كوجهة
|
||||
myDestination = latLng;
|
||||
|
||||
// قم بتشغيل المنطق الخاص بك لعرض المسار
|
||||
// (تأكد من أن `passengerLocation` لديها قيمة أولاً)
|
||||
if (passengerLocation != null) {
|
||||
getDirectionMap(
|
||||
'${passengerLocation.latitude},${passengerLocation.longitude}',
|
||||
'${myDestination.latitude},${myDestination.longitude}');
|
||||
} else {
|
||||
// يمكنك إظهار رسالة للمستخدم لتمكين الموقع أولاً
|
||||
print(
|
||||
'Cannot process deep link route yet, passenger location is null.');
|
||||
}
|
||||
|
||||
// إعادة تعيين القيمة إلى null لمنع التشغيل مرة أخرى عند إعادة بناء الواجهة
|
||||
_deepLinkController.deepLinkLatLng.value = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
|
||||
// // --- إضافة جديدة: تهيئة وحدة التحكم في الروابط العميقة ---
|
||||
// Get.put(DeepLinkController(), permanent: true);
|
||||
// // ----------------------------------------------------
|
||||
// مرحلة 0: الضروري جداً لعرض الخريطة سريعاً
|
||||
mapAPIKEY = await storage.read(key: BoxName.mapAPIKEY);
|
||||
await initilizeGetStorage(); // إعداد سريع
|
||||
await _initMinimalIcons(); // start/end فقط
|
||||
await addToken(); // لو لازم للمصادقة
|
||||
await _initUniLinks();
|
||||
_listenForDeepLink();
|
||||
await getLocation(); // لتحديد الكاميرا
|
||||
box.write(BoxName.carType, 'yet');
|
||||
box.write(BoxName.tipPercentage, '0');
|
||||
|
||||
Reference in New Issue
Block a user