2026-02-28-1
This commit is contained in:
@@ -6,6 +6,7 @@ class BoxName {
|
||||
static const String tokenParent = "tokenParent";
|
||||
static const String lang = "lang";
|
||||
static const String serverChosen = "serverChosen";
|
||||
static const String security_check = "security_check";
|
||||
static const String gender = "gender";
|
||||
static const String jwt = "jwt";
|
||||
static const String lowEndMode = "lowEndMode";
|
||||
|
||||
@@ -2,29 +2,43 @@ import 'package:Intaleq/constant/box_name.dart';
|
||||
import 'package:Intaleq/main.dart';
|
||||
|
||||
class AppLink {
|
||||
static String serverPHP = box.read('serverPHP');
|
||||
|
||||
///https://walletintaleq.intaleq.xyz/v1/main
|
||||
static String paymentServer = 'https://walletintaleq.intaleq.xyz/v1/main';
|
||||
|
||||
///https://api.intaleq.xyz/intaleq/ride/location
|
||||
static String location = 'https://api.intaleq.xyz/intaleq/ride/location';
|
||||
static String locationServer =
|
||||
|
||||
/// هذا الرابط خاص برحلات الركاب، ويستخدمه السيرفر الجانبي للرحلات (Ride Server Side) للتعامل مع عمليات إلغاء الرحلات وتحديث حالة الرحلات وغيرها من العمليات المتعلقة بالرحلات.
|
||||
/// https://routesy.intaleq.xyz for syria
|
||||
/// for jordan https://routesjo.intaleq.xyz
|
||||
static String routesOsm = 'https://routesjo.intaleq.xyz';
|
||||
|
||||
///https://location.intaleq.xyz/intaleq/ride/location
|
||||
///locationServerSide هو السيرفر الجانبي الخاص بموقع السائقين، حيث يتم إرسال تحديثات الموقع من التطبيق إلى هذا السيرفر، ومن ثم يقوم هذا السيرفر بتوزيع هذه التحديثات إلى الركاب المتصلين الذين يتابعون السائق في الوقت الحقيقي.
|
||||
static String locationServerSide =
|
||||
'https://location.intaleq.xyz/intaleq/ride/location';
|
||||
static String seferPaymentServer0 = box.read('seferPaymentServer');
|
||||
|
||||
///https://api.intaleq.xyz/intaleq
|
||||
static final String endPoint = 'https://api.intaleq.xyz/intaleq';
|
||||
static final String ride = 'https://rides.intaleq.xyz/intaleq';
|
||||
// box.read(BoxName.serverChosen) ?? box.read(BoxName.basicLink);
|
||||
static final String server = 'https://api.intaleq.xyz/intaleq';
|
||||
static final String serverSocket = 'https://rides.intaleq.xyz';
|
||||
static String IntaleqSyriaServer = endPoint;
|
||||
static String IntaleqGizaServer = box.read('Giza');
|
||||
static String IntaleqAlexandriaServer = box.read('Alexandria');
|
||||
|
||||
/// هذا الرابط خاص برحلات الركاب، ويستخدمه السيرفر الجانبي للرحلات (Ride Server Side) للتعامل مع عمليات إلغاء الرحلات وتحديث حالة الرحلات وغيرها من العمليات المتعلقة بالرحلات.
|
||||
/// https://rides.intaleq.xyz/intaleq
|
||||
static final String rideServerSide = 'https://rides.intaleq.xyz/intaleq';
|
||||
|
||||
///https://api.intaleq.xyz/intaleq
|
||||
/// main api link for all api calls except rides and location
|
||||
static final String server = 'https://api.intaleq.xyz/intaleq';
|
||||
|
||||
///https://rides.intaleq.xyz
|
||||
/// هذا الرابط خاص برحلات الركاب، ويستخدمه السيرفر الجانبي للرحلات (Ride Server Side) للتعامل مع عمليات إلغاء الرحلات وتحديث حالة الرحلات وغيرها من العمليات المتعلقة بالرحلات.
|
||||
static final String serverSocket = 'https://rides.intaleq.xyz';
|
||||
|
||||
///
|
||||
static String googleMapsLink = 'https://maps.googleapis.com/maps/api/';
|
||||
|
||||
/// here map link for searching for places
|
||||
static String searcMaps =
|
||||
'https://autosuggest.search.hereapi.com/v1/autosuggest';
|
||||
static String llama = 'https://api.llama-api.com/chat/completions';
|
||||
static String gemini =
|
||||
'https://generativelanguage.googleapis.com/v1beta3/models/text-bison-001:generateText';
|
||||
|
||||
static String test = "$server/test.php";
|
||||
//===============firebase==========================
|
||||
@@ -80,20 +94,23 @@ class AppLink {
|
||||
|
||||
////=======================cancelRide===================
|
||||
// static String ride = '$server/ride';
|
||||
static String addCancelRideFromPassenger = "$ride/cancelRide/add.php";
|
||||
static String cancelRide = "$ride/cancelRide/get.php";
|
||||
static String addCancelRideFromPassenger =
|
||||
"$rideServerSide/cancelRide/add.php";
|
||||
static String cancelRide = "$rideServerSide/cancelRide/get.php";
|
||||
//-----------------ridessss------------------
|
||||
static String addRides = "$ride/ride/rides/add.php";
|
||||
static String getRides = "$ride/ride/rides/get.php";
|
||||
static String getRideOrderID = "$ride/ride/rides/getRideOrderID.php";
|
||||
static String getRideStatus = "$ride/ride/rides/getRideStatus.php";
|
||||
static String getRideStatusBegin = "$ride/ride/rides/getRideStatusBegin.php";
|
||||
static String addRides = "$rideServerSide/ride/rides/add.php";
|
||||
static String getRides = "$rideServerSide/ride/rides/get.php";
|
||||
static String getRideOrderID =
|
||||
"$rideServerSide/ride/rides/getRideOrderID.php";
|
||||
static String getRideStatus = "$rideServerSide/ride/rides/getRideStatus.php";
|
||||
static String getRideStatusBegin =
|
||||
"$rideServerSide/ride/rides/getRideStatusBegin.php";
|
||||
static String getRideStatusFromStartApp =
|
||||
"$server/ride/rides/getRideStatusFromStartApp.php";
|
||||
static String updateRides = "$ride/ride/rides/update.php";
|
||||
static String updateRides = "$rideServerSide/ride/rides/update.php";
|
||||
static String updateStausFromSpeed =
|
||||
"$ride/ride/rides/updateStausFromSpeed.php";
|
||||
static String deleteRides = "$ride/ride/rides/delete.php";
|
||||
"$rideServerSide/ride/rides/updateStausFromSpeed.php";
|
||||
static String deleteRides = "$rideServerSide/ride/rides/delete.php";
|
||||
|
||||
//-----------------DriverPayment------------------
|
||||
static String adddriverScam = "$server/driver_scam/add.php";
|
||||
@@ -247,7 +264,7 @@ class AppLink {
|
||||
static String getLocationAreaLinks =
|
||||
'$server/ride/location/get_location_area_links.php';
|
||||
static String addpassengerLocation =
|
||||
"$locationServer/addpassengerLocation.php";
|
||||
"$locationServerSide/addpassengerLocation.php";
|
||||
static String getCarsLocationByPassengerSpeed = "$location/getSpeed.php";
|
||||
static String getCarsLocationByPassengerComfort = "$location/getComfort.php";
|
||||
static String getCarsLocationByPassengerBalash = "$location/getBalash.php";
|
||||
|
||||
@@ -87,16 +87,13 @@ class LoginController extends GetxController {
|
||||
update();
|
||||
}
|
||||
|
||||
Future<String> getJwtWallet() async {
|
||||
getJwtWallet() async {
|
||||
try {
|
||||
final random = Random();
|
||||
|
||||
// Perform security check randomly
|
||||
if (random.nextBool()) {
|
||||
await SecurityHelper.performSecurityChecks();
|
||||
} else {
|
||||
await SecurityChecks.isDeviceRootedFromNative(Get.context!);
|
||||
if (box.read(BoxName.security_check).toString() != 'passed') {
|
||||
Log.print('Security check failed');
|
||||
return;
|
||||
}
|
||||
Log.print('Security check passed');
|
||||
|
||||
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
||||
final dev = GetPlatform.isAndroid ? 'android' : 'ios';
|
||||
|
||||
@@ -82,13 +82,13 @@ class FirebaseMessagesController extends GetxController {
|
||||
RemoteNotification? notification = message.notification;
|
||||
AndroidNotification? android = notification?.android;
|
||||
// if (notification != null && android != null) {
|
||||
if (message.data.isNotEmpty && message.notification != null) {
|
||||
if (message.data.isNotEmpty) {
|
||||
fireBaseTitles(message);
|
||||
}
|
||||
});
|
||||
FirebaseMessaging.onBackgroundMessage((RemoteMessage message) async {
|
||||
// Handle background message
|
||||
if (message.data.isNotEmpty && message.notification != null) {
|
||||
if (message.data.isNotEmpty) {
|
||||
fireBaseTitles(message);
|
||||
}
|
||||
});
|
||||
@@ -109,8 +109,8 @@ class FirebaseMessagesController extends GetxController {
|
||||
? Get.find<MapPassengerController>()
|
||||
: null;
|
||||
// اقرأ العنوان (للعرض)
|
||||
String title = message.notification?.title ?? '';
|
||||
String body = message.notification?.body ?? '';
|
||||
String title = message.data['title'] ?? message.notification?.title ?? '';
|
||||
String body = message.data['body'] ?? message.notification?.body ?? '';
|
||||
|
||||
if (category == 'ORDER') {
|
||||
// <-- مثال: كان 'Order'.tr
|
||||
|
||||
@@ -12,6 +12,8 @@ import '../../main.dart';
|
||||
class NotificationController extends GetxController {
|
||||
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
FlutterLocalNotificationsPlugin get plugin =>
|
||||
_flutterLocalNotificationsPlugin;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@@ -32,7 +34,8 @@ class NotificationController extends GetxController {
|
||||
);
|
||||
InitializationSettings initializationSettings =
|
||||
InitializationSettings(android: android, iOS: ios);
|
||||
await _flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
||||
await _flutterLocalNotificationsPlugin.initialize(
|
||||
settings: initializationSettings);
|
||||
|
||||
tz.initializeTimeZones();
|
||||
print('Notifications initialized');
|
||||
@@ -58,7 +61,8 @@ class NotificationController extends GetxController {
|
||||
|
||||
final NotificationDetails details =
|
||||
NotificationDetails(android: android, iOS: ios);
|
||||
await _flutterLocalNotificationsPlugin.show(0, title, message, details);
|
||||
await _flutterLocalNotificationsPlugin.show(
|
||||
id: 0, title: title, body: message, notificationDetails: details);
|
||||
print('Notification shown: $title - $message');
|
||||
}
|
||||
// /Users/hamzaaleghwairyeen/development/App/ride 2/lib/controller/firebase/local_notification.dart
|
||||
@@ -267,11 +271,11 @@ class NotificationController extends GetxController {
|
||||
print('Scheduling notification for: $scheduledTZDateTime');
|
||||
|
||||
await _flutterLocalNotificationsPlugin.zonedSchedule(
|
||||
notificationId, // Unique ID for each notification
|
||||
title,
|
||||
message,
|
||||
scheduledTZDateTime,
|
||||
details,
|
||||
id: notificationId, // Unique ID for each notification
|
||||
title: title,
|
||||
body: message,
|
||||
scheduledDate: scheduledTZDateTime,
|
||||
notificationDetails: details,
|
||||
androidScheduleMode: AndroidScheduleMode.exact,
|
||||
// uiLocalNotificationDateInterpretation:
|
||||
// UILocalNotificationDateInterpretation.absoluteTime,
|
||||
@@ -314,11 +318,11 @@ class NotificationController extends GetxController {
|
||||
print('Scheduling notification for: $scheduledDate');
|
||||
|
||||
await _flutterLocalNotificationsPlugin.zonedSchedule(
|
||||
notificationId, // Unique ID for each notification
|
||||
title,
|
||||
message,
|
||||
scheduledDate,
|
||||
details,
|
||||
id: notificationId, // Unique ID for each notification
|
||||
title: title,
|
||||
body: message,
|
||||
scheduledDate: scheduledDate,
|
||||
notificationDetails: details,
|
||||
androidScheduleMode: AndroidScheduleMode.exact,
|
||||
// uiLocalNotificationDateInterpretation:
|
||||
// UILocalNotificationDateInterpretation.absoluteTime,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -244,6 +245,9 @@ class SecurityHelper {
|
||||
// print("security_warning".tr); //using easy_localization
|
||||
// Use a more robust approach to show a warning, like a dialog:
|
||||
_showSecurityWarning();
|
||||
} else {
|
||||
box.write(BoxName.security_check, 'passed');
|
||||
Log.print('Security checks passed successfully.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../main.dart';
|
||||
|
||||
class SecurityChecks {
|
||||
static const platform = MethodChannel(
|
||||
'com.Intaleq.intaleq/security'); // Choose a unique channel name
|
||||
@@ -40,6 +43,9 @@ class SecurityChecks {
|
||||
);
|
||||
} else {
|
||||
// Continue with normal app flow
|
||||
|
||||
box.write(BoxName.security_check, 'passed');
|
||||
|
||||
print("Device is secure.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,41 @@
|
||||
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);
|
||||
// تخزين الرابط الخام (URL) ليتم معالجته لاحقاً في MapPassengerController
|
||||
final Rx<String?> rawDeepLink = Rx<String?>(null);
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// ابدأ بالاستماع للروابط عند تشغيل التطبيق
|
||||
initDeepLinks();
|
||||
}
|
||||
|
||||
Future<void> initDeepLinks() async {
|
||||
// الاستماع إلى الروابط القادمة
|
||||
// الاستماع للروابط والتطبيق يعمل في الخلفية
|
||||
_linkSubscription = _appLinks.uriLinkStream.listen((uri) {
|
||||
print('Received deep link: $uri');
|
||||
_handleLink(uri);
|
||||
print('🔗 Received deep link (Stream): $uri');
|
||||
rawDeepLink.value = uri.toString();
|
||||
});
|
||||
|
||||
// جلب الرابط الأولي الذي قد يكون فتح التطبيق
|
||||
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}');
|
||||
}
|
||||
// الاستماع للروابط إذا كان التطبيق مغلقاً تماماً (Cold Start)
|
||||
try {
|
||||
final initialUri = await _appLinks.getInitialLink();
|
||||
if (initialUri != null) {
|
||||
print('🔗 Received initial deep link (Cold Start): $initialUri');
|
||||
rawDeepLink.value = initialUri.toString();
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error getting initial link: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
// تأكد من إلغاء الاشتراك عند إغلاق وحدة التحكم
|
||||
_linkSubscription?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
73
lib/controller/home/ios_live_activity_service.dart
Normal file
73
lib/controller/home/ios_live_activity_service.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:live_activities/live_activities.dart';
|
||||
|
||||
class IosLiveActivityService {
|
||||
static final _liveActivitiesPlugin = LiveActivities();
|
||||
static String? _activityId;
|
||||
|
||||
static void init() {
|
||||
if (Platform.isIOS) {
|
||||
_liveActivitiesPlugin.init(
|
||||
appGroupId: "group.com.Intaleq.intaleq",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> startRideActivity({
|
||||
required String rideId,
|
||||
required String driverName,
|
||||
required String carDetails,
|
||||
required String etaText,
|
||||
required double progress,
|
||||
}) async {
|
||||
if (!Platform.isIOS) return;
|
||||
|
||||
try {
|
||||
await _liveActivitiesPlugin.endAllActivities();
|
||||
|
||||
// استخدام dynamic يسمح للحزمة بحفظ النصوص والأرقام في الذاكرة المشتركة
|
||||
final Map<String, dynamic> activityModel = {
|
||||
'status': 'waiting',
|
||||
'driverName': driverName,
|
||||
'carDetails': carDetails,
|
||||
'etaText': etaText,
|
||||
'progress': progress,
|
||||
};
|
||||
|
||||
// الدالة هنا تأخذ المعاملات مباشرة
|
||||
_activityId = await _liveActivitiesPlugin.createActivity(
|
||||
rideId,
|
||||
activityModel,
|
||||
);
|
||||
} catch (e) {
|
||||
print("❌ Live Activity Start Error: $e");
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> updateRideActivity({
|
||||
required String status,
|
||||
required String driverName,
|
||||
required String carDetails,
|
||||
required String etaText,
|
||||
required double progress,
|
||||
}) async {
|
||||
if (!Platform.isIOS || _activityId == null) return;
|
||||
|
||||
final Map<String, dynamic> updatedModel = {
|
||||
'status': status,
|
||||
'driverName': driverName,
|
||||
'carDetails': carDetails,
|
||||
'etaText': etaText,
|
||||
'progress': progress,
|
||||
};
|
||||
|
||||
await _liveActivitiesPlugin.updateActivity(_activityId!, updatedModel);
|
||||
}
|
||||
|
||||
static Future<void> endRideActivity() async {
|
||||
if (!Platform.isIOS || _activityId == null) return;
|
||||
await _liveActivitiesPlugin.endActivity(_activityId!);
|
||||
_activityId = null;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'dart:math' show Random, atan2, cos, max, min, pi, pow, sin, sqrt;
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
import 'dart:convert';
|
||||
import 'package:Intaleq/services/ride_live_notification.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:Intaleq/views/Rate/rate_captain.dart';
|
||||
import 'package:Intaleq/views/Rate/rating_driver_bottom.dart';
|
||||
@@ -44,6 +45,7 @@ import '../../main.dart';
|
||||
import '../../models/model/locations.dart';
|
||||
import '../../models/model/painter_copoun.dart';
|
||||
import '../../print.dart';
|
||||
import '../../services/ride_tracking_native.dart';
|
||||
import '../../views/home/map_widget.dart/cancel_raide_page.dart';
|
||||
import '../../views/home/map_widget.dart/car_details_widget_to_go.dart';
|
||||
import '../../views/home/map_widget.dart/select_driver_mishwari.dart';
|
||||
@@ -61,6 +63,7 @@ import '../payment/payment_controller.dart';
|
||||
import 'decode_polyline_isolate.dart';
|
||||
import 'deep_link_controller.dart';
|
||||
import 'device_performance.dart';
|
||||
import 'ios_live_activity_service.dart';
|
||||
import 'vip_waitting_page.dart';
|
||||
|
||||
enum RideState {
|
||||
@@ -333,6 +336,7 @@ class MapPassengerController extends GetxController {
|
||||
// ==============================================================================
|
||||
// 1. الدالة الرئيسية لتأسيس الاتصال (تستدعى عند بدء البحث startSearchingForDriver)
|
||||
// ==============================================================================
|
||||
Timer? _heartbeatTimer;
|
||||
void initConnectionWithSocket() {
|
||||
if (isSocketConnected && socket != null) return;
|
||||
|
||||
@@ -345,21 +349,36 @@ class MapPassengerController extends GetxController {
|
||||
.setTransports(['websocket'])
|
||||
.disableAutoConnect()
|
||||
.setQuery({'id': passengerId})
|
||||
.setReconnectionAttempts(5)
|
||||
.setReconnectionDelay(2000)
|
||||
.setReconnectionAttempts(20)
|
||||
.setReconnectionDelay(2400)
|
||||
// ✅ أضف هذا السطر لحل مشكلة الـ Heartbeat مع PHPSocketIO
|
||||
.setExtraHeaders({'Connection': 'Upgrade'})
|
||||
.build(),
|
||||
);
|
||||
|
||||
socket.connect();
|
||||
|
||||
// ✅ إضافة النبضة (Heartbeat) لمنع السيرفر من قطع الاتصال
|
||||
_heartbeatTimer?.cancel(); // إيقاف أي نبضة قديمة
|
||||
_heartbeatTimer = Timer.periodic(const Duration(seconds: 25), (timer) {
|
||||
if (isSocketConnected && socket != null && socket!.connected) {
|
||||
socket!.emit('heartbeat', {'passenger_id': passengerId});
|
||||
// Log.print("💓 Socket Heartbeat sent"); // اختياري للتأكد أنه يعمل
|
||||
} else {
|
||||
timer.cancel(); // إيقاف النبضة إذا انقطع السوكيت
|
||||
}
|
||||
});
|
||||
// ✅ معالج الاتصال
|
||||
|
||||
socket.onConnect((_) {
|
||||
Log.print("✅ Socket Connected Successfully");
|
||||
isSocketConnected = true;
|
||||
_reconnectAttempts = 0; // إعادة تعيين عداد المحاولات
|
||||
_reconnectAttempts = 0;
|
||||
_startHeartbeat(); // ← أضف هذا
|
||||
update();
|
||||
});
|
||||
|
||||
// دالة منفصلة للـ heartbeat
|
||||
|
||||
// ⚠️ معالج الانقطاع
|
||||
socket.onDisconnect((_) {
|
||||
Log.print("⚠️ Socket Disconnected");
|
||||
@@ -391,6 +410,16 @@ class MapPassengerController extends GetxController {
|
||||
});
|
||||
}
|
||||
|
||||
void _startHeartbeat() {
|
||||
_heartbeatTimer?.cancel();
|
||||
_heartbeatTimer = Timer.periodic(const Duration(seconds: 25), (timer) {
|
||||
if (isSocketConnected && socket.connected) {
|
||||
socket.emit('heartbeat',
|
||||
{'passenger_id': box.read(BoxName.passengerID).toString()});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// دالة مساعدة
|
||||
bool _isActiveRideState() {
|
||||
return currentRideState.value == RideState.searching ||
|
||||
@@ -515,7 +544,7 @@ class MapPassengerController extends GetxController {
|
||||
// 1. تجهيز الرابط (نفس API الـ Direction)
|
||||
// نستخدم overview=full للحصول على الرسمة، و steps=false لتخفيف البيانات
|
||||
String dynamicApiUrl =
|
||||
'https://routesjo.intaleq.xyz/route/v1/driving/${driverPos.longitude},${driverPos.latitude};${passengerPos.longitude},${passengerPos.latitude}';
|
||||
'${AppLink.routesOsm}/route/v1/driving/${driverPos.longitude},${driverPos.latitude};${passengerPos.longitude},${passengerPos.latitude}';
|
||||
|
||||
var uri = Uri.parse('$dynamicApiUrl?steps=false&overview=full');
|
||||
Log.print('📍 Calculating Driver Route: $uri');
|
||||
@@ -678,13 +707,17 @@ class MapPassengerController extends GetxController {
|
||||
///
|
||||
/// تستدعى من [Socket] أو [FCM] عند قيام السائق بإلغاء الرحلة.
|
||||
/// تضمن عدم تضارب الإشعارات وتوحد تجربة المستخدم.
|
||||
void processRideCancelledByDriver(dynamic data, {String source = "Unknown"}) {
|
||||
Future<void> processRideCancelledByDriver(dynamic data,
|
||||
{String source = "Unknown"}) async {
|
||||
if (_isCancelProcessed) return;
|
||||
|
||||
_isCancelProcessed = true;
|
||||
stopAllTimers();
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
|
||||
await RideLiveNotification.cancel();
|
||||
IosLiveActivityService.endRideActivity(); // ✅ أضف هذا السطر
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
await RideLiveNotification.cancel();
|
||||
Get.defaultDialog(
|
||||
title: "Sorry 😔".tr, // استخدام المفتاح الإنجليزي
|
||||
titleStyle:
|
||||
@@ -726,8 +759,10 @@ class MapPassengerController extends GetxController {
|
||||
);
|
||||
}
|
||||
|
||||
void handleNoDriverFound() {
|
||||
Future<void> handleNoDriverFound() async {
|
||||
stopAllTimers();
|
||||
await RideLiveNotification.cancel();
|
||||
IosLiveActivityService.endRideActivity(); // ✅ أضف هذا السطر
|
||||
_isCancelProcessed = false;
|
||||
currentRideState.value = RideState.noRide;
|
||||
Get.offAll(() => const MapPagePassenger());
|
||||
@@ -789,7 +824,7 @@ class MapPassengerController extends GetxController {
|
||||
};
|
||||
|
||||
var response = await CRUD().post(
|
||||
link: "${AppLink.ride}/rides/retry_search_drivers.php",
|
||||
link: "${AppLink.rideServerSide}/rides/retry_search_drivers.php",
|
||||
payload: payload,
|
||||
);
|
||||
|
||||
@@ -813,11 +848,12 @@ class MapPassengerController extends GetxController {
|
||||
///
|
||||
/// يبدأ عداداً (مثلاً 90 ثانية). إذا لم يتم قبول الرحلة خلال هذه المدة،
|
||||
/// يتم إنهاء البحث واستدعاء [handleNoDriverFound].
|
||||
void startSearchingTimer() {
|
||||
Future<void> startSearchingTimer() async {
|
||||
_searchTimer?.cancel();
|
||||
int seconds = 0;
|
||||
|
||||
Log.print("⏳ Search Timer Started (90s)...");
|
||||
await RideLiveNotification.showSearching(driversStatusForSearchWindow);
|
||||
|
||||
_searchTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
seconds++;
|
||||
@@ -850,7 +886,7 @@ class MapPassengerController extends GetxController {
|
||||
/// 3. **تفعيل الواجهة:** تظهر ديالوج "السائق وصل" وتبدأ عداد الانتظار المجاني (5 دقائق).
|
||||
///
|
||||
/// * [source] : نص يوضح مصدر الاستدعاء (مثل "Socket" أو "FCM") لأغراض التتبع (Logging).
|
||||
void processDriverArrival(String source) {
|
||||
Future<void> processDriverArrival(String source) async {
|
||||
// 1. الحارس: إذا تم التنفيذ سابقاً، توقف
|
||||
if (currentRideState.value == RideState.driverArrived ||
|
||||
_isArrivalProcessed) {
|
||||
@@ -864,6 +900,7 @@ class MapPassengerController extends GetxController {
|
||||
// 2. تحديث الحالة
|
||||
currentRideState.value = RideState.driverArrived;
|
||||
statusRide = 'Arrived';
|
||||
await RideLiveNotification.showDriverArrived(driverName ?? '');
|
||||
|
||||
// 3. تشغيل واجهة الوصول والعداد
|
||||
driverArrivePassengerDialoge();
|
||||
@@ -881,8 +918,8 @@ class MapPassengerController extends GetxController {
|
||||
/// تقوم بإغلاق السوكيت، إيقاف التتبع، والانتقال لشاشة التقييم والدفع.
|
||||
///
|
||||
/// * [driverList]: قائمة البيانات [driverId, rideId, token, price].
|
||||
void processRideFinished(List<dynamic> driverList,
|
||||
{String source = "Unknown"}) {
|
||||
Future<void> processRideFinished(List<dynamic> driverList,
|
||||
{String source = "Unknown"}) async {
|
||||
// 1. الحارس: منع التكرار
|
||||
if (currentRideState.value == RideState.finished || _isFinishProcessed) {
|
||||
Log.print("✋ Ignored Finish Request from $source. Already Finished.");
|
||||
@@ -912,7 +949,8 @@ class MapPassengerController extends GetxController {
|
||||
"Please make sure not to leave any personal belongings in the car.".tr,
|
||||
'tone1',
|
||||
);
|
||||
|
||||
IosLiveActivityService.endRideActivity();
|
||||
await RideLiveNotification.cancel();
|
||||
// 5. استخراج البيانات والانتقال
|
||||
if (driverList.length >= 4) {
|
||||
String price = driverList[3].toString();
|
||||
@@ -1185,9 +1223,24 @@ class MapPassengerController extends GetxController {
|
||||
Log.print("⚠️ No Data in Payload. Fallback to API.");
|
||||
await getUpdatedRideForDriverApply(rideId);
|
||||
}
|
||||
|
||||
// أضف هذا بعد السطر الذي تستدعي فيه RideTrackingNative
|
||||
await IosLiveActivityService.startRideActivity(
|
||||
rideId: rideId,
|
||||
driverName: driverName ?? 'السائق',
|
||||
carDetails: '$make • $carColor',
|
||||
etaText: stringRemainingTimeToPassenger,
|
||||
progress: 0.0,
|
||||
);
|
||||
// إشعارات (الأسعار، الأمان...)
|
||||
_showRideStartNotifications();
|
||||
final etaText = stringRemainingTimeToPassenger; // مثال: "8 دقائق"
|
||||
final carInfo = '$make • $model • $licensePlate';
|
||||
|
||||
await RideLiveNotification.showDriverOnWay(
|
||||
driverName: driverName,
|
||||
etaText: etaText,
|
||||
carInfo: carInfo,
|
||||
);
|
||||
|
||||
update(); // تحديث الواجهة لإظهار بيانات السائق
|
||||
|
||||
@@ -1206,6 +1259,24 @@ class MapPassengerController extends GetxController {
|
||||
// ج) تشغيل التايمر المحلي (للعد التنازلي فقط)
|
||||
startTimerFromDriverToPassengerAfterApplied();
|
||||
}
|
||||
final int timeToPassengerSeconds =
|
||||
timeToPassengerFromDriverAfterApplied; // مثلاً من السيرفر
|
||||
final double distanceDriverToPassengerMeters =
|
||||
double.parse(distanceByPassenger);
|
||||
await RideTrackingNative.updateRideTracking(
|
||||
driverName: driverName,
|
||||
driverPhone: driverPhone,
|
||||
carDetails: '$make • $carColor • $licensePlate',
|
||||
driverLat: driverCarsLocationToPassengerAfterApplied.last.latitude,
|
||||
driverLng: driverCarsLocationToPassengerAfterApplied.last.longitude,
|
||||
passengerLat: passengerLocation.latitude,
|
||||
passengerLng: passengerLocation.longitude,
|
||||
destLat: myDestination.latitude,
|
||||
destLng: myDestination.longitude,
|
||||
rideState: 'waiting', // يعني السائق بالطريق للراكب
|
||||
estimatedTimeMinutes: (timeToPassengerSeconds / 60).round(),
|
||||
totalDistanceMeters: distanceDriverToPassengerMeters,
|
||||
);
|
||||
|
||||
// 6. بدء تتبع الموقع الدوري (Polling Backup + Smart Rerouting)
|
||||
// سيبدأ العمل بعد 6 ثواني
|
||||
@@ -1350,7 +1421,7 @@ class MapPassengerController extends GetxController {
|
||||
break;
|
||||
}
|
||||
_noRideSearchCount++;
|
||||
Log.print('_noRideSearchCount: ${_noRideSearchCount}');
|
||||
Log.print('_noRideSearchCount: $_noRideSearchCount');
|
||||
_noRideNextAllowed = now.add(Duration(seconds: _noRideIntervalSec));
|
||||
String currentCarType = box.read(BoxName.carType) ?? 'yet';
|
||||
getCarsLocationByPassengerAndReloadMarker();
|
||||
@@ -1805,40 +1876,57 @@ class MapPassengerController extends GetxController {
|
||||
///
|
||||
/// تستدعى عند استلام حدث بدء الرحلة سواء من السوكيت أو FCM.
|
||||
/// تضمن انتقال التطبيق لحالة [RideState.inProgress] مرة واحدة فقط.
|
||||
void processRideBegin({String source = "Unknown"}) {
|
||||
// 1. الحارس: إذا بدأت الرحلة مسبقاً، تجاهل
|
||||
Future<void> processRideBegin({String source = "Unknown"}) async {
|
||||
// منطقك الحالي
|
||||
if (currentRideState.value == RideState.inProgress ||
|
||||
_isRideStartedProcessed) {
|
||||
Log.print("✋ Ignored Start Request from $source. Already Started.");
|
||||
return;
|
||||
}
|
||||
|
||||
// شرط إضافي: يجب أن نكون في حالة "وصل السائق" أو "تم القبول" لبدء الرحلة
|
||||
if (currentRideState.value != RideState.driverArrived &&
|
||||
currentRideState.value != RideState.driverApplied) {
|
||||
Log.print(
|
||||
"⚠️ Start Request ignored due to invalid previous state: ${currentRideState.value}");
|
||||
// يمكن السماح بها كحالة استثنائية (Fail-safe) إذا انقطع الاتصال سابقاً
|
||||
// return;
|
||||
}
|
||||
|
||||
_isRideStartedProcessed = true;
|
||||
Log.print("🚀 Ride Started via $source! Processing...");
|
||||
|
||||
// 2. إغلاق أي ديالوج مفتوح (مثل ديالوج السائق وصل)
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
|
||||
// 3. تحديث الحالة
|
||||
currentRideState.value = RideState.inProgress;
|
||||
statusRide = 'Begin';
|
||||
|
||||
// 4. تصفير وإيقاف عدادات الانتظار
|
||||
// إيقاف مؤقت الانتظار
|
||||
remainingTimeDriverWaitPassenger5Minute = 0;
|
||||
_stopWaitPassengerTimer(); // دالة إيقاف تايمر الانتظار
|
||||
_stopWaitPassengerTimer();
|
||||
|
||||
// 5. بدء عداد وقت الرحلة الفعلي
|
||||
// 1) بيانات السائق والرحلة
|
||||
final driverName = this.driverName ?? 'السائق';
|
||||
final driverPhone = this.driverPhone ?? '';
|
||||
final carBrand = this.make ?? '';
|
||||
final carColor = this.carColor ?? '';
|
||||
final carPlate = this.licensePlate ?? '';
|
||||
final carDetails = '$carBrand • $carColor • $carPlate';
|
||||
final driverLat = driverCarsLocationToPassengerAfterApplied.last.latitude;
|
||||
final driverLng = driverCarsLocationToPassengerAfterApplied.last.longitude;
|
||||
final passengerLat = passengerLocation.latitude;
|
||||
final passengerLng = passengerLocation.longitude;
|
||||
final destLat = myDestination.latitude ?? 0.0;
|
||||
final destLng = myDestination.longitude ?? 0.0;
|
||||
|
||||
final int timeToDestinationSeconds =
|
||||
durationToRide; // موجود عندك من التايمر
|
||||
final double totalDistanceMeters = double.parse(distanceByPassenger);
|
||||
|
||||
// 2) استدعاء خدمة الأندرويد لتحديث الإشعار لحالة "inProgress"
|
||||
await RideTrackingNative.updateRideTracking(
|
||||
driverName: driverName,
|
||||
driverPhone: driverPhone,
|
||||
carDetails: carDetails,
|
||||
driverLat: driverLat,
|
||||
driverLng: driverLng,
|
||||
passengerLat: passengerLat,
|
||||
passengerLng: passengerLng,
|
||||
destLat: destLat,
|
||||
destLng: destLng,
|
||||
rideState: 'inProgress',
|
||||
estimatedTimeMinutes: (timeToDestinationSeconds / 60).round(),
|
||||
totalDistanceMeters: totalDistanceMeters,
|
||||
);
|
||||
|
||||
// 3) بدء التايمر الداخلي الخاص بك (للـ ETA داخل التطبيق نفسه)
|
||||
rideIsBeginPassengerTimer();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -2057,8 +2145,13 @@ class MapPassengerController extends GetxController {
|
||||
}
|
||||
|
||||
// final mainBottomMenuMap = GlobalKey<AnimatedContainer>();
|
||||
void changeBottomSheetShown() {
|
||||
isBottomSheetShown = !isBottomSheetShown;
|
||||
void changeBottomSheetShown({bool? forceValue}) {
|
||||
if (forceValue != null) {
|
||||
isBottomSheetShown = forceValue;
|
||||
} else {
|
||||
isBottomSheetShown = !isBottomSheetShown;
|
||||
}
|
||||
|
||||
heightBottomSheetShown = isBottomSheetShown == true ? 250 : 0;
|
||||
update();
|
||||
}
|
||||
@@ -2239,7 +2332,28 @@ class MapPassengerController extends GetxController {
|
||||
int seconds = remainingTimeToPassengerFromDriverAfterApplied % 60;
|
||||
stringRemainingTimeToPassenger =
|
||||
'$minutes:${seconds.toString().padLeft(2, '0')}';
|
||||
// تحويل الوقت أو المسافة إلى نسبة من 0.0 إلى 1.0
|
||||
double currentProgress = 1 -
|
||||
(remainingTimeToPassengerFromDriverAfterApplied /
|
||||
timeToPassengerFromDriverAfterApplied);
|
||||
|
||||
// 🔴 التعديل هنا: نحدث الآيفون كل 5 ثواني فقط للحفاظ على البطارية وتجنب حظر أبل
|
||||
if (secondsElapsed % 5 == 0) {
|
||||
double currentProgress = 1 -
|
||||
(remainingTimeToPassengerFromDriverAfterApplied /
|
||||
(timeToPassengerFromDriverAfterApplied == 0
|
||||
? 1
|
||||
: timeToPassengerFromDriverAfterApplied));
|
||||
|
||||
IosLiveActivityService.updateRideActivity(
|
||||
status: 'waiting',
|
||||
driverName: driverName ?? 'السائق',
|
||||
carDetails:
|
||||
'$make • $model • $carColor', // من الأفضل إظهار اللون أيضاً
|
||||
etaText: stringRemainingTimeToPassenger,
|
||||
progress: currentProgress.clamp(0.0, 1.0),
|
||||
);
|
||||
}
|
||||
// جلب موقع السائق كل 4 ثواني (Polling) ما دامت الرحلة نشطة
|
||||
if (secondsElapsed % beginRideInterval == 0) {
|
||||
// 2. تحديث موقع الراكب للسائق
|
||||
@@ -2365,7 +2479,8 @@ class MapPassengerController extends GetxController {
|
||||
Log.print("⏳ Ride Timer Started. Duration: $durationToRide sec");
|
||||
|
||||
// 3. بدء التايمر الدوري
|
||||
_rideProgressTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
_rideProgressTimer =
|
||||
Timer.periodic(const Duration(seconds: 1), (timer) async {
|
||||
// أ) شرط الإيقاف الحاسم: إذا انتهت الرحلة أو ألغيت
|
||||
if (currentRideState.value != RideState.inProgress) {
|
||||
timer.cancel();
|
||||
@@ -2392,15 +2507,42 @@ class MapPassengerController extends GetxController {
|
||||
stringRemainingTimeRideBegin =
|
||||
'$minutes:${seconds.toString().padLeft(2, '0')}';
|
||||
|
||||
// د) منطق الإشعارات (ربع الوقت)
|
||||
// نحول progressTimerRideBegin (0..1) إلى نسبة (0..100)
|
||||
final percent = (progressTimerRideBegin * 100).clamp(0, 100).toInt();
|
||||
|
||||
// ==============================================================
|
||||
// 🔔 د) تحديث الإشعارات (هنا تم حل مشكلة الإزعاج)
|
||||
// ==============================================================
|
||||
|
||||
// 1. تحديث الآيفون (Live Activity): يمكن تحديثه كل 5 ثواني لأنه "تحديث صامت" للشاشة فقط ولا يصدر صوتاً
|
||||
if (remainingSeconds % 5 == 0 || remainingSeconds == 0) {
|
||||
IosLiveActivityService.updateRideActivity(
|
||||
status: 'inProgress', // الحالة تتغير هنا إلى جارية
|
||||
driverName: driverName ?? '',
|
||||
carDetails: '$make • $model • $carColor',
|
||||
etaText: stringRemainingTimeRideBegin,
|
||||
progress: progressTimerRideBegin.clamp(0.0, 1.0),
|
||||
);
|
||||
}
|
||||
|
||||
// 2. تحديث إشعار الهاتف العادي (RideLiveNotification):
|
||||
// نحدثه كل دقيقة (60 ثانية) بدلاً من 5 ثواني حتى لا يزعج الراكب بالرنين المستمر!
|
||||
if (remainingSeconds % 60 == 0 || remainingSeconds == 0) {
|
||||
await RideLiveNotification.showTripInProgress(
|
||||
percentage: percent,
|
||||
etaText: stringRemainingTimeRideBegin,
|
||||
);
|
||||
}
|
||||
// ==============================================================
|
||||
|
||||
// هـ) منطق الإشعارات لمنتصف الرحلة (يصدر تنبيه مرة واحدة فقط)
|
||||
if (progressTimerRideBegin >= 0.25 &&
|
||||
progressTimerRideBegin < 0.26 &&
|
||||
!_hasShownSpeedWarning) {
|
||||
// يمكن إضافة منطق إشعار منتصف الرحلة هنا
|
||||
}
|
||||
|
||||
// هـ) مراقبة السرعة (Speed Check)
|
||||
// نستخدم المتغير _hasShownSpeedWarning لمنع تكرار الديالوج بشكل مزعج
|
||||
// و) مراقبة السرعة (Speed Check)
|
||||
if (speed > 100 && !_hasShownSpeedWarning) {
|
||||
_hasShownSpeedWarning = true; // ✅ قفل التنبيه حتى لا يتكرر
|
||||
_triggerSpeedWarning();
|
||||
@@ -2411,7 +2553,7 @@ class MapPassengerController extends GetxController {
|
||||
_hasShownSpeedWarning = false;
|
||||
}
|
||||
|
||||
// و) إنهاء التايمر إذا انتهى الوقت
|
||||
// ز) إنهاء التايمر إذا انتهى الوقت
|
||||
if (remainingSeconds <= 0) {
|
||||
timer.cancel();
|
||||
}
|
||||
@@ -2640,7 +2782,7 @@ class MapPassengerController extends GetxController {
|
||||
link: AppLink.getRideStatusFromStartApp,
|
||||
payload: {'passenger_id': box.read(BoxName.passengerID)});
|
||||
// print(res);
|
||||
Log.print('rideStatusFromStartApp: ${res}');
|
||||
Log.print('rideStatusFromStartApp: $res');
|
||||
// print('1070');
|
||||
if (res == 'failure') {
|
||||
rideStatusFromStartApp = {
|
||||
@@ -2796,60 +2938,66 @@ class MapPassengerController extends GetxController {
|
||||
}));
|
||||
}
|
||||
|
||||
Map<String, double>? extractCoordinatesFromLink(String link) {
|
||||
Future<Map<String, double>?> extractCoordinatesFromLinkAsync(
|
||||
String link) async {
|
||||
try {
|
||||
// Extract the URL part from the link by finding the first occurrence of "http"
|
||||
// 1. استخراج الرابط فقط من النص (في حال كان هناك نص مع الرابط في الواتساب)
|
||||
int urlStartIndex = link.indexOf(RegExp(r'https?://'));
|
||||
if (urlStartIndex == -1) {
|
||||
throw const FormatException('No URL found in the provided link.');
|
||||
}
|
||||
if (urlStartIndex == -1) return null;
|
||||
String cleanLink = link.substring(urlStartIndex).trim();
|
||||
|
||||
// Extract the URL and clean it
|
||||
link = link.substring(urlStartIndex).trim();
|
||||
Uri uri = Uri.parse(cleanLink);
|
||||
String finalUrl = cleanLink;
|
||||
|
||||
Uri uri = Uri.parse(link);
|
||||
|
||||
// Common coordinate query parameters
|
||||
List<String> coordinateParams = ['q', 'cp', 'll'];
|
||||
|
||||
// Try to extract coordinates from query parameters
|
||||
for (var param in coordinateParams) {
|
||||
String? value = uri.queryParameters[param];
|
||||
if (value != null && (value.contains(',') || value.contains('~'))) {
|
||||
List<String> coordinates =
|
||||
value.contains(',') ? value.split(',') : value.split('~');
|
||||
if (coordinates.length == 2) {
|
||||
double? latitude = double.tryParse(coordinates[0].trim());
|
||||
double? longitude = double.tryParse(coordinates[1].trim());
|
||||
if (latitude != null && longitude != null) {
|
||||
return {
|
||||
'latitude': latitude,
|
||||
'longitude': longitude,
|
||||
};
|
||||
}
|
||||
}
|
||||
// 2. فك الروابط المختصرة (Unshorten URLs)
|
||||
if (cleanLink.contains('goo.gl') ||
|
||||
cleanLink.contains('maps.app.goo.gl')) {
|
||||
try {
|
||||
// نقوم بطلب HTTP عادي، وhttp يتبع التوجيه التلقائي (Redirects)
|
||||
var response = await http.get(uri);
|
||||
// نأخذ الرابط النهائي بعد التوجيه
|
||||
finalUrl = response.request?.url.toString() ?? cleanLink;
|
||||
} catch (e) {
|
||||
Log.print('Failed to follow redirect: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Try to extract coordinates from the path
|
||||
List<String> pathSegments = uri.pathSegments;
|
||||
for (var segment in pathSegments) {
|
||||
if (segment.contains(',')) {
|
||||
List<String> coordinates = segment.split(',');
|
||||
if (coordinates.length == 2) {
|
||||
double? latitude = double.tryParse(coordinates[0].trim());
|
||||
double? longitude = double.tryParse(coordinates[1].trim());
|
||||
if (latitude != null && longitude != null) {
|
||||
return {
|
||||
'latitude': latitude,
|
||||
'longitude': longitude,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.print('Final Unshortened URL: $finalUrl');
|
||||
|
||||
// 3. استخراج الإحداثيات باستخدام تعبيرات نمطية (Regex) قوية تغطي خرائط جوجل وغيرها
|
||||
|
||||
// النمط الأول: @lat,lng (الأكثر شيوعاً في خرائط جوجل)
|
||||
RegExp regexAt = RegExp(r'@(-?\d+\.\d+),(-?\d+\.\d+)');
|
||||
var matchAt = regexAt.firstMatch(finalUrl);
|
||||
if (matchAt != null) {
|
||||
return {
|
||||
'latitude': double.parse(matchAt.group(1)!),
|
||||
'longitude': double.parse(matchAt.group(2)!),
|
||||
};
|
||||
}
|
||||
|
||||
// النمط الثاني: q=lat,lng أو ll=lat,lng أو query=lat,lng
|
||||
RegExp regexQuery =
|
||||
RegExp(r'(?:q|ll|query)=(-?\d+\.\d+)[,~](-?\d+\.\d+)');
|
||||
var matchQuery = regexQuery.firstMatch(finalUrl);
|
||||
if (matchQuery != null) {
|
||||
return {
|
||||
'latitude': double.parse(matchQuery.group(1)!),
|
||||
'longitude': double.parse(matchQuery.group(2)!),
|
||||
};
|
||||
}
|
||||
|
||||
// النمط الثالث: search/lat,lng (موجود في بعض أشكال خرائط جوجل)
|
||||
RegExp regexSearch = RegExp(r'search/(-?\d+\.\d+),(-?\d+\.\d+)');
|
||||
var matchSearch = regexSearch.firstMatch(finalUrl);
|
||||
if (matchSearch != null) {
|
||||
return {
|
||||
'latitude': double.parse(matchSearch.group(1)!),
|
||||
'longitude': double.parse(matchSearch.group(2)!),
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error parsing location link: $e');
|
||||
Log.print('Error parsing location link: $e');
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -2857,8 +3005,9 @@ class MapPassengerController extends GetxController {
|
||||
|
||||
double latitudeWhatsApp = 0;
|
||||
double longitudeWhatsApp = 0;
|
||||
void handleWhatsAppLink(String link) {
|
||||
Map<String, double>? coordinates = extractCoordinatesFromLink(link);
|
||||
void handleWhatsAppLink(String link) async {
|
||||
Map<String, double>? coordinates =
|
||||
await extractCoordinatesFromLinkAsync(link);
|
||||
|
||||
if (coordinates != null) {
|
||||
latitudeWhatsApp = coordinates['latitude']!;
|
||||
@@ -3019,7 +3168,7 @@ class MapPassengerController extends GetxController {
|
||||
"step3": placesCoordinate.length > 3 ? placesCoordinate[3] : "",
|
||||
"step4": placesCoordinate.length > 4 ? placesCoordinate[4] : "",
|
||||
};
|
||||
Log.print('payload add_ride: ${payload}');
|
||||
Log.print('payload add_ride: $payload');
|
||||
|
||||
try {
|
||||
// الاتصال بـ add_ride.php
|
||||
@@ -3412,7 +3561,7 @@ class MapPassengerController extends GetxController {
|
||||
|
||||
Future<String> getRideStatus(String rideId) async {
|
||||
final response = await CRUD().get(
|
||||
link: "${AppLink.ride}/ride/rides/getRideStatus.php",
|
||||
link: "${AppLink.rideServerSide}/ride/rides/getRideStatus.php",
|
||||
payload: {'id': rideId});
|
||||
print(response);
|
||||
print('2176');
|
||||
@@ -3731,27 +3880,27 @@ class MapPassengerController extends GetxController {
|
||||
box.write(BoxName.countryCode, 'Jordan');
|
||||
// يمكنك تعيين AppLink.endPoint هنا إذا كان منطقك الداخلي لا يزال يعتمد عليه
|
||||
box.write(BoxName.serverChosen,
|
||||
AppLink.IntaleqSyriaServer); // مثال: اختر سيرفر سوريا للبيانات
|
||||
AppLink.server); // مثال: اختر سيرفر سوريا للبيانات
|
||||
return 'Jordan';
|
||||
}
|
||||
|
||||
// 2. فحص سوريا
|
||||
if (isPointInPolygon(passengerPoint, CountryPolygons.syriaBoundary)) {
|
||||
box.write(BoxName.countryCode, 'Syria');
|
||||
box.write(BoxName.serverChosen, AppLink.IntaleqSyriaServer);
|
||||
box.write(BoxName.serverChosen, AppLink.server);
|
||||
return 'Syria';
|
||||
}
|
||||
|
||||
// 3. فحص مصر
|
||||
if (isPointInPolygon(passengerPoint, CountryPolygons.egyptBoundary)) {
|
||||
box.write(BoxName.countryCode, 'Egypt');
|
||||
box.write(BoxName.serverChosen, AppLink.IntaleqAlexandriaServer);
|
||||
box.write(BoxName.serverChosen, AppLink.server);
|
||||
return 'Egypt';
|
||||
}
|
||||
|
||||
// 4. الافتراضي (إذا كان خارج المناطق المخدومة)
|
||||
box.write(BoxName.countryCode, 'Jordan');
|
||||
box.write(BoxName.serverChosen, AppLink.IntaleqSyriaServer);
|
||||
box.write(BoxName.serverChosen, AppLink.server);
|
||||
return 'Unknown Location (Defaulting to Jordan)';
|
||||
}
|
||||
|
||||
@@ -4574,7 +4723,7 @@ Intaleq Team''';
|
||||
_timer?.cancel();
|
||||
_masterTimer?.cancel(); // (أضف المؤقت الرئيسي)
|
||||
_camThrottle?.cancel(); // (أضف مؤقت الكاميرا)
|
||||
|
||||
_heartbeatTimer?.cancel();
|
||||
if (isSocketConnected) {
|
||||
socket.emit('unsubscribe_all',
|
||||
{'passenger_id': box.read(BoxName.passengerID).toString()});
|
||||
@@ -4720,9 +4869,12 @@ Intaleq Team''';
|
||||
clearPolyline();
|
||||
data = [];
|
||||
|
||||
// إيقاف جميع التايمرات
|
||||
// إيقاف جميع التايمرات
|
||||
stopAllTimers();
|
||||
currentRideState.value = RideState.cancelled;
|
||||
await RideLiveNotification.cancel(); // إغلاق أندرويد
|
||||
IosLiveActivityService.endRideActivity(); // ✅ إغلاق iOS
|
||||
|
||||
// 4. الاتصال بالسيرفر لإلغاء الرحلة وإبلاغ السائق
|
||||
if (rideId != 'yet' && rideId != null) {
|
||||
@@ -6143,7 +6295,7 @@ Intaleq Team''';
|
||||
// Waypoints غير مدعومة حالياً في OSRM، لذلك تم تجاهلها
|
||||
var uri = Uri.parse(
|
||||
'$dynamicApiUrl?origin=$origin&destination=$destination&steps=false&overview=false');
|
||||
Log.print('uri: ${uri}');
|
||||
Log.print('uri: $uri');
|
||||
|
||||
// 3. إرسال الطلب مع الهيدر
|
||||
http.Response response;
|
||||
@@ -6169,7 +6321,7 @@ Intaleq Team''';
|
||||
isDrawingRoute = false; // Reset state
|
||||
|
||||
responseData = json.decode(response.body);
|
||||
Log.print('responseData: ${responseData}');
|
||||
Log.print('responseData: $responseData');
|
||||
|
||||
if (responseData['status'] != 'ok') {
|
||||
print('API returned an error: ${responseData['message']}');
|
||||
@@ -6225,10 +6377,10 @@ Intaleq Team''';
|
||||
// 2. الاتصال بالسيرفر - New OSRM format
|
||||
var originCoords = origin.split(',');
|
||||
String dynamicApiUrl =
|
||||
'https://routesjo.intaleq.xyz/route/v1/driving/${originCoords[1]},${originCoords[0]};${lngDest},${latDest}';
|
||||
'${AppLink.routesOsm}/route/v1/driving/${originCoords[1]},${originCoords[0]};$lngDest,$latDest';
|
||||
|
||||
var uri = Uri.parse('$dynamicApiUrl?steps=false&overview=full');
|
||||
Log.print('Requesting Route URI (Attempt: ${attemptCount + 1}): ${uri}');
|
||||
Log.print('Requesting Route URI (Attempt: ${attemptCount + 1}): $uri');
|
||||
|
||||
http.Response response;
|
||||
Map<String, dynamic> responseData;
|
||||
@@ -7344,7 +7496,7 @@ Intaleq Team''';
|
||||
} catch (_) {}
|
||||
|
||||
update();
|
||||
changeBottomSheetShown();
|
||||
changeBottomSheetShown(forceValue: true);
|
||||
}
|
||||
|
||||
List<LatLng> polylineCoordinate = [];
|
||||
@@ -7779,27 +7931,59 @@ Intaleq Team''';
|
||||
|
||||
// --- دالة جديدة للاستماع ومعالجة الرابط ---
|
||||
void _listenForDeepLink() {
|
||||
// استمع إلى أي تغيير في الإحداثيات القادمة من الرابط
|
||||
ever(_deepLinkController.deepLinkLatLng, (LatLng? latLng) {
|
||||
if (latLng != null) {
|
||||
print('MapPassengerController detected deep link LatLng: $latLng');
|
||||
// عندما يتم استلام إحداثيات جديدة، عينها كوجهة
|
||||
myDestination = latLng;
|
||||
ever(_deepLinkController.rawDeepLink, (String? link) async {
|
||||
if (link != null && link.isNotEmpty) {
|
||||
Log.print('📍 MapPassengerController processing link: $link');
|
||||
|
||||
// قم بتشغيل المنطق الخاص بك لعرض المسار
|
||||
// (تأكد من أن `passengerLocation` لديها قيمة أولاً)
|
||||
if (passengerLocation != null) {
|
||||
getDirectionMap(
|
||||
'${passengerLocation.latitude},${passengerLocation.longitude}',
|
||||
'${myDestination.latitude},${myDestination.longitude}');
|
||||
// 1. استخراج الإحداثيات باستخدام الدالة الموجودة لديك مسبقاً
|
||||
Map<String, double>? coordinates =
|
||||
await extractCoordinatesFromLinkAsync(link);
|
||||
|
||||
if (coordinates != null) {
|
||||
double destLat = coordinates['latitude']!;
|
||||
double destLng = coordinates['longitude']!;
|
||||
myDestination = LatLng(destLat, destLng);
|
||||
|
||||
// 2. التحقق من موقع الراكب الحالي
|
||||
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');
|
||||
|
||||
// 3. مسح أي مسارات سابقة
|
||||
clearPolyline();
|
||||
|
||||
// 4. استدعاء دالة رسم المسار وحساب التكلفة التي برمجتها
|
||||
await getDirectionMap(originStr, destStr);
|
||||
|
||||
// 5. إظهار الواجهة السفلية للرحلة ليكون الطلب جاهزاً بنقرة واحدة
|
||||
isBottomSheetShown = true;
|
||||
heightBottomSheetShown = 250;
|
||||
update();
|
||||
|
||||
Get.snackbar(
|
||||
'Location Received'.tr,
|
||||
'Route and prices have been calculated successfully!'.tr,
|
||||
backgroundColor: AppColor.greenColor,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// يمكنك إظهار رسالة للمستخدم لتمكين الموقع أولاً
|
||||
print(
|
||||
'Cannot process deep link route yet, passenger location is null.');
|
||||
Log.print('⚠️ Could not extract valid coordinates from link: $link');
|
||||
}
|
||||
|
||||
// إعادة تعيين القيمة إلى null لمنع التشغيل مرة أخرى عند إعادة بناء الواجهة
|
||||
_deepLinkController.deepLinkLatLng.value = null;
|
||||
// تفريغ الرابط بعد معالجته حتى لا يتم استدعاؤه مرة أخرى بالخطأ
|
||||
_deepLinkController.rawDeepLink.value = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,14 +7,11 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:Intaleq/constant/box_name.dart';
|
||||
import 'package:Intaleq/constant/colors.dart';
|
||||
import 'package:Intaleq/constant/links.dart';
|
||||
import 'package:Intaleq/constant/style.dart';
|
||||
import 'package:Intaleq/controller/functions/crud.dart';
|
||||
import 'package:Intaleq/main.dart';
|
||||
import 'package:Intaleq/views/widgets/elevated_btn.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
|
||||
import '../../../constant/api_key.dart';
|
||||
import '../../../env/env.dart';
|
||||
import '../../../print.dart';
|
||||
import '../../../views/widgets/mydialoug.dart';
|
||||
@@ -85,7 +82,7 @@ class ComplaintController extends GetxController {
|
||||
isUploading.value = true;
|
||||
update();
|
||||
|
||||
var uri = Uri.parse('${AppLink.IntaleqSyriaServer}/upload_audio.php');
|
||||
var uri = Uri.parse('${AppLink.server}/upload_audio.php');
|
||||
var request = http.MultipartRequest('POST', uri);
|
||||
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ class SplashScreenController extends GetxController
|
||||
final fcm = Get.find<FirebaseMessagesController>();
|
||||
await fcm.requestFirebaseMessagingPermission();
|
||||
|
||||
_scheduleDailyNotifications(notificationController);
|
||||
// _scheduleDailyNotifications(notificationController);
|
||||
_initializeQuickActions();
|
||||
} catch (e, st) {
|
||||
CRUD.addError('background_init_error: $e', st.toString(), 'SplashScreen');
|
||||
|
||||
@@ -17,6 +17,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
import 'constant/api_key.dart';
|
||||
import 'constant/info.dart';
|
||||
import 'controller/home/ios_live_activity_service.dart';
|
||||
import 'controller/local/local_controller.dart';
|
||||
import 'controller/local/translations.dart';
|
||||
import 'firebase_options.dart';
|
||||
@@ -48,7 +49,8 @@ void main() {
|
||||
options: DefaultFirebaseOptions.currentPlatform);
|
||||
FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler);
|
||||
}
|
||||
|
||||
// ✅ التعديل هنا: تهيئة خدمة الـ Live Activity للآيفون
|
||||
IosLiveActivityService.init();
|
||||
// Stripe key initialization is very fast.
|
||||
Stripe.publishableKey = AK.publishableKey;
|
||||
|
||||
|
||||
@@ -8,6 +8,6 @@ class Log {
|
||||
}
|
||||
|
||||
static Object? inspect(Object? object) {
|
||||
return developer.inspect(object);
|
||||
// return developer.inspect(object);
|
||||
}
|
||||
}
|
||||
|
||||
113
lib/services/ride_live_notification.dart
Normal file
113
lib/services/ride_live_notification.dart
Normal file
@@ -0,0 +1,113 @@
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../controller/firebase/local_notification.dart';
|
||||
|
||||
class RideLiveNotification {
|
||||
static const int _notificationId = 888; // رقم ثابت لإشعار الرحلة
|
||||
// نستخدم نفس الـ plugin من NotificationController
|
||||
static FlutterLocalNotificationsPlugin get _plugin =>
|
||||
Get.find<NotificationController>().plugin;
|
||||
// static bool _initialized = false;
|
||||
|
||||
static Future<void> _showOrUpdate({
|
||||
required String title,
|
||||
required String body,
|
||||
required int progress,
|
||||
required int maxProgress,
|
||||
bool indeterminate = false,
|
||||
}) async {
|
||||
// await init();
|
||||
|
||||
final android = AndroidNotificationDetails(
|
||||
'live_ride_tracking', // channel id
|
||||
'Ride Tracking', // channel name
|
||||
channelDescription: 'Live updates for the current Intaleq ride',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
ongoing: true, // إشعار ثابت
|
||||
autoCancel: false,
|
||||
showProgress: true,
|
||||
onlyAlertOnce: true,
|
||||
maxProgress: maxProgress,
|
||||
progress: progress,
|
||||
indeterminate: indeterminate,
|
||||
icon: '@mipmap/launcher_icon', // غيّرها لو عندك أيقونة أخرى
|
||||
);
|
||||
|
||||
final details = NotificationDetails(android: android);
|
||||
|
||||
await _plugin.show(
|
||||
id: _notificationId,
|
||||
title: title,
|
||||
body: body,
|
||||
notificationDetails: details,
|
||||
);
|
||||
}
|
||||
|
||||
/// إلغاء إشعار الرحلة
|
||||
static Future<void> cancel() async {
|
||||
// await init();
|
||||
await _plugin.cancel(id: _notificationId);
|
||||
}
|
||||
|
||||
// ========= حالات جاهزة للحالات المختلفة =========
|
||||
|
||||
/// حالة البحث عن سائق
|
||||
static Future<void> showSearching(String statusText) async {
|
||||
await _showOrUpdate(
|
||||
title: 'جاري البحث عن سائق…',
|
||||
body: statusText,
|
||||
progress: 0,
|
||||
maxProgress: 0,
|
||||
indeterminate: true, // شريط متحرّك بدون نسبة
|
||||
);
|
||||
}
|
||||
|
||||
/// السائق في الطريق للراكب
|
||||
static Future<void> showDriverOnWay({
|
||||
required String driverName,
|
||||
required String etaText, // مثل: "8 دقائق"
|
||||
String? carInfo, // مثل: "هيونداي • أبيض • ABC-123"
|
||||
}) async {
|
||||
final info = [
|
||||
driverName,
|
||||
if (carInfo != null && carInfo.isNotEmpty) carInfo,
|
||||
etaText,
|
||||
].join(' • ');
|
||||
|
||||
await _showOrUpdate(
|
||||
title: 'السائق في الطريق إليك',
|
||||
body: info,
|
||||
progress: 0,
|
||||
maxProgress: 0,
|
||||
indeterminate: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// السائق وصل لموقع الراكب
|
||||
static Future<void> showDriverArrived(String driverName) async {
|
||||
await _showOrUpdate(
|
||||
title: 'السائق وصل',
|
||||
body: 'الرجاء التوجّه لمقابلة $driverName عند نقطة الالتقاء',
|
||||
progress: 100,
|
||||
maxProgress: 100,
|
||||
indeterminate: false,
|
||||
);
|
||||
}
|
||||
|
||||
/// الرحلة جارية (Progress حقيقي)
|
||||
static Future<void> showTripInProgress({
|
||||
required int percentage, // من 0 إلى 100
|
||||
required String etaText, // "8 دقائق" مثلاً
|
||||
}) async {
|
||||
final safePercent = percentage.clamp(0, 100);
|
||||
await _showOrUpdate(
|
||||
title: 'الرحلة جارية الآن',
|
||||
body: 'المتبقي تقريبًا: $etaText',
|
||||
progress: safePercent,
|
||||
maxProgress: 100,
|
||||
indeterminate: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
43
lib/services/ride_tracking_native.dart
Normal file
43
lib/services/ride_tracking_native.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class RideTrackingNative {
|
||||
static const MethodChannel _channel = MethodChannel('intaleq/ride_tracking');
|
||||
|
||||
static Future<void> updateRideTracking({
|
||||
required String driverName,
|
||||
String driverPhone = '',
|
||||
required String carDetails,
|
||||
required double driverLat,
|
||||
required double driverLng,
|
||||
required double passengerLat,
|
||||
required double passengerLng,
|
||||
required double destLat,
|
||||
required double destLng,
|
||||
required String rideState, // "waiting" أو "inProgress"
|
||||
required int estimatedTimeMinutes,
|
||||
required double totalDistanceMeters,
|
||||
}) async {
|
||||
if (!Platform.isAndroid) return;
|
||||
|
||||
await _channel.invokeMethod('updateRideTracking', {
|
||||
'driverName': driverName,
|
||||
'driverPhone': driverPhone,
|
||||
'carDetails': carDetails,
|
||||
'driverLat': driverLat,
|
||||
'driverLng': driverLng,
|
||||
'passengerLat': passengerLat,
|
||||
'passengerLng': passengerLng,
|
||||
'destLat': destLat,
|
||||
'destLng': destLng,
|
||||
'rideState': rideState,
|
||||
'estimatedTime': estimatedTimeMinutes,
|
||||
'totalDistance': totalDistanceMeters,
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> stopRideTracking() async {
|
||||
if (!Platform.isAndroid) return;
|
||||
await _channel.invokeMethod('stopRideTracking');
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import 'package:Intaleq/constant/box_name.dart';
|
||||
import 'package:Intaleq/controller/firebase/firbase_messge.dart';
|
||||
import 'package:Intaleq/controller/firebase/notification_service.dart';
|
||||
import 'package:Intaleq/main.dart';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -9,13 +7,11 @@ import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
|
||||
|
||||
import '../../../constant/colors.dart';
|
||||
import '../../../controller/functions/encrypt_decrypt.dart';
|
||||
import '../../../controller/functions/tts.dart';
|
||||
import '../../../controller/home/ios_live_activity_service.dart';
|
||||
import '../../../controller/home/map_passenger_controller.dart';
|
||||
import '../../../controller/home/vip_waitting_page.dart';
|
||||
import '../../../env/env.dart';
|
||||
import '../../../print.dart';
|
||||
import '../../auth/otp_page.dart';
|
||||
|
||||
// --- الدالة الرئيسية بالتصميم الجديد ---
|
||||
GetBuilder<MapPassengerController> leftMainMenuIcons() {
|
||||
@@ -118,25 +114,98 @@ Widget _buildVerticalDivider() {
|
||||
}
|
||||
|
||||
// --- باقي الكود الخاص بك يبقى كما هو بدون تغيير ---
|
||||
|
||||
class TestPage extends StatelessWidget {
|
||||
const TestPage({
|
||||
super.key,
|
||||
});
|
||||
const TestPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final random = Random();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: Center(
|
||||
child: TextButton(
|
||||
onPressed: () async {
|
||||
// print(box.read(BoxName.lowEndMode));
|
||||
// box.read(BoxName.lowEndMode)
|
||||
Get.to(PhoneNumberScreen());
|
||||
},
|
||||
child: Text(
|
||||
"Text Button",
|
||||
),
|
||||
appBar: AppBar(
|
||||
title: const Text('iOS Live Activity Test'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// زر البدء
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
debugPrint("🍎 محاولة تشغيل Live Activity (Start)...");
|
||||
try {
|
||||
await IosLiveActivityService.startRideActivity(
|
||||
rideId: "123",
|
||||
driverName: "تجربة مبدئية",
|
||||
carDetails: "تويوتا • أسود",
|
||||
etaText: "5 دقائق",
|
||||
progress: 0.2,
|
||||
);
|
||||
debugPrint(
|
||||
"✅ تم تشغيل Live Activity بنجاح! أغلق الشاشة لترى النتيجة.");
|
||||
} catch (e) {
|
||||
debugPrint("❌ خطأ في Start Live Activity: $e");
|
||||
}
|
||||
},
|
||||
child: const Text('Start Activity'),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// زر التحديث العشوائي
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
debugPrint("🔄 محاولة تحديث Live Activity (Update)...");
|
||||
|
||||
// توليد بيانات عشوائية للاختبار
|
||||
final statuses = ['waiting', 'ongoing'];
|
||||
final status = statuses[random.nextInt(statuses.length)];
|
||||
|
||||
final int minutes = random.nextInt(15) + 1; // 1–15
|
||||
final String eta = "$minutes دقائق";
|
||||
|
||||
final double progress = (random.nextDouble() * 0.9) + 0.05;
|
||||
// بين 0.05 و 0.95 تقريبًا
|
||||
|
||||
try {
|
||||
await IosLiveActivityService.updateRideActivity(
|
||||
status: status,
|
||||
driverName:
|
||||
status == 'waiting' ? 'السائق في الطريق' : 'السائق معك',
|
||||
carDetails: "تويوتا • أسود",
|
||||
etaText: eta,
|
||||
progress: progress,
|
||||
);
|
||||
debugPrint(
|
||||
"✅ تم تحديث Live Activity: status=$status, eta=$eta, progress=$progress");
|
||||
} catch (e) {
|
||||
debugPrint("❌ خطأ في Update Live Activity: $e");
|
||||
}
|
||||
},
|
||||
child: const Text('Update (Random)'),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// زر الإنهاء
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
onPressed: () async {
|
||||
debugPrint("🛑 محاولة إنهاء Live Activity (End)...");
|
||||
try {
|
||||
await IosLiveActivityService.endRideActivity();
|
||||
debugPrint("✅ تم إنهاء Live Activity.");
|
||||
} catch (e) {
|
||||
debugPrint("❌ خطأ في End Live Activity: $e");
|
||||
}
|
||||
},
|
||||
child: const Text('End Activity'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -140,7 +140,9 @@ class PickerAnimtionContainerFormPlaces extends StatelessWidget {
|
||||
controller
|
||||
.changePickerShown();
|
||||
controller
|
||||
.changeBottomSheetShown();
|
||||
.changeBottomSheetShown(
|
||||
forceValue:
|
||||
true);
|
||||
controller
|
||||
.bottomSheet();
|
||||
Get.back();
|
||||
@@ -196,7 +198,8 @@ class PickerAnimtionContainerFormPlaces extends StatelessWidget {
|
||||
'${controller.newMyLocation.latitude},${controller.newMyLocation.longitude}',
|
||||
);
|
||||
controller.changePickerShown();
|
||||
controller.changeBottomSheetShown();
|
||||
controller.changeBottomSheetShown(
|
||||
forceValue: true);
|
||||
controller.bottomSheet();
|
||||
// await sql
|
||||
// .getAllData(TableName.placesFavorite)
|
||||
|
||||
@@ -47,12 +47,12 @@ class CupertinoDriverListWidget extends StatelessWidget {
|
||||
leading: CircleAvatar(
|
||||
radius: 25,
|
||||
backgroundImage: NetworkImage(
|
||||
'${AppLink.IntaleqSyriaServer}/portrate_captain_image/${driver['id']}.jpg',
|
||||
'${AppLink.server}/portrate_captain_image/${driver['id']}.jpg',
|
||||
),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return Image.network(
|
||||
'${AppLink.IntaleqSyriaServer}/portrate_captain_image/${driver['id']}.jpg',
|
||||
'${AppLink.server}/portrate_captain_image/${driver['id']}.jpg',
|
||||
fit: BoxFit.cover,
|
||||
loadingBuilder: (BuildContext context,
|
||||
Widget child,
|
||||
|
||||
Reference in New Issue
Block a user