Fixes & Updates - 2026-06-01: Integrate Back-End v3 updates, fix call/connection issues across apps

This commit is contained in:
Hamza-Ayed
2026-06-01 23:36:27 +03:00
parent 118781fd66
commit 97945aa362
76 changed files with 19806 additions and 10822 deletions

File diff suppressed because one or more lines are too long

83
compare.sh Normal file
View File

@@ -0,0 +1,83 @@
#!/bin/bash
ORIG_FILE="lib/controller/home/map_passenger_controller.dart"
SPLIT_DIR="lib/controller/home/map"
echo "Extracting methods from original controller..."
# Methods typically start with spaces and have patterns like:
# returnType methodName( or methodName(
# Let's extract words that precede ( on lines that don't start with keywords (if, for, while, switch, catch, etc.)
# We will use awk to parse.
METHODS=$(cat "$ORIG_FILE" | awk '
# Skip single-line comments
/\/\// { next }
# Skip imports and class declarations
/import/ || /class/ { next }
# Find lines with "("
/\(/ {
# Replace anything inside parentheses and curly braces to simplify
gsub(/\(.*\)/, "()")
# Find word before "()"
for (i = 1; i <= NF; i++) {
if ($i ~ /[a-zA-Z0-9_]+\(\)/) {
name = $i
sub(/\(\)/, "", name)
# Remove any leading modifiers like async, Future, static, etc.
# Only keep valid identifiers that are not control keywords
if (name !~ /^(if|for|while|switch|catch|super|await|print|assert|dynamic|void|return|with|override|get|set|else|try|final|const|var|late|static|factory|new|abstract|covariant|external|operator|part|required|typedef|yield)$/ && name ~ /^[a-zA-Z_][a-zA-Z0-9_]*$/) {
print name
}
}
}
}' | sort -u)
echo "Extracting fields/variables from original controller..."
# Fields are usually declared inside the class at the beginning of lines or indented.
# e.g., RxBool isSearching = false.obs; or String? rideId;
VARS=$(cat "$ORIG_FILE" | awk '
/\/\// { next }
/import/ || /class/ { next }
# Lines ending with ";" or containing "=" followed by ";"
/;/ {
# Extract words that look like declarations.
# We look for typical type names or var/final followed by variable name
for (i = 1; i < NF; i++) {
if ($i ~ /^(var|final|const|late|RxBool|RxInt|RxDouble|RxString|RxList|RxMap|RxSet|Rx|String|int|double|bool|List|Map|Set|Timer|LatLng|Position|IntaleqMapController)$/) {
# The next field might be the variable name, or it might have a type like String?
name = $(i+1)
# Remove trailing ?, ;, =
sub(/\?/, "", name)
sub(/;/, "", name)
sub(/=/, "", name)
if (name ~ /^[a-zA-Z_][a-zA-Z0-9_]*$/) {
print name
}
}
}
}' | sort -u)
echo "Checking split files for methods..."
echo "--- MISSING METHODS ---"
MISSING_METHODS_COUNT=0
for method in $METHODS; do
# Search for this method name as a whole word in split controllers
FOUND=$(grep -rw "$method" "$SPLIT_DIR" 2>/dev/null)
if [ -z "$FOUND" ]; then
echo " - $method"
MISSING_METHODS_COUNT=$((MISSING_METHODS_COUNT+1))
fi
done
echo "Total missing methods: $MISSING_METHODS_COUNT"
echo ""
echo "Checking split files for variables/fields..."
echo "--- MISSING VARIABLES ---"
MISSING_VARS_COUNT=0
for var in $VARS; do
FOUND=$(grep -rw "$var" "$SPLIT_DIR" 2>/dev/null)
if [ -z "$FOUND" ]; then
echo " - $var"
MISSING_VARS_COUNT=$((MISSING_VARS_COUNT+1))
fi
done
echo "Total missing variables: $MISSING_VARS_COUNT"

79
compare_controllers.py Normal file
View File

@@ -0,0 +1,79 @@
import os
import re
original_path = 'lib/controller/home/map_passenger_controller.dart'
split_dir = 'lib/controller/home/map'
# Read original file
with open(original_path, 'r', encoding='utf-8') as f:
orig_content = f.read()
# Read all split files
split_contents = {}
for filename in os.listdir(split_dir):
if filename.endswith('.dart') and filename != 'map_screen_binding.dart':
filepath = os.path.join(split_dir, filename)
with open(filepath, 'r', encoding='utf-8') as f:
split_contents[filename] = f.read()
# Combined content of all split files
combined_split_content = '\n'.join(split_contents.values())
# Regex to find method/function declarations inside a class in Dart
keywords = {
'if', 'for', 'while', 'switch', 'catch', 'super', 'await', 'print',
'assert', 'dynamic', 'void', 'return', 'with', 'override', 'get', 'set',
'class', 'import', 'extends', 'implements', 'mixin', 'this', 'else', 'try',
'final', 'const', 'var', 'late', 'static', 'factory', 'new', 'abstract',
'covariant', 'external', 'operator', 'part', 'required', 'typedef', 'yield'
}
def strip_comments(text):
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL)
text = re.sub(r'//.*', '', text)
return text
orig_clean = strip_comments(orig_content)
combined_split_clean = strip_comments(combined_split_content)
method_decl_pattern = re.compile(
r'(?:[a-zA-Z0-9_<>\?\[\]]+(?:\s+[a-zA-Z0-9_<>\?\[\]]+)*\s+)?([a-zA-Z0-9_]+)\s*\([^\)]*\)\s*(?:async)?\s*(?:=>|\{)'
)
original_methods = set()
for match in method_decl_pattern.finditer(orig_clean):
method_name = match.group(1)
if method_name not in keywords and not method_name.isdigit():
original_methods.add(method_name)
var_decl_pattern = re.compile(
r'\b(?:var|final|const|late|Rx[a-zA-Z]+|String|int|double|bool|List|Map|Set|Timer|LatLng|Position|IntaleqMapController)\??\s+([a-zA-Z0-9_]+)\b'
)
original_vars = set()
for match in var_decl_pattern.finditer(orig_clean):
var_name = match.group(1)
if var_name not in keywords and not var_name.isdigit():
original_vars.add(var_name)
missing_methods = []
for method in sorted(original_methods):
if not re.search(r'\b' + re.escape(method) + r'\b', combined_split_clean):
missing_methods.append(method)
missing_vars = []
for var in sorted(original_vars):
if not re.search(r'\b' + re.escape(var) + r'\b', combined_split_clean):
missing_vars.append(var)
print("--- MISSING METHODS ---")
print(f"Total original methods found: {len(original_methods)}")
print(f"Total missing: {len(missing_methods)}")
for m in missing_methods:
print(f" - {m}")
print("\n--- MISSING VARIABLES/FIELDS ---")
print(f"Total original variables found: {len(original_vars)}")
print(f"Total missing: {len(missing_vars)}")
for v in missing_vars:
print(f" - {v}")

0
comparison_results.txt Normal file
View File

View File

@@ -46,6 +46,11 @@ post_install do |installer|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)', '$(inherited)',
'PERMISSION_CONTACTS=1', 'PERMISSION_CONTACTS=1',
'PERMISSION_LOCATION=1',
'PERMISSION_MICROPHONE=1',
'PERMISSION_NOTIFICATIONS=1',
'PERMISSION_CAMERA=1',
'PERMISSION_PHOTOS=1',
] ]
end end
end end

View File

@@ -81,6 +81,9 @@ PODS:
- FlutterMacOS - FlutterMacOS
- flutter_tts (0.0.1): - flutter_tts (0.0.1):
- Flutter - Flutter
- flutter_webrtc (1.4.0):
- Flutter
- WebRTC-SDK (= 144.7559.01)
- geolocator_apple (1.2.0): - geolocator_apple (1.2.0):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
@@ -246,6 +249,7 @@ PODS:
- FlutterMacOS - FlutterMacOS
- wakelock_plus (0.0.1): - wakelock_plus (0.0.1):
- Flutter - Flutter
- WebRTC-SDK (144.7559.01)
- webview_flutter_wkwebview (0.0.1): - webview_flutter_wkwebview (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
@@ -264,6 +268,7 @@ DEPENDENCIES:
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`) - flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
- flutter_tts (from `.symlinks/plugins/flutter_tts/ios`) - flutter_tts (from `.symlinks/plugins/flutter_tts/ios`)
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`) - geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
- google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`) - google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`)
- image_cropper (from `.symlinks/plugins/image_cropper/ios`) - image_cropper (from `.symlinks/plugins/image_cropper/ios`)
@@ -322,6 +327,7 @@ SPEC REPOS:
- StripePaymentsUI - StripePaymentsUI
- StripeUICore - StripeUICore
- TOCropViewController - TOCropViewController
- WebRTC-SDK
EXTERNAL SOURCES: EXTERNAL SOURCES:
app_links: app_links:
@@ -350,6 +356,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin" :path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
flutter_tts: flutter_tts:
:path: ".symlinks/plugins/flutter_tts/ios" :path: ".symlinks/plugins/flutter_tts/ios"
flutter_webrtc:
:path: ".symlinks/plugins/flutter_webrtc/ios"
geolocator_apple: geolocator_apple:
:path: ".symlinks/plugins/geolocator_apple/darwin" :path: ".symlinks/plugins/geolocator_apple/darwin"
google_sign_in_ios: google_sign_in_ios:
@@ -424,6 +432,7 @@ SPEC CHECKSUMS:
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23 flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
flutter_tts: 35ac3c7d42412733e795ea96ad2d7e05d0a75113 flutter_tts: 35ac3c7d42412733e795ea96ad2d7e05d0a75113
flutter_webrtc: ec91d94b484ad49cf191ef93413f64a40ffd3b4c
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
google_sign_in_ios: 000870aa06da9b28d1d0bf7ef70ff0213059dd28 google_sign_in_ios: 000870aa06da9b28d1d0bf7ef70ff0213059dd28
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
@@ -467,8 +476,9 @@ SPEC CHECKSUMS:
vibration: ca8104a8875b9c493e15b21b04e456befd0ff6eb vibration: ca8104a8875b9c493e15b21b04e456befd0ff6eb
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
WebRTC-SDK: ab9b5319e458c2bfebdc92b3600740da35d5630d
webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d
PODFILE CHECKSUM: 8a0b04ec79a0d49122ae6c10242e7cb023122802 PODFILE CHECKSUM: 2ba2e4898a3d9a1615dafa81db0705bd67da92c3
COCOAPODS: 1.16.2 COCOAPODS: 1.16.2

View File

@@ -5,6 +5,17 @@ import 'package:Intaleq/controller/firebase/local_notification.dart';
import 'package:Intaleq/controller/home/deep_link_controller.dart'; import 'package:Intaleq/controller/home/deep_link_controller.dart';
import 'package:Intaleq/controller/local/local_controller.dart'; import 'package:Intaleq/controller/local/local_controller.dart';
import 'package:Intaleq/controller/functions/tts.dart'; import 'package:Intaleq/controller/functions/tts.dart';
import 'package:Intaleq/controller/voice_call_controller.dart';
import 'package:Intaleq/controller/home/map/map_socket_controller.dart';
import 'package:Intaleq/controller/home/map/map_engine_controller.dart';
import 'package:Intaleq/controller/home/map/location_search_controller.dart';
import 'package:Intaleq/controller/home/map/nearby_drivers_controller.dart';
import 'package:Intaleq/controller/home/map/ride_lifecycle_controller.dart';
import 'package:Intaleq/controller/home/map/ui_interactions_controller.dart';
import 'package:Intaleq/controller/home/menu_controller.dart';
import 'package:Intaleq/controller/functions/crud.dart';
import 'package:Intaleq/controller/home/points_for_rider_controller.dart';
/// This is the central dependency injection file for the app. /// This is the central dependency injection file for the app.
/// It uses GetX Bindings to make the app start faster and manage memory better. /// It uses GetX Bindings to make the app start faster and manage memory better.
@@ -39,5 +50,19 @@ class AppBindings extends Bindings {
// TextToSpeechController for global accessibility // TextToSpeechController for global accessibility
Get.lazyPut(() => TextToSpeechController(), fenix: true); Get.lazyPut(() => TextToSpeechController(), fenix: true);
// VoiceCallController for WebRTC calls
Get.lazyPut(() => VoiceCallController(), fenix: true);
// Map & Ride controllers registered globally to prevent route-disposal race conditions.
Get.put(MapSocketController(), permanent: true);
Get.put(MapEngineController(), permanent: true);
Get.put(LocationSearchController(), permanent: true);
Get.put(NearbyDriversController(), permanent: true);
Get.put(RideLifecycleController(), permanent: true);
Get.put(UiInteractionsController(), permanent: true);
Get.put(MyMenuController(), permanent: true);
Get.put(CRUD(), permanent: true);
Get.put(WayPointController(), permanent: true);
} }
} }

View File

@@ -231,6 +231,7 @@ class AppLink {
static String addMishwari = "$server/ride/mishwari/add.php"; static String addMishwari = "$server/ride/mishwari/add.php";
static String cancelMishwari = "$server/ride/mishwari/cancel.php"; static String cancelMishwari = "$server/ride/mishwari/cancel.php";
static String getMishwari = "$server/ride/mishwari/get.php"; static String getMishwari = "$server/ride/mishwari/get.php";
static String sendChatMessage = "$server/ride/chat/send_message.php";
//-----------------DriverOrder------------------ //-----------------DriverOrder------------------

View File

@@ -107,13 +107,14 @@ class PhoneAuthHelper {
/// Verifies the OTP and logs the user in. /// Verifies the OTP and logs the user in.
static Future<void> verifyOtp(String phoneNumber) async { static Future<void> verifyOtp(String phoneNumber, String otpCode) async {
try { try {
final fixedPhone = formatSyrianPhone(phoneNumber); final fixedPhone = formatSyrianPhone(phoneNumber);
final response = await CRUD().post( final response = await CRUD().post(
link: _verifyOtpUrl, link: _verifyOtpUrl,
payload: { payload: {
'phone_number': fixedPhone, 'phone_number': fixedPhone,
'otp': otpCode,
}, },
); );

View File

@@ -16,8 +16,9 @@ import '../../views/Rate/rate_captain.dart';
import '../../views/home/map_page_passenger.dart'; import '../../views/home/map_page_passenger.dart';
import '../../views/home/profile/promos_passenger_page.dart'; import '../../views/home/profile/promos_passenger_page.dart';
import '../auth/google_sign.dart'; import '../auth/google_sign.dart';
import '../functions/audio_record1.dart'; import 'package:Intaleq/controller/voice_call_controller.dart';
import '../home/map_passenger_controller.dart'; import '../home/map/ride_lifecycle_controller.dart';
import '../home/map/ride_state.dart';
import 'local_notification.dart'; import 'local_notification.dart';
class FirebaseMessagesController extends GetxController { class FirebaseMessagesController extends GetxController {
@@ -105,8 +106,8 @@ class FirebaseMessagesController extends GetxController {
// اقرأ "النوع" من حمولة البيانات، وليس من العنوان // اقرأ "النوع" من حمولة البيانات، وليس من العنوان
String category = message.data['category'] ?? ''; String category = message.data['category'] ?? '';
final mapCtrl = Get.isRegistered<MapPassengerController>() final mapCtrl = Get.isRegistered<RideLifecycleController>()
? Get.find<MapPassengerController>() ? Get.find<RideLifecycleController>()
: null; : null;
// اقرأ العنوان (للعرض) // اقرأ العنوان (للعرض)
String title = message.data['title'] ?? message.notification?.title ?? ''; String title = message.data['title'] ?? message.notification?.title ?? '';
@@ -167,8 +168,8 @@ class FirebaseMessagesController extends GetxController {
GoogleSignInHelper.signOut(); GoogleSignInHelper.signOut();
} else if (category == 'Driver Is Going To Passenger') { } else if (category == 'Driver Is Going To Passenger') {
// <-- كان 'Driver Is Going To Passenger' // <-- كان 'Driver Is Going To Passenger'
Get.find<MapPassengerController>().isDriverInPassengerWay = true; Get.find<RideLifecycleController>().isDriverInPassengerWay = true;
Get.find<MapPassengerController>().update(); Get.find<RideLifecycleController>().update();
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'tone1'); notificationController.showNotification(title, body, 'tone1');
} }
@@ -214,7 +215,7 @@ class FirebaseMessagesController extends GetxController {
} }
if (driverList.isNotEmpty) { if (driverList.isNotEmpty) {
Get.find<MapPassengerController>() Get.find<RideLifecycleController>()
.processRideFinished(driverList, source: "FCM"); .processRideFinished(driverList, source: "FCM");
} }
} else if (category == 'Finish Monitor') { } else if (category == 'Finish Monitor') {
@@ -232,9 +233,9 @@ class FirebaseMessagesController extends GetxController {
Log.print("🔔 FCM: Ride Cancelled by Driver received."); Log.print("🔔 FCM: Ride Cancelled by Driver received.");
// لا داعي لكتابة منطق التنظيف هنا، الكنترولر يتكفل بكل شيء // لا داعي لكتابة منطق التنظيف هنا، الكنترولر يتكفل بكل شيء
if (Get.isRegistered<MapPassengerController>()) { if (Get.isRegistered<RideLifecycleController>()) {
// استدعاء الحارس (سيتجاهل الأمر إذا كان السوكيت قد سبقه) // استدعاء الحارس (سيتجاهل الأمر إذا كان السوكيت قد سبقه)
Get.find<MapPassengerController>() Get.find<RideLifecycleController>()
.processRideCancelledByDriver(message.data, source: "FCM"); .processRideCancelledByDriver(message.data, source: "FCM");
} }
@@ -247,6 +248,19 @@ class FirebaseMessagesController extends GetxController {
// ... (باقي الحالات مثل Call Income, Call End, إلخ) ... // ... (باقي الحالات مثل Call Income, Call End, إلخ) ...
// ... بنفس الطريقة ... // ... بنفس الطريقة ...
else if (category == 'incoming_call') {
final sessionId = message.data['session_id'];
final callerName = message.data['caller_name'];
final rideId = message.data['ride_id'];
if (sessionId != null && callerName != null && rideId != null) {
Get.find<VoiceCallController>().receiveCall(
sessionIdVal: sessionId.toString(),
remoteNameVal: callerName.toString(),
rideIdVal: rideId.toString(),
);
}
}
else if (category == 'Order Applied') { else if (category == 'Order Applied') {
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(
@@ -277,7 +291,7 @@ class FirebaseMessagesController extends GetxController {
// var myList = jsonDecode(driverListJson) as List<dynamic>; // var myList = jsonDecode(driverListJson) as List<dynamic>;
// Log.print('myList: ${myList}'); // Log.print('myList: ${myList}');
// final controller = Get.find<MapPassengerController>(); // final controller = Get.find<RideLifecycleController>();
// // استدعاء الدالة الموحدة الجديدة التي أنشأناها // // استدعاء الدالة الموحدة الجديدة التي أنشأناها
// await controller.processRideAcceptance( // await controller.processRideAcceptance(
@@ -311,8 +325,8 @@ class FirebaseMessagesController extends GetxController {
// } // }
// GoogleSignInHelper.signOut(); // GoogleSignInHelper.signOut();
// } else if (message.notification!.title! == 'Driver Is Going To Passenger') { // } else if (message.notification!.title! == 'Driver Is Going To Passenger') {
// Get.find<MapPassengerController>().isDriverInPassengerWay = true; // Get.find<RideLifecycleController>().isDriverInPassengerWay = true;
// Get.find<MapPassengerController>().update(); // Get.find<RideLifecycleController>().update();
// if (Platform.isAndroid) { // if (Platform.isAndroid) {
// notificationController.showNotification('Driver is Going To You'.tr, // notificationController.showNotification('Driver is Going To You'.tr,
// 'Please stay on the picked point.'.tr, 'tone1'); // 'Please stay on the picked point.'.tr, 'tone1');
@@ -341,13 +355,13 @@ class FirebaseMessagesController extends GetxController {
// // (تم حذف الإشعار المحلي من هنا، نُقل إلى الدالة الموحدة) // // (تم حذف الإشعار المحلي من هنا، نُقل إلى الدالة الموحدة)
// final controller = Get.find<MapPassengerController>(); // final controller = Get.find<RideLifecycleController>();
// // استدعاء حارس البوابة الجديد والآمن // // استدعاء حارس البوابة الجديد والآمن
// controller.processRideBegin(); // controller.processRideBegin();
// // (تم حذف كل الأوامر التالية من هنا) // // (تم حذف كل الأوامر التالية من هنا)
// // Get.find<MapPassengerController>().getBeginRideFromDriver(); // // Get.find<RideLifecycleController>().getBeginRideFromDriver();
// // box.write(BoxName.passengerWalletTotal, '0'); // // box.write(BoxName.passengerWalletTotal, '0');
// // update(); // // update();
// } else if (message.notification!.title! == 'Hi ,I will go now'.tr) { // } else if (message.notification!.title! == 'Hi ,I will go now'.tr) {
@@ -360,7 +374,7 @@ class FirebaseMessagesController extends GetxController {
// update(); // update();
// } // ... داخل معالج الإشعارات (FCM Handler) ... // } // ... داخل معالج الإشعارات (FCM Handler) ...
// if (message.notification!.title! == 'Hi ,I Arrive your site'.tr) { // if (message.notification!.title! == 'Hi ,I Arrive your site'.tr) {
// final controller = Get.find<MapPassengerController>(); // final controller = Get.find<RideLifecycleController>();
// // 1. التأكد أننا في الحالة الصحيحة (السائق كان في الطريق) // // 1. التأكد أننا في الحالة الصحيحة (السائق كان في الطريق)
// if (controller.currentRideState.value == RideState.driverApplied) { // if (controller.currentRideState.value == RideState.driverApplied) {
@@ -383,7 +397,7 @@ class FirebaseMessagesController extends GetxController {
// title: 'Ok'.tr, // title: 'Ok'.tr,
// onPressed: () async { // onPressed: () async {
// Get.back(); // Get.back();
// await Get.find<MapPassengerController>() // await Get.find<RideLifecycleController>()
// .reSearchAfterCanceledFromDriver(); // .reSearchAfterCanceledFromDriver();
// }, // },
// ), // ),
@@ -394,7 +408,7 @@ class FirebaseMessagesController extends GetxController {
// Get.offAll(() => const MapPagePassenger()); // Get.offAll(() => const MapPagePassenger());
// }, // },
// ) // )
// // Get.find<MapPassengerController>() // // Get.find<RideLifecycleController>()
// // .searchNewDriverAfterRejectingFromDriver(); // // .searchNewDriverAfterRejectingFromDriver();
// ); // );
// } else if (message.notification!.title! == 'Driver Finish Trip'.tr) { // } else if (message.notification!.title! == 'Driver Finish Trip'.tr) {
@@ -434,7 +448,7 @@ class FirebaseMessagesController extends GetxController {
// box.write(BoxName.passengerWalletTotal, 0); // box.write(BoxName.passengerWalletTotal, 0);
// } // }
// Get.find<MapPassengerController>().tripFinishedFromDriver(); // Get.find<RideLifecycleController>().tripFinishedFromDriver();
// NotificationController().showNotification( // NotificationController().showNotification(
// 'Dont forget your personal belongings.'.tr, // 'Dont forget your personal belongings.'.tr,
@@ -535,7 +549,7 @@ class FirebaseMessagesController extends GetxController {
// box.write(BoxName.parentTripSelected, false); // box.write(BoxName.parentTripSelected, false);
// box.remove(BoxName.tokenParent); // box.remove(BoxName.tokenParent);
// Get.find<MapPassengerController>().restCounter(); // Get.find<RideLifecycleController>().restCounter();
// Get.offAll(() => const MapPagePassenger()); // Get.offAll(() => const MapPagePassenger());
// } // }
// // else if (message.notification!.title! == 'Order Applied') { // // else if (message.notification!.title! == 'Order Applied') {
@@ -595,8 +609,8 @@ class FirebaseMessagesController extends GetxController {
// Get.find<FirebaseMessagesController>().sendNotificationToPassengerToken( // Get.find<FirebaseMessagesController>().sendNotificationToPassengerToken(
// 'Hi ,I will go now'.tr, // 'Hi ,I will go now'.tr,
// 'I will go now'.tr, // 'I will go now'.tr,
// Get.find<MapPassengerController>().driverToken, []); // Get.find<RideLifecycleController>().driverToken, []);
// Get.find<MapPassengerController>() // Get.find<RideLifecycleController>()
// .startTimerDriverWaitPassenger5Minute(); // .startTimerDriverWaitPassenger5Minute();
Get.back(); Get.back();
@@ -639,12 +653,12 @@ class DriverTipWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) { return GetBuilder<RideLifecycleController>(builder: (controller) {
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
// Text( // Text(
// '${'Your fee is '.tr}${Get.find<MapPassengerController>().totalPassenger.toStringAsFixed(2)}'), // '${'Your fee is '.tr}${Get.find<RideLifecycleController>().totalPassenger.toStringAsFixed(2)}'),
Text( Text(
'Do you want to pay Tips for this Driver'.tr, 'Do you want to pay Tips for this Driver'.tr,
textAlign: TextAlign.center, textAlign: TextAlign.center,

View File

@@ -30,7 +30,7 @@ class AudioRecorderController extends GetxController {
} }
// Start recording // Start recording
Future<void> startRecording() async { Future<void> startRecording({String? rideId}) async {
final bool isPermissionGranted = await recorder.hasPermission(); final bool isPermissionGranted = await recorder.hasPermission();
if (!isPermissionGranted) { if (!isPermissionGranted) {
// RecordingPermissionException('l'); // RecordingPermissionException('l');
@@ -38,10 +38,12 @@ class AudioRecorderController extends GetxController {
} }
final directory = await getApplicationDocumentsDirectory(); final directory = await getApplicationDocumentsDirectory();
final String dateStr =
'${DateTime.now().year}-${DateTime.now().month.toString().padLeft(2, '0')}-${DateTime.now().day.toString().padLeft(2, '0')}';
// Generate a unique file name using the current timestamp // Generate a unique file name using the current timestamp
String fileName = String fileName = (rideId != null && rideId.isNotEmpty && rideId != 'yet' && rideId != 'null')
// '${DateTime.now().year}-${DateTime.now().month}-${DateTime.now().day}_${Get.find<MapPassengerController>().rideId}.m4a'; ? '${dateStr}_$rideId.m4a'
'${DateTime.now().year}-${DateTime.now().month}-${DateTime.now().day}.m4a'; : '$dateStr.m4a';
filePath = '${directory.path}/$fileName'; filePath = '${directory.path}/$fileName';
// Define the configuration for the recording // Define the configuration for the recording

View File

@@ -12,6 +12,14 @@ import 'package:Intaleq/views/widgets/elevated_btn.dart';
import 'package:Intaleq/views/widgets/my_textField.dart'; import 'package:Intaleq/views/widgets/my_textField.dart';
import '../../constant/style.dart'; import '../../constant/style.dart';
import 'package:Intaleq/controller/home/map/map_socket_controller.dart';
import 'package:Intaleq/controller/home/map/map_engine_controller.dart';
import 'package:Intaleq/controller/home/map/location_search_controller.dart';
import 'package:Intaleq/controller/home/map/nearby_drivers_controller.dart';
import 'package:Intaleq/controller/home/map/ride_lifecycle_controller.dart';
import 'package:Intaleq/controller/home/map/ui_interactions_controller.dart';
import 'package:Intaleq/controller/home/menu_controller.dart';
import 'package:Intaleq/controller/home/points_for_rider_controller.dart';
class LogOutController extends GetxController { class LogOutController extends GetxController {
TextEditingController checkTxtController = TextEditingController(); TextEditingController checkTxtController = TextEditingController();
@@ -110,6 +118,15 @@ class LogOutController extends GetxController {
box.remove(BoxName.accountIdStripeConnect); box.remove(BoxName.accountIdStripeConnect);
box.remove(BoxName.passengerWalletTotal); box.remove(BoxName.passengerWalletTotal);
box.remove(BoxName.isVerified); box.remove(BoxName.isVerified);
Get.delete<MapSocketController>(force: true);
Get.delete<MapEngineController>(force: true);
Get.delete<LocationSearchController>(force: true);
Get.delete<NearbyDriversController>(force: true);
Get.delete<RideLifecycleController>(force: true);
Get.delete<UiInteractionsController>(force: true);
Get.delete<MyMenuController>(force: true);
Get.delete<CRUD>(force: true);
Get.delete<WayPointController>(force: true);
Get.offAll(OnBoardingPage()); Get.offAll(OnBoardingPage());
}, },
child: Text( child: Text(

View File

@@ -76,9 +76,11 @@ class ImageController extends GetxController {
length, length,
filename: basename(file.path), filename: basename(file.path),
); );
final String fingerPrint = box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
request.headers.addAll({ request.headers.addAll({
'Authorization': 'Authorization':
'Bearer ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}' 'Bearer ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}',
'X-Device-FP': fingerPrint,
}); });
// Set the file name to the driverID // Set the file name to the driverID
request.files.add( request.files.add(

View File

@@ -0,0 +1,89 @@
#!/bin/bash
ORIG_FILE="lib/controller/home/map_passenger_controller.dart"
ALL_FILES="lib/controller/home/map/location_search_controller.dart lib/controller/home/map/map_engine_controller.dart lib/controller/home/map/map_screen_binding.dart lib/controller/home/map/map_socket_controller.dart lib/controller/home/map/nearby_drivers_controller.dart lib/controller/home/map/ride_lifecycle_controller.dart lib/controller/home/map/ui_interactions_controller.dart"
echo "Extracting methods from original controller..."
# Methods typically start with spaces and have patterns like:
# returnType methodName( or methodName(
# Let's extract words that precede ( on lines that don't start with keywords (if, for, while, switch, catch, etc.)
# We will use awk to parse.
METHODS=$(cat "$ORIG_FILE" | awk '
# Skip single-line comments
/\/\// { next }
# Skip imports and class declarations
/import/ || /class/ { next }
# Find lines with "("
/\(/ {
# Replace anything inside parentheses and curly braces to simplify
gsub(/\(.*\)/, "()")
# Find word before "()"
for (i = 1; i <= NF; i++) {
if ($i ~ /[a-zA-Z0-9_]+\(\)/) {
name = $i
sub(/\(\)/, "", name)
# Remove any leading modifiers like async, Future, static, etc.
# Only keep valid identifiers that are not control keywords
if (name !~ /^(if|for|while|switch|catch|super|await|print|assert|dynamic|void|return|with|override|get|set|else|try|final|const|var|late|static|factory|new|abstract|covariant|external|operator|part|required|typedef|yield)$/ && name ~ /^[a-zA-Z_][a-zA-Z0-9_]*$/) {
print name
}
}
}
}' | sort -u)
echo "Extracting fields/variables from original controller..."
# Fields are usually declared inside the class at the beginning of lines or indented.
# e.g., RxBool isSearching = false.obs; or String? rideId;
VARS=$(cat "$ORIG_FILE" | awk '
/\/\// { next }
/import/ || /class/ { next }
# Lines ending with ";" or containing "=" followed by ";"
/;/ {
# Extract words that look like declarations.
# We look for typical type names or var/final followed by variable name
for (i = 1; i < NF; i++) {
if ($i ~ /^(var|final|const|late|RxBool|RxInt|RxDouble|RxString|RxList|RxMap|RxSet|Rx|String|int|double|bool|List|Map|Set|Timer|LatLng|Position|IntaleqMapController)$/) {
# The next field might be the variable name, or it might have a type like String?
name = $(i+1)
# Remove trailing ?, ;, =
sub(/\?/, "", name)
sub(/;/, "", name)
sub(/=/, "", name)
if (name ~ /^[a-zA-Z_][a-zA-Z0-9_]*$/) {
print name
}
}
}
}' | sort -u)
echo "Checking split files for methods..."
echo "--- MISSING METHODS ---"
MISSING_METHODS_COUNT=0
# Create a temporary file with all split contents to search efficiently
cat $ALL_FILES > lib/controller/home/temp_split_combined.txt
for method in $METHODS; do
# Search for this method name as a whole word in split controllers
FOUND=$(grep -w "$method" lib/controller/home/temp_split_combined.txt 2>/dev/null)
if [ -z "$FOUND" ]; then
echo " - $method"
MISSING_METHODS_COUNT=$((MISSING_METHODS_COUNT+1))
fi
done
echo "Total missing methods: $MISSING_METHODS_COUNT"
echo ""
echo "Checking split files for variables/fields..."
echo "--- MISSING VARIABLES ---"
MISSING_VARS_COUNT=0
for var in $VARS; do
FOUND=$(grep -w "$var" lib/controller/home/temp_split_combined.txt 2>/dev/null)
if [ -z "$FOUND" ]; then
echo " - $var"
MISSING_VARS_COUNT=$((MISSING_VARS_COUNT+1))
fi
done
echo "Total missing variables: $MISSING_VARS_COUNT"
# Clean up temp file
rm lib/controller/home/temp_split_combined.txt

View File

@@ -0,0 +1,104 @@
import sys
import re
def parse_stream(stream_text):
# Splits the stream by our custom file delimiters
files = {}
parts = re.split(r'=== FILE: (.*?) ===\n', stream_text)
# The first part is the original monolithic file
if parts:
files['original'] = parts[0]
for i in range(1, len(parts), 2):
filename = parts[i]
content = parts[i+1] if i+1 < len(parts) else ""
files[filename] = content
return files
def strip_comments(text):
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL)
text = re.sub(r'//.*', '', text)
return text
def extract_declarations(text):
clean = strip_comments(text)
# Matches method/function declarations inside a class in Dart
# e.g., void myMethod(..., Future<void> myMethod(..., myMethod(..., get myProp, set myProp
# We look for word followed by ( or get/set followed by word.
method_decl_pattern = re.compile(
r'(?:[a-zA-Z0-9_<>\?\[\]]+(?:\s+[a-zA-Z0-9_<>\?\[\]]+)*\s+)?([a-zA-Z0-9_]+)\s*\([^\)]*\)\s*(?:async)?\s*(?:=>|\{)'
)
methods = set()
for match in method_decl_pattern.finditer(clean):
method_name = match.group(1)
if method_name not in keywords and not method_name.isdigit():
methods.add(method_name)
# Also extract getters and setters
getset_pattern = re.compile(r'\b(?:get|set)\s+([a-zA-Z0-9_]+)\b')
for match in getset_pattern.finditer(clean):
name = match.group(1)
if name not in keywords:
methods.add(name)
# Extract variables/fields declarations
var_decl_pattern = re.compile(
r'\b(?:var|final|const|late|RxBool|RxInt|RxDouble|RxString|RxList|RxMap|RxSet|Rx|String|int|double|bool|List|Map|Set|Timer|LatLng|Position|IntaleqMapController)\??\s+([a-zA-Z0-9_]+)\b'
)
variables = set()
for match in var_decl_pattern.finditer(clean):
var_name = match.group(1)
if var_name not in keywords and not var_name.isdigit():
variables.add(var_name)
return methods, variables
keywords = {
'if', 'for', 'while', 'switch', 'catch', 'super', 'await', 'print',
'assert', 'dynamic', 'void', 'return', 'with', 'override', 'get', 'set',
'class', 'import', 'extends', 'implements', 'mixin', 'this', 'else', 'try',
'final', 'const', 'var', 'late', 'static', 'factory', 'new', 'abstract',
'covariant', 'external', 'operator', 'part', 'required', 'typedef', 'yield'
}
def main():
stream_text = sys.stdin.read()
files = parse_stream(stream_text)
orig_content = files.get('original', '')
split_contents = {k: v for k, v in files.items() if k != 'original'}
orig_methods, orig_vars = extract_declarations(orig_content)
# Combined declarations in split files
split_methods = set()
split_vars = set()
for filename, content in split_contents.items():
m, v = extract_declarations(content)
split_methods.update(m)
split_vars.update(v)
missing_methods = sorted(orig_methods - split_methods)
missing_vars = sorted(orig_vars - split_vars)
print("--- PRECISE MISSING METHODS ---")
print(f"Total original methods/getters/setters: {len(orig_methods)}")
print(f"Total defined in split controllers: {len(split_methods)}")
print(f"Total missing: {len(missing_methods)}")
for m in missing_methods:
print(f" - {m}")
print("\n--- PRECISE MISSING VARIABLES/FIELDS ---")
print(f"Total original variables: {len(orig_vars)}")
print(f"Total defined in split controllers: {len(split_vars)}")
print(f"Total missing: {len(missing_vars)}")
for v in missing_vars:
print(f" - {v}")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,103 @@
Extracting methods from original controller...
Extracting fields/variables from original controller...
Checking split files for methods...
--- MISSING METHODS ---
- _applyLowEndModeIfNeeded
- _buildOsrmWaypointCoords
- _calculateDistance
- _checkAndRecalculateIfDeviated
- _fillDriverDataLocally
- _haversineKm
- _initMinimalIcons
- _initializePolygons
- _isActiveRideState
- _kmToLatDelta
- _kmToLngDelta
- _onDriverArrivedWithSocket
- _onRideCancelledWithSocket
- _onRideStartedWithSocket
- _relevanceScore
- _restorePolyline
- _stageNiceToHave
- _stagePricingAndState
- _startMasterTimer
- _startMasterTimerWithInterval
- _startPollingFallback
- _stopDriverLocationPolling
- _updateDriverMarker
- cancelRide
- detectPerfMode
- getAIKey
- getMapPointsForAllMethods
- getPassengerLocationUniversity
- handleActiveRideOnStartup
- isDriversDataValid
- onChangedPassengerCount
- onChangedPassengersChoose
- showDrawingBottomSheet
- showNoDriversDialog
- startSearchingTimer
Total missing methods: 35
Checking split files for variables/fields...
--- MISSING VARIABLES ---
- _isStateProcessing
- _isUsingFallback
- _maxReconnectAttempts
- apiDistanceMeters
- c
- carInfo
- carsOrder
- coordDestination
- currentCarType
- currentDriverLocation
- currentLocationOfDrivers
- currentRideId
- currentTimeSearchingCaptainWindow
- dInfo
- dLat
- datadriverCarsLocationToPassengerAfterApplied
- distanceOfTrip
- driverCarPlate
- driverLocationToPassenger
- driverOrderStatus
- durationByPassenger
- endLocation
- fName
- finalReason
- headingList
- increaseFeeFormKey
- isDriversTokensSend
- isFirstWaypoint
- isInUniversity
- isSaaSRequest
- kmInDegree
- lName
- latDest
- latestPosition
- lngDest
- lowPerf
- messagesFormKey
- originCoords
- pLower
- passengerLocationStringUnvirsity
- previousLocationOfDrivers
- progressTimerRideBeginVip
- qLower
- rLat1
- rLat2
- ram
- rideData
- sdk
- selectedPassengerCount
- startLng
- startLocation
- stringElapsedTimeRideBegin
- tax
- totalPassengerBalashDiscount
- totalPassengerComfortDiscount
- totalPassengerElectricDiscount
- totalPassengerLadyDiscount
- totalPassengerRaihGaiDiscount
- totalPassengerSpeedDiscount
Total missing variables: 59

View File

@@ -0,0 +1,15 @@
class CarLocation {
final String id;
final double latitude;
final double longitude;
final double distance;
final double duration;
CarLocation({
required this.id,
required this.latitude,
required this.longitude,
this.distance = 10000,
this.duration = 10000,
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,809 @@
import 'dart:async';
import 'dart:math' show cos, max, min, pi, pow, sqrt;
import 'dart:typed_data';
import 'package:Intaleq/controller/home/map/ride_lifecycle_controller.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:image/image.dart' as img;
import '../../../constant/colors.dart';
// contains global 'box'
import '../../../print.dart';
import '../../../views/home/map_widget.dart/cancel_raide_page.dart';
import 'location_search_controller.dart';
import 'nearby_drivers_controller.dart';
import 'ride_lifecycle_controller.dart';
import '../points_for_rider_controller.dart';
import '../../../constant/univeries_polygon.dart';
class MapEngineController extends GetxController {
IntaleqMapController? mapController;
bool isStyleLoaded = false;
bool isIconsLoaded = false;
Set<Marker> markers = {};
Set<Polyline> polyLines = {};
List<LatLng> polylineCoordinates = [];
Set<Polygon> polygons = {};
Set<Circle> circles = {};
LatLngBounds? lastComputedBounds;
bool mapType = false;
bool mapTrafficON = false;
bool isMarkersShown = false;
String markerIcon = "marker_icon";
String tripIcon = "trip_icon";
String startIcon = "start_icon";
String endIcon = "end_icon";
String carIcon = "car_icon";
String motoIcon = "moto_icon";
String ladyIcon = "lady_icon";
double height = 150;
double heightMenu = 0;
double widthMenu = 0;
double heightPickerContainer = 90;
double heightPointsPageForRider = 0;
double mainBottomMenuMapHeight = Get.height * .2;
double wayPointSheetHeight = 0;
bool heightMenuBool = false;
bool isPickerShown = false;
bool isPointsPageForRider = false;
bool isBottomSheetShown = false;
bool reloadStartApp = false;
bool isCancelRidePageShown = false;
bool isCashConfirmPageShown = false;
bool isPaymentMethodPageShown = false;
bool isRideFinished = false;
bool rideConfirm = false;
bool isMainBottomMenuMap = true;
bool isWayPointSheet = false;
bool isWayPointStopsSheet = false;
bool isWayPointStopsSheetUtilGetMap = false;
double heightBottomSheetShown = 0;
double cashConfirmPageShown = 250;
double widthMapTypeAndTraffic = 50;
double paymentPageShown = Get.height * .6;
bool isAnotherOreder = false;
bool isWhatsAppOrder = false;
Map<String, Timer> _animationTimers = {};
final int updateIntervalMs = 100;
final double minMovementThreshold = 1.0;
void onMapCreated(IntaleqMapController controller) {
mapController = controller;
update();
}
void onStyleLoaded() async {
Log.print('🗺️ Intaleq Map Style Loaded. Initializing...');
isStyleLoaded = true;
await _loadMapIcons();
final locationSearch = Get.find<LocationSearchController>();
Get.find<RideLifecycleController>().reinit();
if (mapController != null) {
if (markers.isNotEmpty && lastComputedBounds != null) {
await _safeAnimateCameraBounds(lastComputedBounds);
} else {
mapController!.animateCamera(
CameraUpdate.newLatLng(locationSearch.passengerLocation),
);
}
}
update();
}
Future<void> _safeAnimateCameraBounds(LatLngBounds? bounds,
{double left = 60,
double top = 60,
double right = 60,
double bottom = 60}) async {
if (bounds == null || mapController == null) return;
try {
if (bounds.northeast.latitude == bounds.southwest.latitude &&
bounds.northeast.longitude == bounds.southwest.longitude) {
Log.print(
'⚠️ _safeAnimateCameraBounds: Bounds are a single point, zooming to point instead.');
await mapController
?.animateCamera(CameraUpdate.newLatLngZoom(bounds.northeast, 15));
return;
}
await Future.delayed(const Duration(milliseconds: 200));
await mapController?.animateCamera(
CameraUpdate.newLatLngBounds(
bounds,
left: left,
top: top,
right: right,
bottom: bottom,
),
);
} catch (e) {
Log.print('❌ _safeAnimateCameraBounds CRASH PREVENTED: $e');
try {
await mapController
?.animateCamera(CameraUpdate.newLatLngZoom(bounds.northeast, 14));
} catch (_) {}
}
}
Future<void> _loadMapIcons() async {
isIconsLoaded = false;
for (int i = 0; i < 15; i++) {
if (mapController != null && isStyleLoaded) break;
await Future.delayed(const Duration(milliseconds: 200));
}
if (mapController == null || !isStyleLoaded) {
Log.print(
'⚠️ _loadMapIcons: mapController or style not ready. Icons may not load.');
}
await _addMapImage(startIcon, 'assets/images/A.png');
await _addMapImage(endIcon, 'assets/images/b.png');
await _addMapImage(carIcon, 'assets/images/car.png');
await _addMapImage(motoIcon, 'assets/images/moto.png');
await _addMapImage(ladyIcon, 'assets/images/lady.png');
await _addMapImage('picker_icon', 'assets/images/picker.png');
await _addMapImage('orange_marker', 'assets/images/moto1.png');
await _addMapImage('violet_marker', 'assets/images/lady1.png');
isIconsLoaded = true;
markers = markers.map((m) => m.copyWith()).toSet();
update();
if (Get.isRegistered<NearbyDriversController>()) {
Get.find<NearbyDriversController>()
.getCarsLocationByPassengerAndReloadMarker();
}
}
Future<void> _addMapImage(String id, String path) async {
try {
final ByteData bytes = await rootBundle.load(path);
final size = _getImageSize(id);
if (size != null && (id == carIcon || id == motoIcon || id == ladyIcon)) {
final resized = await _resizeImage(bytes.buffer.asUint8List(), size);
await mapController?.addImage(id, resized);
Log.print(
'delimited: successfully added resized map image: $id (${size}x${size})');
} else {
await mapController?.addImage(id, bytes.buffer.asUint8List());
Log.print('delimited: successfully added map image: $id');
}
} catch (e) {
Log.print('❌ Error loading map icon $id: $e');
}
}
int? _getImageSize(String id) {
if (id == carIcon || id == motoIcon || id == ladyIcon) return 120;
return null;
}
Future<Uint8List> _resizeImage(Uint8List bytes, int size) async {
return await compute((Uint8List data) {
final image = img.decodeImage(data);
if (image == null) return data;
final resized = img.copyResize(image, width: size, height: size);
return Uint8List.fromList(img.encodePng(resized));
}, bytes);
}
void clearPolyline() {
polyLines.clear();
update();
}
LatLngBounds calculateBounds(double lat, double lng, double radiusInMeters) {
const double earthRadius = 6378137.0;
double latDelta = (radiusInMeters / earthRadius) * (180 / pi);
double lngDelta =
(radiusInMeters / (earthRadius * cos(pi * lat / 180))) * (180 / pi);
double minLat = lat - latDelta;
double maxLat = lat + latDelta;
double minLng = lng - lngDelta;
double maxLng = lng + lngDelta;
minLat = max(-90.0, minLat);
maxLat = min(90.0, maxLat);
minLng = (minLng + 180) % 360 - 180;
maxLng = (maxLng + 180) % 360 - 180;
if (minLng > maxLng) {
double temp = minLng;
minLng = maxLng;
maxLng = temp;
}
return LatLngBounds(
southwest: LatLng(minLat, minLng),
northeast: LatLng(maxLat, maxLng),
);
}
Future<void> playRouteAnimation(
List<LatLng> coords, LatLngBounds? bounds) async {
const List<Color> segmentColors = [
Color(0xFF109642), // Green
Color(0xFFF59E0B), // Amber
Color(0xFF7C3AED), // Purple
Color(0xFFEF4444), // Red
];
Set<Polyline> newPolylines = {};
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.activeMenuWaypointCount > 0) {
List<int> splitIndices = [];
for (int w = 0; w < locationSearch.activeMenuWaypointCount; w++) {
final wp = locationSearch.menuWaypoints[w];
if (wp == null) continue;
int bestIdx = 0;
double bestDist = double.infinity;
for (int j = 0; j < coords.length; j++) {
final dx = coords[j].latitude - wp.latitude;
final dy = coords[j].longitude - wp.longitude;
final d = dx * dx + dy * dy;
if (d < bestDist) {
bestDist = d;
bestIdx = j;
}
}
splitIndices.add(bestIdx);
}
splitIndices.sort();
List<int> boundaries = [0, ...splitIndices, coords.length - 1];
for (int s = 0; s < boundaries.length - 1; s++) {
int from = boundaries[s];
int to = boundaries[s + 1] + 1;
if (to > coords.length) to = coords.length;
if (from >= to - 1) continue;
final segCoords = coords.sublist(from, to);
if (segCoords.length < 2) continue;
final color = segmentColors[s % segmentColors.length];
newPolylines.add(Polyline(
polylineId: PolylineId('segment_$s'),
points: segCoords,
color: color,
width: 6,
));
}
} else {
newPolylines.add(Polyline(
polylineId: const PolylineId('route_primary'),
points: coords,
color: AppColor.primaryColor,
width: 6,
));
}
polyLines = newPolylines;
update();
Log.print(
'🗺️ Drawing ${markers.length} markers + ${polyLines.length} polylines on map');
if (bounds != null) {
await _safeAnimateCameraBounds(bounds);
}
}
void _fitCameraToPoints(LatLng p1, LatLng p2) async {
if (mapController == null) return;
if (p1.latitude == p2.latitude && p1.longitude == p2.longitude) {
try {
mapController?.animateCamera(CameraUpdate.newLatLngZoom(p1, 17));
} catch (e) {
Log.print("Error animating to single point: $e");
}
return;
}
double minLat = min(p1.latitude, p2.latitude);
double maxLat = max(p1.latitude, p2.latitude);
double minLng = min(p1.longitude, p2.longitude);
double maxLng = max(p1.longitude, p2.longitude);
if ((maxLat - minLat).abs() < 0.002 && (maxLng - minLng).abs() < 0.002) {
try {
mapController?.animateCamera(CameraUpdate.newLatLngZoom(p1, 16));
} catch (e) {
Log.print("Error animating to single point: $e");
}
return;
}
double padding = 50.0;
try {
await mapController?.animateCamera(
CameraUpdate.newLatLngBounds(
LatLngBounds(
southwest: LatLng(minLat, minLng),
northeast: LatLng(maxLat, maxLng),
),
left: padding,
top: padding,
right: padding,
bottom: padding,
),
);
} catch (e) {
Log.print("Error animating bounds: $e");
try {
LatLng center = LatLng((minLat + maxLat) / 2, (minLng + maxLng) / 2);
mapController?.animateCamera(CameraUpdate.newLatLngZoom(center, 14));
} catch (_) {}
}
}
void fitCameraToPoints(LatLng p1, LatLng p2) {
_fitCameraToPoints(p1, p2);
}
void clearMarkersExceptStartEndAndDriver() {
const String currentDriverMarkerId = 'assigned_driver_marker';
markers.removeWhere((marker) {
String id = marker.markerId.value;
if (id == 'start') return false;
if (id == 'end') return false;
if (id == currentDriverMarkerId) return false;
return true;
});
update();
}
void clearMarkersExceptStartEnd() {
markers.removeWhere((marker) {
String id = marker.markerId.value;
return id != 'start' && id != 'end';
});
update();
}
void _updateMarkerPosition(
LatLng newPosition, double newHeading, String icon) {
const String markerId = 'driverToPassengers';
final mId = MarkerId(markerId);
final existingMarker = markers.cast<Marker?>().firstWhere(
(m) => m?.markerId == mId,
orElse: () => null,
);
if (existingMarker != null) {
_smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon);
} else {
markers = {
...markers,
Marker(
markerId: mId,
position: newPosition,
rotation: newHeading,
icon: InlqBitmap.fromStyleImage(icon),
anchor: const Offset(0.5, 0.5),
),
};
update();
}
mapController?.animateCamera(CameraUpdate.newLatLng(newPosition));
}
void updateMarkerPosition(
LatLng newPosition, double newHeading, String icon) {
_updateMarkerPosition(newPosition, newHeading, icon);
}
void _smoothlyUpdateMarker(
Marker oldMarker, LatLng newPosition, double newHeading, String icon) {
double distance = Geolocator.distanceBetween(
oldMarker.position.latitude,
oldMarker.position.longitude,
newPosition.latitude,
newPosition.longitude);
if (distance < 2.0) return;
final MarkerId markerIdKey = oldMarker.markerId;
_animationTimers[markerIdKey.value]?.cancel();
int ticks = 0;
const int totalSteps = 20;
const int stepDuration = 50;
double latStep =
(newPosition.latitude - oldMarker.position.latitude) / totalSteps;
double lngStep =
(newPosition.longitude - oldMarker.position.longitude) / totalSteps;
double headingStep = (newHeading - oldMarker.rotation) / totalSteps;
LatLng currentPos = oldMarker.position;
double currentHeading = oldMarker.rotation;
_animationTimers[markerIdKey.value] =
Timer.periodic(const Duration(milliseconds: stepDuration), (timer) {
ticks++;
currentPos =
LatLng(currentPos.latitude + latStep, currentPos.longitude + lngStep);
currentHeading += headingStep;
final updatedMarker = oldMarker.copyWith(
position: currentPos,
rotation: currentHeading,
icon: InlqBitmap.fromStyleImage(icon),
);
markers = {
...markers.where((m) => m.markerId != markerIdKey),
updatedMarker,
};
if (mapController != null) {
mapController!.animateCamera(CameraUpdate.newLatLng(currentPos));
}
update();
if (ticks >= totalSteps) {
timer.cancel();
_animationTimers.remove(markerIdKey.value);
}
});
}
// تحديث موقع العلامة (Marker) واتجاهها بسلاسة على الخريطة.
// تحسب الدالة المسافة بين الموقع الحالي والجديد؛ وإذا كانت أكبر من مترين،
// تقوم بتقسيم الحركة والدوران إلى 20 خطوة متباعدة بـ 50 مللي ثانية (إجمالي ثانية واحدة).
// يتم تحديث موضع العلامة وتحريك الكاميرا تدريجياً لتبدو حركة السيارة انسيابية.
void smoothlyUpdateMarker(
Marker oldMarker, LatLng newPosition, double newHeading, String icon) {
_smoothlyUpdateMarker(oldMarker, newPosition, newHeading, icon);
}
void changeBottomSheetShown({bool? forceValue}) {
if (forceValue != null) {
isBottomSheetShown = forceValue;
} else {
isBottomSheetShown = !isBottomSheetShown;
}
heightBottomSheetShown = isBottomSheetShown == true ? 250 : 0;
update();
}
void changeCashConfirmPageShown() {
isCashConfirmPageShown = !isCashConfirmPageShown;
final rideLife = Get.find<RideLifecycleController>();
rideLife.isCashSelectedBeforeConfirmRide = true;
cashConfirmPageShown = isCashConfirmPageShown == true ? 250 : 0;
update();
rideLife.update();
}
void changePaymentMethodPageShown() {
isPaymentMethodPageShown = !isPaymentMethodPageShown;
paymentPageShown = isPaymentMethodPageShown == true ? Get.height * .6 : 0;
update();
}
void changeMapType() {
mapType = !mapType;
update();
}
void changeMapTraffic() {
mapTrafficON = !mapTrafficON;
update();
}
void changeisAnotherOreder(bool val) {
isAnotherOreder = val;
update();
}
void changeIsWhatsAppOrder(bool val) {
isWhatsAppOrder = val;
update();
}
void changeCancelRidePageShow() {
showCancelRideBottomSheet();
isCancelRidePageShown = !isCancelRidePageShown;
update();
if (Get.isRegistered<RideLifecycleController>()) {
Get.find<RideLifecycleController>().update();
}
}
void getDrawerMenu() {
heightMenuBool = !heightMenuBool;
widthMapTypeAndTraffic = heightMenuBool == true ? 0 : 50;
heightMenu = heightMenuBool == true ? 80 : 0;
widthMenu = heightMenuBool == true ? 110 : 0;
update();
}
void changeMainBottomMenuMap() {
if (isWayPointStopsSheetUtilGetMap == true) {
changeWayPointSheet();
} else {
isMainBottomMenuMap = !isMainBottomMenuMap;
mainBottomMenuMapHeight =
isMainBottomMenuMap == true ? Get.height * .22 : Get.height * .6;
isWayPointSheet = false;
if (heightMenuBool == true) {
getDrawerMenu();
}
Get.find<RideLifecycleController>().initilizeGetStorage();
update();
}
}
void downPoints() {
if (Get.find<WayPointController>().wayPoints.length < 2) {
isWayPointStopsSheetUtilGetMap = false;
isWayPointSheet = false;
wayPointSheetHeight = isWayPointStopsSheet ? Get.height * .45 : 0;
update();
}
update();
}
void changeWayPointSheet() {
isWayPointSheet = !isWayPointSheet;
wayPointSheetHeight = isWayPointSheet == false ? 0 : Get.height * .45;
update();
}
void changeWayPointStopsSheet() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.wayPointIndex > -1) {
isWayPointStopsSheet = true;
isWayPointStopsSheetUtilGetMap = true;
}
isWayPointStopsSheet = !isWayPointStopsSheet;
wayPointSheetHeight = isWayPointStopsSheet ? Get.height * .45 : 0;
update();
}
void changeHeightPlaces() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.placesDestination.isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void changeHeightStartPlaces() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.placesStart.isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void changeHeightPlacesAll(int index) {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.placeListResponseAll[index].isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void changeHeightPlaces1() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.wayPoint1.isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void changeHeightPlaces2() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.wayPoint2.isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void changeHeightPlaces3() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.wayPoint3.isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void changeHeightPlaces4() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.wayPoint4.isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void hidePlaces() {
height = 0;
update();
}
void changePickerShown() {
isPickerShown = !isPickerShown;
heightPickerContainer = isPickerShown == true ? 150 : 90;
update();
}
void _initializePolygons() {
List<List<LatLng>> universityPolygons =
UniversitiesPolygons.universityPolygons;
for (int i = 0; i < universityPolygons.length; i++) {
Polygon polygon = Polygon(
polygonId: PolygonId('univ_$i'),
points: universityPolygons[i],
fillColor: Colors.blueAccent.withOpacity(0.2),
strokeColor: Colors.blueAccent,
strokeWidth: 2,
);
polygons.add(polygon);
}
update();
}
void _applyLowEndModeIfNeeded() {
// Placeholder comment from original
}
Future<void> _initMinimalIcons() async {
// Icons are loaded dynamically
}
Future<void> _playRouteAnimation(
List<LatLng> coords, LatLngBounds? bounds) async {
const List<Color> segmentColors = [
Color(0xFF109642), // Green
Color(0xFFF59E0B), // Amber
Color(0xFF7C3AED), // Purple
Color(0xFFEF4444), // Red
];
Set<Polyline> newPolylines = {};
final locSearch = Get.find<LocationSearchController>();
if (locSearch.activeMenuWaypointCount > 0) {
List<int> splitIndices = [];
for (int w = 0; w < locSearch.activeMenuWaypointCount; w++) {
final wp = locSearch.menuWaypoints[w];
if (wp == null) continue;
int bestIdx = 0;
double bestDist = double.infinity;
for (int j = 0; j < coords.length; j++) {
final dx = coords[j].latitude - wp.latitude;
final dy = coords[j].longitude - wp.longitude;
final d = dx * dx + dy * dy;
if (d < bestDist) {
bestDist = d;
bestIdx = j;
}
}
splitIndices.add(bestIdx);
}
splitIndices.sort();
List<int> boundaries = [0, ...splitIndices, coords.length - 1];
for (int s = 0; s < boundaries.length - 1; s++) {
int from = boundaries[s];
int to = boundaries[s + 1] + 1;
if (to > coords.length) to = coords.length;
if (from >= to - 1) continue;
final segCoords = coords.sublist(from, to);
if (segCoords.length < 2) continue;
final color = segmentColors[s % segmentColors.length];
newPolylines.add(Polyline(
polylineId: PolylineId('segment_$s'),
points: segCoords,
color: color,
width: 6,
));
}
} else {
newPolylines.add(Polyline(
polylineId: const PolylineId('route_primary'),
points: coords,
color: AppColor.primaryColor,
width: 6,
));
}
polyLines = newPolylines;
update();
Log.print(
'🗺️ Drawing ${markers.length} markers + ${polyLines.length} polylines on map');
update();
if (bounds != null) {
await _safeAnimateCameraBounds(bounds);
}
}
void reset() {
isPickerShown = false;
isPointsPageForRider = false;
isBottomSheetShown = false;
isCancelRidePageShown = false;
isCashConfirmPageShown = false;
isPaymentMethodPageShown = false;
isRideFinished = false;
rideConfirm = false;
isMainBottomMenuMap = true;
isWayPointSheet = false;
isWayPointStopsSheet = false;
isWayPointStopsSheetUtilGetMap = false;
heightBottomSheetShown = 0;
mainBottomMenuMapHeight = Get.height * 0.22;
wayPointSheetHeight = 0;
markers.clear();
polyLines.clear();
polylineCoordinates.clear();
_animationTimers.forEach((key, timer) => timer.cancel());
_animationTimers.clear();
update();
}
@override
void onClose() {
_animationTimers.forEach((key, timer) => timer.cancel());
_animationTimers.clear();
mapController = null;
super.onClose();
}
}

View File

@@ -0,0 +1,25 @@
import 'package:get/get.dart';
import 'map_socket_controller.dart';
import 'map_engine_controller.dart';
import 'location_search_controller.dart';
import 'nearby_drivers_controller.dart';
import 'ride_lifecycle_controller.dart';
import 'ui_interactions_controller.dart';
class MapScreenBinding extends Bindings {
@override
void dependencies() {
// 1. WebSocket Controller: Permanent and immediate
Get.put(MapSocketController());
// 2. Core Controllers (initialized when the screen opens or on demand)
Get.lazyPut(() => MapEngineController());
Get.lazyPut(() => LocationSearchController());
Get.lazyPut(() => NearbyDriversController());
// 3. Lifecycle and UI Interaction Controllers
Get.lazyPut(() => RideLifecycleController());
Get.lazyPut(() => UiInteractionsController(), fenix: true);
}
}

View File

@@ -0,0 +1,326 @@
import 'dart:async';
import 'dart:convert';
import 'package:get/get.dart';
import 'package:socket_io_client/socket_io_client.dart' as io_client;
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
import '../../../main.dart'; // contains global 'box'
import '../../../print.dart';
import 'ride_lifecycle_controller.dart';
import 'nearby_drivers_controller.dart';
import 'map_engine_controller.dart';
class MapSocketController extends GetxController {
late io_client.Socket socket;
bool isSocketConnected = false;
bool _isSocketInitialized = false;
Timer? _heartbeatTimer;
DateTime? _lastSocketLocationTime;
int _socketLocationUpdatesCount = 0;
Timer? _watchdogTimer;
DateTime? get lastDriverLocationTime => _lastSocketLocationTime;
int get socketLocationUpdatesCount => _socketLocationUpdatesCount;
void initConnectionWithSocket() {
if (isSocketConnected) return;
String passengerId = box.read(BoxName.passengerID).toString();
Log.print("🔌 Initializing Socket for Passenger: $passengerId");
socket = io_client.io(
AppLink.serverSocket,
io_client.OptionBuilder()
.setTransports(['websocket'])
.disableAutoConnect()
.setQuery({'id': passengerId})
.setReconnectionAttempts(20)
.setReconnectionDelay(2000)
.setReconnectionDelayMax(10000)
.enableReconnection()
.setTimeout(20000)
.setExtraHeaders({'Connection': 'Upgrade'})
.build(),
);
_isSocketInitialized = true;
socket.connect();
socket.onConnect((_) {
Log.print("✅ Socket Connected Successfully");
isSocketConnected = true;
_startHeartbeat();
final rideLifecycle = Get.find<RideLifecycleController>();
if (rideLifecycle.rideId != 'yet' && rideLifecycle.driverId.isNotEmpty) {
socket.emit('subscribe_driver_location', {
'ride_id': rideLifecycle.rideId,
'driver_id': rideLifecycle.driverId,
});
Log.print("📡 Re-subscribed to driver location after connect");
}
update();
});
socket.onDisconnect((_) {
Log.print("⚠️ Socket Disconnected — Auto-Reconnect will handle it");
isSocketConnected = false;
final rideLifecycle = Get.find<RideLifecycleController>();
if (rideLifecycle.isActiveRideState()) {
Log.print("🔄 Enabling Fast Polling Fallback (4s) until reconnect...");
rideLifecycle.startMasterTimerWithInterval(4);
}
update();
});
socket.onReconnect((_) {
Log.print("🔁 Socket Reconnected Successfully!");
isSocketConnected = true;
_startHeartbeat();
final rideLifecycle = Get.find<RideLifecycleController>();
if (rideLifecycle.rideId != 'yet' && rideLifecycle.driverId.isNotEmpty) {
socket.emit('subscribe_driver_location', {
'ride_id': rideLifecycle.rideId,
'driver_id': rideLifecycle.driverId,
});
Log.print("📡 Re-subscribed to driver location after reconnect");
}
if (rideLifecycle.isActiveRideState()) {
Log.print("✅ Socket back online — stopping Fast Polling Fallback");
rideLifecycle.cancelMasterTimer();
}
update();
});
socket.onReconnectAttempt((attemptNumber) {
Log.print("🔄 Socket Reconnect Attempt #$attemptNumber...");
});
socket.onError((error) {
Log.print("❌ Socket Error: $error");
isSocketConnected = false;
});
socket.on('connect_error', (error) {
Log.print("❌ Socket Connect Error: $error");
isSocketConnected = false;
// في الإصدار 1.0.2 أحياناً auto-reconnect لا يعمل بعد connect_error
// نتأكد يدوياً من إعادة الاتصال
Future.delayed(const Duration(seconds: 3), () {
if (!isSocketConnected && _isSocketInitialized) {
Log.print("🔄 Manual reconnect after connect_error...");
try {
socket.connect();
} catch (e) {
Log.print("Manual reconnect error: $e");
}
}
});
});
socket.on('ride_status_change', (data) {
Log.print("📩 Socket Event: ride_status_change -> $data");
_handleRideStatusChangeWithSocket(data);
});
socket.on('driver_location_update', (data) {
handleDriverLocationUpdate(data);
});
}
void _startHeartbeat() {
_heartbeatTimer?.cancel();
_heartbeatTimer = Timer.periodic(const Duration(seconds: 15), (timer) {
if (isSocketConnected && socket.connected) {
socket.emit('heartbeat',
{'passenger_id': box.read(BoxName.passengerID).toString()});
}
});
}
bool isSocketHealthy() {
if (!isSocketConnected) return false;
if (_lastSocketLocationTime == null) return false;
final diff = DateTime.now().difference(_lastSocketLocationTime!).inSeconds;
return diff < 20;
}
void _handleRideStatusChangeWithSocket(dynamic data) {
if (data == null || data['status'] == null) return;
String newStatus = data['status'].toString().toLowerCase();
Log.print("🔔 Socket Status Update: $newStatus");
final rideLifecycle = Get.find<RideLifecycleController>();
Map<String, dynamic>? driverInfo;
if (data['driver_info'] != null && data['driver_info'] is Map) {
driverInfo = Map<String, dynamic>.from(data['driver_info']);
}
switch (newStatus) {
case 'accepted':
case 'apply':
case 'applied':
rideLifecycle.processRideAcceptance(
driverData: driverInfo, source: "Socket");
break;
case 'arrived':
rideLifecycle.processDriverArrival("Socket");
break;
case 'started':
case 'begin':
rideLifecycle.processRideBegin(source: "Socket");
break;
case 'finished':
case 'ended':
_onRideFinishedWithSocket(data);
break;
case 'cancelled':
rideLifecycle.processRideCancelledByDriver(data, source: "Socket");
break;
case 'no_drivers_found':
rideLifecycle.showNoDriverDialog();
break;
}
}
void _onRideFinishedWithSocket(dynamic data) {
Log.print("🏁 Ride Finished (Socket)");
final rideLifecycle = Get.find<RideLifecycleController>();
var rawList = data['DriverList'];
List<dynamic> listToSend = [];
if (rawList != null) {
if (rawList is List) {
listToSend = rawList;
} else if (rawList is String) {
try {
listToSend = jsonDecode(rawList);
} catch (e) {
Log.print("Error decoding DriverList: $e");
}
}
}
if (listToSend.isEmpty && data['price'] != null) {
listToSend = [
rideLifecycle.driverId,
rideLifecycle.rideId,
rideLifecycle.driverToken,
data['price'].toString()
];
}
rideLifecycle.processRideFinished(listToSend, source: "Socket");
}
void handleDriverLocationUpdate(dynamic data) {
if (!isSocketConnected || data == null) return;
_lastSocketLocationTime = DateTime.now();
_socketLocationUpdatesCount++;
final rideLifecycle = Get.find<RideLifecycleController>();
if (rideLifecycle.driverId.isEmpty &&
(data['driver_id'] ?? data['driverId']) != null) {
rideLifecycle.driverId =
(data['driver_id'] ?? data['driverId']).toString();
}
if (_socketLocationUpdatesCount >= 3 &&
rideLifecycle.locationPollingTimer != null) {
Log.print("✅ Socket delivering locations reliably. Stopping polling.");
rideLifecycle.stopDriverLocationPolling();
}
try {
double lat = double.tryParse(
(data['latitude'] ?? data['lat'])?.toString() ?? '0') ??
0;
double lng = double.tryParse(
(data['longitude'] ?? data['lng'])?.toString() ?? '0') ??
0;
double heading = double.tryParse(data['heading']?.toString() ?? '0') ?? 0;
if (lat == 0 || lng == 0) return;
LatLng newPos = LatLng(lat, lng);
final nearbyDrivers = Get.find<NearbyDriversController>();
if (nearbyDrivers.driverCarsLocationToPassengerAfterApplied.isEmpty) {
nearbyDrivers.driverCarsLocationToPassengerAfterApplied.add(newPos);
} else {
nearbyDrivers.driverCarsLocationToPassengerAfterApplied[0] = newPos;
}
double speed = double.tryParse(data['speed']?.toString() ?? '0') ?? 0;
rideLifecycle.checkAndRecalculateIfDeviated(
newPos,
heading: heading,
speed: speed,
);
final mapEngine = Get.find<MapEngineController>();
if (mapEngine.mapController != null) {
double zoom = 16.5;
if (speed > 0) {
zoom = 17.0 - ((speed - 10) / 70) * 2.5;
zoom = zoom.clamp(14.5, 17.0);
}
mapEngine.mapController!
.animateCamera(CameraUpdate.newLatLngZoom(newPos, zoom));
}
final dynamic distanceValue =
data['distance_m'] ?? data['distance_meters'] ?? data['distance'];
final double? distanceMeters =
double.tryParse(distanceValue?.toString() ?? '');
final int? etaSeconds = data['eta_seconds'] == null
? null
: int.tryParse(data['eta_seconds'].toString());
final bool hasServerMetrics = (etaSeconds != null && etaSeconds > 0) ||
(distanceMeters != null && distanceMeters > 0);
if (hasServerMetrics) {
rideLifecycle.updateDriverRouteMetrics(
etaSeconds: etaSeconds != null && etaSeconds > 0 ? etaSeconds : null,
distanceMeters: distanceMeters,
);
}
rideLifecycle.updateDriverMarker(newPos, heading);
rideLifecycle.updateRemainingRoute(newPos, updateEta: !hasServerMetrics);
rideLifecycle.update();
} catch (e) {
Log.print('Error in handleDriverLocationUpdate: $e');
}
}
void disposeRideSocket() {
_heartbeatTimer?.cancel();
_watchdogTimer?.cancel();
if (_isSocketInitialized) {
socket.disconnect();
socket.dispose();
isSocketConnected = false;
_isSocketInitialized = false;
Log.print("🔌 Socket Disposed");
}
}
@override
void onClose() {
disposeRideSocket();
super.onClose();
}
}

View File

@@ -0,0 +1,475 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math' show Random, atan2, cos, pi, pow, sin, sqrt;
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/links.dart';
import '../../../constant/api_key.dart';
import '../../../print.dart';
import '../../functions/crud.dart';
import 'map_engine_controller.dart';
import 'location_search_controller.dart';
import 'ride_lifecycle_controller.dart';
import '../../../models/model/locations.dart';
import 'car_location.dart';
import 'package:device_info_plus/device_info_plus.dart';
class NearbyDriversController extends GetxController {
List carsLocationByPassenger = [];
List<LatLng> driverCarsLocationToPassengerAfterApplied = [];
List<CarLocationModel> carLocationsModels = [];
String? currentDriverMarkerId;
bool lowPerf = false;
dynamic dataCarsLocationByPassenger;
bool noCarString = false;
final double minMovementThreshold = 2.0;
final Map<String, Timer> _animationTimers = {};
final List<Map<String, dynamic>> fakeCarData = [];
Future<bool> getCarsLocationByPassengerAndReloadMarker() async {
carsLocationByPassenger = [];
final locSearch = Get.find<LocationSearchController>();
if (locSearch.passengerLocation.latitude == 0 && locSearch.passengerLocation.longitude == 0) {
return false;
}
var res = await CRUD().get(
link: AppLink.getCarsLocationByPassenger,
payload: {
'lat': locSearch.passengerLocation.latitude.toString(),
'lng': locSearch.passengerLocation.longitude.toString(),
'radius': '5',
'limit': '50',
},
);
if (res == 'failure') {
noCarString = true;
dataCarsLocationByPassenger = 'failure';
update();
return false;
}
noCarString = false;
var responseData = jsonDecode(res);
dataCarsLocationByPassenger = responseData;
List driversList = [];
if (responseData['status'] == true && responseData['data'] != null) {
driversList = responseData['data'];
} else if (responseData['message'] != null) {
driversList = responseData['message'];
}
final mapEngine = Get.find<MapEngineController>();
if (driversList.isEmpty) {
carsLocationByPassenger.clear();
mapEngine.update();
return false;
}
carsLocationByPassenger.clear();
for (var i = 0; i < driversList.length; i++) {
var carData = driversList[i];
double lat = double.tryParse(carData['latitude'].toString()) ?? 0.0;
double lng = double.tryParse(carData['longitude'].toString()) ?? 0.0;
double heading = double.tryParse(carData['heading'].toString()) ?? 0.0;
if (lat == 0.0 || lng == 0.0) continue;
String driverId = (carData['driver_id'] ?? carData['id'] ?? '').toString();
if (driverId.isEmpty || driverId == 'null') continue;
_updateOrCreateMarker(
driverId,
LatLng(lat, lng),
heading,
_getIconForCar(carData),
);
}
mapEngine.update();
return true;
}
void _addFakeCarMarkers(LatLng center, int count) {
if (fakeCarData.isEmpty) {
Random random = Random();
double radiusInKm = 2.5;
for (int i = 0; i < count; i++) {
double angle = random.nextDouble() * 2 * pi;
double distance = sqrt(random.nextDouble()) * radiusInKm;
double latOffset = (distance / 111.32);
double lonOffset =
(distance / (111.32 * cos(center.latitude * pi / 180.0)));
double lat = center.latitude + (latOffset * cos(angle));
double lon = center.longitude + (lonOffset * sin(angle));
double heading = random.nextDouble() * 360;
fakeCarData.add({
'id': 'fake_$i',
'latitude': lat,
'longitude': lon,
'heading': heading,
'gender': 'Male',
});
}
}
for (var carData in fakeCarData) {
_updateOrCreateMarker(
carData['id'].toString(),
LatLng(carData['latitude'], carData['longitude']),
carData['heading'],
_getIconForCar(carData),
);
}
}
void addFakeCarMarkers(LatLng center, int count) {
_addFakeCarMarkers(center, count);
}
Future<CarLocation?> getNearestDriverByPassengerLocation() async {
final rideLife = Get.find<RideLifecycleController>();
final locSearch = Get.find<LocationSearchController>();
if (!rideLife.rideConfirm) {
if (dataCarsLocationByPassenger != 'failure' &&
dataCarsLocationByPassenger != null &&
dataCarsLocationByPassenger.containsKey('message') &&
dataCarsLocationByPassenger['message'] != null &&
dataCarsLocationByPassenger['message'].length > 0) {
double nearestDistance = double.infinity;
CarLocation? nearestCar;
for (var i = 0;
i < dataCarsLocationByPassenger['message'].length;
i++) {
var carLocation = dataCarsLocationByPassenger['message'][i];
try {
final distance = Geolocator.distanceBetween(
locSearch.passengerLocation.latitude,
locSearch.passengerLocation.longitude,
double.parse(carLocation['latitude']),
double.parse(carLocation['longitude']),
);
int durationToPassenger = (distance / 1000 / 25 * 3600).round();
update();
if (distance < nearestDistance) {
nearestDistance = distance;
nearestCar = CarLocation(
distance: distance,
duration: durationToPassenger.toDouble(),
id: carLocation['driver_id'],
latitude: double.parse(carLocation['latitude']),
longitude: double.parse(carLocation['longitude']),
);
update();
}
} catch (e) {
Log.print('Error calculating distance/duration: $e');
}
}
return nearestCar;
}
}
return null;
}
Future<CarLocation?> getNearestDriverByPassengerLocationAPIGOOGLE() async {
final rideLife = Get.find<RideLifecycleController>();
final mapEngine = Get.find<MapEngineController>();
final locSearch = Get.find<LocationSearchController>();
if (mapEngine.polyLines.isEmpty || rideLife.totalCostPassenger == 0) {
return null;
}
if (!rideLife.rideConfirm) {
double nearestDistance = double.infinity;
if (dataCarsLocationByPassenger != 'failure' &&
dataCarsLocationByPassenger != null &&
dataCarsLocationByPassenger.containsKey('message') &&
dataCarsLocationByPassenger['message'] != null) {
if (dataCarsLocationByPassenger['message'].length > 0) {
CarLocation? nearestCar;
for (var i = 0;
i < dataCarsLocationByPassenger['message'].length;
i++) {
var carLocation = dataCarsLocationByPassenger['message'][i];
update();
String apiUrl =
'${AppLink.googleMapsLink}distancematrix/json?destinations=${carLocation['latitude']},${carLocation['longitude']}&origins=${locSearch.passengerLocation.latitude},${locSearch.passengerLocation.longitude}&units=metric&key=${AK.mapAPIKEY}';
var response = await CRUD().getGoogleApi(link: apiUrl, payload: {});
if (response != null && response['status'] == "OK") {
var data = response;
int distance1 =
data['rows'][0]['elements'][0]['distance']['value'];
rideLife.distanceByPassenger =
data['rows'][0]['elements'][0]['distance']['text'];
rideLife.durationToPassenger =
data['rows'][0]['elements'][0]['duration']['value'];
Duration durationFromDriverToPassenger =
Duration(seconds: rideLife.durationToPassenger.toInt());
rideLife.stringRemainingTimeToPassenger =
data['rows'][0]['elements'][0]['duration']['text'];
update();
if (distance1 < nearestDistance) {
nearestDistance = distance1.toDouble();
nearestCar = CarLocation(
distance: distance1.toDouble(),
duration: rideLife.durationToPassenger.toDouble(),
id: carLocation['driver_id'],
latitude: double.parse(carLocation['latitude']),
longitude: double.parse(carLocation['longitude']),
);
update();
}
} else {
Log.print('${response?['status']}: error Google distance matrix');
}
}
return nearestCar;
}
}
}
return null;
}
Future getCarForFirstConfirm(String carType) async {
bool foundCars = false;
int attempt = 0;
Timer.periodic(const Duration(seconds: 4), (Timer t) async {
foundCars = await getCarsLocationByPassengerAndReloadMarker();
Log.print('foundCars: $foundCars');
if (foundCars) {
t.cancel();
} else if (attempt >= 4) {
t.cancel();
if (!foundCars) {
noCarString = true;
dataCarsLocationByPassenger = 'failure';
}
update();
}
attempt++;
});
}
void startCarLocationSearch(String carType) {
int searchInterval = 5;
Log.print('searchInterval: $searchInterval');
int boundIncreaseStep = 2500;
Log.print('boundIncreaseStep: $boundIncreaseStep');
int maxAttempts = 3;
int maxBoundIncreaseStep = 6000;
int attempt = 0;
Log.print('initial attempt: $attempt');
Timer.periodic(Duration(seconds: searchInterval), (Timer timer) async {
Log.print('Current attempt: $attempt');
bool foundCars = false;
final mapEngine = Get.find<MapEngineController>();
if (attempt >= maxAttempts) {
timer.cancel();
if (foundCars == false) {
noCarString = true;
update();
}
} else if (mapEngine.reloadStartApp == true) {
Log.print('reloadStartApp: ${mapEngine.reloadStartApp}');
foundCars = await getCarsLocationByPassengerAndReloadMarker();
Log.print('foundCars: $foundCars');
if (foundCars) {
timer.cancel();
} else {
attempt++;
Log.print('Incrementing attempt to: $attempt');
if (boundIncreaseStep < maxBoundIncreaseStep) {
boundIncreaseStep += 1500;
if (boundIncreaseStep > maxBoundIncreaseStep) {
boundIncreaseStep = maxBoundIncreaseStep;
}
Log.print('New boundIncreaseStep: $boundIncreaseStep');
}
}
}
});
}
String _getIconForCar(Map<String, dynamic> carData) {
final mapEngine = Get.find<MapEngineController>();
if (carData['model'].toString().contains('دراجة')) {
return mapEngine.motoIcon;
} else if (carData['gender'] == 'Female') {
return mapEngine.ladyIcon;
} else {
return mapEngine.carIcon;
}
}
void _updateOrCreateMarker(
String markerId, LatLng newPosition, double newHeading, String icon) {
final mapEngine = Get.find<MapEngineController>();
if (!mapEngine.isIconsLoaded) {
Log.print("⚠️ Skipping drawing marker $markerId because map icons are not fully loaded yet.");
return;
}
final mId = MarkerId(markerId);
final existingMarker = mapEngine.markers.cast<Marker?>().firstWhere(
(m) => m?.markerId == mId,
orElse: () => null,
);
if (existingMarker == null) {
mapEngine.markers = {
...mapEngine.markers,
Marker(
markerId: mId,
position: newPosition,
rotation: newHeading,
icon: InlqBitmap.fromStyleImage(icon),
anchor: const Offset(0.5, 0.5),
),
};
mapEngine.update();
} else {
double distance = Geolocator.distanceBetween(
existingMarker.position.latitude,
existingMarker.position.longitude,
newPosition.latitude,
newPosition.longitude);
if (distance >= minMovementThreshold) {
_smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon);
}
}
}
void _smoothlyUpdateMarker(
Marker oldMarker, LatLng newPosition, double newHeading, String icon) {
final mapEngine = Get.find<MapEngineController>();
final MarkerId markerIdKey = oldMarker.markerId;
_animationTimers[markerIdKey.value]?.cancel();
int ticks = 0;
const int totalSteps = 20;
const int stepDuration = 50;
double latStep =
(newPosition.latitude - oldMarker.position.latitude) / totalSteps;
double lngStep =
(newPosition.longitude - oldMarker.position.longitude) / totalSteps;
double headingStep = (newHeading - oldMarker.rotation) / totalSteps;
LatLng currentPos = oldMarker.position;
double currentHeading = oldMarker.rotation;
_animationTimers[markerIdKey.value] =
Timer.periodic(const Duration(milliseconds: stepDuration), (timer) {
ticks++;
currentPos =
LatLng(currentPos.latitude + latStep, currentPos.longitude + lngStep);
currentHeading += headingStep;
final updatedMarker = oldMarker.copyWith(
position: currentPos,
rotation: currentHeading,
icon: InlqBitmap.fromStyleImage(icon),
);
mapEngine.markers = {
...mapEngine.markers.where((m) => m.markerId != markerIdKey),
updatedMarker,
};
if (mapEngine.mapController != null) {
mapEngine.mapController!.animateCamera(CameraUpdate.newLatLng(currentPos));
}
mapEngine.update();
if (ticks >= totalSteps) {
timer.cancel();
_animationTimers.remove(markerIdKey.value);
}
});
}
double calculateBearing(double lat1, double lon1, double lat2, double lon2) {
double deltaLon = lon2 - lon1;
double y = sin(deltaLon) * cos(lat2);
double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(deltaLon);
double bearing = atan2(y, x);
return (bearing * 180 / pi + 360) % 360;
}
void analyzeBehavior(Position currentPosition, List<LatLng> routePoints) {
double actualBearing = currentPosition.heading;
double expectedBearing = calculateBearing(
routePoints[0].latitude,
routePoints[0].longitude,
routePoints[1].latitude,
routePoints[1].longitude,
);
double bearingDifference = (expectedBearing - actualBearing).abs();
if (bearingDifference > 30) {
Log.print("⚠️ السائق انحرف عن المسار!");
}
}
void detectStops(Position currentPosition) {
if (currentPosition.speed < 0.5) {
Log.print("🚦 السائق توقف في موقع غير متوقع!");
}
}
Future<void> detectPerfMode() async {
try {
if (GetPlatform.isAndroid) {
final info = await DeviceInfoPlugin().androidInfo;
final sdk = info.version.sdkInt;
final ram = info.availableRamSize;
lowPerf = (sdk < 28) || (ram > 0 && ram < 3 * 1024 * 1024 * 1024);
} else {
lowPerf = false;
}
} catch (_) {
lowPerf = false;
}
update();
}
@override
void onClose() {
_animationTimers.forEach((key, timer) => timer.cancel());
_animationTimers.clear();
super.onClose();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
enum RideState {
noRide, // لا يوجد رحلة جارية، عرض واجهة البحث
cancelled, // تم إلغاء الرحلة
preCheckReview, // يوجد رحلة منتهية، تحقق من التقييم
searching, // جاري البحث عن كابتن
driverApplied, // تم قبول الطلب
driverArrived, // وصل السائق
inProgress, // الرحلة بدأت بالفعل
finished, // انتهت الرحلة (سيتم تحويلها إلى preCheckReview)
}

View File

@@ -0,0 +1,436 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../constant/style.dart';
import '../../../constant/info.dart';
import '../../../main.dart'; // contains global 'box'
import '../../../print.dart';
import '../../../services/emergency_signal_service.dart';
import '../../../views/widgets/elevated_btn.dart';
import '../../../views/widgets/mydialoug.dart';
import '../../../views/widgets/my_textField.dart';
import '../../../views/home/map_page_passenger.dart';
import '../../../views/widgets/error_snakbar.dart';
import '../../../models/model/painter_copoun.dart';
import '../../functions/launch.dart';
import '../../firebase/local_notification.dart';
import '../../firebase/notification_service.dart';
import '../../functions/crud.dart';
import '../../functions/tts.dart';
import 'ride_lifecycle_controller.dart';
import 'location_search_controller.dart';
import 'map_engine_controller.dart';
class UiInteractionsController extends GetxController {
TextEditingController sosPhonePassengerProfile = TextEditingController();
TextEditingController whatsAppLocationText = TextEditingController();
final sosFormKey = GlobalKey<FormState>();
@override
void onInit() {
super.onInit();
EmergencySignalService.instance.startListening(() {
final rideLifecycle = Get.find<RideLifecycleController>();
if (rideLifecycle.statusRide == 'Begin' ||
rideLifecycle.statusRide == 'start') {
Log.print("🚨 Emergency shake verified! Prompting SOS...");
sosPassenger();
}
});
}
Future<void> _ensureSosNumber(Function onSuccess) async {
String? storedPhone = box.read(BoxName.sosPhonePassenger);
if (storedPhone != null && storedPhone.isNotEmpty) {
onSuccess();
return;
}
sosPhonePassengerProfile.clear();
Get.defaultDialog(
title: 'Add SOS Phone'.tr,
titleStyle: AppStyle.title,
content: Form(
key: sosFormKey,
child: Column(
children: [
MyTextForm(
controller: sosPhonePassengerProfile,
label: 'insert sos phone'.tr,
hint: 'e.g. 0912345678 (Default +963)'.tr,
type: TextInputType.phone,
),
const SizedBox(height: 10),
Text(
"Note: If no country code is entered, it will be saved as Syrian (+963).".tr,
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
],
),
),
confirm: MyElevatedButton(
title: 'Save'.tr,
onPressed: () async {
if (sosFormKey.currentState!.validate()) {
Get.back();
var numberPhone =
formatSyrianPhoneNumber(sosPhonePassengerProfile.text);
await CRUD().post(
link: AppLink.updateprofile,
payload: {
'id': box.read(BoxName.passengerID),
'sosPhone': numberPhone,
},
);
box.write(BoxName.sosPhonePassenger, numberPhone);
onSuccess();
}
},
),
cancel: MyElevatedButton(
title: 'Cancel'.tr,
onPressed: () => Get.back(),
kolor: AppColor.redColor,
)
);
}
void sosPassenger() {
_ensureSosNumber(() {
Get.defaultDialog(
barrierDismissible: false,
title: "Emergency SOS".tr,
titleStyle: AppStyle.title.copyWith(color: AppColor.redColor),
content: Column(
children: [
Icon(Icons.warning_amber_rounded, size: 50, color: AppColor.redColor),
const SizedBox(height: 10),
Text(
"Do you want to send an emergency message to your SOS contact?".tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),
],
),
confirm: MyElevatedButton(
title: "Send SOS".tr,
kolor: AppColor.redColor,
onPressed: () {
Get.back();
_shareTripDetailsSOS();
},
),
cancel: MyElevatedButton(
title: "I'm Safe".tr,
kolor: AppColor.greenColor,
onPressed: () {
Get.back();
},
),
);
});
}
void _shareTripDetailsSOS() {
final rideLifecycle = Get.find<RideLifecycleController>();
final locSearch = Get.find<LocationSearchController>();
String message = "**Emergency SOS from Passenger:**\n";
String origin = locSearch.startNameAddress;
String destination = locSearch.endNameAddress;
message += "* ${'Origin'.tr}: $origin\n";
message += "* ${'Destination'.tr}: $destination\n";
message += "* ${'Driver Name'.tr}: ${rideLifecycle.driverName}\n";
message +=
"* ${'Car'.tr}: ${rideLifecycle.make} - ${rideLifecycle.model} - ${rideLifecycle.licensePlate}\n";
message += "* ${'Phone'.tr}: ${rideLifecycle.driverPhone}\n\n";
message +=
"${'Location'.tr}: https://www.google.com/maps/search/?api=1&query=${locSearch.passengerLocation.latitude},${locSearch.passengerLocation.longitude}\n";
message += "Please help! Contact me as soon as possible.".tr;
launchCommunication(
'whatsapp', box.read(BoxName.sosPhonePassenger), message);
}
String formatSyrianPhone(String phone) {
phone = phone.replaceAll(' ', '').replaceAll('+', '');
if (phone.startsWith('00963')) {
phone = phone.replaceFirst('00963', '963');
}
if (phone.startsWith('0963')) {
phone = phone.replaceFirst('0963', '963');
}
if (phone.startsWith('963')) {
return phone;
}
if (phone.startsWith('09')) {
return '963' + phone.substring(1);
}
if (phone.startsWith('9') && phone.length == 9) {
return '963' + phone;
}
return phone;
}
String formatSyrianPhoneNumber(String phoneNumber) {
String trimmedPhone = phoneNumber.trim();
if (trimmedPhone.startsWith('09')) {
return '963${trimmedPhone.substring(1)}';
}
if (trimmedPhone.startsWith('963')) {
return trimmedPhone;
}
return '963$trimmedPhone';
}
void sendSMS(String to) async {
final rideLifecycle = Get.find<RideLifecycleController>();
String formattedDriverPhone =
rideLifecycle.driverPhone.replaceAll(' ', '').replaceAll('+', '');
String message =
'Hi! This is ${(box.read(BoxName.name).toString().split(' ')[0]).toString()}.\n I am using ${box.read(AppInformation.appName)} to ride with ${rideLifecycle.passengerName} as the driver. ${rideLifecycle.passengerName} \nis driving a ${rideLifecycle.model}\n with license plate ${rideLifecycle.licensePlate}.\n I am currently located at ${Get.find<LocationSearchController>().passengerLocation}.\n If you need to reach me, please contact the driver directly at\n\n $formattedDriverPhone.';
launchCommunication('sms', to, message);
}
void sendWhatsapp(String to) async {
final rideLifecycle = Get.find<RideLifecycleController>();
final locSearch = Get.find<LocationSearchController>();
String formattedPhone = formatSyrianPhone(to);
String message =
'${'${'Hi! This is'.tr} ${(box.read(BoxName.name).toString().split(' ')[0]).toString()}.\n${' I am using'.tr}'} ${AppInformation.appName}${' to ride with'.tr} ${rideLifecycle.passengerName}${' as the driver.'.tr} ${rideLifecycle.passengerName} \n${'is driving a '.tr}${rideLifecycle.model}\n${' with license plate '.tr}${rideLifecycle.licensePlate}.\n${' I am currently located at '.tr} https://www.google.com/maps/place/${locSearch.passengerLocation.latitude},${locSearch.passengerLocation.longitude}.\n${' If you need to reach me, please contact the driver directly at'.tr}\n\n ${rideLifecycle.driverPhone}.';
launchCommunication('whatsapp', formattedPhone, message);
}
Future<dynamic> driverArrivePassengerDialoge() {
final rideLifecycle = Get.find<RideLifecycleController>();
return Get.defaultDialog(
barrierDismissible: false,
title: 'Hi ,I Arrive your location'.tr,
titleStyle: AppStyle.title,
middleText: 'Please go to Car Driver'.tr,
middleTextStyle: AppStyle.title,
confirm: MyElevatedButton(
title: 'Ok I will go now.'.tr,
onPressed: () {
NotificationService.sendNotification(
target: rideLifecycle.driverToken.toString(),
title: 'Hi ,I will go now'.tr,
body: 'I will go now'.tr,
isTopic: false,
tone: 'ding',
driverList: [],
category: 'Hi ,I will go now',
);
Get.back();
rideLifecycle.remainingTime = 0;
rideLifecycle.update();
},
),
);
}
void getDialog(String title, String? midTitle, VoidCallback onPressed) {
final textToSpeechController = Get.find<TextToSpeechController>();
Get.defaultDialog(
title: title,
titleStyle: AppStyle.title,
middleTextStyle: AppStyle.title,
content: Column(
children: [
IconButton(
onPressed: () async {
await textToSpeechController.speakText(title ?? midTitle!);
},
icon: const Icon(Icons.headphones),
),
Text(
midTitle!,
style: AppStyle.title,
)
],
),
confirm: MyElevatedButton(
title: 'Ok'.tr,
onPressed: onPressed,
kolor: AppColor.greenColor,
),
cancel: MyElevatedButton(
title: 'Cancel',
kolor: AppColor.redColor,
onPressed: () {
Get.back();
},
),
);
}
Future shareTripWithFamily() async {
_ensureSosNumber(() {
final rideLifecycle = Get.find<RideLifecycleController>();
String storedPhone = box.read(BoxName.sosPhonePassenger)!;
if (rideLifecycle.rideId == 'yet' || rideLifecycle.driverId.isEmpty) {
Get.snackbar("Alert".tr, "Wait for the trip to start first".tr);
return;
}
var numberPhone = formatSyrianPhoneNumber(storedPhone);
String trackingLink = rideLifecycle.generateTrackingLink(
rideLifecycle.rideId, rideLifecycle.driverId);
String message = """
مرحباً، تابع رحلتي مباشرة على تطبيق انطلق 🚗
يمكنك تتبع مسار الرحلة من هنا:
$trackingLink
السائق: ${rideLifecycle.passengerName}
السيارة: ${rideLifecycle.model} - ${rideLifecycle.licensePlate}
شكراً لاستخدامك انطلق!
"""
.tr;
String messageEn = """Hello, follow my trip live on Intaleq 🚗
Track my ride here:
$trackingLink
Driver: ${rideLifecycle.passengerName}
Car: ${rideLifecycle.model} - ${rideLifecycle.licensePlate}
Thank you for using Intaleq!
""";
String userLanguage = box.read(BoxName.lang) ?? 'ar';
message = (userLanguage == 'ar') ? message : messageEn;
Log.print("Sending WhatsApp to: $numberPhone");
launchCommunication('whatsapp', numberPhone, message);
box.write(BoxName.parentTripSelected, true);
update();
});
}
Future getTokenForParent() async {
_ensureSosNumber(() async {
String storedPhone = box.read(BoxName.sosPhonePassenger)!;
var numberPhone = formatSyrianPhoneNumber(storedPhone);
Log.print("Searching for Parent Token with Phone: $numberPhone");
var res = await CRUD()
.post(link: AppLink.getTokenParent, payload: {'phone': numberPhone});
if (res is Map<String, dynamic>) {
handleResponse(res);
} else {
try {
var decoded = jsonDecode(res);
handleResponse(decoded);
} catch (e) {
Log.print("Error parsing parent response: $res");
}
}
});
}
void handleResponse(Map<String, dynamic> res) {
final rideLifecycle = Get.find<RideLifecycleController>();
if (res['status'] == 'failure') {
if (Get.isDialogOpen ?? false) Get.back();
Get.defaultDialog(
title: "No user found".tr,
titleStyle: AppStyle.title,
content: Column(
children: [
Text(
"No passenger found for the given phone number".tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
Text(
"Send Intaleq app to him".tr,
style: AppStyle.title
.copyWith(color: AppColor.greenColor, fontSize: 14),
textAlign: TextAlign.center,
)
],
),
confirm: MyElevatedButton(
title: 'Send Invite'.tr,
onPressed: () {
Get.back();
var rawPhone = box.read(BoxName.sosPhonePassenger);
if (rawPhone == null) return;
var phone = formatSyrianPhoneNumber(rawPhone);
var message = '''Dear Friend,
🚀 I have just started an exciting trip on Intaleq!
Download the app to track my ride:
👉 Android: https://play.google.com/store/apps/details?id=com.Intaleq.intaleq&hl=en-US
👉 iOS: https://apps.apple.com/st/app/intaleq-rider/id6748075179
See you there!
Intaleq Team''';
launchCommunication('whatsapp', phone, message);
},
),
cancel: MyElevatedButton(
title: 'Cancel'.tr,
onPressed: () {
Get.back();
},
),
);
} else if (res['status'] == 'success') {
if (Get.isDialogOpen ?? false) Get.back();
Get.snackbar("Success".tr, "The invitation was sent successfully".tr,
backgroundColor: AppColor.greenColor, colorText: Colors.white);
List tokensData = res['data'];
for (var device in tokensData) {
String tokenParent = device['token'];
NotificationService.sendNotification(
category: "Trip Monitoring",
target: tokenParent,
title: "Trip Monitoring".tr,
body: "Click to track the trip".tr,
isTopic: false,
tone: 'tone1',
driverList: [rideLifecycle.rideId, rideLifecycle.driverId],
);
box.write(BoxName.tokenParent, tokenParent);
}
box.write(BoxName.parentTripSelected, true);
}
}
@override
void onClose() {
EmergencySignalService.instance.stopListening();
super.onClose();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/controller/home/map/ride_lifecycle_controller.dart';
import 'package:Intaleq/controller/home/map_passenger_controller.dart';
import '../../constant/api_key.dart'; import '../../constant/api_key.dart';
import '../../constant/links.dart'; import '../../constant/links.dart';
import '../../constant/style.dart';
import '../functions/crud.dart'; import '../functions/crud.dart';
import '../functions/location_controller.dart'; import '../functions/location_controller.dart';
@@ -114,14 +114,21 @@ class WayPointController extends GetxController {
void onInit() { void onInit() {
// Get.put(LocationController()); // Get.put(LocationController());
addWayPoints(); addWayPoints();
myLocation = Get.find<MapPassengerController>().passengerLocation; myLocation = Get.find<RideLifecycleController>().passengerLocation;
super.onInit(); super.onInit();
} }
void reset() {
wayPoints.clear();
addWayPoints();
placeListResponse.clear();
update();
}
} }
class PlaceList extends StatelessWidget { class PlaceList extends StatelessWidget {
// Get the controller instance // Get the controller instance
final controller = Get.put(WayPointController()); final controller = Get.find<WayPointController>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@@ -0,0 +1,159 @@
--- PRECISE MISSING METHODS ---
Total original methods/getters/setters: 270
Total defined in split controllers: 270
Total missing: 53
- Column
- CupertinoDialogAction
- Future
- _applyLowEndModeIfNeeded
- _buildOsrmWaypointCoords
- _calculateDistance
- _checkAndRecalculateIfDeviated
- _fillDriverDataLocally
- _haversineKm
- _initMinimalIcons
- _initializePolygons
- _isActiveRideState
- _kmToLatDelta
- _kmToLngDelta
- _onDriverAcceptedWithSocket
- _onDriverArrivedWithSocket
- _onRideCancelledWithSocket
- _onRideStartedWithSocket
- _playRouteAnimation
- _relevanceScore
- _restorePolyline
- _retryProcess
- _stageNiceToHave
- _stagePricingAndState
- _startMasterTimer
- _startMasterTimerWithInterval
- _startPollingFallback
- _stopDriverLocationPolling
- _updateDriverMarker
- addPostFrameCallback
- cancelRide
- checkPassengerLocation
- currentDriverMarkerId
- detectPerfMode
- directions
- getAIKey
- getDirectionMap
- getDistanceFromDriverAfterAcceptedRide
- getMapPointsForAllMethods
- getPassengerLocationUniversity
- getRideStatus
- handleActiveRideOnStartup
- handleNoDriverFound
- isDriversDataValid
- lastWhere
- onChangedPassengerCount
- onChangedPassengersChoose
- processRideAcceptance
- retrySearchForDrivers
- showDrawingBottomSheet
- showNoDriversDialog
- startSearchingTimer
- wait
--- PRECISE MISSING VARIABLES/FIELDS ---
Total original variables: 626
Total defined in split controllers: 558
Total missing: 97
- EdgeInsets
- Error
- InfoWindow
- LatLngBounds
- LocationData
- R
- _buildOsrmWaypointCoords
- _calculateDistance
- _haversineKm
- _isActiveRideState
- _isStateProcessing
- _isUsingFallback
- _kmToLatDelta
- _kmToLngDelta
- _reconnectTimer
- _relevanceScore
- a
- aerialDistance
- apiDistanceMeters
- apiKey
- attemptCount
- c
- carInfo
- cardNumber
- carsOrder
- checkPassengerLocation
- commissionPct
- context
- coordDestination
- currentAttempt
- currentCarType
- currentLocationOfDrivers
- currentRideId
- currentTimeSearchingCaptainWindow
- dInfo
- dLat
- dataCarsLocationByPassenger
- datadriverCarsLocationToPassengerAfterApplied
- dest
- deviation
- distanceOfTrip
- driverCarPlate
- driverLocationToPassenger
- driverName
- driverOrderStatus
- driverPhone
- durationByPassenger
- dynamicApiUrl
- etaText
- fName
- finalReason
- firebaseMessagesController
- increaseFeeFormKey
- info
- isBeginRideFromDriverRunning
- isDrawingRoute
- isDriversDataValid
- isDriversTokensSend
- isInUniversity
- isRequestValid
- kDurationScalar
- key
- km
- kmInDegree
- lName
- latDest
- latestWaypoint
- lngDest
- lowPerf
- mapAPIKEY
- messagesFormKey
- might
- minBillableKm
- minFareSYP
- newValue
- northeast
- originCoords
- pLower
- passengerLocation
- passengerLocationStringUnvirsity
- placeName
- polylineString
- previousLocationOfDrivers
- progressTimerRideBeginVip
- promoFormKey
- qLower
- query
- rLat1
- rLat2
- ram
- rideData
- sdk
- selectedPassengerCount
- southwest
- startLng
- status
- stringElapsedTimeRideBegin

View File

@@ -85,11 +85,13 @@ class ComplaintController extends GetxController {
var uri = Uri.parse('${AppLink.server}/upload_audio.php'); var uri = Uri.parse('${AppLink.server}/upload_audio.php');
var request = http.MultipartRequest('POST', uri); var request = http.MultipartRequest('POST', uri);
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
final String fingerPrint = box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
var mimeType = lookupMimeType(audioFile.path); var mimeType = lookupMimeType(audioFile.path);
// ** التعديل: تم استخدام نفس هيدر التوثيق ** // ** التعديل: تم استخدام نفس هيدر التوثيق **
request.headers.addAll({ request.headers.addAll({
'Authorization': 'Bearer $token', 'Authorization': 'Bearer $token',
'X-Device-FP': fingerPrint,
}); });
request.files.add( request.files.add(
await http.MultipartFile.fromPath( await http.MultipartFile.fromPath(

View File

@@ -4,8 +4,7 @@ import 'dart:convert';
import 'package:Intaleq/constant/box_name.dart'; import 'package:Intaleq/constant/box_name.dart';
import 'package:Intaleq/constant/colors.dart'; import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/links.dart'; import 'package:Intaleq/constant/links.dart';
import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/controller/home/map/ride_lifecycle_controller.dart';
import 'package:Intaleq/controller/home/map_passenger_controller.dart';
import 'package:Intaleq/main.dart'; import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/widgets/elevated_btn.dart'; import 'package:Intaleq/views/widgets/elevated_btn.dart';
import 'package:Intaleq/views/widgets/my_scafold.dart'; import 'package:Intaleq/views/widgets/my_scafold.dart';
@@ -15,6 +14,7 @@ import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../../constant/style.dart';
import '../functions/crud.dart'; import '../functions/crud.dart';
import '../functions/encrypt_decrypt.dart'; import '../functions/encrypt_decrypt.dart';
@@ -42,7 +42,7 @@ class VipOrderController extends GetxController {
Future<void> fetchOrder() async { Future<void> fetchOrder() async {
try { try {
isLoading.value = true; isLoading.value = true;
var mapPassengerController = Get.find<MapPassengerController>(); var mapPassengerController = Get.find<RideLifecycleController>();
var res = await CRUD().get( var res = await CRUD().get(
link: AppLink.getMishwari, link: AppLink.getMishwari,
@@ -239,7 +239,7 @@ class VipWaittingPage extends StatelessWidget {
title: "Cancel Trip".tr, title: "Cancel Trip".tr,
kolor: AppColor.redColor, kolor: AppColor.redColor,
onPressed: () { onPressed: () {
Get.find<MapPassengerController>().cancelVip( Get.find<RideLifecycleController>().cancelVip(
data['token'].toString(), data['token'].toString(),
data['id'].toString(), data['id'].toString(),
); );
@@ -272,7 +272,7 @@ class VipWaittingPage extends StatelessWidget {
title: "Send to Driver Again".tr, title: "Send to Driver Again".tr,
kolor: AppColor.greenColor, kolor: AppColor.greenColor,
onPressed: () { onPressed: () {
Get.find<MapPassengerController>() Get.find<RideLifecycleController>()
.sendToDriverAgain(data['token']); .sendToDriverAgain(data['token']);
vipOrderController.fetchOrder(); vipOrderController.fetchOrder();
}, },
@@ -292,7 +292,7 @@ class VipWaittingPage extends StatelessWidget {
kolor: AppColor.greenColor, kolor: AppColor.greenColor,
onPressed: () { onPressed: () {
final mapPassengerController = final mapPassengerController =
Get.find<MapPassengerController>(); Get.find<RideLifecycleController>();
mapPassengerController.make = data['make']; mapPassengerController.make = data['make'];
mapPassengerController.licensePlate = mapPassengerController.licensePlate =
data['car_plate']; data['car_plate'];

View File

@@ -24,6 +24,7 @@ class MyTranslation extends Translations {
"4 Passengers": "٤ ركاب", "4 Passengers": "٤ ركاب",
"2. Attach Recorded Audio (Optional)": "2. Attach Recorded Audio (Optional)":
"٢. إرفاق تسجيل صوتي (اختياري)", "٢. إرفاق تسجيل صوتي (اختياري)",
"Accept": "قبول",
"Account": "الحساب", "Account": "الحساب",
"Actions": "الإجراءات", "Actions": "الإجراءات",
"Active Users": "المستخدمين النشطين", "Active Users": "المستخدمين النشطين",
@@ -41,9 +42,12 @@ class MyTranslation extends Translations {
"Arrived": "وصلنا", "Arrived": "وصلنا",
"Audio Recording": "تسجيل صوتي", "Audio Recording": "تسجيل صوتي",
"Call": "اتصال", "Call": "اتصال",
"Call Connected": "تم فتح الاتصال",
"Call Support": "اتصل بالدعم", "Call Support": "اتصل بالدعم",
"Call left": "مكالمات متبقية", "Call left": "مكالمات متبقية",
"Calling": "عم نتصل بـ",
"Change Photo": "تغيير الصورة", "Change Photo": "تغيير الصورة",
"Captain": "الكابتن",
"Choose from Gallery": "اختر من المعرض", "Choose from Gallery": "اختر من المعرض",
"Choose from contact": "اختر من جهات الاتصال", "Choose from contact": "اختر من جهات الاتصال",
"Click to track the trip": "اضغط لتتبع المشوار", "Click to track the trip": "اضغط لتتبع المشوار",
@@ -52,11 +56,13 @@ class MyTranslation extends Translations {
"Complete Payment": "إتمام الدفع", "Complete Payment": "إتمام الدفع",
"Confirm Cancellation": "تأكيد الإلغاء", "Confirm Cancellation": "تأكيد الإلغاء",
"Confirm Pickup Location": "تأكيد موقع الانطلاق", "Confirm Pickup Location": "تأكيد موقع الانطلاق",
"Connecting...": "عم يتم الاتصال...",
"Connection failed. Please try again.": "فشل الاتصال. جرب مرة تانية.", "Connection failed. Please try again.": "فشل الاتصال. جرب مرة تانية.",
"Could not create ride. Please try again.": "Could not create ride. Please try again.":
"ما قدرنا نعمل مشوار. جرب مرة تانية.", "ما قدرنا نعمل مشوار. جرب مرة تانية.",
"Crop Photo": "قص الصورة", "Crop Photo": "قص الصورة",
"Dark Mode": "الوضع الليلي", "Dark Mode": "الوضع الليلي",
"Decline": "رفض",
"Delete": "حذف", "Delete": "حذف",
"Delete Account": "حذف الحساب", "Delete Account": "حذف الحساب",
"Delete All": "حذف الكل", "Delete All": "حذف الكل",
@@ -73,6 +79,7 @@ class MyTranslation extends Translations {
"Driver is Going To You": "الكابتن جاي لعندك", "Driver is Going To You": "الكابتن جاي لعندك",
"Emergency Mode Triggered": "تم تفعيل وضع الطوارئ", "Emergency Mode Triggered": "تم تفعيل وضع الطوارئ",
"Emergency SOS": "طوارئ SOS", "Emergency SOS": "طوارئ SOS",
"End": "إنهاء",
"Enter the 5-digit code": "أدخل الكود المكون من ٥ أرقام", "Enter the 5-digit code": "أدخل الكود المكون من ٥ أرقام",
"Enter your City": "أدخل مدينتك", "Enter your City": "أدخل مدينتك",
"Enter your Password": "أدخل كلمة السر", "Enter your Password": "أدخل كلمة السر",
@@ -84,6 +91,7 @@ class MyTranslation extends Translations {
"Failed to upload photo": "فشل رفع الصورة", "Failed to upload photo": "فشل رفع الصورة",
"Finished": "انتهى", "Finished": "انتهى",
"Fixed Price": "سعر ثابت", "Fixed Price": "سعر ثابت",
"Free Call": "مكالمة مجانية",
"General": "عام", "General": "عام",
"Grant": "منح الإذن", "Grant": "منح الإذن",
"Have a Promo Code?": "معك كود خصم؟", "Have a Promo Code?": "معك كود خصم؟",
@@ -109,6 +117,7 @@ class MyTranslation extends Translations {
"Logout": "تسجيل خروج", "Logout": "تسجيل خروج",
"Map Error": "خطأ بالخريطة", "Map Error": "خطأ بالخريطة",
"Message": "رسالة", "Message": "رسالة",
"Mute": "كتم الصوت",
"Move map to select destination": "حرك الخريطة لتحديد الوجهة", "Move map to select destination": "حرك الخريطة لتحديد الوجهة",
"Move map to set start location": "حرك الخريطة لتحديد نقطة البداية", "Move map to set start location": "حرك الخريطة لتحديد نقطة البداية",
"Move map to set stop": "حرك الخريطة لتحديد التوقف", "Move map to set stop": "حرك الخريطة لتحديد التوقف",
@@ -205,6 +214,7 @@ class MyTranslation extends Translations {
"Share your experience to help us improve...": "Share your experience to help us improve...":
"شاركنا تجربتك لنحسن خدمتنا...", "شاركنا تجربتك لنحسن خدمتنا...",
"Something went wrong. Please try again.": "صار غلط. جرب مرة تانية.", "Something went wrong. Please try again.": "صار غلط. جرب مرة تانية.",
"Speaker": "مكبر الصوت",
"Speaking...": "عم يحكي...", "Speaking...": "عم يحكي...",
"Speed Over": "تجاوز السرعة", "Speed Over": "تجاوز السرعة",
"Start Point": "نقطة البداية", "Start Point": "نقطة البداية",
@@ -254,6 +264,7 @@ class MyTranslation extends Translations {
"cancelled": "تكنسل", "cancelled": "تكنسل",
"due to a previous trip.": "عن مشوار قديم.", "due to a previous trip.": "عن مشوار قديم.",
"insert sos phone": "دخل رقم طوارئ", "insert sos phone": "دخل رقم طوارئ",
"is calling you": "عم يتصل فيك",
"is driving a": "عم يسوق", "is driving a": "عم يسوق",
"min added to fare": "دقيقة نضافت للأجرة", "min added to fare": "دقيقة نضافت للأجرة",
"phone not verified": "رقم الموبايل مو متأكد", "phone not verified": "رقم الموبايل مو متأكد",

View File

@@ -10,7 +10,9 @@ import 'package:flutter_paypal/flutter_paypal.dart';
import 'package:flutter_stripe/flutter_stripe.dart'; import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart'; import 'package:local_auth/local_auth.dart';
import 'package:Intaleq/controller/home/map_passenger_controller.dart'; import 'package:Intaleq/controller/home/map/ride_lifecycle_controller.dart';
import 'package:Intaleq/controller/home/map/ride_state.dart';
import 'package:Intaleq/controller/home/map/ride_state.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
import '../../constant/box_name.dart'; import '../../constant/box_name.dart';
@@ -34,7 +36,7 @@ class PaymentController extends GetxController {
final formKey = GlobalKey<FormState>(); final formKey = GlobalKey<FormState>();
final promo = TextEditingController(); final promo = TextEditingController();
final walletphoneController = TextEditingController(); final walletphoneController = TextEditingController();
double totalPassenger = Get.find<MapPassengerController>().totalPassenger; double totalPassenger = Get.find<RideLifecycleController>().totalPassenger;
int? selectedAmount = 0; int? selectedAmount = 0;
List<dynamic> totalPassengerWalletDetails = []; List<dynamic> totalPassengerWalletDetails = [];
String passengerTotalWalletAmount = ''; String passengerTotalWalletAmount = '';
@@ -79,7 +81,7 @@ class PaymentController extends GetxController {
Future<String> generateTokenDriver(String amount) async { Future<String> generateTokenDriver(String amount) async {
var res = await CRUD().post(link: AppLink.addPaymentTokenDriver, payload: { var res = await CRUD().post(link: AppLink.addPaymentTokenDriver, payload: {
'driverID': Get.find<MapPassengerController>().driverId, 'driverID': Get.find<RideLifecycleController>().driverId,
'amount': amount.toString(), 'amount': amount.toString(),
}); });
var d = jsonDecode(res); var d = jsonDecode(res);
@@ -121,7 +123,7 @@ class PaymentController extends GetxController {
'payment_method': 'cancel-from-near', 'payment_method': 'cancel-from-near',
'passengerID': box.read(BoxName.passengerID).toString(), 'passengerID': box.read(BoxName.passengerID).toString(),
'token': paymentTokenWait, 'token': paymentTokenWait,
'driverID': Get.find<MapPassengerController>().driverId.toString(), 'driverID': Get.find<RideLifecycleController>().driverId.toString(),
}); });
var paymentTokenWait1 = var paymentTokenWait1 =
await generateTokenDriver(costOfWaiting5Minute.toString()); await generateTokenDriver(costOfWaiting5Minute.toString());
@@ -131,7 +133,7 @@ class PaymentController extends GetxController {
'amount': (costOfWaiting5Minute).toStringAsFixed(0), 'amount': (costOfWaiting5Minute).toStringAsFixed(0),
'paymentMethod': 'cancel-from-near', 'paymentMethod': 'cancel-from-near',
'token': paymentTokenWait1, 'token': paymentTokenWait1,
'driverID': Get.find<MapPassengerController>().driverId.toString(), 'driverID': Get.find<RideLifecycleController>().driverId.toString(),
}); });
if (res != 'failure') { if (res != 'failure') {
@@ -139,13 +141,13 @@ class PaymentController extends GetxController {
// 'Cancel', // 'Cancel',
// 'Trip Cancelled. The cost of the trip will be added to your wallet.' // 'Trip Cancelled. The cost of the trip will be added to your wallet.'
// .tr, // .tr,
// Get.find<MapPassengerController>().driverToken, // Get.find<RideLifecycleController>().driverToken,
// [], // [],
// 'cancel', // 'cancel',
// ); // );
await NotificationService.sendNotification( await NotificationService.sendNotification(
category: 'Cancel', category: 'Cancel',
target: Get.find<MapPassengerController>().driverToken.toString(), target: Get.find<RideLifecycleController>().driverToken.toString(),
title: 'Cancel'.tr, title: 'Cancel'.tr,
body: body:
'Trip Cancelled. The cost of the trip will be added to your wallet.' 'Trip Cancelled. The cost of the trip will be added to your wallet.'
@@ -226,7 +228,7 @@ class PaymentController extends GetxController {
var firstElement = decod["message"][0]; var firstElement = decod["message"][0];
totalPassenger = totalPassenger - totalPassenger = totalPassenger -
(totalPassenger * int.parse(firstElement['amount'])); (totalPassenger * int.parse(firstElement['amount']));
MapPassengerController().promoTaken = true; Get.find<RideLifecycleController>().promoTaken = true;
update(); update();
} }
}); });

View File

@@ -4,7 +4,7 @@ import 'package:Intaleq/constant/box_name.dart';
import 'package:Intaleq/constant/links.dart'; import 'package:Intaleq/constant/links.dart';
import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/constant/style.dart';
import 'package:Intaleq/controller/functions/crud.dart'; import 'package:Intaleq/controller/functions/crud.dart';
import 'package:Intaleq/controller/home/map_passenger_controller.dart'; import 'package:Intaleq/controller/home/map/ride_lifecycle_controller.dart';
import 'package:Intaleq/main.dart'; import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/home/map_page_passenger.dart'; import 'package:Intaleq/views/home/map_page_passenger.dart';
import 'package:Intaleq/views/widgets/elevated_btn.dart'; import 'package:Intaleq/views/widgets/elevated_btn.dart';
@@ -45,14 +45,14 @@ class RateController extends GetxController {
confirm: MyElevatedButton(title: 'Ok', onPressed: () => Get.back())); confirm: MyElevatedButton(title: 'Ok', onPressed: () => Get.back()));
} else if (Get.find<PaymentController>().isWalletChecked == true) { } else if (Get.find<PaymentController>().isWalletChecked == true) {
double tip = 0; double tip = 0;
tip = (Get.find<MapPassengerController>().totalPassenger) * tip = (Get.find<RideLifecycleController>().totalPassenger) *
(double.parse(box.read(BoxName.tipPercentage).toString())); (double.parse(box.read(BoxName.tipPercentage).toString()));
if (tip > 0) { if (tip > 0) {
var res = await CRUD().post(link: AppLink.addTips, payload: { var res = await CRUD().post(link: AppLink.addTips, payload: {
'passengerID': box.read(BoxName.passengerID), 'passengerID': box.read(BoxName.passengerID),
'driverID': Get.find<MapPassengerController>().driverId.toString(), 'driverID': Get.find<RideLifecycleController>().driverId.toString(),
'rideID': Get.find<MapPassengerController>().rideId.toString(), 'rideID': Get.find<RideLifecycleController>().rideId.toString(),
'tipAmount': tip.toString(), 'tipAmount': tip.toString(),
}); });
await Get.find<PaymentController>() await Get.find<PaymentController>()
@@ -62,8 +62,8 @@ class RateController extends GetxController {
? tip.toStringAsFixed(0) ? tip.toStringAsFixed(0)
: (tip * 100).toString()); : (tip * 100).toString());
await CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: { await CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: {
'driverID': Get.find<MapPassengerController>().driverId.toString(), 'driverID': Get.find<RideLifecycleController>().driverId.toString(),
'paymentID': '${Get.find<MapPassengerController>().rideId}tip', 'paymentID': '${Get.find<RideLifecycleController>().rideId}tip',
'amount': box.read(BoxName.countryCode) == 'Egypt' 'amount': box.read(BoxName.countryCode) == 'Egypt'
? tip.toStringAsFixed(0) ? tip.toStringAsFixed(0)
: (tip * 100).toString(), : (tip * 100).toString(),
@@ -73,10 +73,10 @@ class RateController extends GetxController {
if (res != 'failure') { if (res != 'failure') {
await NotificationService.sendNotification( await NotificationService.sendNotification(
category: 'You Have Tips', category: 'You Have Tips',
target: Get.find<MapPassengerController>().driverToken.toString(), target: Get.find<RideLifecycleController>().driverToken.toString(),
title: 'You Have Tips'.tr, title: 'You Have Tips'.tr,
body: body:
'${'${tip.toString()}\$${' tips\nTotal is'.tr}'} ${tip + (Get.find<MapPassengerController>().totalPassenger)}', '${'${tip.toString()}\$${' tips\nTotal is'.tr}'} ${tip + (Get.find<RideLifecycleController>().totalPassenger)}',
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'ding', tone: 'ding',
driverList: [], driverList: [],
@@ -95,7 +95,7 @@ class RateController extends GetxController {
}, },
); );
Get.find<MapPassengerController>().restCounter(); Get.find<RideLifecycleController>().restCounter();
Get.offAll(const MapPagePassenger()); Get.offAll(const MapPagePassenger());
} }
} }

View File

@@ -0,0 +1,722 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:get/get.dart' hide Response;
import 'package:permission_handler/permission_handler.dart';
import 'package:just_audio/just_audio.dart';
import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../../main.dart';
import '../../print.dart';
import '../../services/signaling_service.dart';
import '../../views/widgets/voice_call_bottom_sheet.dart';
import 'functions/crud.dart';
// EN: Enum representing the different states of a voice call.
// AR: تعداد يمثل الحالات المختلفة للمكالمة الصوتية.
enum VoiceCallState { idle, dialing, ringing, connecting, active, ended }
class VoiceCallController extends GetxController with WidgetsBindingObserver {
// EN: Instance of the signaling service to manage WebSocket communication.
// AR: مثيل لخدمة الإشارات لإدارة الاتصال عبر الـ WebSocket.
final SignalingService _signaling = SignalingService();
// --- Observable Variables (GetX) / المتغيرات التفاعلية ---
// EN: Current state of the call.
// AR: الحالة الحالية للمكالمة.
var state = VoiceCallState.idle.obs;
// EN: Unique identifier for the WebRTC session.
// AR: المعرف الفريد لجلسة الاتصال.
var sessionId = "".obs;
// EN: ID of the current active ride.
// AR: معرف الرحلة النشطة الحالية.
var rideId = "".obs;
// EN: Name of the other party (Driver/Passenger).
// AR: اسم الطرف الآخر في المكالمة (سائق/راكب).
var remoteName = "User".obs;
// EN: Microphone mute status.
// AR: حالة كتم الميكروفون.
var isMuted = false.obs;
// EN: Speakerphone status.
// AR: حالة مكبر الصوت الخارجي.
var isSpeakerOn = false.obs;
// EN: Timer countdown variable, starts from 60 seconds.
// AR: متغير العد التنازلي للمؤقت، يبدأ من 60 ثانية.
var elapsedSeconds = 60.obs;
// --- Core State Variables / متغيرات الحالة الأساسية ---
// EN: Flag to determine if the current user initiated the call.
// AR: مؤشر لتحديد ما إذا كان المستخدم الحالي هو من بدأ المكالمة.
bool isCaller = false;
// EN: ID of the current user.
// AR: معرف المستخدم الحالي.
String currentUserId = "";
// --- WebRTC Internal Variables / متغيرات WebRTC الداخلية ---
// EN: The main connection object between peers.
// AR: كائن الاتصال الرئيسي بين الطرفين.
rtc.RTCPeerConnection? _peerConnection;
// EN: The local audio stream captured from the microphone.
// AR: دفق الصوت المحلي الملتقط من الميكروفون.
rtc.MediaStream? _localStream;
// EN: Timer to enforce the 60-second call limit.
// AR: مؤقت لفرض حد الـ 60 ثانية للمكالمة.
Timer? _countdownTimer;
// EN: Timer to hang up if the call is not answered within 30 seconds.
// AR: مؤقت لإنهاء المكالمة إذا لم يتم الرد خلال 30 ثانية.
Timer? _ringingTimeoutTimer;
// EN: Flag to indicate if the peer connection is currently attempting ICE reconnection.
// AR: مؤشر يوضح ما إذا كان الاتصال يحاول إعادة بناء مسارات الشبكة حالياً.
bool _isReconnecting = false;
Timer? _reconnectTimer;
List<dynamic> _dynamicIceServers = [];
AudioPlayer? _ringtonePlayer;
void _startRingtone() async {
try {
_ringtonePlayer ??= AudioPlayer();
await _ringtonePlayer!.setAsset('assets/start.wav');
await _ringtonePlayer!.setLoopMode(LoopMode.one);
_ringtonePlayer!.play();
} catch (e) {
Log.print("Error playing ringtone: $e");
}
}
void _stopRingtone() {
try {
_ringtonePlayer?.stop();
} catch (e) {
Log.print("Error stopping ringtone: $e");
}
}
@override
void onInit() {
super.onInit();
// EN: Add lifecycle observer.
// AR: إضافة مراقب لدورة حياة التطبيق.
WidgetsBinding.instance.addObserver(this);
// EN: Initialize WebSocket signaling listeners.
// AR: تهيئة مستمعي إشارات الـ WebSocket.
_initSignalingCallbacks();
}
// EN: Lifecycle hook: handle app switching background/foreground.
// AR: معالجة انتقال التطبيق إلى الخلفية أو العودة للواجهة.
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
Log.print("VoiceCall: didChangeAppLifecycleState -> $state");
if (state == AppLifecycleState.paused) {
Log.print(
"WARNING: App is in background. Microphone access might be suspended by the OS.",
);
} else if (state == AppLifecycleState.resumed) {
Log.print("App resumed. Verifying WebRTC connection health.");
if (this.state.value == VoiceCallState.active) {
_ensureMicrophoneActive();
_attemptIceRestart();
}
}
}
// EN: Registers all event listeners for the signaling server.
// AR: تسجيل جميع مستمعي الأحداث لخادم الإشارات.
void _initSignalingCallbacks() {
// EN: Triggered when successfully connected to the signaling server.
// AR: يُستدعى عند الاتصال بنجاح بخادم الإشارات.
_signaling.onConnected = (iceServers) {
Log.print("WebRTC Signaling Connected & Authenticated");
_dynamicIceServers = iceServers;
};
// EN: Triggered when the WebSocket connection drops.
// AR: يُستدعى عند انقطاع اتصال الـ WebSocket.
_signaling.onDisconnected = (reason) {
Log.print("WebRTC Signaling Disconnected: $reason");
if (state.value != VoiceCallState.idle) {
_endCallInternal("signaling_disconnected");
}
};
// EN: Triggered when the remote user joins the room.
// AR: يُستدعى عند انضمام الطرف الآخر إلى غرفة الاتصال.
_signaling.onParticipantJoined = () async {
Log.print("Remote participant joined signaling session");
// EN: If we are the caller, initiate the WebRTC handshake by creating an Offer.
// AR: إذا كنا نحن المتصل، نبدأ مصافحة WebRTC بإنشاء عرض (Offer).
if (isCaller && state.value == VoiceCallState.dialing) {
state.value = VoiceCallState.connecting;
await _createOffer();
}
};
// EN: Triggered when an SDP Offer is received from the remote peer.
// AR: يُستدعى عند استلام عرض اتصال (Offer) من الطرف الآخر.
_signaling.onOffer = (sdpMap) async {
Log.print("Received WebRTC SDP Offer");
if (!isCaller) {
state.value = VoiceCallState.connecting;
await _initializePeerConnection();
// EN: Set the remote peer's settings.
// AR: تعيين إعدادات الطرف الآخر.
final description = rtc.RTCSessionDescription(
sdpMap['sdp'],
sdpMap['type'],
);
await _peerConnection!.setRemoteDescription(description);
// EN: Respond with an Answer.
// AR: الرد بإجابة (Answer).
await _createAnswer();
}
};
// EN: Triggered when an SDP Answer is received.
// AR: يُستدعى عند استلام إجابة (Answer) من الطرف الآخر.
_signaling.onAnswer = (sdpMap) async {
Log.print("Received WebRTC SDP Answer");
if (isCaller && _peerConnection != null) {
final description = rtc.RTCSessionDescription(
sdpMap['sdp'],
sdpMap['type'],
);
await _peerConnection!.setRemoteDescription(description);
}
};
// EN: Triggered when ICE candidates (Network routing info) are exchanged.
// AR: يُستدعى عند تبادل مسارات الشبكة (ICE Candidates) لتأسيس الاتصال.
_signaling.onIceCandidate = (candidateMap) async {
Log.print("Received Remote ICE Candidate");
if (_peerConnection != null) {
final candidate = rtc.RTCIceCandidate(
candidateMap['candidate'],
candidateMap['sdpMid'],
candidateMap['sdpMLineIndex'],
);
await _peerConnection!.addCandidate(candidate);
}
};
// EN: Triggered when a hangup event is received from the server.
// AR: يُستدعى عند استلام حدث إنهاء المكالمة من السيرفر.
_signaling.onCallEnded = (reason) {
Log.print("WebRTC Call Ended: $reason");
_endCallInternal(reason);
};
}
// --- CALL LIFECYCLE / دورة حياة المكالمة ---
// EN: Initiates an outgoing call.
// AR: يبدأ مكالمة صادرة.
Future<void> startCall({
required String rideIdVal,
required String driverId,
required String passengerId,
required String remoteNameVal,
}) async {
if (state.value != VoiceCallState.idle) return;
// EN: Setup call variables.
// AR: إعداد متغيرات المكالمة.
state.value = VoiceCallState.dialing;
isCaller = true;
currentUserId = passengerId;
rideId.value = rideIdVal;
remoteName.value = remoteNameVal;
isMuted.value = false;
isSpeakerOn.value = false;
elapsedSeconds.value = 60;
_isReconnecting = false;
_showCallBottomSheet();
HapticFeedback.vibrate();
try {
// 1. EN: Request Microphone Permission / AR: طلب صلاحية الميكروفون
final permissionStatus = await Permission.microphone.request();
if (!permissionStatus.isGranted) {
_endCallInternal("permission_denied");
Get.snackbar(
"Error",
"Microphone permission is required for voice calls".tr,
);
return;
}
// 2. EN: Call PHP Backend to create Node.js session & notify Driver via FCM.
// AR: استدعاء واجهة PHP لإنشاء الجلسة على Node.js وإشعار السائق عبر FCM.
final response = await CRUD().post(
link: "${AppLink.server}/ride/call/passenger/create_call_session.php",
payload: {'ride_id': rideIdVal},
);
if (response == null ||
response == 'failure' ||
response['status'] != 'success') {
_endCallInternal("session_creation_failed");
Get.snackbar(
"Error",
"Failed to initiate call session. Please try again.".tr,
);
return;
}
final data = response['data'];
sessionId.value = data['session_id'];
// 3. EN: Connect to WebRTC signaling server / AR: الاتصال بخادم الإشارات
await _signaling.connect(sessionId.value, currentUserId);
// 4. EN: Initialize Local WebRTC Audio Stream / AR: تهيئة دفق الصوت المحلي
await _initializeLocalStream();
// 5. EN: Start Ringing Timeout Timer (30s max wait for driver to answer).
// AR: بدء مؤقت الرنين (أقصى انتظار 30 ثانية لرد السائق).
_ringingTimeoutTimer = Timer(const Duration(seconds: 30), () {
if (state.value == VoiceCallState.dialing) {
_signaling.send("hangup", {"reason": "no_answer"});
_endCallInternal("no_answer");
}
});
} catch (e) {
Log.print("Error starting WebRTC call: $e");
_endCallInternal("error");
}
}
// EN: Handles incoming call requests via FCM/Socket.
// AR: معالجة طلبات المكالمات الواردة.
Future<void> receiveCall({
required String sessionIdVal,
required String remoteNameVal,
required String rideIdVal,
}) async {
// EN: If already in a call, send busy signal.
// AR: إذا كان في مكالمة بالفعل، إرسال إشارة مشغول.
if (state.value != VoiceCallState.idle) {
_signaling.send("hangup", {"reason": "busy"});
return;
}
state.value = VoiceCallState.ringing;
isCaller = false;
currentUserId = box.read(BoxName.passengerID).toString();
sessionId.value = sessionIdVal;
rideId.value = rideIdVal;
remoteName.value = remoteNameVal;
isMuted.value = false;
isSpeakerOn.value = false;
elapsedSeconds.value = 60;
_isReconnecting = false;
_showCallBottomSheet();
_startRingtone();
HapticFeedback.vibrate();
// EN: Max 30s ringing timeout for receiver before auto-decline.
// AR: أقصى مدة للرنين 30 ثانية قبل الرفض التلقائي.
_ringingTimeoutTimer = Timer(const Duration(seconds: 30), () {
if (state.value == VoiceCallState.ringing) {
declineCall();
}
});
}
// EN: Accepts the incoming call.
// AR: قبول المكالمة الواردة.
Future<void> acceptCall() async {
if (state.value != VoiceCallState.ringing) return;
_ringingTimeoutTimer?.cancel();
_stopRingtone();
state.value = VoiceCallState.connecting;
try {
// EN: Check Mic permissions / AR: التحقق من صلاحيات الميكروفون
final permissionStatus = await Permission.microphone.request();
if (!permissionStatus.isGranted) {
declineCall();
Get.snackbar(
"Error",
"Microphone permission is required for voice calls".tr,
);
return;
}
await _signaling.connect(sessionId.value, currentUserId);
await _initializeLocalStream();
// EN: Notify caller we accepted / AR: إشعار المتصل بأننا قبلنا المكالمة
_signaling.send("join", {});
} catch (e) {
Log.print("Error accepting call: $e");
declineCall();
}
}
// EN: Declines an incoming call.
// AR: رفض المكالمة الواردة.
void declineCall() {
_ringingTimeoutTimer?.cancel();
_stopRingtone();
_signaling.send("hangup", {"reason": "declined"});
_endCallInternal("declined");
}
// EN: Ends an active or dialing call.
// AR: إنهاء المكالمة النشطة أو الجاري الاتصال بها.
void hangup() {
_signaling.send("hangup", {"reason": "normal"});
_endCallInternal("hangup");
}
// --- WEBRTC CORE HELPERS / دوال WebRTC الأساسية ---
// EN: Captures the audio from the microphone with optimization constraints.
// AR: التقاط الصوت من الميكروفون مع قيود تحسين الجودة (إلغاء الصدى والضوضاء).
Future<void> _initializeLocalStream() async {
final Map<String, dynamic> mediaConstraints = {
'audio': {
'echoCancellation': true,
'noiseSuppression': true,
'autoGainControl': true,
},
'video': false, // EN: Audio only / AR: صوت فقط
};
_localStream = await rtc.navigator.mediaDevices.getUserMedia(
mediaConstraints,
);
rtc.Helper.setSpeakerphoneOn(isSpeakerOn.value);
}
// EN: Verifies local microphone stream health on app resume and recreates/replaces track if suspended.
// AR: التحقق من سلامة مسار الميكروفون المحلي عند استئناف التطبيق وإعادة إنشائه إذا تم تعليقه.
Future<void> _ensureMicrophoneActive() async {
if (_localStream == null || _peerConnection == null) return;
bool needsRecreation = false;
if (_localStream!.active == false) {
needsRecreation = true;
} else {
for (var track in _localStream!.getAudioTracks()) {
if (!track.enabled && !isMuted.value) {
needsRecreation = true;
break;
}
}
}
if (needsRecreation) {
Log.print(
"Local audio track ended or disabled. Recreating local stream...",
);
try {
_localStream?.getTracks().forEach((track) => track.stop());
_localStream?.dispose();
_localStream = null;
await _initializeLocalStream();
final senders = await _peerConnection!.getSenders();
for (var sender in senders) {
final track = sender.track;
if (track != null && track.kind == 'audio') {
final newTracks = _localStream?.getAudioTracks();
if (newTracks != null && newTracks.isNotEmpty) {
await sender.replaceTrack(newTracks.first);
Log.print(
"Replaced suspended/ended audio track with a new active one.",
);
}
break;
}
}
} catch (e) {
Log.print("Error recreating local stream on resume: $e");
}
} else {
_localStream!.getAudioTracks().forEach((track) {
track.enabled = !isMuted.value;
});
}
}
// EN: Creates the peer connection object and sets up ICE servers (STUN/TURN).
// AR: إنشاء كائن الاتصال المباشر وإعداد خوادم STUN/TURN لاختراق الجدران النارية.
Future<void> _initializePeerConnection() async {
if (_peerConnection != null) return;
final List<Map<String, dynamic>> iceServers = [];
if (_dynamicIceServers.isNotEmpty) {
for (var server in _dynamicIceServers) {
if (server is Map) {
iceServers.add({
"urls": server["urls"] ?? server["url"],
if (server["username"] != null) "username": server["username"],
if (server["credential"] != null)
"credential": server["credential"],
});
}
}
} else {
// EN: Fallback STUN servers / AR: خوادم STUN الاحتياطية
iceServers.addAll([
{"urls": "stun:stun.l.google.com:19302"},
{"urls": "stun:stun1.l.google.com:19302"},
]);
}
final Map<String, dynamic> configuration = {"iceServers": iceServers};
_peerConnection = await rtc.createPeerConnection(configuration);
// EN: Gather local network routing info and send to remote peer.
// AR: جمع بيانات مسارات الشبكة المحلية وإرسالها للطرف الآخر.
_peerConnection!.onIceCandidate = (candidate) {
if (candidate.candidate != null) {
_signaling.send("ice_candidate", {
"candidate": {
"candidate": candidate.candidate,
"sdpMid": candidate.sdpMid,
"sdpMLineIndex": candidate.sdpMLineIndex,
},
});
}
};
// EN: Monitor connection status changes and handle disconnections.
// AR: مراقبة تغيرات حالة الاتصال ومعالجة انقطاع الشبكة.
_peerConnection!.onConnectionState = (connState) {
Log.print("RTCPeerConnectionState: $connState");
if (connState ==
rtc.RTCPeerConnectionState.RTCPeerConnectionStateConnected) {
_onCallConnected();
} else if (connState ==
rtc.RTCPeerConnectionState.RTCPeerConnectionStateFailed ||
connState ==
rtc.RTCPeerConnectionState.RTCPeerConnectionStateDisconnected) {
_handleIceConnectionFailure();
}
};
// EN: Add local audio stream to the connection to send it to the other peer.
// AR: إضافة دفق الصوت المحلي للاتصال لإرساله للطرف الآخر.
if (_localStream != null) {
_localStream!.getTracks().forEach((track) {
_peerConnection!.addTrack(track, _localStream!);
});
}
}
// EN: Attempts an ICE restart to reconnect the WebRTC session when disconnections occur.
// AR: محاولة إعادة تأسيس الاتصال (ICE Restart) في حالة انقطاع الشبكة.
void _handleIceConnectionFailure() {
if (_isReconnecting) return;
_isReconnecting = true;
Log.print(
"ICE connection dropped. Attempting ICE Restart reconnection for 5s...",
);
if (isCaller) {
_attemptIceRestart();
}
_reconnectTimer?.cancel();
_reconnectTimer = Timer(const Duration(seconds: 5), () {
if (state.value == VoiceCallState.active &&
_peerConnection?.connectionState !=
rtc.RTCPeerConnectionState.RTCPeerConnectionStateConnected) {
Log.print("ICE reconnection timed out. Hanging up.");
_endCallInternal("connection_lost");
} else {
_isReconnecting = false;
Log.print("ICE Reconnection succeeded!");
}
});
}
// EN: Initiates ICE Restart SDP exchange.
// AR: بدء تبادل حزم SDP لإعادة بناء مسارات الاتصال.
Future<void> _attemptIceRestart() async {
if (_peerConnection == null || !isCaller) return;
try {
Log.print("Caller initiating WebRTC ICE Restart...");
final constraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': false,
},
'optional': [
{'IceRestart': true},
],
};
final offer = await _peerConnection!.createOffer(constraints);
await _peerConnection!.setLocalDescription(offer);
_signaling.send("offer", {
"sdp": {"sdp": offer.sdp, "type": offer.type},
});
} catch (e) {
Log.print("Error initiating WebRTC ICE Restart: $e");
}
}
// EN: Generates an SDP Offer to initialize the connection.
// AR: إنشاء عرض (Offer) لبدء الاتصال وتحديد قدرات الجهاز.
Future<void> _createOffer() async {
await _initializePeerConnection();
final constraints = {
'mandatory': {'OfferToReceiveAudio': true, 'OfferToReceiveVideo': false},
'optional': [],
};
final offer = await _peerConnection!.createOffer(constraints);
await _peerConnection!.setLocalDescription(offer);
_signaling.send("offer", {
"sdp": {"sdp": offer.sdp, "type": offer.type},
});
}
// EN: Generates an SDP Answer in response to an Offer.
// AR: الرد بإنشاء إجابة (Answer) بناءً على العرض المستلم.
Future<void> _createAnswer() async {
final constraints = {
'mandatory': {'OfferToReceiveAudio': true, 'OfferToReceiveVideo': false},
'optional': [],
};
final answer = await _peerConnection!.createAnswer(constraints);
await _peerConnection!.setLocalDescription(answer);
_signaling.send("answer", {
"sdp": {"sdp": answer.sdp, "type": answer.type},
});
}
// EN: Triggered when connection is fully established. Starts the 60s timer.
// AR: يُستدعى عند تأسيس الاتصال بنجاح، ويقوم ببدء مؤقت الـ 60 ثانية.
void _onCallConnected() {
_ringingTimeoutTimer?.cancel();
_reconnectTimer?.cancel();
_isReconnecting = false;
if (state.value != VoiceCallState.active) {
state.value = VoiceCallState.active;
HapticFeedback.vibrate();
// EN: Start 120s countdown timer / AR: بدء العد التنازلي لمدة 120 ثانية
_countdownTimer?.cancel();
elapsedSeconds.value = 120;
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (elapsedSeconds.value > 1) {
elapsedSeconds.value--;
} else {
elapsedSeconds.value = 0;
_countdownTimer?.cancel();
// EN: Force hangup when timer reaches 0 / AR: إغلاق إجباري عند وصول المؤقت لصفر
hangup();
}
});
}
}
// EN: Internal cleanup function. Closes all connections and streams.
// AR: دالة التنظيف الداخلية. تقوم بإغلاق جميع الاتصالات وتفريغ الذاكرة.
void _endCallInternal(String reason) {
_countdownTimer?.cancel();
_ringingTimeoutTimer?.cancel();
_reconnectTimer?.cancel();
_stopRingtone();
state.value = VoiceCallState.ended;
// EN: Close WebRTC connection / AR: إغلاق اتصال WebRTC
_peerConnection?.close();
_peerConnection = null;
// EN: Stop mic capture / AR: إيقاف التقاط الميكروفون
_localStream?.getTracks().forEach((track) => track.stop());
_localStream?.dispose();
_localStream = null;
// EN: Disconnect WebSockets / AR: إغلاق اتصال الـ WebSockets
_signaling.disconnect();
// EN: Close UI BottomSheet after delay / AR: إغلاق واجهة المكالمة بعد فترة زمنية قصيرة
Future.delayed(const Duration(milliseconds: 1500), () {
if (state.value == VoiceCallState.ended) {
state.value = VoiceCallState.idle;
Get.back();
}
});
}
// --- ACTIONS (UI Controls) / إجراءات الواجهة ---
// EN: Toggles microphone mute state.
// AR: تبديل حالة كتم الميكروفون.
void toggleMute() {
isMuted.value = !isMuted.value;
_localStream?.getAudioTracks().forEach((track) {
track.enabled = !isMuted.value;
});
}
// EN: Toggles loudspeaker mode.
// AR: تبديل حالة مكبر الصوت الخارجي.
void toggleSpeaker() {
isSpeakerOn.value = !isSpeakerOn.value;
rtc.Helper.setSpeakerphoneOn(isSpeakerOn.value);
}
// EN: Displays the call UI overlay.
// AR: إظهار نافذة المكالمة السفلية.
void _showCallBottomSheet() {
Get.bottomSheet(
const VoiceCallBottomSheet(),
isScrollControlled: true,
enableDrag: false,
isDismissible: false,
);
}
// EN: Lifecycle hook: clean up resources when controller is destroyed.
// AR: دورة الحياة: تفريغ الذاكرة وإغلاق الموارد عند تدمير المتحكم.
@override
void onClose() {
WidgetsBinding.instance.removeObserver(this);
_countdownTimer?.cancel();
_ringingTimeoutTimer?.cancel();
_reconnectTimer?.cancel();
_stopRingtone();
_ringtonePlayer?.dispose();
_peerConnection?.close();
_localStream?.dispose();
_signaling.disconnect();
super.onClose();
}
}

View File

@@ -1,19 +0,0 @@
import 'package:Intaleq/print.dart';
import 'package:flutter/services.dart';
class WidgetManager {
static const platform = MethodChannel('com.mobileapp.store.ride/widget');
static Future<void> updateWidget() async {
try {
await platform.invokeMethod('updateWidget');
} on PlatformException catch (e) {
Log.print("Failed to update widget: '${e.message}'.");
}
}
}
// Example usage:
void updateHomeScreenWidget() {
WidgetManager.updateWidget();
}

View File

@@ -0,0 +1,111 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:Intaleq/print.dart';
class SignalingService {
WebSocket? _socket;
final String _url = "wss://calls.intaleqapp.com/ws";
// Callbacks
Function(List<dynamic> iceServers)? onConnected;
Function(String reason)? onDisconnected;
Function(Map<String, dynamic> offer)? onOffer;
Function(Map<String, dynamic> answer)? onAnswer;
Function(Map<String, dynamic> candidate)? onIceCandidate;
Function(String reason)? onCallEnded;
Function()? onParticipantJoined;
bool get isConnected => _socket != null && _socket!.readyState == WebSocket.open;
Future<void> connect(String sessionId, String userId) async {
if (isConnected) return;
try {
Log.print("Signaling: Connecting to $_url");
_socket = await WebSocket.connect(_url)
.timeout(const Duration(seconds: 8));
_socket!.listen(
(data) {
_handleMessage(data);
},
onError: (err) {
Log.print("Signaling socket error: $err");
disconnect("socket_error");
},
onDone: () {
Log.print("Signaling socket closed by server");
disconnect("socket_closed");
},
cancelOnError: true,
);
// Send the authenticate message as the first message
send("authenticate", {
"session_id": sessionId,
"user_id": userId,
});
} catch (e) {
Log.print("Signaling connection failed: $e");
onDisconnected?.call("connection_failed");
}
}
void _handleMessage(dynamic data) {
try {
Log.print("Signaling received raw: $data");
final message = jsonDecode(data);
if (message is! Map<String, dynamic>) return;
final type = message['type'];
switch (type) {
case 'authenticated':
final iceServers = message['ice_servers'] as List<dynamic>? ?? [];
onConnected?.call(iceServers);
break;
case 'participant_joined':
onParticipantJoined?.call();
break;
case 'offer':
if (message['sdp'] != null) {
onOffer?.call(message['sdp']);
}
break;
case 'answer':
if (message['sdp'] != null) {
onAnswer?.call(message['sdp']);
}
break;
case 'ice_candidate':
if (message['candidate'] != null) {
onIceCandidate?.call(message['candidate']);
}
break;
case 'call_ended':
onCallEnded?.call(message['reason'] ?? 'normal');
break;
}
} catch (e) {
Log.print("Error handling signaling message: $e");
}
}
void send(String type, Map<String, dynamic> data) {
if (!isConnected) return;
final msg = jsonEncode({
'type': type,
...data,
});
Log.print("Signaling sending: $msg");
_socket!.add(msg);
}
void disconnect([String reason = "user_hangup"]) {
if (_socket != null) {
_socket!.close();
_socket = null;
onDisconnected?.call(reason);
}
}
}

View File

@@ -1,4 +1,4 @@
import 'package:Intaleq/controller/home/map_passenger_controller.dart'; import 'package:Intaleq/controller/home/map/ride_lifecycle_controller.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart'; import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -40,7 +40,7 @@ class RateDriverFromPassenger extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
Text( Text(
'${'Total price to '.tr}${Get.find<MapPassengerController>().driverName}', '${'Total price to '.tr}${Get.find<RideLifecycleController>().driverName}',
style: AppStyle.title, style: AppStyle.title,
), ),
Row( Row(
@@ -189,7 +189,7 @@ class RateDriverFromPassenger extends StatelessWidget {
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderSide: BorderSide( borderSide: BorderSide(
color: color:
AppColor.grayColor.withOpacity(0.5)), // Customize the border color AppColor.grayColor.withValues(alpha: 0.5)), // Customize the border color
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderSide: BorderSide( borderSide: BorderSide(

View File

@@ -410,7 +410,7 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
final rawPhone = _phoneController.text.trim().replaceFirst('+', ''); final rawPhone = _phoneController.text.trim().replaceFirst('+', '');
final success = await PhoneAuthHelper.sendOtp(rawPhone); final success = await PhoneAuthHelper.sendOtp(rawPhone);
if (success && mounted) { if (success && mounted) {
await PhoneAuthHelper.verifyOtp(rawPhone); Get.to(() => OtpVerificationScreen(phoneNumber: rawPhone));
} }
if (mounted) setState(() => _isLoading = false); if (mounted) setState(() => _isLoading = false);
} }
@@ -537,7 +537,7 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
void _submit() async { void _submit() async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
setState(() => _isLoading = true); setState(() => _isLoading = true);
// Logic for OTP verification here await PhoneAuthHelper.verifyOtp(widget.phoneNumber, _otpController.text.trim());
if (mounted) setState(() => _isLoading = false); if (mounted) setState(() => _isLoading = false);
} }
} }
@@ -596,7 +596,7 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
controller: _otpController, controller: _otpController,
textAlign: TextAlign.center, textAlign: TextAlign.center,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
maxLength: 5, maxLength: 3,
style: TextStyle( style: TextStyle(
fontSize: 30, fontSize: 30,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
@@ -605,7 +605,7 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
), ),
decoration: InputDecoration( decoration: InputDecoration(
counterText: '', counterText: '',
hintText: '·····', hintText: '···',
hintStyle: TextStyle( hintStyle: TextStyle(
color: isDark ? Colors.white12 : const Color(0xFFD1D5DB), color: isDark ? Colors.white12 : const Color(0xFFD1D5DB),
letterSpacing: 20, letterSpacing: 20,
@@ -615,7 +615,7 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
border: InputBorder.none, border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 6), contentPadding: const EdgeInsets.symmetric(vertical: 6),
), ),
validator: (v) => v == null || v.length < 5 ? '' : null, validator: (v) => v == null || v.length < 3 ? '' : null,
), ),
), ),
), ),
@@ -625,10 +625,10 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
ValueListenableBuilder<TextEditingValue>( ValueListenableBuilder<TextEditingValue>(
valueListenable: _otpController, valueListenable: _otpController,
builder: (_, value, __) { builder: (_, value, __) {
final filled = value.text.length.clamp(0, 5); final filled = value.text.length.clamp(0, 3);
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(5, (i) { children: List.generate(3, (i) {
final active = i < filled; final active = i < filled;
return AnimatedContainer( return AnimatedContainer(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),

View File

@@ -23,9 +23,9 @@ class OtpVerificationPage extends StatefulWidget {
class _OtpVerificationPageState extends State<OtpVerificationPage> { class _OtpVerificationPageState extends State<OtpVerificationPage> {
late final OtpVerificationController controller; late final OtpVerificationController controller;
final List<FocusNode> _focusNodes = List.generate(6, (index) => FocusNode()); final List<FocusNode> _focusNodes = List.generate(3, (index) => FocusNode());
final List<TextEditingController> _textControllers = final List<TextEditingController> _textControllers =
List.generate(5, (index) => TextEditingController()); List.generate(3, (index) => TextEditingController());
@override @override
void initState() { void initState() {
@@ -50,7 +50,7 @@ class _OtpVerificationPageState extends State<OtpVerificationPage> {
void _onOtpChanged(String value, int index) { void _onOtpChanged(String value, int index) {
if (value.isNotEmpty) { if (value.isNotEmpty) {
if (index < 5) { if (index < 2) {
_focusNodes[index + 1].requestFocus(); _focusNodes[index + 1].requestFocus();
} else { } else {
_focusNodes[index].unfocus(); // إلغاء التركيز بعد آخر حقل _focusNodes[index].unfocus(); // إلغاء التركيز بعد آخر حقل
@@ -67,7 +67,7 @@ class _OtpVerificationPageState extends State<OtpVerificationPage> {
textDirection: TextDirection.ltr, // لضمان ترتيب الحقول من اليسار لليمين textDirection: TextDirection.ltr, // لضمان ترتيب الحقول من اليسار لليمين
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(5, (index) { children: List.generate(3, (index) {
return SizedBox( return SizedBox(
width: 45, width: 45,
height: 55, height: 55,

View File

@@ -3,8 +3,13 @@ import 'package:get/get.dart';
import '../../constant/box_name.dart'; import '../../constant/box_name.dart';
import '../../constant/colors.dart'; import '../../constant/colors.dart';
import '../../controller/functions/crud.dart'; import '../../controller/functions/crud.dart';
import '../../controller/functions/package_info.dart'; import '../../controller/home/map/map_socket_controller.dart';
import '../../controller/home/map_passenger_controller.dart'; import '../../controller/home/map/map_engine_controller.dart';
import '../../controller/home/map/location_search_controller.dart';
import '../../controller/home/map/nearby_drivers_controller.dart';
import '../../controller/home/map/ride_lifecycle_controller.dart';
import '../../controller/home/map/ui_interactions_controller.dart';
import '../../controller/home/map/ride_state.dart';
import '../../main.dart'; import '../../main.dart';
import '../../views/home/map_widget.dart/ride_begin_passenger.dart'; import '../../views/home/map_widget.dart/ride_begin_passenger.dart';
@@ -17,7 +22,7 @@ import 'map_widget.dart/google_map_passenger_widget.dart';
import 'map_widget.dart/left_main_menu_icons.dart'; import 'map_widget.dart/left_main_menu_icons.dart';
import 'map_widget.dart/main_bottom_menu_map.dart'; import 'map_widget.dart/main_bottom_menu_map.dart';
import 'map_widget.dart/map_menu_widget.dart'; import 'map_widget.dart/map_menu_widget.dart';
import 'map_widget.dart/menu_map_page.dart'; import '../../controller/functions/package_info.dart';
import 'map_widget.dart/passengerRideLoctionWidget.dart'; import 'map_widget.dart/passengerRideLoctionWidget.dart';
import 'map_widget.dart/payment_method.page.dart'; import 'map_widget.dart/payment_method.page.dart';
import 'map_widget.dart/points_page_for_rider.dart'; import 'map_widget.dart/points_page_for_rider.dart';
@@ -30,9 +35,14 @@ class MapPagePassenger extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Get.put(MapPassengerController()); Get.find<MapSocketController>();
Get.put(MyMenuController()); Get.find<MapEngineController>();
Get.put(CRUD()); Get.find<LocationSearchController>();
Get.find<NearbyDriversController>();
Get.find<RideLifecycleController>();
Get.find<UiInteractionsController>();
Get.find<MyMenuController>();
Get.find<CRUD>();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
checkForUpdate(context); checkForUpdate(context);
}); });
@@ -118,7 +128,7 @@ class CancelRidePageShow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>( return GetBuilder<RideLifecycleController>(
builder: (controller) { builder: (controller) {
// نستخدم RideState Enum لأنه أدق، أو نصلح المنطق النصي // نستخدم RideState Enum لأنه أدق، أو نصلح المنطق النصي
// الشرط: // الشرط:
@@ -175,7 +185,7 @@ class PickerIconOnMap extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>( return GetBuilder<RideLifecycleController>(
builder: (controller) => controller.isPickerShown builder: (controller) => controller.isPickerShown
? Positioned( ? Positioned(
bottom: Get.height * .2, bottom: Get.height * .2,

View File

@@ -1,7 +1,9 @@
import 'package:Intaleq/constant/colors.dart'; import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/links.dart'; import 'package:Intaleq/constant/links.dart';
import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/constant/style.dart';
import 'package:Intaleq/controller/home/map_passenger_controller.dart'; import 'package:Intaleq/controller/home/map/ride_lifecycle_controller.dart';
import 'package:Intaleq/controller/home/map/ride_state.dart';
import 'package:Intaleq/controller/voice_call_controller.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -10,6 +12,7 @@ import 'package:intl/intl.dart';
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../controller/firebase/notification_service.dart'; import '../../../controller/firebase/notification_service.dart';
import '../../../controller/functions/launch.dart'; import '../../../controller/functions/launch.dart';
import '../../../controller/functions/crud.dart';
import '../../../main.dart'; import '../../../main.dart';
class ApplyOrderWidget extends StatelessWidget { class ApplyOrderWidget extends StatelessWidget {
@@ -29,7 +32,7 @@ class ApplyOrderWidget extends StatelessWidget {
} }
return Obx(() { return Obx(() {
final controller = Get.find<MapPassengerController>(); final controller = Get.find<RideLifecycleController>();
final bool isVisible = final bool isVisible =
controller.currentRideState.value == RideState.driverApplied || controller.currentRideState.value == RideState.driverApplied ||
@@ -57,7 +60,7 @@ class ApplyOrderWidget extends StatelessWidget {
), ),
// تغيير: تقليل الحواف الخارجية بشكل كبير // تغيير: تقليل الحواف الخارجية بشكل كبير
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: GetBuilder<MapPassengerController>( child: GetBuilder<RideLifecycleController>(
builder: (c) { builder: (c) {
return Column( return Column(
mainAxisSize: mainAxisSize:
@@ -106,15 +109,18 @@ class ApplyOrderWidget extends StatelessWidget {
// [NEW] 1. صف الرأس المضغوط (يحتوي الحالة + الإحصائيات + السعر) // [NEW] 1. صف الرأس المضغوط (يحتوي الحالة + الإحصائيات + السعر)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
Widget _buildCompactHeaderRow( Widget _buildCompactHeaderRow(
BuildContext context, MapPassengerController controller) { BuildContext context, RideLifecycleController controller) {
// تنسيق السعر // تنسيق السعر
final formatter = NumberFormat("#,###"); final formatter = NumberFormat("#,###");
String formattedPrice = formatter.format(controller.totalPassenger); String formattedPrice = formatter.format(controller.totalPassenger);
// حساب الدقائق // حساب الدقائق من الوقت المتبقي الحي، وليس ETA الأصلي فقط.
int minutes = final int secondsToPassenger =
(controller.timeToPassengerFromDriverAfterApplied / 60).ceil(); controller.remainingTimeToPassengerFromDriverAfterApplied > 0
if (minutes < 1) minutes = 1; ? controller.remainingTimeToPassengerFromDriverAfterApplied
: controller.timeToPassengerFromDriverAfterApplied;
final int minutes =
secondsToPassenger <= 0 ? 0 : (secondsToPassenger / 60).ceil();
// تنسيق المسافة // تنسيق المسافة
String distanceDisplay = ""; String distanceDisplay = "";
@@ -151,7 +157,7 @@ class ApplyOrderWidget extends StatelessWidget {
children: [ children: [
_buildMiniStatChip( _buildMiniStatChip(
icon: Icons.access_time_filled_rounded, icon: Icons.access_time_filled_rounded,
text: "$minutes ${'min'.tr}", text: minutes > 0 ? "$minutes ${'min'.tr}" : "--",
color: AppColor.primaryColor, color: AppColor.primaryColor,
bgColor: AppColor.primaryColor.withOpacity(0.1), bgColor: AppColor.primaryColor.withOpacity(0.1),
), ),
@@ -229,7 +235,7 @@ class ApplyOrderWidget extends StatelessWidget {
// [MODIFIED] 2. كرت المعلومات المضغوط جداً // [MODIFIED] 2. كرت المعلومات المضغوط جداً
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
Widget _buildCompactInfoCard(BuildContext context, Widget _buildCompactInfoCard(BuildContext context,
MapPassengerController controller, Color Function(String) parseColor) { RideLifecycleController controller, Color Function(String) parseColor) {
return Container( return Container(
// تقليل الحواف الداخلية للكرت // تقليل الحواف الداخلية للكرت
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
@@ -312,7 +318,7 @@ class ApplyOrderWidget extends StatelessWidget {
} }
Widget _buildMicroCarIcon( Widget _buildMicroCarIcon(
MapPassengerController controller, Color Function(String) parseColor) { RideLifecycleController controller, Color Function(String) parseColor) {
Color carColor = parseColor(controller.colorHex); Color carColor = parseColor(controller.colorHex);
return Container( return Container(
height: 40, // تصغير من 50 height: 40, // تصغير من 50
@@ -343,9 +349,8 @@ class ApplyOrderWidget extends StatelessWidget {
color: Get.isDarkMode ? Colors.grey[850] : const Color(0xFFF5F5F5), color: Get.isDarkMode ? Colors.grey[850] : const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
border: Border.all( border: Border.all(
color: Get.isDarkMode color:
? Colors.white10 Get.isDarkMode ? Colors.white10 : Colors.grey.withOpacity(0.3)),
: Colors.grey.withOpacity(0.3)),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -374,7 +379,7 @@ class ApplyOrderWidget extends StatelessWidget {
// [MODIFIED] 3. أزرار الاتصال (Slim Buttons) // [MODIFIED] 3. أزرار الاتصال (Slim Buttons)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
Widget _buildCompactButtonsRow( Widget _buildCompactButtonsRow(
BuildContext context, MapPassengerController controller) { BuildContext context, RideLifecycleController controller) {
return SizedBox( return SizedBox(
height: 40, // تحديد ارتفاع ثابت وصغير للأزرار height: 40, // تحديد ارتفاع ثابت وصغير للأزرار
child: Row( child: Row(
@@ -397,7 +402,7 @@ class ApplyOrderWidget extends StatelessWidget {
bgColor: AppColor.greenColor, bgColor: AppColor.greenColor,
onTap: () { onTap: () {
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
makePhoneCall(controller.driverPhone); _showCallSelectionDialog(context, controller);
}, },
isPrimary: true, isPrimary: true,
), ),
@@ -407,6 +412,73 @@ class ApplyOrderWidget extends StatelessWidget {
); );
} }
void _showCallSelectionDialog(
BuildContext context, RideLifecycleController controller) {
Get.dialog(
Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Call Options'.tr,
style: AppStyle.title
.copyWith(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
Text(
'Choose how you want to call the driver'.tr,
style: const TextStyle(color: Colors.grey, fontSize: 14),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
ListTile(
leading: CircleAvatar(
backgroundColor: AppColor.greenColor.withOpacity(0.1),
child: Icon(Icons.phone_android_rounded,
color: AppColor.greenColor),
),
title: Text('Standard Call'.tr,
style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text('Uses cellular network'.tr,
style: const TextStyle(fontSize: 12)),
onTap: () {
Get.back();
makePhoneCall(controller.driverPhone);
},
),
const Divider(),
ListTile(
leading: CircleAvatar(
backgroundColor: AppColor.primaryColor.withOpacity(0.1),
child: Icon(Icons.wifi_calling_3_rounded,
color: AppColor.primaryColor),
),
title: Text('Free Call'.tr,
style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text('Voice call over internet'.tr,
style: const TextStyle(fontSize: 12)),
onTap: () {
Get.back();
final voiceCtrl = Get.find<VoiceCallController>();
final passengerId = box.read(BoxName.passengerID).toString();
voiceCtrl.startCall(
rideIdVal: controller.rideId,
driverId: controller.driverId,
passengerId: passengerId,
remoteNameVal: controller.driverName,
);
},
),
],
),
),
),
);
}
Widget _buildSlimButton({ Widget _buildSlimButton({
required String label, required String label,
required IconData icon, required IconData icon,
@@ -444,7 +516,7 @@ class ApplyOrderWidget extends StatelessWidget {
// --- النوافذ المنبثقة للرسائل (نفس الكود السابق مع تحسين بسيط) --- // --- النوافذ المنبثقة للرسائل (نفس الكود السابق مع تحسين بسيط) ---
void _showContactOptionsDialog( void _showContactOptionsDialog(
BuildContext context, MapPassengerController controller) { BuildContext context, RideLifecycleController controller) {
Get.bottomSheet( Get.bottomSheet(
Container( Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
@@ -470,7 +542,7 @@ class ApplyOrderWidget extends StatelessWidget {
); );
} }
List<Widget> _buildPredefinedMessages(MapPassengerController controller) { List<Widget> _buildPredefinedMessages(RideLifecycleController controller) {
const messages = [ const messages = [
'Hello, I\'m at the agreed-upon location', 'Hello, I\'m at the agreed-upon location',
'I\'m waiting for you', 'I\'m waiting for you',
@@ -510,7 +582,7 @@ class ApplyOrderWidget extends StatelessWidget {
} }
Widget _buildCustomMessageInput( Widget _buildCustomMessageInput(
MapPassengerController controller, BuildContext context) { RideLifecycleController controller, BuildContext context) {
return Row( return Row(
children: [ children: [
Expanded( Expanded(
@@ -555,7 +627,22 @@ class ApplyOrderWidget extends StatelessWidget {
); );
} }
void _sendMessage(MapPassengerController controller, String text) { void _sendMessage(RideLifecycleController controller, String text) async {
try {
await CRUD().post(
link: AppLink.sendChatMessage,
payload: {
'ride_id': controller.rideId.toString(),
'sender_id': box.read(BoxName.passengerID).toString(),
'receiver_id': controller.driverId.toString(),
'sender_type': 'passenger',
'message_content': text.tr,
},
);
} catch (e) {
// Ignore or log error
}
NotificationService.sendNotification( NotificationService.sendNotification(
category: 'MSG_FROM_PASSENGER', category: 'MSG_FROM_PASSENGER',
target: controller.driverToken.toString(), target: controller.driverToken.toString(),
@@ -577,7 +664,7 @@ class DriverArrivePassengerAndWaitMinute extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) { return GetBuilder<RideLifecycleController>(builder: (controller) {
return Column( return Column(
children: [ children: [
Row( Row(
@@ -619,7 +706,7 @@ class TimeDriverToPassenger extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) { return GetBuilder<RideLifecycleController>(builder: (controller) {
if (controller.timeToPassengerFromDriverAfterApplied <= 0) { if (controller.timeToPassengerFromDriverAfterApplied <= 0) {
return const SizedBox(); return const SizedBox();
} }

View File

@@ -3,11 +3,11 @@ import 'package:get/get.dart';
import 'package:Intaleq/controller/payment/payment_controller.dart'; import 'package:Intaleq/controller/payment/payment_controller.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart';
GetBuilder<MapPassengerController> buttomSheetMapPage() { GetBuilder<RideLifecycleController> buttomSheetMapPage() {
Get.put(PaymentController()); Get.put(PaymentController());
return GetBuilder<MapPassengerController>( return GetBuilder<RideLifecycleController>(
builder: (controller) => builder: (controller) =>
controller.isBottomSheetShown && controller.rideConfirm == false controller.isBottomSheetShown && controller.rideConfirm == false
? const Positioned( ? const Positioned(
@@ -508,7 +508,7 @@ class Details extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>( return GetBuilder<RideLifecycleController>(
builder: (controller) => Column( builder: (controller) => Column(
children: [ children: [
Row( Row(

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:Intaleq/constant/colors.dart'; import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/constant/style.dart';
import 'package:Intaleq/controller/home/map_passenger_controller.dart'; import 'package:Intaleq/controller/home/map/ride_lifecycle_controller.dart';
import '../../widgets/elevated_btn.dart'; import '../../widgets/elevated_btn.dart';
// دالة لإظهار الشيت // دالة لإظهار الشيت
@@ -21,7 +21,7 @@ class CancelRidePageWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// تأكد من وجود الكنترولر // تأكد من وجود الكنترولر
final controller = Get.find<MapPassengerController>(); final controller = Get.find<RideLifecycleController>();
final List<String> reasons = [ final List<String> reasons = [
"Changed my mind".tr, "Changed my mind".tr,
@@ -39,7 +39,7 @@ class CancelRidePageWidget extends StatelessWidget {
color: AppColor.secondaryColor, color: AppColor.secondaryColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(25)), borderRadius: const BorderRadius.vertical(top: Radius.circular(25)),
), ),
child: GetBuilder<MapPassengerController>( child: GetBuilder<RideLifecycleController>(
builder: (controller) => Column( builder: (controller) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [

View File

@@ -12,7 +12,7 @@ import 'dart:ui';
import '../../../constant/info.dart'; import '../../../constant/info.dart';
import '../../../controller/functions/tts.dart'; import '../../../controller/functions/tts.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../widgets/mydialoug.dart'; import '../../widgets/mydialoug.dart';
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
@@ -62,7 +62,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
CarDetailsTypeToChoose({super.key}); CarDetailsTypeToChoose({super.key});
final textToSpeechController = Get.find<TextToSpeechController>(); final textToSpeechController = Get.find<TextToSpeechController>();
void _prepareCarTypes(MapPassengerController controller) { void _prepareCarTypes(RideLifecycleController controller) {
if (controller.distance > 23) { if (controller.distance > 23) {
if (!carTypes.any((car) => car.carType == 'Rayeh Gai')) { if (!carTypes.any((car) => car.carType == 'Rayeh Gai')) {
carTypes.add(CarType( carTypes.add(CarType(
@@ -77,7 +77,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) { return GetBuilder<RideLifecycleController>(builder: (controller) {
_prepareCarTypes(controller); _prepareCarTypes(controller);
if (!(controller.isBottomSheetShown) && controller.rideConfirm == false) { if (!(controller.isBottomSheetShown) && controller.rideConfirm == false) {
@@ -170,7 +170,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
// HEADER // HEADER
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
Widget _buildHeader(MapPassengerController controller) { Widget _buildHeader(RideLifecycleController controller) {
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(22, 4, 22, 8), padding: const EdgeInsets.fromLTRB(22, 4, 22, 8),
child: Column( child: Column(
@@ -290,7 +290,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
// CAR CARD // CAR CARD
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
Widget _buildCarCard(BuildContext context, MapPassengerController controller, Widget _buildCarCard(BuildContext context, RideLifecycleController controller,
CarType carType, bool isSelected, int index) { CarType carType, bool isSelected, int index) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
@@ -437,7 +437,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
// PROMO BUTTON // PROMO BUTTON
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
Widget _buildPromoButton( Widget _buildPromoButton(
BuildContext context, MapPassengerController controller) { BuildContext context, RideLifecycleController controller) {
if (controller.promoTaken) return const SizedBox.shrink(); if (controller.promoTaken) return const SizedBox.shrink();
return Padding( return Padding(
@@ -511,7 +511,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
// NEGATIVE BALANCE WARNING // NEGATIVE BALANCE WARNING
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
Widget _buildNegativeBalanceWarning(MapPassengerController controller) { Widget _buildNegativeBalanceWarning(RideLifecycleController controller) {
final passengerWallet = final passengerWallet =
double.tryParse(box.read(BoxName.passengerWalletTotal) ?? '0.0') ?? 0.0; double.tryParse(box.read(BoxName.passengerWalletTotal) ?? '0.0') ?? 0.0;
if (passengerWallet < 0.0) { if (passengerWallet < 0.0) {
@@ -556,7 +556,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
// PRICING HELPERS (Unchanged logic) // PRICING HELPERS (Unchanged logic)
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
String _getPassengerPriceText( String _getPassengerPriceText(
CarType carType, MapPassengerController mapPassengerController) { CarType carType, RideLifecycleController mapPassengerController) {
double rawPrice; double rawPrice;
switch (carType.carType) { switch (carType.carType) {
case 'Comfort': case 'Comfort':
@@ -596,7 +596,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
// DIALOGS // DIALOGS
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
void _showPromoCodeDialog( void _showPromoCodeDialog(
BuildContext context, MapPassengerController controller) { BuildContext context, RideLifecycleController controller) {
Get.dialog( Get.dialog(
Dialog( Dialog(
shape: shape:
@@ -671,7 +671,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
void _showCarDetailsDialog( void _showCarDetailsDialog(
BuildContext context, BuildContext context,
MapPassengerController mapPassengerController, RideLifecycleController mapPassengerController,
CarType carType, CarType carType,
TextToSpeechController textToSpeechController) { TextToSpeechController textToSpeechController) {
Get.dialog( Get.dialog(
@@ -843,7 +843,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
// LOGIC HELPERS (Unchanged) // LOGIC HELPERS (Unchanged)
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
String _getCarDescription( String _getCarDescription(
MapPassengerController mapPassengerController, CarType carType) { RideLifecycleController mapPassengerController, CarType carType) {
switch (carType.carType) { switch (carType.carType) {
case 'Comfort': case 'Comfort':
return mapPassengerController.endNameAddress return mapPassengerController.endNameAddress
@@ -881,7 +881,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
} }
void _handleCarSelection(BuildContext context, void _handleCarSelection(BuildContext context,
MapPassengerController mapPassengerController, CarType carType) { RideLifecycleController mapPassengerController, CarType carType) {
box.write(BoxName.carType, carType.carType); box.write(BoxName.carType, carType.carType);
mapPassengerController.totalPassenger = mapPassengerController.totalPassenger =
_getOriginalPrice(carType, mapPassengerController); _getOriginalPrice(carType, mapPassengerController);
@@ -932,7 +932,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
} }
double _getOriginalPrice( double _getOriginalPrice(
CarType carType, MapPassengerController mapPassengerController) { CarType carType, RideLifecycleController mapPassengerController) {
switch (carType.carType) { switch (carType.carType) {
case 'Comfort': case 'Comfort':
return mapPassengerController.totalPassengerComfort; return mapPassengerController.totalPassengerComfort;
@@ -953,7 +953,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
Widget _buildRayehGaiOption( Widget _buildRayehGaiOption(
BuildContext context, BuildContext context,
MapPassengerController mapPassengerController, RideLifecycleController mapPassengerController,
String carTypeName, String carTypeName,
double price) { double price) {
return GestureDetector( return GestureDetector(
@@ -983,7 +983,7 @@ class BurcMoney extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>( return GetBuilder<RideLifecycleController>(
builder: (mapPassengerController) { builder: (mapPassengerController) {
final passengerWallet = final passengerWallet =
double.tryParse(box.read(BoxName.passengerWalletTotal) ?? '0.0') ?? double.tryParse(box.read(BoxName.passengerWalletTotal) ?? '0.0') ??

View File

@@ -6,7 +6,7 @@ import 'package:Intaleq/views/home/my_wallet/passenger_wallet.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/info.dart'; import '../../../constant/info.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../controller/payment/payment_controller.dart'; import '../../../controller/payment/payment_controller.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../widgets/elevated_btn.dart'; import '../../widgets/elevated_btn.dart';
@@ -17,7 +17,7 @@ class CashConfirmPageShown extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) { return GetBuilder<RideLifecycleController>(builder: (controller) {
// شرط الإظهار الرئيسي لم يتغير // شرط الإظهار الرئيسي لم يتغير
return Positioned( return Positioned(
bottom: 0, bottom: 0,

View File

@@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart';
import 'hexegone_clipper.dart'; import 'hexegone_clipper.dart';
GetBuilder<MapPassengerController> hexagonClipper() { GetBuilder<RideLifecycleController> hexagonClipper() {
return GetBuilder<MapPassengerController>( return GetBuilder<RideLifecycleController>(
builder: ((controller) => controller.rideConfirm builder: ((controller) => controller.rideConfirm
? Positioned( ? Positioned(
top: Get.height * .1, top: Get.height * .1,

View File

@@ -4,14 +4,14 @@ import 'package:intl/intl.dart';
// import 'package:intl/intl.dart'; // import 'package:intl/intl.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart';
class DriverTimeArrivePassengerPage extends StatelessWidget { class DriverTimeArrivePassengerPage extends StatelessWidget {
const DriverTimeArrivePassengerPage({super.key}); const DriverTimeArrivePassengerPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>( return GetBuilder<RideLifecycleController>(
builder: (controller) { builder: (controller) {
return controller.remainingTime == 0 return controller.remainingTime == 0
? Positioned( ? Positioned(

View File

@@ -12,54 +12,51 @@ import 'package:Intaleq/views/widgets/elevated_btn.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/functions/toast.dart'; import '../../../controller/functions/toast.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../controller/home/map/ride_state.dart';
import '../../../main.dart'; import '../../../main.dart';
// --------------------------------------------------- // ---------------------------------------------------
// -- Widget for Destination Point Search (Optimized) -- // -- Widget for Destination Point Search (Optimized) --
// --------------------------------------------------- // ---------------------------------------------------
/// A more optimized and cleaner implementation of the destination search form. GetBuilder<LocationSearchController> formSearchPlacesDestenation() {
///
/// Improvements:
/// 1. **Widget Refactoring**: The UI is broken down into smaller, focused widgets
/// (_SearchField, _QuickActions, _SearchResults) to prevent unnecessary rebuilds.
/// 2. **State Management Scoping**: `GetBuilder` is used only on widgets that
/// actually need to update, not the entire form.
/// 3. **Reduced Build Logic**: Logic like reading from `box` is done once.
/// 4. **Readability**: Code is cleaner and easier to follow.
GetBuilder<MapPassengerController> formSearchPlacesDestenation() {
// --- [تحسين] قراءة القيم مرة واحدة في بداية البناء ---
// Store box values in local variables to avoid repeated calls inside the build method.
final String addWorkValue = final String addWorkValue =
box.read(BoxName.addWork)?.toString() ?? 'addWork'; box.read(BoxName.addWork)?.toString() ?? 'addWork';
final String addHomeValue = final String addHomeValue =
box.read(BoxName.addHome)?.toString() ?? 'addHome'; box.read(BoxName.addHome)?.toString() ?? 'addHome';
// --- [ملاحظة] تأكد من أن القيم الأولية موجودة ---
// This initialization can be moved to your app's startup logic or a splash screen controller.
if (addWorkValue.isEmpty || addHomeValue.isEmpty) { if (addWorkValue.isEmpty || addHomeValue.isEmpty) {
box.write(BoxName.addWork, 'addWork'); box.write(BoxName.addWork, 'addWork');
box.write(BoxName.addHome, 'addHome'); box.write(BoxName.addHome, 'addHome');
} }
return GetBuilder<MapPassengerController>( return GetBuilder<LocationSearchController>(
id: 'destination_form', // Use an ID to allow targeted updates id: 'destination_form',
builder: (controller) { builder: (controller) {
final mapEngine = Get.find<MapEngineController>();
final rideLifecycle = Get.find<RideLifecycleController>();
return Column( return Column(
children: [ children: [
// --- Widget for the search text field --- _SearchField(
_SearchField(controller: controller), controller: controller,
mapEngine: mapEngine,
// --- Widget for "Add Work" and "Add Home" buttons --- rideLifecycle: rideLifecycle,
),
_QuickActions( _QuickActions(
controller: controller, controller: controller,
mapEngine: mapEngine,
rideLifecycle: rideLifecycle,
addWorkValue: addWorkValue, addWorkValue: addWorkValue,
addHomeValue: addHomeValue, addHomeValue: addHomeValue,
), ),
_SearchResults(
// --- Widget for displaying search results, wrapped in its own GetBuilder --- controller: controller,
_SearchResults(), mapEngine: mapEngine,
rideLifecycle: rideLifecycle,
),
], ],
); );
}, },
@@ -70,11 +67,16 @@ GetBuilder<MapPassengerController> formSearchPlacesDestenation() {
// -- Private Helper Widgets for Cleaner Code -- // -- Private Helper Widgets for Cleaner Code --
// --------------------------------------------------- // ---------------------------------------------------
/// A dedicated widget for the search input field.
class _SearchField extends StatefulWidget { class _SearchField extends StatefulWidget {
final MapPassengerController controller; final LocationSearchController controller;
final MapEngineController mapEngine;
final RideLifecycleController rideLifecycle;
const _SearchField({required this.controller}); const _SearchField({
required this.controller,
required this.mapEngine,
required this.rideLifecycle,
});
@override @override
State<_SearchField> createState() => _SearchFieldState(); State<_SearchField> createState() => _SearchFieldState();
@@ -83,7 +85,6 @@ class _SearchField extends StatefulWidget {
class _SearchFieldState extends State<_SearchField> { class _SearchFieldState extends State<_SearchField> {
Timer? _debounce; Timer? _debounce;
// --- [إصلاح] Listener لتحديث الواجهة عند تغيير النص لإظهار/إخفاء زر المسح ---
void _onTextChanged() { void _onTextChanged() {
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
@@ -93,20 +94,18 @@ class _SearchFieldState extends State<_SearchField> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Add listener to update the suffix icon when text changes
widget.controller.placeDestinationController.addListener(_onTextChanged); widget.controller.placeDestinationController.addListener(_onTextChanged);
} }
// --- [تحسين] إضافة Debouncer لتأخير البحث أثناء الكتابة ---
void _onSearchChanged(String query) { void _onSearchChanged(String query) {
if (_debounce?.isActive ?? false) _debounce!.cancel(); if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 500), () { _debounce = Timer(const Duration(milliseconds: 500), () {
if (query.length > 2) { if (query.length > 2) {
widget.controller.getPlaces(); widget.controller.getPlaces();
widget.controller.changeHeightPlaces(); widget.mapEngine.changeHeightPlaces();
} else if (query.isEmpty) { } else if (query.isEmpty) {
widget.controller.clearPlacesDestination(); widget.controller.clearPlacesDestination();
widget.controller.changeHeightPlaces(); widget.mapEngine.changeHeightPlaces();
} }
}); });
} }
@@ -114,7 +113,6 @@ class _SearchFieldState extends State<_SearchField> {
@override @override
void dispose() { void dispose() {
_debounce?.cancel(); _debounce?.cancel();
// Remove the listener to prevent memory leaks
widget.controller.placeDestinationController.removeListener(_onTextChanged); widget.controller.placeDestinationController.removeListener(_onTextChanged);
super.dispose(); super.dispose();
} }
@@ -133,18 +131,15 @@ class _SearchFieldState extends State<_SearchField> {
hintText: widget.controller.hintTextDestinationPoint, hintText: widget.controller.hintTextDestinationPoint,
hintStyle: AppStyle.subtitle.copyWith(color: Colors.grey[600]), hintStyle: AppStyle.subtitle.copyWith(color: Colors.grey[600]),
prefixIcon: Icon(Icons.search, color: AppColor.primaryColor), prefixIcon: Icon(Icons.search, color: AppColor.primaryColor),
// --- [إصلاح] تم استبدال Obx بشرط بسيط لأن `setState` يعيد بناء الواجهة الآن ---
suffixIcon: widget suffixIcon: widget
.controller.placeDestinationController.text.isNotEmpty .controller.placeDestinationController.text.isNotEmpty
? IconButton( ? IconButton(
icon: Icon(Icons.clear, color: Colors.grey[400]), icon: Icon(Icons.clear, color: Colors.grey[400]),
onPressed: () { onPressed: () {
widget.controller.placeDestinationController.clear(); widget.controller.placeDestinationController.clear();
// The listener will automatically handle the UI update
// And _onSearchChanged will handle clearing the results
}, },
) )
: null, // Use null instead of SizedBox for better practice : null,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 10.0), horizontal: 16.0, vertical: 10.0),
border: OutlineInputBorder( border: OutlineInputBorder(
@@ -163,12 +158,12 @@ class _SearchFieldState extends State<_SearchField> {
const SizedBox(width: 8.0), const SizedBox(width: 8.0),
IconButton( IconButton(
onPressed: () { onPressed: () {
widget.controller.changeMainBottomMenuMap(); widget.mapEngine.changeMainBottomMenuMap();
widget.controller.changePickerShown(); widget.mapEngine.changePickerShown();
}, },
icon: Icon(Icons.location_on_outlined, icon: Icon(Icons.location_on_outlined,
color: AppColor.accentColor, size: 30), color: AppColor.accentColor, size: 30),
tooltip: widget.controller.isAnotherOreder tooltip: widget.rideLifecycle.isAnotherOreder
? 'Pick destination on map'.tr ? 'Pick destination on map'.tr
: 'Pick on map'.tr, : 'Pick on map'.tr,
), ),
@@ -178,14 +173,17 @@ class _SearchFieldState extends State<_SearchField> {
} }
} }
/// A dedicated widget for the quick action buttons (Work/Home).
class _QuickActions extends StatelessWidget { class _QuickActions extends StatelessWidget {
final MapPassengerController controller; final LocationSearchController controller;
final MapEngineController mapEngine;
final RideLifecycleController rideLifecycle;
final String addWorkValue; final String addWorkValue;
final String addHomeValue; final String addHomeValue;
const _QuickActions({ const _QuickActions({
required this.controller, required this.controller,
required this.mapEngine,
required this.rideLifecycle,
required this.addWorkValue, required this.addWorkValue,
required this.addHomeValue, required this.addHomeValue,
}); });
@@ -203,13 +201,20 @@ class _QuickActions extends StatelessWidget {
onTap: () { onTap: () {
if (addWorkValue == 'addWork') { if (addWorkValue == 'addWork') {
controller.workLocationFromMap = true; controller.workLocationFromMap = true;
controller.changeMainBottomMenuMap(); mapEngine.changeMainBottomMenuMap();
controller.changePickerShown(); mapEngine.changePickerShown();
} else { } else {
_handleQuickAction(controller, BoxName.addWork, 'To Work'); _handleQuickAction(
controller,
mapEngine,
rideLifecycle,
BoxName.addWork,
'To Work',
);
} }
}, },
onLongPress: () => _showChangeLocationDialog(controller, 'Work'), onLongPress: () =>
_showChangeLocationDialog(controller, mapEngine, 'Work'),
), ),
_buildQuickActionButton( _buildQuickActionButton(
icon: Icons.home_outlined, icon: Icons.home_outlined,
@@ -217,13 +222,20 @@ class _QuickActions extends StatelessWidget {
onTap: () { onTap: () {
if (addHomeValue == 'addHome') { if (addHomeValue == 'addHome') {
controller.homeLocationFromMap = true; controller.homeLocationFromMap = true;
controller.changeMainBottomMenuMap(); mapEngine.changeMainBottomMenuMap();
controller.changePickerShown(); mapEngine.changePickerShown();
} else { } else {
_handleQuickAction(controller, BoxName.addHome, 'To Home'); _handleQuickAction(
controller,
mapEngine,
rideLifecycle,
BoxName.addHome,
'To Home',
);
} }
}, },
onLongPress: () => _showChangeLocationDialog(controller, 'Home'), onLongPress: () =>
_showChangeLocationDialog(controller, mapEngine, 'Home'),
), ),
], ],
), ),
@@ -231,17 +243,25 @@ class _QuickActions extends StatelessWidget {
} }
} }
/// A dedicated widget for the search results list.
/// It uses its own `GetBuilder` to only rebuild when the list of places changes.
class _SearchResults extends StatelessWidget { class _SearchResults extends StatelessWidget {
final LocationSearchController controller;
final MapEngineController mapEngine;
final RideLifecycleController rideLifecycle;
const _SearchResults({
required this.controller,
required this.mapEngine,
required this.rideLifecycle,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>( return GetBuilder<LocationSearchController>(
id: 'places_list', // Use a specific ID for targeted updates id: 'places_list',
builder: (controller) { builder: (locCtrl) {
return AnimatedContainer( return AnimatedContainer(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
height: controller.placesDestination.isNotEmpty ? 300 : 0, height: locCtrl.placesDestination.isNotEmpty ? 300 : 0,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8.0),
@@ -250,11 +270,11 @@ class _SearchResults extends StatelessWidget {
child: ListView.separated( child: ListView.separated(
shrinkWrap: true, shrinkWrap: true,
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
itemCount: controller.placesDestination.length, itemCount: locCtrl.placesDestination.length,
separatorBuilder: (context, index) => separatorBuilder: (context, index) =>
const Divider(height: 1, color: Colors.grey), const Divider(height: 1, color: Colors.grey),
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final res = controller.placesDestination[index]; final res = locCtrl.placesDestination[index];
final title = res['name_ar'] ?? res['name'] ?? 'Unknown Place'; final title = res['name_ar'] ?? res['name'] ?? 'Unknown Place';
final address = res['address'] ?? 'Details not available'; final address = res['address'] ?? 'Details not available';
final latitude = res['latitude']; final latitude = res['latitude'];
@@ -277,7 +297,14 @@ class _SearchResults extends StatelessWidget {
context, latitude, longitude, title), context, latitude, longitude, title),
), ),
onTap: () => _handlePlaceSelection( onTap: () => _handlePlaceSelection(
controller, latitude, longitude, title, index), controller,
mapEngine,
rideLifecycle,
latitude,
longitude,
title,
index,
),
); );
}, },
), ),
@@ -286,7 +313,6 @@ class _SearchResults extends StatelessWidget {
); );
} }
// --- [تحسين] استخراج المنطق المعقد إلى دوال مساعدة ---
Future<void> _handleAddToFavorites(BuildContext context, dynamic latitude, Future<void> _handleAddToFavorites(BuildContext context, dynamic latitude,
dynamic longitude, String title) async { dynamic longitude, String title) async {
if (latitude != null && longitude != null) { if (latitude != null && longitude != null) {
@@ -311,14 +337,19 @@ class _SearchResults extends StatelessWidget {
} }
} }
Future<void> _handlePlaceSelection(MapPassengerController controller, Future<void> _handlePlaceSelection(
dynamic latitude, dynamic longitude, String title, int index) async { LocationSearchController controller,
MapEngineController mapEngine,
RideLifecycleController rideLifecycle,
dynamic latitude,
dynamic longitude,
String title,
int index) async {
if (latitude == null || longitude == null) { if (latitude == null || longitude == null) {
Toast.show(Get.context!, 'Invalid location data', AppColor.redColor); Toast.show(Get.context!, 'Invalid location data', AppColor.redColor);
return; return;
} }
// Save to recent locations
await sql.insertMapLocation({ await sql.insertMapLocation({
'latitude': latitude, 'latitude': latitude,
'longitude': longitude, 'longitude': longitude,
@@ -330,53 +361,55 @@ class _SearchResults extends StatelessWidget {
final destLatLng = LatLng( final destLatLng = LatLng(
double.parse(latitude.toString()), double.parse(longitude.toString())); double.parse(latitude.toString()), double.parse(longitude.toString()));
if (controller.isAnotherOreder) { if (rideLifecycle.isAnotherOreder) {
// **Another Order Flow** await _handleAnotherOrderSelection(
await _handleAnotherOrderSelection(controller, destLatLng); controller, mapEngine, rideLifecycle, destLatLng);
} else { } else {
// **Regular Order Flow** _handleRegularOrderSelection(
_handleRegularOrderSelection(controller, destLatLng, index); controller, mapEngine, rideLifecycle, destLatLng, index);
} }
} }
Future<void> _handleAnotherOrderSelection( Future<void> _handleAnotherOrderSelection(
MapPassengerController controller, LatLng destination) async { LocationSearchController controller,
MapEngineController mapEngine,
RideLifecycleController rideLifecycle,
LatLng destination) async {
controller.myDestination = destination; controller.myDestination = destination;
controller.clearPlacesDestination(); // Helper method in controller controller.clearPlacesDestination();
await controller.getDirectionMap( await rideLifecycle.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${controller.myDestination.latitude},${controller.myDestination.longitude}'); '${controller.myDestination.latitude},${controller.myDestination.longitude}');
controller.isPickerShown = false; mapEngine.isPickerShown = false;
controller.passengerStartLocationFromMap = false; controller.passengerStartLocationFromMap = false;
controller.changeMainBottomMenuMap(); mapEngine.changeMainBottomMenuMap();
controller.showBottomSheet1(); rideLifecycle.showBottomSheet1();
} }
void _handleRegularOrderSelection( void _handleRegularOrderSelection(
MapPassengerController controller, LatLng destination, int index) { LocationSearchController controller,
MapEngineController mapEngine,
RideLifecycleController rideLifecycle,
LatLng destination,
int index) {
controller.passengerLocation = controller.newMyLocation; controller.passengerLocation = controller.newMyLocation;
controller.myDestination = destination; controller.myDestination = destination;
controller.convertHintTextDestinationNewPlaces(index); controller.convertHintTextDestinationNewPlaces(index);
controller.clearPlacesDestination(); // Helper method in controller controller.clearPlacesDestination();
controller.changeMainBottomMenuMap(); mapEngine.changeMainBottomMenuMap();
controller.passengerStartLocationFromMap = true; controller.passengerStartLocationFromMap = true;
controller.isPickerShown = true; mapEngine.isPickerShown = true;
// ✅ FIX: Draw the route after setting destination (matching the "Another Order" flow) rideLifecycle.getDirectionMap(
controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${controller.myDestination.latitude},${controller.myDestination.longitude}'); '${controller.myDestination.latitude},${controller.myDestination.longitude}');
} }
} }
// ---------------------------------------------------
// -- Helper Functions (kept from original code) --
// ---------------------------------------------------
Widget _buildQuickActionButton({ Widget _buildQuickActionButton({
required IconData icon, required IconData icon,
required String text, required String text,
@@ -410,10 +443,12 @@ Widget _buildQuickActionButton({
); );
} }
void _showChangeLocationDialog( void _showChangeLocationDialog(LocationSearchController controller,
MapPassengerController controller, String locationType) { MapEngineController mapEngine, String locationType) {
MyDialog().getDialog( MyDialog().getDialog(
locationType == 'Work' ? 'Change Work location ?'.tr : 'Change Home location ?'.tr, locationType == 'Work'
? 'Change Work location ?'.tr
: 'Change Home location ?'.tr,
'', '',
() { () {
if (locationType == 'Work') { if (locationType == 'Work') {
@@ -421,15 +456,18 @@ void _showChangeLocationDialog(
} else { } else {
controller.homeLocationFromMap = true; controller.homeLocationFromMap = true;
} }
controller.changeMainBottomMenuMap(); mapEngine.changeMainBottomMenuMap();
controller.changePickerShown(); mapEngine.changePickerShown();
}, },
); );
} }
void _handleQuickAction( void _handleQuickAction(
MapPassengerController controller, String boxName, String hintText) async { LocationSearchController controller,
// --- [تحسين] قراءة وتحويل الإحداثيات بأمان أكبر --- MapEngineController mapEngine,
RideLifecycleController rideLifecycle,
String boxName,
String hintText) async {
try { try {
final locationString = box.read(boxName).toString(); final locationString = box.read(boxName).toString();
final parts = locationString.split(','); final parts = locationString.split(',');
@@ -439,20 +477,19 @@ void _handleQuickAction(
); );
controller.hintTextDestinationPoint = hintText; controller.hintTextDestinationPoint = hintText;
controller.changeMainBottomMenuMap(); mapEngine.changeMainBottomMenuMap();
await controller.getDirectionMap( await rideLifecycle.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${latLng.latitude},${latLng.longitude}', '${latLng.latitude},${latLng.longitude}',
); );
controller.currentLocationToFormPlaces = false; controller.currentLocationToFormPlaces = false;
controller.clearPlacesDestination(); // Helper method in controller controller.clearPlacesDestination();
controller.passengerStartLocationFromMap = false; controller.passengerStartLocationFromMap = false;
controller.isPickerShown = false; mapEngine.isPickerShown = false;
controller.showBottomSheet1(); rideLifecycle.showBottomSheet1();
} catch (e) { } catch (e) {
// Handle error if parsing fails
Log.print("Error handling quick action: $e"); Log.print("Error handling quick action: $e");
Toast.show(Get.context!, "Failed to get location".tr, AppColor.redColor); Toast.show(Get.context!, "Failed to get location".tr, AppColor.redColor);
} }

View File

@@ -4,135 +4,122 @@ import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart';
// --------------------------------------------------- // ---------------------------------------------------
// -- Widget for Start Point Search (Updated) -- // -- Widget for Start Point Search (Updated) --
// --------------------------------------------------- // ---------------------------------------------------
GetBuilder<MapPassengerController> formSearchPlacesStart() { GetBuilder<LocationSearchController> formSearchPlacesStart() {
return GetBuilder<MapPassengerController>( return GetBuilder<LocationSearchController>(
id: 'start_point_form', // إضافة معرف لتحديث هذا الجزء فقط عند الحاجة id: 'start_point_form',
builder: (controller) => Column( builder: (controller) {
children: [ final mapEngine = Get.find<MapEngineController>();
Padding( return Column(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), children: [
child: Row( Padding(
children: [ padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
// --- حقل البحث النصي --- child: Row(
Expanded( children: [
child: TextFormField( Expanded(
controller: controller.placeStartController, child: TextFormField(
onChanged: (value) { controller: controller.placeStartController,
if (controller.placeStartController.text.length > 2) { onChanged: (value) {
controller.getPlacesStart(); if (controller.placeStartController.text.length > 2) {
} else if (controller.placeStartController.text.isEmpty) { controller.getPlacesStart();
controller.clearPlacesStart(); } else if (controller.placeStartController.text.isEmpty) {
} controller.clearPlacesStart();
}, }
decoration: InputDecoration( },
hintText: 'Search for a starting point'.tr, decoration: InputDecoration(
hintStyle: hintText: 'Search for a starting point'.tr,
AppStyle.subtitle.copyWith(color: Colors.grey[600]), hintStyle:
prefixIcon: AppStyle.subtitle.copyWith(color: Colors.grey[600]),
Icon(Icons.search, color: AppColor.primaryColor), prefixIcon:
suffixIcon: controller.placeStartController.text.isNotEmpty Icon(Icons.search, color: AppColor.primaryColor),
? IconButton( suffixIcon: controller.placeStartController.text.isNotEmpty
icon: Icon(Icons.clear, color: Colors.grey[400]), ? IconButton(
onPressed: () { icon: Icon(Icons.clear, color: Colors.grey[400]),
controller.placeStartController.clear(); onPressed: () {
controller.clearPlacesStart(); controller.placeStartController.clear();
}, controller.clearPlacesStart();
) },
: null, )
contentPadding: const EdgeInsets.symmetric( : null,
horizontal: 16.0, vertical: 10.0), contentPadding: const EdgeInsets.symmetric(
border: OutlineInputBorder( horizontal: 16.0, vertical: 10.0),
borderRadius: BorderRadius.circular(8.0), border: OutlineInputBorder(
borderSide: BorderSide.none, borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: AppColor.primaryColor),
),
filled: true,
fillColor: Colors.grey[50],
), ),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: AppColor.primaryColor),
),
filled: true,
fillColor: Colors.grey[50],
), ),
), ),
), const SizedBox(width: 8.0),
IconButton(
const SizedBox(width: 8.0), onPressed: () {
controller.passengerStartLocationFromMap = true;
// --- أيقونة اختيار الموقع من الخريطة (الجزء المضاف) --- mapEngine.changeMainBottomMenuMap();
IconButton( mapEngine.changePickerShown();
onPressed: () { },
// هذا السطر مهم جداً: نخبر الكونترولر أننا نحدد نقطة البداية الآن icon: Icon(Icons.location_on_outlined,
controller.passengerStartLocationFromMap = true; color: AppColor.accentColor, size: 30),
tooltip: 'Pick start point on map'.tr,
// إخفاء القائمة السفلية وفتح مؤشر الخريطة (Picker) ),
controller.changeMainBottomMenuMap(); ],
controller.changePickerShown(); ),
},
icon: Icon(Icons.location_on_outlined,
color: AppColor.accentColor, size: 30),
tooltip: 'Pick start point on map'.tr,
),
],
), ),
), AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: controller.placesStart.isNotEmpty ? 300 : 0,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.0),
),
margin: const EdgeInsets.symmetric(horizontal: 16.0),
child: ListView.separated(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemCount: controller.placesStart.length,
separatorBuilder: (context, index) =>
const Divider(height: 1, color: Colors.grey),
itemBuilder: (BuildContext context, int index) {
var res = controller.placesStart[index];
var title = res['name_ar'] ?? res['name'] ?? 'Unknown Place';
var address = res['address'] ?? 'Details not available';
// --- قائمة نتائج البحث --- return ListTile(
AnimatedContainer( leading: const Icon(Icons.place, size: 30, color: Colors.grey),
duration: const Duration(milliseconds: 200), title: Text(title,
height: controller.placesStart.isNotEmpty ? 300 : 0, style: AppStyle.subtitle
decoration: BoxDecoration( .copyWith(fontWeight: FontWeight.w500)),
color: Colors.white, subtitle: Text(address,
borderRadius: BorderRadius.circular(8.0), style: TextStyle(color: Colors.grey[600], fontSize: 12)),
onTap: () {
var latitude = res['latitude'];
var longitude = res['longitude'];
if (latitude != null && longitude != null) {
controller.passengerLocation =
LatLng(double.parse(latitude), double.parse(longitude));
controller.placeStartController.text = title;
controller.clearPlacesStart();
mapEngine.changeMainBottomMenuMap();
controller.update();
}
},
);
},
),
), ),
margin: const EdgeInsets.symmetric(horizontal: 16.0), ],
child: ListView.separated( );
shrinkWrap: true, },
physics: const ClampingScrollPhysics(),
itemCount: controller.placesStart.length,
separatorBuilder: (context, index) =>
const Divider(height: 1, color: Colors.grey),
itemBuilder: (BuildContext context, int index) {
var res = controller.placesStart[index];
var title = res['name_ar'] ?? res['name'] ?? 'Unknown Place';
var address = res['address'] ?? 'Details not available';
return ListTile(
leading: const Icon(Icons.place, size: 30, color: Colors.grey),
title: Text(title,
style: AppStyle.subtitle
.copyWith(fontWeight: FontWeight.w500)),
subtitle: Text(address,
style: TextStyle(color: Colors.grey[600], fontSize: 12)),
onTap: () {
var latitude = res['latitude'];
var longitude = res['longitude'];
if (latitude != null && longitude != null) {
// تحديث موقع الراكب (نقطة الانطلاق) بناءً على الاختيار
controller.passengerLocation =
LatLng(double.parse(latitude), double.parse(longitude));
// تحديث النص في الحقل
controller.placeStartController.text = title;
// مسح النتائج
controller.clearPlacesStart();
// إغلاق القائمة والعودة للخريطة لرؤية النتيجة (اختياري حسب منطق تطبيقك)
controller.changeMainBottomMenuMap();
controller.update();
}
},
);
},
),
),
],
),
); );
} }

View File

@@ -6,183 +6,181 @@ import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/functions/toast.dart'; import '../../../controller/functions/toast.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../main.dart'; import '../../../main.dart';
GetBuilder<MapPassengerController> formSearchPlaces(int index) { GetBuilder<LocationSearchController> formSearchPlaces(int index) {
// DbSql sql = DbSql.instance; return GetBuilder<LocationSearchController>(
return GetBuilder<MapPassengerController>( builder: (controller) {
builder: (controller) => Column( final mapEngine = Get.find<MapEngineController>();
children: [ final rideLifecycle = Get.find<RideLifecycleController>();
Padding( return Column(
padding: const EdgeInsets.all(16), children: [
child: Container( Padding(
decoration: BoxDecoration(color: AppColor.secondaryColor), padding: const EdgeInsets.all(16),
child: TextField( child: Container(
decoration: InputDecoration( decoration: BoxDecoration(color: AppColor.secondaryColor),
border: const OutlineInputBorder( child: TextField(
borderRadius: BorderRadius.only(), decoration: InputDecoration(
gapPadding: 4, border: const OutlineInputBorder(
borderSide: BorderSide( borderRadius: BorderRadius.only(),
color: AppColor.redColor, gapPadding: 4,
width: 2, borderSide: BorderSide(
)), color: AppColor.redColor,
suffixIcon: const Icon(Icons.search), width: 2,
hintText: controller.hintTextwayPoint0.tr, )),
hintStyle: AppStyle.title, suffixIcon: const Icon(Icons.search),
hintMaxLines: 1, hintText: controller.hintTextwayPoint0.tr,
prefixIcon: IconButton( hintStyle: AppStyle.title,
onPressed: () { hintMaxLines: 1,
controller.allTextEditingPlaces[index].clear(); prefixIcon: IconButton(
controller.clearPlaces(index); onPressed: () {
}, controller.allTextEditingPlaces[index].clear();
icon: Icon( controller.clearPlaces(index);
Icons.clear,
color: Colors.red[300],
),
),
),
controller: controller.allTextEditingPlaces[index],
onChanged: (value) {
if (controller.allTextEditingPlaces[index].text.length >
5) {
controller.getPlacesListsWayPoint(index);
controller.changeHeightPlacesAll(index);
}
}, },
// onEditingComplete: () => controller.changeHeight(), icon: Icon(
Icons.clear,
color: Colors.red[300],
),
), ),
), ),
controller: controller.allTextEditingPlaces[index],
onChanged: (value) {
if (controller.allTextEditingPlaces[index].text.length > 5) {
controller.getPlacesListsWayPoint(index);
mapEngine.changeHeightPlacesAll(index);
}
},
), ),
controller.placeListResponseAll[index].isEmpty ),
? InkWell( ),
onTap: () { controller.placeListResponseAll[index].isEmpty
controller.startLocationFromMapAll[index] = true; ? InkWell(
controller.wayPointIndex = index; onTap: () {
Get.back(); controller.startLocationFromMapAll[index] = true;
// controller.changeMainBottomMenuMap(); controller.wayPointIndex = index;
controller.changeWayPointStopsSheet(); Get.back();
controller.changePickerShown(); mapEngine.changeWayPointStopsSheet();
}, mapEngine.changePickerShown();
child: Text( },
'Choose from Map'.tr + ' $index'.tr, child: Text(
style: 'Choose from Map'.tr + ' $index'.tr,
AppStyle.title.copyWith(color: AppColor.blueColor), style:
), AppStyle.title.copyWith(color: AppColor.blueColor),
) ),
: const SizedBox(), )
Container( : const SizedBox(),
height: controller.placeListResponseAll[index].isNotEmpty Container(
? controller.height height: controller.placeListResponseAll[index].isNotEmpty
: 0, ? mapEngine.height
color: AppColor.secondaryColor, : 0,
child: ListView.builder( color: AppColor.secondaryColor,
itemCount: controller.placeListResponseAll[index].length, child: ListView.builder(
itemBuilder: (BuildContext context, int i) { itemCount: controller.placeListResponseAll[index].length,
var res = controller.placeListResponseAll[index][i]; itemBuilder: (BuildContext context, int i) {
return InkWell( var res = controller.placeListResponseAll[index][i];
onTap: () async { return InkWell(
// ── Extract selected location ── onTap: () async {
final double lat = res['geometry']['location']['lat']; final double lat = res['geometry']['location']['lat'];
final double lng = res['geometry']['location']['lng']; final double lng = res['geometry']['location']['lng'];
final String placeName = res['name'].toString(); final String placeName = res['name'].toString();
final selectedLatLng = LatLng(lat, lng); final selectedLatLng = LatLng(lat, lng);
controller.changeHeightPlaces(); mapEngine.changeHeightPlaces();
// ── Update start/end based on context ── if (controller.currentLocationToFormPlacesAll[index] ==
if (controller.currentLocationToFormPlacesAll[index] == true) {
true) { controller.newStartPointLocation =
controller.newStartPointLocation = rideLifecycle.passengerLocation;
controller.passengerLocation; } else {
} else { rideLifecycle.passengerLocation =
controller.passengerLocation = controller.newStartPointLocation;
controller.newStartPointLocation; }
}
// ✅ FIX: Set the waypoint to the selected location controller.menuWaypoints[index] = selectedLatLng;
controller.menuWaypoints[index] = selectedLatLng; controller.menuWaypointNames[index] = placeName;
controller.menuWaypointNames[index] = placeName;
// ✅ FIX: Update hint text and coordinates controller.convertHintTextPlaces(index, res);
controller.convertHintTextPlaces(index, res);
// ✅ FIX: Draw the route with the updated waypoint final String start =
final String start = '${rideLifecycle.passengerLocation.latitude},${rideLifecycle.passengerLocation.longitude}';
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}'; final String dest =
final String dest = '${rideLifecycle.myDestination.latitude},${rideLifecycle.myDestination.longitude}';
'${controller.myDestination.latitude},${controller.myDestination.longitude}';
await controller.getDirectionMap(start, dest); await rideLifecycle.getDirectionMap(start, dest);
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10), padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column( child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( Image.network(
children: [ res['icon'],
Image.network( width: 20,
res['icon'],
width: 20,
),
IconButton(
onPressed: () async {
await sql.insertMapLocation({
'latitude': res['geometry']
['location']['lat'],
'longitude': res['geometry']
['location']['lng'],
'name': res['name'].toString(),
'rate': res['rating'].toString(),
}, TableName.placesFavorite);
Toast.show(
context,
'${res['name']} ${'Saved Sucssefully'.tr}',
AppColor.primaryColor);
},
icon: const Icon(Icons.favorite_border),
),
],
), ),
Column( IconButton(
children: [ onPressed: () async {
Text( await sql.insertMapLocation({
res['name'].toString(), 'latitude': res['geometry']
style: AppStyle.title, ['location']['lat'],
), 'longitude': res['geometry']
Text( ['location']['lng'],
res['vicinity'].toString(), 'name': res['name'].toString(),
style: AppStyle.subtitle, 'rate': res['rating'].toString(),
), }, TableName.placesFavorite);
], Toast.show(
), context,
Column( '${res['name']} ${'Saved Sucssefully'.tr}',
children: [ AppColor.primaryColor);
Text( },
'rate', icon: const Icon(Icons.favorite_border),
style: AppStyle.subtitle, ),
), ],
Text( ),
res['rating'].toString(), Column(
style: AppStyle.subtitle, children: [
), Text(
], res['name'].toString(),
style: AppStyle.title,
),
Text(
res['vicinity'].toString(),
style: AppStyle.subtitle,
),
],
),
Column(
children: [
Text(
'rate',
style: AppStyle.subtitle,
),
Text(
res['rating'].toString(),
style: AppStyle.subtitle,
), ),
], ],
), ),
const Divider(
thickness: 1,
)
], ],
), ),
), const Divider(
); thickness: 1,
}, )
), ],
) ),
], ),
)); );
},
),
)
],
);
},
);
} }

View File

@@ -6,19 +6,26 @@ import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:Intaleq/controller/home/points_for_rider_controller.dart'; import 'package:Intaleq/controller/home/points_for_rider_controller.dart';
import 'package:Intaleq/services/offline_map_service.dart'; import 'package:Intaleq/services/offline_map_service.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/map/nearby_drivers_controller.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../widgets/mycircular.dart'; import '../../widgets/mycircular.dart';
import '../../widgets/mydialoug.dart'; import '../../widgets/mydialoug.dart';
class GoogleMapPassengerWidget extends StatelessWidget { class GoogleMapPassengerWidget extends StatelessWidget {
GoogleMapPassengerWidget({super.key}); GoogleMapPassengerWidget({super.key});
final WayPointController wayPointController = Get.put(WayPointController()); final WayPointController wayPointController = Get.find<WayPointController>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>( final locationSearch = Get.find<LocationSearchController>();
builder: (controller) => controller.isLoading final rideLifecycle = Get.find<RideLifecycleController>();
final nearbyDrivers = Get.find<NearbyDriversController>();
return GetBuilder<MapEngineController>(
builder: (controller) => rideLifecycle.isLoading
? const MyCircularProgressIndicator() ? const MyCircularProgressIndicator()
: Positioned( : Positioned(
bottom: Get.height * .2, bottom: Get.height * .2,
@@ -32,13 +39,13 @@ class GoogleMapPassengerWidget extends StatelessWidget {
: 'assets/style.json', : 'assets/style.json',
onMapCreated: controller.onMapCreated, onMapCreated: controller.onMapCreated,
onStyleLoaded: controller.onStyleLoaded, onStyleLoaded: controller.onStyleLoaded,
onCameraMove: controller.onCameraMoveThrottled, onCameraMove: locationSearch.onCameraMoveThrottled,
onCameraIdle: () { onCameraIdle: () {
if (controller.mapController != null) { if (controller.mapController != null) {
final position = controller.mapController!.cameraPosition; final position = controller.mapController!.cameraPosition;
if (position != null) { if (position != null) {
Log.print('✅ onCameraIdle targeted: ${position.target}'); Log.print('✅ onCameraIdle targeted: ${position.target}');
controller locationSearch
.updateCurrentLocationFromCamera(position.target); .updateCurrentLocationFromCamera(position.target);
OfflineMapService.instance OfflineMapService.instance
.downloadRegion(position.target, radiusKm: 1.0); .downloadRegion(position.target, radiusKm: 1.0);
@@ -54,8 +61,8 @@ class GoogleMapPassengerWidget extends StatelessWidget {
polygons: controller.polygons, polygons: controller.polygons,
circles: controller.circles, circles: controller.circles,
initialCameraPosition: CameraPosition( initialCameraPosition: CameraPosition(
target: controller.passengerLocation, target: locationSearch.passengerLocation,
zoom: controller.lowPerf ? 14.5 : 15, zoom: nearbyDrivers.lowPerf ? 14.5 : 15,
), ),
myLocationEnabled: true, myLocationEnabled: true,
onTap: (latlng) => controller.hidePlaces(), onTap: (latlng) => controller.hidePlaces(),
@@ -63,11 +70,11 @@ class GoogleMapPassengerWidget extends StatelessWidget {
MyDialog().getDialog('Are you want to go to this site'.tr, '', MyDialog().getDialog('Are you want to go to this site'.tr, '',
() async { () async {
controller.clearPolyline(); controller.clearPolyline();
controller.getDirectionMap( rideLifecycle.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', '${locationSearch.passengerLocation.latitude},${locationSearch.passengerLocation.longitude}',
'${latlng.latitude},${latlng.longitude}', '${latlng.latitude},${latlng.longitude}',
); );
controller.showBottomSheet1(); rideLifecycle.showBottomSheet1();
}); });
}, },
), ),

View File

@@ -1,95 +1,82 @@
import 'dart:math'; import 'dart:math';
import 'package:Intaleq/views/widgets/elevated_btn.dart'; import 'package:Intaleq/views/widgets/elevated_btn.dart';
import 'package:Intaleq/views/widgets/error_snakbar.dart';
import 'package:Intaleq/views/widgets/mycircular.dart'; import 'package:Intaleq/views/widgets/mycircular.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart'; import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'dart:ui'; // مهم لإضافة تأثير الضبابية import 'dart:ui';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../controller/functions/tts.dart'; import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/vip_waitting_page.dart'; import '../../../controller/home/vip_waitting_page.dart';
import '../navigation/navigation_view.dart'; import '../navigation/navigation_view.dart';
// --- الدالة الرئيسية بالتصميم الجديد --- // --- الدالة الرئيسية بالتصميم الجديد ---
GetBuilder<MapPassengerController> leftMainMenuIcons() { GetBuilder<MapEngineController> leftMainMenuIcons() {
return GetBuilder<MapPassengerController>( return GetBuilder<MapEngineController>(
builder: (controller) => Positioned( builder: (controller) {
// تم تعديل الموضع ليتناسب مع التصميم الجديد final locationSearch = Get.find<LocationSearchController>();
top: Get.height * .01, return Positioned(
left: 0, top: Get.height * .01,
right: 0, left: 0,
child: Center( right: 0,
child: ClipRRect( child: Center(
borderRadius: BorderRadius.circular(50.0), // لإنشاء شكل الكبسولة child: ClipRRect(
child: BackdropFilter( borderRadius: BorderRadius.circular(50.0),
filter: ImageFilter.blur( child: BackdropFilter(
sigmaX: 8.0, sigmaY: 8.0), // تأثير الزجاج المصنفر filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.secondaryColor.withOpacity(0.4), // لون شبه شفاف color: AppColor.secondaryColor.withValues(alpha: 0.4),
borderRadius: BorderRadius.circular(50.0), borderRadius: BorderRadius.circular(50.0),
border: Border.all(color: AppColor.secondaryColor), border: Border.all(color: AppColor.secondaryColor),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, // ليأخذ الشريط حجم الأزرار فقط mainAxisSize: MainAxisSize.min,
children: [ children: [
// --- تم استخدام دالة مساعدة جديدة للزر --- _buildMapActionButton(
_buildMapActionButton( icon: Icons.near_me_outlined,
icon: Icons.near_me_outlined, tooltip: 'Toggle Map Type',
tooltip: 'Toggle Map Type', onPressed: () => Get.to(() => NavigationView()),
onPressed: () => Get.to(() => NavigationView()), ),
), _buildVerticalDivider(),
// _buildVerticalDivider(), _buildMapActionButton(
// _buildMapActionButton( icon: Icons.my_location_rounded,
// icon: Icons.traffic_outlined, tooltip: 'Go to My Location',
// tooltip: 'Toggle Traffic', onPressed: () {
// onPressed: () => controller.changeMapTraffic(), controller.mapController?.animateCamera(
// ), CameraUpdate.newLatLng(
_buildVerticalDivider(), LatLng(
_buildMapActionButton( locationSearch.passengerLocation.latitude,
icon: Icons.my_location_rounded, locationSearch.passengerLocation.longitude,
tooltip: 'Go to My Location', ),
onPressed: () {
controller.mapController?.animateCamera(
CameraUpdate.newLatLng(
LatLng(
controller.passengerLocation.latitude,
controller.passengerLocation.longitude,
), ),
), );
); },
}, ),
), _buildVerticalDivider(),
_buildVerticalDivider(), _buildMapActionButton(
_buildMapActionButton( icon: Octicons.watch,
icon: Octicons.watch, tooltip: 'VIP Waiting Page',
tooltip: 'VIP Waiting Page', onPressed: () => Get.to(() => VipWaittingPage()),
onPressed: () => Get.to(() => VipWaittingPage()), ),
), ],
// _buildMapActionButton( ),
// icon: Octicons.ellipsis,
// tooltip: 'test',
// onPressed: () => Get.to(() => TestPage()),
// ),
],
), ),
), ),
), ),
), ),
), );
), },
); );
} }
// --- دالة مساعدة جديدة لإنشاء الأزرار بشكل أنيق ---
Widget _buildMapActionButton({ Widget _buildMapActionButton({
required IconData icon, required IconData icon,
required String tooltip, required String tooltip,
@@ -101,28 +88,23 @@ Widget _buildMapActionButton({
tooltip: tooltip, tooltip: tooltip,
splashRadius: 22, splashRadius: 22,
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
constraints: const BoxConstraints(), // لإزالة المساحات الافتراضية constraints: const BoxConstraints(),
); );
} }
// --- ويدجت للفاصل الرأسي بين الأزرار ---
Widget _buildVerticalDivider() { Widget _buildVerticalDivider() {
return Container( return Container(
height: 20, height: 20,
width: 1, width: 1,
color: AppColor.writeColor.withOpacity(0.2), color: AppColor.writeColor.withValues(alpha: 0.2),
); );
} }
// --- باقي الكود الخاص بك يبقى كما هو بدون تغيير ---
class TestPage extends StatelessWidget { class TestPage extends StatelessWidget {
const TestPage({super.key}); const TestPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final random = Random();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('iOS Live Activity Test'), title: const Text('iOS Live Activity Test'),
@@ -137,7 +119,6 @@ class TestPage extends StatelessWidget {
title: 'title', title: 'title',
onPressed: () {}, onPressed: () {},
), ),
// زر الإنهاء
ElevatedButton( ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.red, backgroundColor: Colors.red,

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ import 'package:url_launcher/url_launcher.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/links.dart'; import '../../../constant/links.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/map_engine_controller.dart';
import '../../notification/notification_page.dart'; import '../../notification/notification_page.dart';
import '../HomePage/contact_us.dart'; import '../HomePage/contact_us.dart';
import '../HomePage/share_app_page.dart'; import '../HomePage/share_app_page.dart';
@@ -28,9 +28,8 @@ Color get _kBg =>
Get.isDarkMode ? const Color(0xFF060B18) : AppColor.secondaryColor; Get.isDarkMode ? const Color(0xFF060B18) : AppColor.secondaryColor;
Color get _kBgSurface => Get.isDarkMode Color get _kBgSurface => Get.isDarkMode
? const Color(0xFF0D1525) ? const Color(0xFF0D1525)
: AppColor.secondaryColor.withOpacity(0.9); : AppColor.secondaryColor.withValues(alpha: 0.9);
const _kAmber = Color(0xFFFFB700); const _kAmber = Color(0xFFFFB700);
Color get _kBorder => _kCyan.withOpacity(0.15);
Color get _kText => AppColor.writeColor; Color get _kText => AppColor.writeColor;
Color get _kTextMuted => AppColor.grayColor; Color get _kTextMuted => AppColor.grayColor;
@@ -39,16 +38,14 @@ class MapMenuWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Get.lazyPut(() => MapPassengerController()); return GetBuilder<MapEngineController>(
return GetBuilder<MapPassengerController>(
builder: (controller) => Stack( builder: (controller) => Stack(
children: [ children: [
// ── تعتيم الخلفية ─────────────────────────────────────────────── // ── تعتيم الخلفية ───────────────────────────────────────────────
if (controller.widthMenu > 0) if (controller.widthMenu > 0)
GestureDetector( GestureDetector(
onTap: controller.getDrawerMenu, onTap: controller.getDrawerMenu,
child: Container(color: Colors.black.withOpacity(0.55)), child: Container(color: Colors.black.withValues(alpha: 0.55)),
), ),
_buildSideMenu(controller), _buildSideMenu(controller),
@@ -59,7 +56,7 @@ class MapMenuWidget extends StatelessWidget {
} }
// ── زر القائمة العائم ──────────────────────────────────────────────────── // ── زر القائمة العائم ────────────────────────────────────────────────────
Widget _buildMenuButton(MapPassengerController controller) { Widget _buildMenuButton(MapEngineController controller) {
return Positioned( return Positioned(
top: 45, top: 45,
left: 16, left: 16,
@@ -76,12 +73,12 @@ class MapMenuWidget extends StatelessWidget {
width: 48, width: 48,
height: 48, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
color: _kBg.withOpacity(0.88), color: _kBg.withValues(alpha: 0.88),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: _kCyan.withOpacity(0.25), width: 1), border: Border.all(color: _kCyan.withValues(alpha: 0.25), width: 1),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: _kCyan.withOpacity(0.12), color: _kCyan.withValues(alpha: 0.12),
blurRadius: 16, blurRadius: 16,
), ),
], ],
@@ -106,7 +103,7 @@ class MapMenuWidget extends StatelessWidget {
} }
// ── القائمة الجانبية ───────────────────────────────────────────────────── // ── القائمة الجانبية ─────────────────────────────────────────────────────
Widget _buildSideMenu(MapPassengerController controller) { Widget _buildSideMenu(MapEngineController controller) {
return AnimatedPositioned( return AnimatedPositioned(
duration: const Duration(milliseconds: 420), duration: const Duration(milliseconds: 420),
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
@@ -120,13 +117,13 @@ class MapMenuWidget extends StatelessWidget {
width: Get.width * 0.8, width: Get.width * 0.8,
constraints: const BoxConstraints(maxWidth: 320), constraints: const BoxConstraints(maxWidth: 320),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _kBg.withOpacity(0.97), color: _kBg.withValues(alpha: 0.97),
border: Border( border: Border(
right: BorderSide(color: _kCyan.withOpacity(0.12), width: 1), right: BorderSide(color: _kCyan.withValues(alpha: 0.12), width: 1),
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.5), color: Colors.black.withValues(alpha: 0.5),
blurRadius: 32, blurRadius: 32,
), ),
], ],
@@ -239,7 +236,7 @@ class MapMenuWidget extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: _kBgSurface, color: _kBgSurface,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: _kCyan.withOpacity(0.15), width: 1), border: Border.all(color: _kCyan.withValues(alpha: 0.15), width: 1),
), ),
child: Row( child: Row(
children: [ children: [
@@ -255,12 +252,12 @@ class MapMenuWidget extends StatelessWidget {
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: [
_kCyan.withOpacity(0.2), _kCyan.withValues(alpha: 0.2),
_kAmber.withOpacity(0.12), _kAmber.withValues(alpha: 0.12),
], ],
), ),
border: border:
Border.all(color: _kCyan.withOpacity(0.35), width: 1.5), Border.all(color: _kCyan.withValues(alpha: 0.35), width: 1.5),
), ),
child: Icon(Icons.person_rounded, color: _kCyan, size: 28), child: Icon(Icons.person_rounded, color: _kCyan, size: 28),
), ),
@@ -277,7 +274,7 @@ class MapMenuWidget extends StatelessWidget {
border: Border.all(color: _kBg, width: 2), border: Border.all(color: _kBg, width: 2),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: const Color(0xFF00E676).withOpacity(0.5), color: const Color(0xFF00E676).withValues(alpha: 0.5),
blurRadius: 6, blurRadius: 6,
), ),
], ],
@@ -365,7 +362,7 @@ class MapMenuWidget extends StatelessWidget {
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
Colors.transparent, Colors.transparent,
_kCyan.withOpacity(0.15), _kCyan.withValues(alpha: 0.15),
Colors.transparent, Colors.transparent,
], ],
), ),
@@ -419,7 +416,7 @@ class _QuickBtn extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: _kBgSurface, color: _kBgSurface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: _kCyan.withOpacity(0.12), width: 1), border: Border.all(color: _kCyan.withValues(alpha: 0.12), width: 1),
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -463,7 +460,7 @@ class MenuListItem extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final iconColor = isDestructive final iconColor = isDestructive
? const Color(0xFFFF5252) ? const Color(0xFFFF5252)
: (color ?? _kCyan.withOpacity(0.80)); : (color ?? _kCyan.withValues(alpha: 0.80));
final textColor = final textColor =
isDestructive ? const Color(0xFFFF5252) : (color ?? _kText); isDestructive ? const Color(0xFFFF5252) : (color ?? _kText);
@@ -472,8 +469,8 @@ class MenuListItem extends StatelessWidget {
child: InkWell( child: InkWell(
onTap: onTap, onTap: onTap,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
splashColor: _kCyan.withOpacity(0.07), splashColor: _kCyan.withValues(alpha: 0.07),
highlightColor: _kCyan.withOpacity(0.04), highlightColor: _kCyan.withValues(alpha: 0.04),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: Row( child: Row(
@@ -484,8 +481,8 @@ class MenuListItem extends StatelessWidget {
height: 36, height: 36,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDestructive color: isDestructive
? const Color(0xFFFF5252).withOpacity(0.08) ? const Color(0xFFFF5252).withValues(alpha: 0.08)
: _kCyan.withOpacity(0.07), : _kCyan.withValues(alpha: 0.07),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
child: Icon(icon, size: 19, color: iconColor), child: Icon(icon, size: 19, color: iconColor),
@@ -504,7 +501,7 @@ class MenuListItem extends StatelessWidget {
), ),
Icon( Icon(
Icons.chevron_right_rounded, Icons.chevron_right_rounded,
color: _kTextMuted.withOpacity(0.4), color: _kTextMuted.withValues(alpha: 0.4),
size: 18, size: 18,
), ),
], ],
@@ -520,7 +517,7 @@ class _MenuGridPainter extends CustomPainter {
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final paint = Paint() final paint = Paint()
..color = AppColor.cyanBlue.withOpacity(0.04) ..color = AppColor.cyanBlue.withValues(alpha: 0.04)
..strokeWidth = 0.5; ..strokeWidth = 0.5;
const spacing = 36.0; const spacing = 36.0;
for (double y = 0; y < size.height; y += spacing) { for (double y = 0; y < size.height; y += spacing) {

View File

@@ -3,7 +3,7 @@ import 'package:get/get.dart';
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/map_engine_controller.dart';
import '../../../main.dart'; import '../../../main.dart';
class MenuIconMapPageWidget extends StatelessWidget { class MenuIconMapPageWidget extends StatelessWidget {
@@ -13,7 +13,7 @@ class MenuIconMapPageWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>( return GetBuilder<MapEngineController>(
builder: (controller) => Positioned( builder: (controller) => Positioned(
top: Get.height * .008, top: Get.height * .008,
left: box.read(BoxName.lang) != 'ar' ? 5 : null, left: box.read(BoxName.lang) != 'ar' ? 5 : null,

View File

@@ -4,7 +4,7 @@ import 'dart:ui'; // مهم لإضافة تأثير الضبابية
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/location_search_controller.dart';
// --- الويدجت الرئيسية بالتصميم الجديد --- // --- الويدجت الرئيسية بالتصميم الجديد ---
class PassengerRideLocationWidget extends StatefulWidget { class PassengerRideLocationWidget extends StatefulWidget {
@@ -43,7 +43,7 @@ class _PassengerRideLocationWidgetState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) { return GetBuilder<LocationSearchController>(builder: (controller) {
// --- نفس شرط الإظهار الخاص بك --- // --- نفس شرط الإظهار الخاص بك ---
return AnimatedPositioned( return AnimatedPositioned(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
@@ -60,9 +60,9 @@ class _PassengerRideLocationWidgetState
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.secondaryColor.withOpacity(0.85), color: AppColor.secondaryColor.withValues(alpha: 0.85),
borderRadius: BorderRadius.circular(50.0), borderRadius: BorderRadius.circular(50.0),
border: Border.all(color: AppColor.writeColor.withOpacity(0.2)), border: Border.all(color: AppColor.writeColor.withValues(alpha: 0.2)),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -89,7 +89,7 @@ class _PassengerRideLocationWidgetState
Text( Text(
"Move the map to adjust the pin".tr, "Move the map to adjust the pin".tr,
style: AppStyle.subtitle.copyWith( style: AppStyle.subtitle.copyWith(
color: AppColor.writeColor.withOpacity(0.7), color: AppColor.writeColor.withValues(alpha: 0.7),
), ),
), ),
], ],

View File

@@ -8,8 +8,7 @@ import 'package:Intaleq/views/widgets/elevated_btn.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/functions/digit_obsecur_formate.dart'; import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/map_passenger_controller.dart';
class PaymentMethodPage extends StatelessWidget { class PaymentMethodPage extends StatelessWidget {
const PaymentMethodPage({ const PaymentMethodPage({
@@ -18,7 +17,7 @@ class PaymentMethodPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>( return GetBuilder<MapEngineController>(
builder: (controller) => Positioned( builder: (controller) => Positioned(
right: 5, right: 5,
bottom: 5, bottom: 5,

View File

@@ -4,20 +4,23 @@ import 'package:Intaleq/constant/table_names.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../widgets/elevated_btn.dart'; import '../../widgets/elevated_btn.dart';
import 'form_search_places_destenation.dart'; import 'form_search_places_destenation.dart';
class PickerAnimtionContainerFormPlaces extends StatelessWidget { class PickerAnimtionContainerFormPlaces extends StatelessWidget {
PickerAnimtionContainerFormPlaces({ const PickerAnimtionContainerFormPlaces({super.key});
super.key,
});
final controller = MapPassengerController();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// DbSql sql = DbSql.instance; final mapEngine = Get.find<MapEngineController>();
return GetBuilder<MapPassengerController>( final locationSearch = Get.find<LocationSearchController>();
final rideLifecycle = Get.find<RideLifecycleController>();
return GetBuilder<MapEngineController>(
builder: (controller) => Positioned( builder: (controller) => Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,
@@ -101,8 +104,7 @@ class PickerAnimtionContainerFormPlaces extends StatelessWidget {
? Center( ? Center(
child: Column( child: Column(
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment MainAxisAlignment.center,
.center,
children: [ children: [
const Icon( const Icon(
Icons Icons
@@ -132,9 +134,9 @@ class PickerAnimtionContainerFormPlaces extends StatelessWidget {
children: [ children: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
await controller await rideLifecycle
.getDirectionMap( .getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', '${locationSearch.passengerLocation.latitude},${locationSearch.passengerLocation.longitude}',
'${favoritePlaces[index]['latitude']},${favoritePlaces[index]['longitude']}', '${favoritePlaces[index]['latitude']},${favoritePlaces[index]['longitude']}',
); );
controller controller
@@ -143,7 +145,7 @@ class PickerAnimtionContainerFormPlaces extends StatelessWidget {
.changeBottomSheetShown( .changeBottomSheetShown(
forceValue: forceValue:
true); true);
controller rideLifecycle
.bottomSheet(); .bottomSheet();
Get.back(); Get.back();
}, },
@@ -189,24 +191,22 @@ class PickerAnimtionContainerFormPlaces extends StatelessWidget {
], ],
), ),
if (controller.isPickerShown && if (controller.isPickerShown &&
controller.placesDestination.isEmpty) locationSearch.placesDestination.isEmpty)
MyElevatedButton( MyElevatedButton(
title: 'Go to this Target'.tr, title: 'Go to this Target'.tr,
onPressed: () async { onPressed: () async {
await controller.getDirectionMap( await rideLifecycle.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', '${locationSearch.passengerLocation.latitude},${locationSearch.passengerLocation.longitude}',
'${controller.newMyLocation.latitude},${controller.newMyLocation.longitude}', '${locationSearch.newMyLocation.latitude},${locationSearch.newMyLocation.longitude}',
); );
controller.changePickerShown(); controller.changePickerShown();
controller.changeBottomSheetShown( controller.changeBottomSheetShown(
forceValue: true); forceValue: true);
controller.bottomSheet(); rideLifecycle.bottomSheet();
// await sql
// .getAllData(TableName.placesFavorite)
}, },
), ),
if (controller.isPickerShown && if (controller.isPickerShown &&
controller.placesDestination.isEmpty) locationSearch.placesDestination.isEmpty)
const SizedBox(), const SizedBox(),
], ],
), ),

View File

@@ -3,21 +3,25 @@ import 'package:get/get.dart';
import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/constant/style.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../controller/home/points_for_rider_controller.dart'; import '../../../controller/home/points_for_rider_controller.dart';
class PointsPageForRider extends StatelessWidget { class PointsPageForRider extends StatelessWidget {
PointsPageForRider({ PointsPageForRider({
super.key, super.key,
}); });
MapPassengerController mapPassengerController =
Get.put(MapPassengerController()); final locationSearch = Get.find<LocationSearchController>();
final mapEngine = Get.find<MapEngineController>();
final rideLifecycle = Get.find<RideLifecycleController>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Get.put(WayPointController()); Get.find<WayPointController>();
return GetBuilder<MapPassengerController>(builder: (controller) { return GetBuilder<RideLifecycleController>(builder: (controller) {
return Positioned( return Positioned(
bottom: 2, bottom: 2,
left: 2, left: 2,
@@ -34,7 +38,7 @@ class PointsPageForRider extends StatelessWidget {
children: [ children: [
IconButton( IconButton(
onPressed: () { onPressed: () {
mapPassengerController.downPoints(); mapEngine.downPoints();
}, },
icon: const Icon(Icons.arrow_drop_down_circle_outlined), icon: const Icon(Icons.arrow_drop_down_circle_outlined),
), ),
@@ -52,7 +56,7 @@ class PointsPageForRider extends StatelessWidget {
wayPointController.wayPoints.length > 1 wayPointController.wayPoints.length > 1
? ElevatedButton( ? ElevatedButton(
onPressed: () async { onPressed: () async {
mapPassengerController locationSearch
.getMapPointsForAllMethods(); .getMapPointsForAllMethods();
}, },
child: const Text('Get Direction'), child: const Text('Get Direction'),
@@ -74,7 +78,6 @@ class PointsPageForRider extends StatelessWidget {
.entries .entries
.map((entry) { .map((entry) {
final index = entry.key; final index = entry.key;
final wayPoint = entry.value;
return Padding( return Padding(
key: ValueKey(index), key: ValueKey(index),
padding: const EdgeInsets.all(1), padding: const EdgeInsets.all(1),
@@ -98,7 +101,7 @@ class PointsPageForRider extends StatelessWidget {
content: SizedBox( content: SizedBox(
width: Get.width, width: Get.width,
height: 400, height: 400,
child: mapPassengerController child: locationSearch
.placeListResponse[index]), .placeListResponse[index]),
); );
}, },
@@ -106,13 +109,13 @@ class PointsPageForRider extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(), border: Border.all(),
color: color:
AppColor.accentColor.withOpacity(.5)), AppColor.accentColor.withValues(alpha: 0.5)),
child: Row( child: Row(
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment.spaceBetween, MainAxisAlignment.spaceBetween,
children: [ children: [
Text(index > 0 Text(index > 0
? mapPassengerController ? locationSearch
.currentLocationStringAll[index] .currentLocationStringAll[index]
.toString() .toString()
: ''), : ''),
@@ -239,7 +242,6 @@ class PointsPageForRider extends StatelessWidget {
} }
void showAddLocationDialog(BuildContext context, int index) { void showAddLocationDialog(BuildContext context, int index) {
final TextEditingController locationController = TextEditingController();
// Get.put(WayPointController()); // Get.put(WayPointController());
showDialog( showDialog(
context: context, context: context,
@@ -298,26 +300,24 @@ class AppBarPointsPageForRider extends StatelessWidget {
color: AppColor.primaryColor, color: AppColor.primaryColor,
), ),
), ),
Container( Row(
child: Row( children: [
children: [ CircleAvatar(
CircleAvatar( backgroundColor: AppColor.primaryColor,
backgroundColor: AppColor.primaryColor, maxRadius: 15,
maxRadius: 15, child: Icon(
child: Icon( Icons.person,
Icons.person, color: AppColor.secondaryColor,
color: AppColor.secondaryColor,
),
), ),
TextButton( ),
onPressed: () {}, TextButton(
child: Text( onPressed: () {},
"Switch Rider".tr, child: Text(
style: AppStyle.title, "Switch Rider".tr,
), style: AppStyle.title,
), ),
], ),
), ],
), ),
Icon( Icon(
Icons.clear, Icons.clear,

View File

@@ -11,7 +11,9 @@ import '../../../constant/style.dart';
import '../../../controller/functions/audio_record1.dart'; import '../../../controller/functions/audio_record1.dart';
import '../../../controller/functions/launch.dart'; import '../../../controller/functions/launch.dart';
import '../../../controller/functions/toast.dart'; import '../../../controller/functions/toast.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../controller/home/map/ui_interactions_controller.dart';
import '../../../controller/home/map/ride_state.dart';
import '../../../controller/profile/profile_controller.dart'; import '../../../controller/profile/profile_controller.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../../views/home/profile/complaint_page.dart'; import '../../../views/home/profile/complaint_page.dart';
@@ -24,9 +26,10 @@ class RideBeginPassenger extends StatelessWidget {
final ProfileController profileController = Get.put(ProfileController()); final ProfileController profileController = Get.put(ProfileController());
final AudioRecorderController audioController = final AudioRecorderController audioController =
Get.put(AudioRecorderController()); Get.put(AudioRecorderController());
final uiController = Get.find<UiInteractionsController>();
return Obx(() { return Obx(() {
final controller = Get.find<MapPassengerController>(); final controller = Get.find<RideLifecycleController>();
// شرط الإظهار // شرط الإظهار
final bool isVisible = final bool isVisible =
@@ -50,8 +53,8 @@ class RideBeginPassenger extends StatelessWidget {
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Get.isDarkMode color: Get.isDarkMode
? Colors.black.withOpacity(0.4) ? Colors.black.withValues(alpha: 0.4)
: Colors.black.withOpacity(0.1), : Colors.black.withValues(alpha: 0.1),
blurRadius: 20, blurRadius: 20,
spreadRadius: 2, spreadRadius: 2,
offset: const Offset(0, -3), offset: const Offset(0, -3),
@@ -69,7 +72,7 @@ class RideBeginPassenger extends StatelessWidget {
width: 40, width: 40,
height: 4, height: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.grayColor.withOpacity(0.3), color: AppColor.grayColor.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
), ),
@@ -85,7 +88,7 @@ class RideBeginPassenger extends StatelessWidget {
Divider( Divider(
height: 1, height: 1,
thickness: 0.5, thickness: 0.5,
color: AppColor.grayColor.withOpacity(0.2)), color: AppColor.grayColor.withValues(alpha: 0.2)),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -104,7 +107,7 @@ class RideBeginPassenger extends StatelessWidget {
} }
// --- الهيدر (بدون تغيير، ممتاز) --- // --- الهيدر (بدون تغيير، ممتاز) ---
Widget _buildCompactHeader(MapPassengerController controller) { Widget _buildCompactHeader(RideLifecycleController controller) {
return Row( return Row(
children: [ children: [
// صورة السائق // صورة السائق
@@ -112,7 +115,7 @@ class RideBeginPassenger extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: AppColor.primaryColor.withOpacity(0.5), width: 1.5), color: AppColor.primaryColor.withValues(alpha: 0.5), width: 1.5),
), ),
child: CircleAvatar( child: CircleAvatar(
radius: 24, radius: 24,
@@ -166,9 +169,9 @@ class RideBeginPassenger extends StatelessWidget {
padding: padding:
const EdgeInsets.symmetric(horizontal: 4, vertical: 1), const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.writeColor.withOpacity(0.05), color: AppColor.writeColor.withValues(alpha: 0.05),
border: Border.all( border: Border.all(
color: AppColor.grayColor.withOpacity(0.2)), color: AppColor.grayColor.withValues(alpha: 0.2)),
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
child: Text( child: Text(
@@ -190,7 +193,7 @@ class RideBeginPassenger extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.08), color: AppColor.primaryColor.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Column( child: Column(
@@ -216,9 +219,10 @@ class RideBeginPassenger extends StatelessWidget {
// --- الأزرار (بدون تغيير) --- // --- الأزرار (بدون تغيير) ---
Widget _buildCompactActionButtons( Widget _buildCompactActionButtons(
BuildContext context, BuildContext context,
MapPassengerController controller, RideLifecycleController controller,
ProfileController profileController, ProfileController profileController,
AudioRecorderController audioController) { AudioRecorderController audioController) {
final uiController = Get.find<UiInteractionsController>();
return SizedBox( return SizedBox(
height: 60, height: 60,
child: Row( child: Row(
@@ -228,7 +232,7 @@ class RideBeginPassenger extends StatelessWidget {
icon: Icons.sos_rounded, icon: Icons.sos_rounded,
label: 'SOS'.tr, label: 'SOS'.tr,
color: AppColor.redColor, color: AppColor.redColor,
bgColor: AppColor.redColor.withOpacity(0.1), bgColor: AppColor.redColor.withValues(alpha: 0.1),
onTap: () async { onTap: () async {
if (box.read(BoxName.sosPhonePassenger) == null) { if (box.read(BoxName.sosPhonePassenger) == null) {
await profileController.updatField( await profileController.updatField(
@@ -244,24 +248,26 @@ class RideBeginPassenger extends StatelessWidget {
icon: FontAwesome.whatsapp, icon: FontAwesome.whatsapp,
label: 'WhatsApp'.tr, label: 'WhatsApp'.tr,
color: const Color(0xFF25D366), color: const Color(0xFF25D366),
bgColor: const Color(0xFF25D366).withOpacity(0.1), bgColor: const Color(0xFF25D366).withValues(alpha: 0.1),
onTap: () async { onTap: () async {
if (box.read(BoxName.sosPhonePassenger) == null) { final phone = box.read(BoxName.sosPhonePassenger);
await profileController.updatField( if (phone == null || phone.toString().isEmpty) {
'sosPhone', TextInputType.phone); // لا يوجد رقم طوارئ — نعرض الديالوج لإدخاله
await uiController.shareTripWithFamily();
} else { } else {
final phone = controller.formatSyrianPhoneNumber( final formattedPhone = uiController.formatSyrianPhoneNumber(
box.read(BoxName.sosPhonePassenger).toString()); phone.toString());
controller.sendWhatsapp(phone); uiController.sendWhatsapp(formattedPhone);
} }
}, },
), ),
_compactBtn( _compactBtn(
icon: Icons.share, icon: Icons.share,
label: 'Share'.tr, label: 'Share'.tr,
color: AppColor.primaryColor, color: AppColor.primaryColor,
bgColor: AppColor.primaryColor.withOpacity(0.1), bgColor: AppColor.primaryColor.withValues(alpha: 0.1),
onTap: () async => await controller.shareTripWithFamily(), onTap: () async => await uiController.shareTripWithFamily(),
), ),
GetBuilder<AudioRecorderController>( GetBuilder<AudioRecorderController>(
init: audioController, init: audioController,
@@ -275,15 +281,19 @@ class RideBeginPassenger extends StatelessWidget {
? AppColor.redColor ? AppColor.redColor
: AppColor.primaryColor, : AppColor.primaryColor,
bgColor: audioCtx.isRecording bgColor: audioCtx.isRecording
? AppColor.redColor.withOpacity(0.1) ? AppColor.redColor.withValues(alpha: 0.1)
: AppColor.primaryColor.withOpacity(0.1), : AppColor.primaryColor.withValues(alpha: 0.1),
onTap: () async { onTap: () async {
if (!audioCtx.isRecording) { if (!audioCtx.isRecording) {
await audioCtx.startRecording(); await audioCtx.startRecording(rideId: controller.rideId);
Toast.show(context, 'Start Record'.tr, AppColor.greenColor); if (context.mounted) {
Toast.show(context, 'Start Record'.tr, AppColor.greenColor);
}
} else { } else {
await audioCtx.stopRecording(); await audioCtx.stopRecording();
Toast.show(context, 'Record saved'.tr, AppColor.greenColor); if (context.mounted) {
Toast.show(context, 'Record saved'.tr, AppColor.greenColor);
}
} }
}, },
); );
@@ -293,7 +303,7 @@ class RideBeginPassenger extends StatelessWidget {
icon: Icons.info_outline_rounded, icon: Icons.info_outline_rounded,
label: 'Report'.tr, label: 'Report'.tr,
color: AppColor.grayColor, color: AppColor.grayColor,
bgColor: AppColor.writeColor.withOpacity(0.1), bgColor: AppColor.writeColor.withValues(alpha: 0.1),
onTap: () => Get.to(() => ComplaintPage()), onTap: () => Get.to(() => ComplaintPage()),
), ),
], ],

View File

@@ -8,7 +8,9 @@ import '../../../constant/box_name.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/links.dart'; import '../../../constant/links.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../controller/home/map/ui_interactions_controller.dart';
import '../../../controller/home/map/ride_state.dart';
import '../../../controller/profile/profile_controller.dart'; import '../../../controller/profile/profile_controller.dart';
import '../../../main.dart'; import '../../../main.dart';
@@ -18,8 +20,10 @@ class RideFromStartApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final profileController = Get.put(ProfileController()); final profileController = Get.put(ProfileController());
final MapPassengerController controller = final RideLifecycleController controller =
Get.find<MapPassengerController>(); Get.find<RideLifecycleController>();
final UiInteractionsController uiController =
Get.find<UiInteractionsController>();
return Obx(() { return Obx(() {
final bool isRideActive = final bool isRideActive =
@@ -59,7 +63,7 @@ class RideFromStartApp extends StatelessWidget {
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Get.isDarkMode color: Get.isDarkMode
? Colors.black.withOpacity(0.4) ? Colors.black.withValues(alpha: 0.4)
: Colors.black12, : Colors.black12,
blurRadius: 15.0, blurRadius: 15.0,
spreadRadius: 5.0, spreadRadius: 5.0,
@@ -78,7 +82,7 @@ class RideFromStartApp extends StatelessWidget {
height: 4, height: 4,
margin: const EdgeInsets.only(bottom: 15), margin: const EdgeInsets.only(bottom: 15),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.grayColor.withOpacity(0.3), color: AppColor.grayColor.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
), ),
@@ -134,7 +138,7 @@ class RideFromStartApp extends StatelessWidget {
Container( Container(
width: 1, width: 1,
height: 12, height: 12,
color: AppColor.grayColor.withOpacity(0.3)), color: AppColor.grayColor.withValues(alpha: 0.3)),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
"$carType - $carModel", "$carType - $carModel",
@@ -160,7 +164,7 @@ class RideFromStartApp extends StatelessWidget {
const EdgeInsets.symmetric(vertical: 12, horizontal: 10), const EdgeInsets.symmetric(vertical: 12, horizontal: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.grayColor color: AppColor.grayColor
.withOpacity(0.1), // خلفية رمادية خفيفة جداً .withValues(alpha: 0.1), // خلفية رمادية خفيفة جداً
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
), ),
child: Row( child: Row(
@@ -188,7 +192,7 @@ class RideFromStartApp extends StatelessWidget {
flex: 2, flex: 2,
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () => _checkAndCall( onPressed: () => _checkAndCall(
controller.sendWhatsapp, profileController), uiController.sendWhatsapp, profileController),
icon: icon:
const Icon(FontAwesome.whatsapp, color: Colors.white), const Icon(FontAwesome.whatsapp, color: Colors.white),
label: Text("Share Trip".tr, label: Text("Share Trip".tr,
@@ -252,9 +256,9 @@ class RideFromStartApp extends StatelessWidget {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color.withOpacity(0.1), color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withOpacity(0.5)), border: Border.all(color: color.withValues(alpha: 0.5)),
), ),
child: Text( child: Text(
text, text,
@@ -297,7 +301,7 @@ class RideFromStartApp extends StatelessWidget {
return Container( return Container(
height: 30, height: 30,
width: 1, width: 1,
color: AppColor.grayColor.withOpacity(0.2), color: AppColor.grayColor.withValues(alpha: 0.2),
); );
} }

View File

@@ -2,7 +2,8 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:Intaleq/constant/colors.dart'; import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/constant/style.dart';
import 'package:Intaleq/controller/home/map_passenger_controller.dart'; import 'package:Intaleq/controller/home/map/ride_lifecycle_controller.dart';
import 'package:Intaleq/controller/home/map/ride_state.dart';
// --- الويدجت الرئيسية بالتصميم الجديد --- // --- الويدجت الرئيسية بالتصميم الجديد ---
class SearchingCaptainWindow extends StatefulWidget { class SearchingCaptainWindow extends StatefulWidget {
@@ -36,7 +37,7 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
// [تعديل 1] نستخدم Obx للاستماع إلى التغييرات في حالة الرحلة // [تعديل 1] نستخدم Obx للاستماع إلى التغييرات في حالة الرحلة
return Obx(() { return Obx(() {
// ابحث عن الكنترولر مرة واحدة // ابحث عن الكنترولر مرة واحدة
final controller = Get.find<MapPassengerController>(); final controller = Get.find<RideLifecycleController>();
// [تعديل 2] شرط الإظهار يعتمد الآن على حالة الرحلة مباشرة // [تعديل 2] شرط الإظهار يعتمد الآن على حالة الرحلة مباشرة
final bool isVisible = final bool isVisible =
@@ -58,7 +59,7 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.2), color: Colors.black.withValues(alpha: 0.2),
blurRadius: 20, blurRadius: 20,
offset: const Offset(0, -5), offset: const Offset(0, -5),
), ),
@@ -83,7 +84,7 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: AppColor.writeColor, foregroundColor: AppColor.writeColor,
side: side:
BorderSide(color: AppColor.writeColor.withOpacity(0.3)), BorderSide(color: AppColor.writeColor.withValues(alpha: 0.3)),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)), borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
@@ -99,7 +100,7 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
} }
// --- ويدجت بناء أنيميشن الرادار --- // --- ويدجت بناء أنيميشن الرادار ---
Widget _buildRadarAnimation(MapPassengerController controller) { Widget _buildRadarAnimation(RideLifecycleController controller) {
return SizedBox( return SizedBox(
height: 180, // ارتفاع ثابت لمنطقة الأنيميشن height: 180, // ارتفاع ثابت لمنطقة الأنيميشن
child: Stack( child: Stack(
@@ -125,7 +126,7 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: AppColor.primaryColor.withOpacity(0.7), color: AppColor.primaryColor.withValues(alpha: 0.7),
width: 2, width: 2,
), ),
), ),
@@ -147,7 +148,7 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
Text( Text(
'Searching for the nearest captain...'.tr, 'Searching for the nearest captain...'.tr,
style: AppStyle.subtitle style: AppStyle.subtitle
.copyWith(color: AppColor.writeColor.withOpacity(0.7)), .copyWith(color: AppColor.writeColor.withValues(alpha: 0.7)),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -166,12 +167,12 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
CircularProgressIndicator( CircularProgressIndicator(
strokeWidth: 3, strokeWidth: 3,
color: AppColor.primaryColor, color: AppColor.primaryColor,
backgroundColor: AppColor.primaryColor.withOpacity(0.2), backgroundColor: AppColor.primaryColor.withValues(alpha: 0.2),
), ),
Center( Center(
child: Icon( child: Icon(
Icons.search, Icons.search,
color: AppColor.writeColor.withOpacity(0.8), color: AppColor.writeColor.withValues(alpha: 0.8),
), ),
), ),
], ],

View File

@@ -1,6 +1,6 @@
import 'package:Intaleq/constant/colors.dart'; import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/constant/style.dart';
import 'package:Intaleq/controller/home/map_passenger_controller.dart'; import 'package:Intaleq/controller/home/map/ride_lifecycle_controller.dart';
import 'package:Intaleq/views/widgets/elevated_btn.dart'; import 'package:Intaleq/views/widgets/elevated_btn.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -10,8 +10,10 @@ import '../../../constant/links.dart';
import '../../../print.dart'; import '../../../print.dart';
class CupertinoDriverListWidget extends StatelessWidget { class CupertinoDriverListWidget extends StatelessWidget {
MapPassengerController mapPassengerController = CupertinoDriverListWidget({super.key});
Get.put(MapPassengerController());
final RideLifecycleController mapPassengerController =
Get.find<RideLifecycleController>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CupertinoPageScaffold( return CupertinoPageScaffold(
@@ -158,7 +160,7 @@ class CupertinoDriverListWidget extends StatelessWidget {
), ),
onTap: () { onTap: () {
Log.print(' driver["id"]: ${driver['driver_id']}'); Log.print(' driver["id"]: ${driver['driver_id']}');
Get.find<MapPassengerController>().driverIdVip = Get.find<RideLifecycleController>().driverIdVip =
driver['driver_id']; driver['driver_id'];
// Handle driver selection // Handle driver selection
@@ -266,8 +268,6 @@ class CupertinoDriverListWidget extends StatelessWidget {
} }
void showDateTimePickerDialog(Map<String, dynamic> driver) { void showDateTimePickerDialog(Map<String, dynamic> driver) {
DateTime selectedDateTime = DateTime.now();
Get.defaultDialog( Get.defaultDialog(
barrierDismissible: false, barrierDismissible: false,
title: "Select date and time of trip".tr, title: "Select date and time of trip".tr,
@@ -302,7 +302,9 @@ class CupertinoDriverListWidget extends StatelessWidget {
} }
class DateTimePickerWidget extends StatelessWidget { class DateTimePickerWidget extends StatelessWidget {
final MapPassengerController controller = Get.put(MapPassengerController()); DateTimePickerWidget({super.key});
final RideLifecycleController controller = Get.find<RideLifecycleController>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart';
GetBuilder<MapPassengerController> timerForCancelTripFromPassenger() { GetBuilder<RideLifecycleController> timerForCancelTripFromPassenger() {
return GetBuilder<MapPassengerController>( return GetBuilder<RideLifecycleController>(
builder: (controller) { builder: (controller) {
final isNearEnd = final isNearEnd =
controller.remainingTime <= 5; // Define a threshold for "near end" controller.remainingTime <= 5; // Define a threshold for "near end"

View File

@@ -4,7 +4,7 @@ import 'package:Intaleq/views/widgets/elevated_btn.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart';
import 'ride_begin_passenger.dart'; import 'ride_begin_passenger.dart';
class TimerToPassengerFromDriver extends StatelessWidget { class TimerToPassengerFromDriver extends StatelessWidget {
@@ -14,7 +14,7 @@ class TimerToPassengerFromDriver extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) { return GetBuilder<RideLifecycleController>(builder: (controller) {
if (controller.remainingTime == 0 && if (controller.remainingTime == 0 &&
(controller.isDriverInPassengerWay == true || (controller.isDriverInPassengerWay == true ||
controller.timeToPassengerFromDriverAfterApplied > 0)) { controller.timeToPassengerFromDriverAfterApplied > 0)) {

View File

@@ -12,7 +12,8 @@ import '../../../constant/style.dart';
import '../../../controller/functions/audio_record1.dart'; import '../../../controller/functions/audio_record1.dart';
import '../../../controller/functions/launch.dart'; import '../../../controller/functions/launch.dart';
import '../../../controller/functions/toast.dart'; import '../../../controller/functions/toast.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../controller/home/map/ui_interactions_controller.dart';
class VipRideBeginPassenger extends StatelessWidget { class VipRideBeginPassenger extends StatelessWidget {
const VipRideBeginPassenger({ const VipRideBeginPassenger({
@@ -24,8 +25,8 @@ class VipRideBeginPassenger extends StatelessWidget {
ProfileController profileController = Get.put(ProfileController()); ProfileController profileController = Get.put(ProfileController());
AudioRecorderController audioController = AudioRecorderController audioController =
Get.put(AudioRecorderController()); Get.put(AudioRecorderController());
// Get.put(MapPassengerController()); final uiController = Get.find<UiInteractionsController>();
return GetBuilder<MapPassengerController>(builder: (controller) { return GetBuilder<RideLifecycleController>(builder: (controller) {
if (controller.statusRideVip == 'Begin' || if (controller.statusRideVip == 'Begin' ||
!controller.statusRideFromStart) { !controller.statusRideFromStart) {
return Positioned( return Positioned(
@@ -148,9 +149,11 @@ class VipRideBeginPassenger extends StatelessWidget {
child: audioController.isRecording == false child: audioController.isRecording == false
? IconButton( ? IconButton(
onPressed: () async { onPressed: () async {
await audioController.startRecording(); await audioController.startRecording(rideId: controller.rideId);
Toast.show(context, 'Start Record'.tr, if (context.mounted) {
AppColor.greenColor); Toast.show(context, 'Start Record'.tr,
AppColor.greenColor);
}
}, },
icon: const Icon( icon: const Icon(
Icons.play_circle_fill_outlined, Icons.play_circle_fill_outlined,
@@ -162,8 +165,10 @@ class VipRideBeginPassenger extends StatelessWidget {
: IconButton( : IconButton(
onPressed: () async { onPressed: () async {
await audioController.stopRecording(); await audioController.stopRecording();
Toast.show(context, 'Record saved'.tr, if (context.mounted) {
AppColor.greenColor); Toast.show(context, 'Record saved'.tr,
AppColor.greenColor);
}
}, },
icon: const Icon( icon: const Icon(
Icons.stop_circle, Icons.stop_circle,
@@ -215,15 +220,11 @@ class VipRideBeginPassenger extends StatelessWidget {
profileController.prfoileData['sosPhone']); profileController.prfoileData['sosPhone']);
} }
} else { } else {
String phoneNumber = box
.read(BoxName.sosPhonePassenger)
.toString();
// phoneNumber = phoneNumber.replaceAll('0', '');
var phone = box.read(BoxName.countryCode) == var phone = box.read(BoxName.countryCode) ==
'Egypt' 'Egypt'
? '+2${box.read(BoxName.sosPhonePassenger)}' ? '+2${box.read(BoxName.sosPhonePassenger)}'
: '+962${box.read(BoxName.sosPhonePassenger)}'; : '+962${box.read(BoxName.sosPhonePassenger)}';
controller.sendWhatsapp(phone); uiController.sendWhatsapp(phone);
} }
}, },
icon: const Icon( icon: const Icon(
@@ -237,7 +238,7 @@ class VipRideBeginPassenger extends StatelessWidget {
width: Get.width * .15, width: Get.width * .15,
child: IconButton( child: IconButton(
onPressed: () async { onPressed: () async {
await controller.shareTripWithFamily(); await uiController.shareTripWithFamily();
}, },
icon: const Icon( icon: const Icon(
AntDesign.Safety, AntDesign.Safety,
@@ -283,13 +284,12 @@ class VipRideBeginPassenger extends StatelessWidget {
} }
class StreamCounter extends StatelessWidget { class StreamCounter extends StatelessWidget {
const StreamCounter({Key? key}) : super(key: key); const StreamCounter({super.key});
@override @override
// Build the UI based on the timer value // Build the UI based on the timer value
Widget build(BuildContext context) { Widget build(BuildContext context) {
Get.put(MapPassengerController()); return GetBuilder<RideLifecycleController>(builder: (controller) {
return GetBuilder<MapPassengerController>(builder: (controller) {
return StreamBuilder<int>( return StreamBuilder<int>(
initialData: 0, initialData: 0,
stream: controller.timerController.stream, stream: controller.timerController.stream,

View File

@@ -268,9 +268,6 @@ class NavigationController extends GetxController
}, },
]; ];
static final String _routeApiBaseUrl =
"${AppLink.routesOsm}/route/v1/driving";
IconData get currentManeuverIcon { IconData get currentManeuverIcon {
switch (currentManeuverModifier) { switch (currentManeuverModifier) {
case 4: // Arrive case 4: // Arrive
@@ -378,7 +375,6 @@ class NavigationController extends GetxController
void onMapCreated(IntaleqMapController controller) async { void onMapCreated(IntaleqMapController controller) async {
Log.print("DEBUG: NavigationController.onMapCreated called"); Log.print("DEBUG: NavigationController.onMapCreated called");
mapController = controller; mapController = controller;
await onStyleLoaded();
} }
Future<void> onStyleLoaded() async { Future<void> onStyleLoaded() async {
@@ -577,7 +573,7 @@ class NavigationController extends GetxController
} }
void _checkOffRoute(LatLng pos) { void _checkOffRoute(LatLng pos) {
if (_autoRecalcInProgress || isLoading) return; if (!isNavigating || _autoRecalcInProgress || isLoading) return;
if (_fullRouteCoordinates.isEmpty) return; if (_fullRouteCoordinates.isEmpty) return;
const int searchWindow = 80; const int searchWindow = 80;
@@ -604,7 +600,6 @@ class NavigationController extends GetxController
if (elapsed >= _offRouteTriggerSeconds) { if (elapsed >= _offRouteTriggerSeconds) {
_offRouteStartTime = null; _offRouteStartTime = null;
_autoRecalcInProgress = true; _autoRecalcInProgress = true;
// Smart reroute: check if we have alternative routes available
_smartRecalculateRoute(pos); _smartRecalculateRoute(pos);
} }
} }
@@ -613,40 +608,11 @@ class NavigationController extends GetxController
} }
} }
/// الحل الذكي: إذا كان هناك مسارات بديلة متاحة، اختر الأقرب. /// Recalculate immediately from the latest GPS point to the destination.
/// وإلا فاطلب مسار جديد من الموقع الحالي إلى الوجهة.
Future<void> _smartRecalculateRoute(LatLng currentPos) async { Future<void> _smartRecalculateRoute(LatLng currentPos) async {
try { try {
// Check if we have alternative routes
if (routes.isNotEmpty && selectedRouteIndex < routes.length - 1) {
// Try using the next alternative route
final nextIndex = selectedRouteIndex + 1;
final nextRoute = routes[nextIndex];
// Calculate distance from current position to this alternative route's start
double minDist = double.infinity;
for (var coord in nextRoute.coordinates) {
final d = Geolocator.distanceBetween(
currentPos.latitude,
currentPos.longitude,
coord.latitude,
coord.longitude,
);
if (d < minDist) minDist = d;
}
// If this alternative is reasonable, switch to it
if (minDist < 100) {
selectRoute(nextIndex);
Log.print("DEBUG: Switched to alternative route due to deviation");
_autoRecalcInProgress = false;
return;
}
}
// No good alternative, recalculate from current position to destination
if (_finalDestination != null) { if (_finalDestination != null) {
await recalculateRoute(); await recalculateRoute(origin: currentPos, keepNavigationActive: true);
} }
_autoRecalcInProgress = false; _autoRecalcInProgress = false;
} catch (e) { } catch (e) {
@@ -906,7 +872,8 @@ class NavigationController extends GetxController
return const LatLng(31.7225, 35.9933); // Queen Alia Airport (JO) return const LatLng(31.7225, 35.9933); // Queen Alia Airport (JO)
} }
Future<void> getRoute(LatLng origin, LatLng destination) async { Future<void> getRoute(LatLng origin, LatLng destination,
{bool keepNavigationActive = false}) async {
isLoading = true; isLoading = true;
update(); update();
@@ -1007,9 +974,8 @@ class NavigationController extends GetxController
currentStepIndex = 0; currentStepIndex = 0;
_nextInstructionSpoken = false; _nextInstructionSpoken = false;
// Don't start navigating immediately, wait for user to press Start isNavigating = keepNavigationActive;
isNavigating = false; _cameraLockedToUser = keepNavigationActive;
_cameraLockedToUser = false;
_offRouteStartTime = null; _offRouteStartTime = null;
isLoading = false; isLoading = false;
@@ -1032,7 +998,10 @@ class NavigationController extends GetxController
// Re-add car marker after polyline updates (ensures it stays on top) // Re-add car marker after polyline updates (ensures it stays on top)
if (isStyleLoaded) _updateCarMarker(); if (isStyleLoaded) _updateCarMarker();
if (_fullRouteCoordinates.length >= 2) { if (keepNavigationActive && myLocation != null) {
animateCameraToPosition(myLocation!,
bearing: _smoothedHeading, zoom: _targetZoom, tilt: _targetTilt);
} else if (_fullRouteCoordinates.length >= 2) {
final bounds = final bounds =
data['bbox'] != null && (data['bbox'] as List).length == 4 data['bbox'] != null && (data['bbox'] as List).length == 4
? LatLngBounds( ? LatLngBounds(
@@ -1117,12 +1086,23 @@ class NavigationController extends GetxController
} }
} }
Future<void> recalculateRoute() async { Future<void> recalculateRoute(
if (myLocation == null || _finalDestination == null || isLoading) return; {LatLng? origin, bool keepNavigationActive = false}) async {
final LatLng? routeOrigin = origin ?? myLocation;
if (routeOrigin == null || _finalDestination == null || isLoading) return;
isLoading = true; isLoading = true;
update(); update();
mySnackbarInfo('جاري حساب مسار جديد...');
await getRoute(myLocation!, _finalDestination!); markers = markers.where((m) => m.markerId.value != 'origin').toSet();
markers.add(Marker(
markerId: const MarkerId('origin'),
position: routeOrigin,
icon: InlqBitmap.fromStyleImage('start_icon'),
));
await getRoute(routeOrigin, _finalDestination!,
keepNavigationActive: keepNavigationActive);
isLoading = false; isLoading = false;
update(); update();
} }
@@ -1290,17 +1270,19 @@ class NavigationController extends GetxController
try { try {
// ✅ Use searchPlaces from intaleq_maps SDK // ✅ Use searchPlaces from intaleq_maps SDK
final results = await mapController!.searchPlaces(q); final results = await mapController!.searchPlaces(q);
if (myLocation != null) { if (myLocation != null) {
for (final p in results) { for (final p in results) {
final plat = double.tryParse(p['latitude']?.toString() ?? '0') ?? 0.0; final plat = double.tryParse(p['latitude']?.toString() ?? '0') ?? 0.0;
final plng = double.tryParse(p['longitude']?.toString() ?? '0') ?? 0.0; final plng =
p['distanceKm'] = _haversineKm(myLocation!.latitude, myLocation!.longitude, plat, plng); double.tryParse(p['longitude']?.toString() ?? '0') ?? 0.0;
p['distanceKm'] = _haversineKm(
myLocation!.latitude, myLocation!.longitude, plat, plng);
} }
results.sort((a, b) => results.sort((a, b) =>
(a['distanceKm'] as double).compareTo(b['distanceKm'] as double)); (a['distanceKm'] as double).compareTo(b['distanceKm'] as double));
} }
placesDestination = results; placesDestination = results;
update(); update();
} catch (e) { } catch (e) {

View File

@@ -41,6 +41,7 @@ class NavigationView extends StatelessWidget {
IntaleqMap( IntaleqMap(
apiKey: Env.mapSaasKey, apiKey: Env.mapSaasKey,
onMapCreated: c.onMapCreated, onMapCreated: c.onMapCreated,
onStyleLoaded: c.onStyleLoaded,
onLongPress: (pos) => c.onMapLongPressed(Point(0, 0), pos), onLongPress: (pos) => c.onMapLongPressed(Point(0, 0), pos),
onTap: (pos) => c.onMapTapped(Point(0, 0), pos), onTap: (pos) => c.onMapTapped(Point(0, 0), pos),
markers: c.markers, markers: c.markers,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,290 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/colors.dart';
import '../../constant/style.dart';
import '../../controller/voice_call_controller.dart';
class VoiceCallBottomSheet extends StatelessWidget {
const VoiceCallBottomSheet({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final controller = Get.find<VoiceCallController>();
final double screenHeight = MediaQuery.of(context).size.height;
final bool isDark = Theme.of(context).brightness == Brightness.dark;
// Harmonious curated colors
final Color bgColor = isDark ? const Color(0xFF121212) : Colors.white;
final Color cardColor = isDark ? const Color(0xFF1E1E1E) : const Color(0xFFF5F5F7);
final Color textColor = isDark ? Colors.white : const Color(0xFF1C1C1E);
final Color subTextColor = isDark ? Colors.white70 : Colors.black54;
return WillPopScope(
onWillPop: () async => false,
child: Container(
height: screenHeight * 0.9,
decoration: BoxDecoration(
color: bgColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(32)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, -5),
)
],
),
child: Obx(() {
final state = controller.state.value;
final seconds = controller.elapsedSeconds.value;
final remoteName = controller.remoteName.value;
final isMuted = controller.isMuted.value;
final isSpeakerOn = controller.isSpeakerOn.value;
// Progress ring logic
final double progress = seconds / 60.0;
final Color ringColor = seconds > 10 ? const Color(0xFF2ECC71) : const Color(0xFFE74C3C);
// Status text translations
String statusText = "";
switch (state) {
case VoiceCallState.dialing:
statusText = "${'Calling'.tr} $remoteName...";
break;
case VoiceCallState.ringing:
statusText = "${'Captain'.tr} $remoteName ${'is calling you'.tr}...";
break;
case VoiceCallState.connecting:
statusText = "Connecting...".tr;
break;
case VoiceCallState.active:
statusText = "Call Connected".tr;
break;
case VoiceCallState.ended:
statusText = "Call Ended".tr;
break;
case VoiceCallState.idle:
statusText = "";
break;
}
return Column(
children: [
// Top Drag Handle Indicator
Center(
child: Container(
margin: const EdgeInsets.only(top: 12, bottom: 24),
width: 44,
height: 5,
decoration: BoxDecoration(
color: isDark ? Colors.white24 : Colors.black12,
borderRadius: BorderRadius.circular(10),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Header Info
Column(
children: [
Text(
"Free Call".tr,
style: TextStyle(
color: ringColor,
fontWeight: FontWeight.w800,
fontSize: 14,
letterSpacing: 1.2,
),
),
const SizedBox(height: 8),
Text(
remoteName,
style: TextStyle(
color: textColor,
fontWeight: FontWeight.w900,
fontSize: 26,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
statusText,
style: TextStyle(
color: subTextColor,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
],
),
// Avatar & Animated Progress Ring
Stack(
alignment: Alignment.center,
children: [
// Progress ring around avatar (Active state only)
if (state == VoiceCallState.active)
SizedBox(
width: 172,
height: 172,
child: CircularProgressIndicator(
value: progress,
strokeWidth: 5,
backgroundColor: isDark ? Colors.white10 : Colors.black12,
valueColor: AlwaysStoppedAnimation<Color>(ringColor),
),
),
// Main Avatar Card
Container(
width: 150,
height: 150,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: cardColor,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 15,
offset: const Offset(0, 8),
)
],
),
child: Center(
child: remoteName.isNotEmpty
? Text(
remoteName[0].toUpperCase(),
style: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
fontSize: 54,
),
)
: Icon(
Icons.person,
color: textColor.withOpacity(0.6),
size: 64,
),
),
),
],
),
// Timer Counter Display
if (state == VoiceCallState.active)
Text(
"0:${seconds.toString().padLeft(2, '0')}",
style: TextStyle(
color: seconds > 10 ? textColor : const Color(0xFFE74C3C),
fontWeight: FontWeight.bold,
fontSize: 22,
fontFamily: 'monospace',
),
)
else
const SizedBox(height: 24),
// Action Controls Block
if (state == VoiceCallState.ringing)
// Incoming Ringing Controls: Accept / Decline
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildCircleActionButton(
icon: Icons.call_end_rounded,
color: Colors.white,
bgColor: const Color(0xFFE74C3C),
onTap: () => controller.declineCall(),
label: "Decline".tr,
),
_buildCircleActionButton(
icon: Icons.call_rounded,
color: Colors.white,
bgColor: const Color(0xFF2ECC71),
onTap: () => controller.acceptCall(),
label: "Accept".tr,
),
],
)
else
// Dialing or Connected Controls: Speaker / Mute / Hangup
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Speakerphone toggle
_buildCircleActionButton(
icon: isSpeakerOn ? Icons.volume_up_rounded : Icons.volume_down_rounded,
color: isSpeakerOn ? Colors.white : textColor,
bgColor: isSpeakerOn ? const Color(0xFF2ECC71) : cardColor,
onTap: () => controller.toggleSpeaker(),
label: "Speaker".tr,
),
// Hangup Call
_buildCircleActionButton(
icon: Icons.call_end_rounded,
color: Colors.white,
bgColor: const Color(0xFFE74C3C),
onTap: () => controller.hangup(),
label: "End".tr,
),
// Mute Microphone
_buildCircleActionButton(
icon: isMuted ? Icons.mic_off_rounded : Icons.mic_rounded,
color: isMuted ? Colors.white : textColor,
bgColor: isMuted ? const Color(0xFFE74C3C) : cardColor,
onTap: () => controller.toggleMute(),
label: "Mute".tr,
),
],
),
],
),
),
),
],
);
}),
),
);
}
Widget _buildCircleActionButton({
required IconData icon,
required Color color,
required Color bgColor,
required VoidCallback onTap,
required String label,
}) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: onTap,
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
padding: const EdgeInsets.all(18),
backgroundColor: bgColor,
foregroundColor: color,
elevation: 2,
),
child: Icon(icon, size: 28),
),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey,
),
),
],
);
}
}

View File

@@ -7,8 +7,6 @@ import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
// Import for Android features. // Import for Android features.
import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart';
// Import for iOS features.
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
import 'src/PaypalServices.dart'; import 'src/PaypalServices.dart';
import 'src/errors/network_error.dart'; import 'src/errors/network_error.dart';
@@ -128,20 +126,10 @@ class UsePaypalState extends State<UsePaypal> {
// Enable hybrid composition. // Enable hybrid composition.
loadPayment(); loadPayment();
// #docregion platform_features const PlatformWebViewControllerCreationParams params =
late final PlatformWebViewControllerCreationParams params; PlatformWebViewControllerCreationParams();
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
params = WebKitWebViewControllerCreationParams(
allowsInlineMediaPlayback: true,
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
);
} else {
params = const PlatformWebViewControllerCreationParams();
}
final WebViewController controller = final WebViewController controller =
WebViewController.fromPlatformCreationParams(params); WebViewController.fromPlatformCreationParams(params);
// #enddocregion platform_features
controller controller
..setJavaScriptMode(JavaScriptMode.unrestricted) ..setJavaScriptMode(JavaScriptMode.unrestricted)

View File

@@ -0,0 +1,701 @@
<div dir="rtl" align="right">
# تقرير تحليل شامل لتطبيق إنتلق - لوحة الإدارة (Intaleq Admin)
---
## 📋 فهرس المحتويات
1. [نظرة عامة على النظام](#1-نظرة-عامة-على-النظام)
2. [هيكل المشاريع والهندسة المعمارية](#2-هيكل-المشاريع-والهندسة-المعمارية)
3. [تحليل الباك إند - الخادم الرئيسي (intaleq_v1)](#3-تحليل-الباك-إند---الخادم-الرئيسي)
4. [تحليل لوحة الإدارة (intaleq_admin)](#4-تحليل-لوحة-الإدارة)
5. [تحليل تطبيق خدمة العملاء (service_intaleq)](#5-تحليل-تطبيق-خدمة-العملاء)
6. [قاعدة البيانات والموديلات](#6-قاعدة-البيانات-والموديلات)
7. [نظام المصادقة والصلاحيات](#7-نظام-المصادقة-والصلاحيات)
8. [تحليل التوافقية بين الباك إند والأدمن](#8-تحليل-التوافقية-بين-الباك-إند-والأدمن)
9. [الإضافات والتحسينات المقترحة](#9-الإضافات-والتحسينات-المقترحة)
10. [ملخص وتوصيات](#10-ملخص-وتوصيات)
---
## 1. نظرة عامة على النظام
### 1.1 وصف النظام
نظام **إنتلق (Intaleq)** هو منصة متكاملة لخدمات نقل الركاب (ride-hailing) تعمل في عدة دول. يتكون النظام من المكونات الرئيسية التالية:
| المكون | التقنية | الوصف |
|-------|---------|-------|
| **تطبيق الركاب (Intaleq)** | Flutter/Dart | تطبيق المستخدمين لطلب الرحلات |
| **تطبيق السائقين (intaleq_driver)** | Flutter/Dart | تطبيق السائقين (الكباتن) لاستقبال وتنفيذ الرحلات |
| **لوحة الإدارة (intaleq_admin)** | Flutter/Dart | لوحة تحكم للمشرفين لإدارة النظام |
| **تطبيق خدمة العملاء (service_intaleq)** | Flutter/Dart | تطبيق للموظفين للدعم وإدارة السائقين |
| **الخادم الرئيسي (intaleq_v1)** | PHP (بدون إطار عمل) | الباك إند الرئيسي REST API |
| **خادم المحفظة (Wallet Server)** | PHP (منفصل) | خدمة مالية مستقلة للمدفوعات |
| **خادم المواقع (Location Server)** | PHP (منفصل) | خدمة تتبع مواقع السائقين |
| **سيرفر السوكت (socket_intaleq)** | PHP WebSocket | خدمة الوقت الفعلي للتواصل المباشر |
| **خدمة OTP (flash-call-otp)** | Node.js | خدمة التحقق عبر المكالمات السريعة |
| **خدمة الخرائط (map-saas)** | خدمة منفصلة | خدمة التوجيه والبحث الجغرافي |
### 1.2 النطاق الجغرافي
النظام يعمل في عدة دول وأقاليم:
- **مصر** (القاهرة، الجيزة، الإسكندرية)
- **سوريا**
- **الأردن** (مسارات خاصة - routesjo)
---
## 2. هيكل المشاريع والهندسة المعمارية
### 2.1 الرسم البياني للهندسة المعمارية
```
┌──────────────────────────────────────────────────────────────┐
│ تطبيقات الواجهة الأمامية │
├─────────────┬──────────────┬──────────────┬──────────────────┤
│ Intaleq │ intaleq_ │ intaleq_ │ service_ │
│ (الركاب) │ driver │ admin │ intaleq │
│ Flutter │ Flutter │ Flutter │ Flutter │
└──────┬──────┴──────┬───────┴──────┬───────┴──────┬───────────┘
│ │ │ │
└─────────────┼──────────────┼──────────────┘
│ │
┌──────▼──────────────▼──────┐
│ API الرئيسية │
│ api.intaleq.xyz │
│ PHP REST (بدون إطار عمل) │
│ + JWT Authentication │
│ + Rate Limiting (Redis) │
└──────┬─────────────────────┘
┌────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌──────────────────┐
│ MySQL │ │ Redis │ │ خوادم فرعية │
│ intaleq │ │ (جلسات/تخزين │ │ - Wallet Server │
│ DB1 │ │ مؤقت/Cache) │ │ - Location Server│
└──────────┘ └──────────────┘ │ - Socket Server │
│ - Map SaaS │
└──────────────────┘
```
### 2.2 هيكل مجلدات المشاريع
#### intaleq_admin (لوحة الإدارة)
```
lib/
├── main.dart # نقطة الدخول
├── routes.dart # تعريف المسارات
├── binding/
│ └── initial_binding.dart # ربط المتحكمات الأولية
├── constant/ # الثوابت
│ ├── api_key.dart # مفاتيح API سرية
│ ├── box_name.dart # مفاتيح التخزين المحلي
│ ├── colors.dart # ألوان التطبيق
│ ├── credential.dart # بيانات الاعتماد
│ ├── links.dart # روابط API (363 سطر - جميع نقاط النهاية)
│ ├── style.dart # أنماط التصميم
│ └── table_names.dart # أسماء الجداول
├── controller/ # طبقة المتحكمات
│ ├── auth/ # المصادقة
│ │ ├── login_controller.dart
│ │ └── otp_helper.dart
│ ├── admin/ # متحكمات لوحة الإدارة
│ │ ├── analytics_v2_controller.dart
│ │ ├── captain_admin_controller.dart
│ │ ├── complaint_controller.dart
│ │ ├── dashboard_controller.dart
│ │ ├── dashboard_v2_controller.dart
│ │ ├── driver_docs_controller.dart
│ │ ├── financial_v2_controller.dart
│ │ ├── get_all_invoice_controller.dart
│ │ ├── kazan_controller.dart
│ │ ├── passenger_admin_controller.dart
│ │ ├── promo_controller.dart
│ │ ├── quality_controller.dart
│ │ ├── register_captain_controller.dart
│ │ ├── ride_admin_controller.dart
│ │ ├── security_v2_controller.dart
│ │ ├── staff_controller.dart
│ │ ├── static_controller.dart
│ │ └── wallet_admin_controller.dart
│ ├── firebase/ # إشعارات Firebase
│ ├── functions/ # دوال مساعدة (CRUD، تشفير، AI)
│ └── bank_account/ # حسابات بنكية
├── models/ # نماذج البيانات
│ ├── db_sql.dart
│ └── model/
│ └── admin/ # نماذج المشرفين
├── views/ # واجهات المستخدم
│ ├── admin/ # شاشات لوحة الإدارة
│ │ ├── admin_home_page.dart # الصفحة الرئيسية (1123 سطر)
│ │ ├── dashboard_v2_widget.dart
│ │ ├── captain/ # إدارة السائقين
│ │ ├── complaints/ # الشكاوى
│ │ ├── drivers/ # تتبع السائقين
│ │ ├── employee/ # الموظفون
│ │ ├── enceypt/ # أدوات التشفير
│ │ ├── financial/ # المالية
│ │ ├── passenger/ # الركاب
│ │ ├── pricing/ # التسعير
│ │ ├── promo/ # العروض الترويجية
│ │ ├── quality/ # الجودة
│ │ ├── rides/ # الرحلات
│ │ ├── security/ # الأمان
│ │ ├── server/ # مراقبة السيرفر
│ │ ├── staff/ # الكوادر
│ │ ├── static/ # الإحصائيات
│ │ └── wallet/ # المحفظة
│ ├── auth/ # شاشات المصادقة
│ └── widgets/ # مكونات قابلة لإعادة الاستخدام
└── services/ # (فارغ - لا توجد خدمات منفصلة)
```
---
## 3. تحليل الباك إند - الخادم الرئيسي
### 3.1 التقنيات المستخدمة
| التقنية | الوصف |
|---------|-------|
| **PHP** (بدون إطار عمل) | لغة السيرفر الأساسية - إصدار حديث مع `declare(strict_types=1)` |
| **MySQL** | قاعدة البيانات الرئيسية (intaleqDB1) |
| **Redis** | للتخزين المؤقت، إدارة الجلسات، Rate Limiting، والقوائم السوداء |
| **JWT** | للمصادقة والتوكنات |
| **PDO** | للاتصال الآمن بقاعدة البيانات |
| **Composer** | لإدارة المكتبات (Firebase JWT, Twilio, Stripe, PHPMailer) |
| **HMAC** | لتوقيع الطلبات المالية |
### 3.2 هيكل الباك إند
```
intaleq_v1/
├── connect.php # نقطة الدخول الموحدة (JWT مطلوب)
├── functions.php # دوال مساعدة (jsonSuccess, jsonError...)
├── login.php # تسجيل دخول API عام
├── loginAdmin.php # تسجيل دخول الأدمن (JWT)
├── core/
│ ├── bootstrap.php # البوابة الرئيسية: Redis, Encryption, Headers
│ ├── helpers.php # دوال البيئة والمساعدة
│ ├── Auth/
│ │ ├── JwtService.php # خدمة JWT كاملة
│ │ └── RateLimiter.php # تحديد معدل الطلبات
│ ├── Database/
│ │ └── Database.php # اتصال قاعدة البيانات (Singleton)
│ ├── Security/
│ │ └── EncryptionHelper.php # مساعد التشفير
│ └── Services/ # خدمات (فارغ حالياً)
├── Admin/ # نقاط نهاية لوحة الإدارة
│ ├── dashbord.php # لوحة المعلومات الرئيسية
│ ├── v2/ # خدمات الجيل الثاني
│ │ ├── realtime_dashboard.php
│ │ ├── smart_alerts.php
│ │ ├── analytics/ # التحليلات (نمو، إيرادات، ترتيب)
│ │ ├── financial/ # مالية (تسويات، إحصائيات)
│ │ ├── quality/ # جودة (قائمة سوداء، تقييم)
│ │ └── security/ # أمان (سجل التدقيق)
│ ├── adminUser/ # إدارة المشرفين
│ ├── driver/ # إدارة السائقين
│ ├── passenger/ # إدارة الركاب
│ ├── rides/ # إدارة الرحلات
│ ├── Staff/ # إدارة الكوادر
│ └── employee/ # الموظفون
├── auth/ # المصادقة
│ ├── captin/ # تسجيل/دخول السائقين
│ ├── sms/ # إرسال الرسائل النصية
│ ├── google_auth/ # مصادقة جوجل
│ ├── syria/ # مصادقة سوريا
│ └── passengerOTP/ # OTP الركاب
├── ride/ # عمليات الرحلات
│ ├── rides/ # إدارة الرحلات
│ ├── cancelRide/ # إلغاء الرحلات
│ ├── driver_order/ # طلبات السائقين
│ ├── kazan/ # التسعير
│ ├── promo/ # العروض الترويجية
│ ├── rate/ # التقييمات
│ ├── location/ # المواقع
│ ├── firebase/ # إشعارات Firebase
│ ├── driverWallet/ # محفظة السائقين
│ ├── passengerWallet/ # محفظة الركاب
│ ├── payment/ # المدفوعات
│ ├── notificationCaptain/ # إشعارات السائقين
│ ├── notificationPassenger/# إشعارات الركاب
│ ├── feedBack/ # التقييمات
│ ├── chat/ # المحادثة
│ ├── gamification/ # نظام التحفيز
│ └── invitor/ # نظام الدعوات
├── serviceapp/ # خدمة العملاء
│ ├── getComplaintAllData.php
│ ├── editCarPlate.php
│ ├── addNotesDriver.php
│ └── ... (20+ نقطة نهاية)
├── migration/ # الترحيل والتحديثات
├── EgyptDocuments/ # وثائق مصر
├── email/ # البريد الإلكتروني
├── webhook_sms/ # Webhook للرسائل
└── schema_*.sql # مخططات قواعد البيانات
```
### 3.3 نظام الأمان في الباك إند
1. **JWT Authentication**: جميع الطلبات تتطلب توكن JWT صالح
2. **Rate Limiting**: عبر Redis لمنع الهجمات
3. **تشفير البيانات**: `EncryptionHelper` مع مفتاح 32 بايت
4. **HMAC للمعاملات المالية**: توقيع إضافي للخادم المالي
5. **CORS Headers**: مقيدة وآمنة
6. **Device Fingerprint**: تتبع الأجهزة عبر `X-Device-FP`
### 3.4 نقاط القوة في الباك إند
- ✅ بنية تحتية قوية مع Redis و JWT و Rate Limiting
- ✅ فصل الخدمات (Wallet, Location, Map SaaS مستقلة)
- ✅ نظام تحقق متعدد (OTP, Google, Flash Call)
- ✅ دعم متعدد الدول (مصر، سوريا، الأردن)
- ✅ نظام ترحيل (Migration) لتحديث قاعدة البيانات
### 3.5 نقاط الضعف في الباك إند
-**عدم استخدام إطار عمل**: PHP خام بدون Laravel/Symfony مما يصعب الصيانة
-**SQL مباشر**: استعلامات SQL مكتوبة يدوياً بدون ORM (عرضة للـ SQL Injection في بعض الأماكن)
-**غياب التوثيق**: لا يوجد API documentation (Swagger/OpenAPI)
-**غياب الاختبارات**: لا توجد Unit Tests أو Integration Tests
-**ملف connect.php المركزي**: نقطة فشل واحدة (Single Point of Failure)
-**كود مكرر**: دوال jsonSuccess/jsonError مكررة في أغلب الملفات
-**غياب logging منظم**: استخدام `error_log` فقط بدون مستويات
---
## 4. تحليل لوحة الإدارة
### 4.1 المميزات الحالية
لوحة الإدارة توفر الوظائف التالية مقسمة حسب الفئات:
#### 🧑‍🤝‍🧑 المستخدمين
- **الركاب**: عرض وإدارة وحذف وحظر
- **السائقون**: عرض وإدارة وتفعيل وحذف
- **المراقب**: تتبع السائقين مباشرة على الخريطة
#### ⚙️ إدارة النظام
- **أكواد الخصم**: إنشاء وتعديل وحذف (Promo Management)
- **تعديل الأسعار**: تعديل نسب التسعير (Kazan Editor)
- **الشكاوى**: عرض وإدارة الشكاوى
- **مراجعة الوثائق**: فحص وثائق السائقين
#### 📊 العمليات
- **الرحلات**: عرض وإدارة جميع الرحلات
- **مراقبة الرحلات**: متابعة الرحلات الجارية
- **الإحصائيات**: إحصائيات عامة
- **التحليلات المتقدمة**: تحليلات V2
#### 🛡️ الجودة والدعم
- **القائمة السوداء**: حظر سائقين/ركاب
#### 💰 المالية والإدارة
- **الإدارة المالية V2**: لوحة مالية متقدمة
- **المحفظة**: إدارة المحافظ
- **هدية 300**: هدايا تحفيزية للسائقين
- **الفواتير**: إدارة الفواتير
- **الموظفون**: إدارة الموظفين
- **موافقة المشرفين**: قبول/رفض مشرفين جدد
#### 🔧 النظام والتواصل
- **سجل العمليات**: سجل تدقيق أمني
- **واتساب جماعي**: رسائل جماعية للسائقين
- **إشعار سائقين/ركاب**: إشعارات Firebase
- **تسجيل سائق**: تسجيل سائقين جدد
- **تحديث التطبيق**: متابعة إصدارات التطبيق
- **مراقب السيرفر**: مراقبة حالة السيرفر
- **سجل الأخطاء**: عرض أخطاء النظام
- **أدوات التشفير**: تشفير بصمات الأجهزة
- **إدارة الكوادر**: إضافة مديرين وخدمة عملاء (للسوبر أدمن فقط)
### 4.2 نظام الصلاحيات
```dart
// التحقق من الصلاحيات في admin_home_page.dart
isSuperAdmin = (role == 'super_admin') ||
(myPhone == '201023248456' || // أرقام محددة صلاحية مطلقة
myPhone == '963992952235' ||
myPhone == '963942542053');
```
**مستويات الصلاحيات:**
1. **super_admin**: صلاحية كاملة - رؤية المحفظة، إضافة كوادر، مراقبة الرحلات
2. **admin**: صلاحية محدودة - إدارة عامة بدون صلاحيات مالية حساسة
3. **service**: خدمة عملاء (غير مفعلة في كل الواجهات)
### 4.3 نقاط القوة في لوحة الإدارة
-**تصميم عصري وجميل**: استخدام ألوان داكنة مع تأثيرات بصرية جذابة
-**لوحة معلومات تفاعلية**: Dashboard V2 مع تحديث تلقائي كل دقيقتين
-**بحث سريع**: شريط بحث لتصفية الخدمات
-**تصنيف منظم**: تجميع الوظائف في فئات منطقية
-**دعم اللغة العربية**: الواجهة بالكامل بالعربية
-**تحديث تلقائي**: RefreshIndicator للسحب للتحديث
### 4.4 نقاط الضعف في لوحة الإدارة
-**ملف routes.dart غير مكتمل**: 7 مسارات فقط معرفة من أصل 30+ شاشة (الباقي عبر `Get.to()`)
-**كود كبير جداً**: `admin_home_page.dart` يحتوي 1123 سطر (يجب تقسيمه)
-**تكرار الكود**: نمط CRUD متكرر في أغلب المتحكمات
-**معالجة أخطاء غير متسقة**: بعض المتحكمات تستخدم try-catch والبعض لا
-**لا يوجد pagination**: جميع البيانات تُجلب دفعة واحدة
-**غياب التخزين المؤقت**: لا يوجد caching للبيانات محلياً
-**التحقق من الصلاحيات عبر أرقام هواتف ثابتة**: ممارسة غير آمنة
-**لا يوجد تسجيل خروج صريح**: إزالة التوكن فقط دون إلغاء في السيرفر
-**استخدام GetX لإدارة الحالة فقط**: بدون استخدام ميزات التنقل القوية في GetX
-**غياب WebSocket**: لا يوجد اتصال مباشر للتحديثات الفورية (باستثناء V2 timer)
---
## 5. تحليل تطبيق خدمة العملاء
### 5.1 الوظائف الرئيسية
تطبيق `service_intaleq` هو أداة لموظفي خدمة العملاء للقيام بـ:
- تسجيل الدخول للموظفين
- استقبال إشعارات Firebase
- الوصول إلى وظائف serviceapp في الباك إند
### 5.2 نقاط القوة
- ✅ دعم اللغات (العربية والإنجليزية)
- ✅ تكامل مع Firebase للإشعارات
### 5.3 نقاط الضعف
- ❌ تطبيق بسيط جداً - يبدو غير مكتمل
- ❌ لا توجد شاشات واضحة للموظفين
- ❌ تكرار مع وظائف لوحة الإدارة
---
## 6. قاعدة البيانات والموديلات
### 6.1 الجداول الرئيسية (44+ جدول)
#### جداول المستخدمين
| الجدول | الوصف | عدد الأعمدة |
|-------|-------|------------|
| `passengers` | الركاب | ~20 عمود |
| `driver` | السائقين | ~25 عمود |
| `adminUser` | المشرفين | 4 أعمدة |
| `employee` | الموظفين | 6 أعمدة |
#### جداول الرحلات
| الجدول | الوصف |
|-------|-------|
| `ride` | الرحلات الرئيسية |
| `canecl` | إلغاءات الرحلات |
| `driver_orders` | طلبات السائقين |
| `driver_behavior` | سلوك السائقين |
| `driver_ride_scam` | احتيال الرحلات |
#### جداول المركبات
| الجدول | الوصف |
|-------|-------|
| `CarRegistration` | تسجيل المركبات |
| `captains_car` | سيارات السائقين |
| `car_locations` | مواقع السيارات (Spatial Index) |
| `car_tracks` | تتبع مسار السيارات |
#### جداول مالية
| الجدول | الوصف |
|-------|-------|
| `driverWallet` | محفظة السائقين |
| `driver_gifts` | هدايا السائقين |
| `invoice_records` | سجلات الفواتير |
| `invoicesAdmin` | فواتير المشرفين |
| `kazan` | التسعير |
#### جداول النظام
| الجدول | الوصف |
|-------|-------|
| `complaint` | الشكاوى |
| `error` | سجل الأخطاء (115,338+ سجل) |
| `notifications` | إشعارات الركاب |
| `notificationCaptain` | إشعارات السائقين |
| `login_attempts` | محاولات الدخول |
| `api_keys` | مفاتيح API |
### 6.2 نقاط القوة
-**تصميم جغرافي مكاني**: استخدام `POINT` و `SPATIAL INDEX` في جدول `car_locations`
-**تتبع كامل**: جداول لتتبع السلوك، المواقع، والمسار
-**أمان**: جداول لتسجيل المحاولات والأخطاء
### 6.3 نقاط الضعف
-**عدم استخدام FK (Foreign Keys)**: العلاقات غير مقيدة
-**عدم تناسق التسمية**: camelCase مع snake_case في نفس الجدول
-**عدم وجود indexing كافٍ**: بعض الأعمدة المستخدمة بكثرة بدون INDEX
-**استخدام جداول قديمة**: مثل `captains_car` و `CarRegistration` المكررين
-**تخزين بيانات حساسة**: `password` غير مشفرة بشكل كافٍ
-**استخدام VARCHAR(6) لـ amount**: يجب أن يكون DECIMAL للمبالغ المالية
---
## 7. نظام المصادقة والصلاحيات
### 7.1 تدفق المصادقة
```
1. المستخدم ← رقم الهاتف + كلمة المرور
2. الباك إند ← التحقق + JWT (مع role)
3. التطبيق ← تخزين JWT مشفر في GetStorage
4. كل طلب ← إرسال JWT في Header Authorization
5. الباك إند ← فك JWT + استخراج role + التحقق
```
### 7.2 نظام التوكن
```dart
// طريقة الحصول على JWT في CRUD
getJWT() async {
var payload = {
'id': 'admin',
'password': AK.passnpassenger,
'aud': '${AK.allowed}$dev',
};
// تشفير التوكن 3 مرات:
await box.write(BoxName.jwt, X.c(X.c(X.c(jwt, cn), cC), cs));
}
// فك التشفير عند الاستخدام:
token = r(rawJwt.toString()).split(AppInformation.addd)[0];
```
**طبقات الأمان:**
1. **JWT** - توقيع رمزي
2. **تشفير 3 طبقات** - `X.c()` ثلاث مرات متتالية
3. **HMAC** - للمعاملات المالية
4. **Device Fingerprint** - `X-Device-FP` header
### 7.3 نقاط القوة
- ✅ تشفير متعدد الطبقات للتوكنات
- ✅ نظام صلاحيات قائم على الأدوار (role-based)
- ✅ فصل مالي مع HMAC
### 7.4 نقاط الضعف
-**أرقام هواتف ثابتة للسوبر أدمن**: ممارسة غير آمنة
-**عدم وجود انتهاء صلاحية فعال**: إعادة تلقائية دون تدخل المستخدم
-**تخزين التوكن محلياً**: بدون Secure Enclave
-**كلمة مرور ثابتة في الكود**: `AK.passnpassenger`
-**لا يوجد 2FA**: للمشرفين
-**لا يوجد audit log للتغييرات الحساسة**
---
## 8. تحليل التوافقية بين الباك إند والأدمن
### 8.1 مصفوفة التوافق
| الوظيفة | الباك إند | الأدمن | الحالة |
|--------|----------|--------|--------|
| لوحة المعلومات | `Admin/dashbord.php` | `DashboardController` | ✅ متوافق |
| لوحة V2 | `Admin/v2/realtime_dashboard.php` | `DashboardV2Controller` | ✅ متوافق |
| قائمة السائقين | `Admin/AdminCaptain/get.php` | `captain_admin_controller.dart` | ✅ متوافق |
| تفاصيل السائق | `auth/syria/driver/driver_details.php` | `captain_details.dart` | ✅ متوافق |
| الركاب | `Admin/getPassengerDetails.php` | `passenger_admin_controller.dart` | ✅ متوافق |
| أكواد الخصم | `ride/promo/` | `promo_controller.dart` | ✅ متوافق |
| التسعير | `ride/kazan/` | `kazan_controller.dart` | ✅ متوافق |
| الشكاوى | `serviceapp/getComplaintAllData.php` | `complaint_controller.dart` | ✅ متوافق |
| المالية V2 | `v2/financial/` | `financial_v2_controller.dart` | ✅ متوافق |
| سجل التدقيق | `v2/security/audit_logs.php` | `security_v2_controller.dart` | ✅ متوافق |
| الإحصائيات | `Admin/getRidesPerMonth.php` | `static_controller.dart` | ✅ متوافق |
| المحادثة | `ride/chat/` | ❌ غير موجود | ❌ غير متوافق |
| نظام التحفيز | `ride/gamification/` | ❌ غير موجود | ❌ غير متوافق |
| تتبع حي | WebSocket | ❌ غير موجود | ❌ غير متوافق |
### 8.2 مشاكل التوافقية
1. **ملف routes.dart غير مكتمل**: 7 مسارات فقط من أصل 30+ وظيفة تستخدم `Get.toNamed()`
2. **استخدام `Get.to()` المباشر**: بدون تعريفها في routes، مما يصعب تتبع التنقل
3. **اختلاف أسماء الحقول**: بعض الـ payload تختلف تسميتها بين الأدمن والباك إند
4. **غياب معالجة Versioning**: لا توجد آلية لتحديد إصدار API
5. **نقاط نهاية غير مستخدمة**: بعض endpoints في الباك إند لا تقابلها شاشات في الأدمن
---
## 9. الإضافات والتحسينات المقترحة
### 9.1 تحسينات أمنية (أولوية عالية) 🔴
1. **إضافة 2FA للمشرفين**
- إضافة مصادقة ثنائية عبر Google Authenticator أو OTP
- حماية الوصول للوحة الإدارة
2. **تحسين نظام الصلاحيات**
- استبدال أرقام الهواتف الثابتة بنظام صلاحيات ديناميكي
- إضافة صلاحيات دقيقة (Granular Permissions) بدلاً من admin/super_admin فقط
3. **تسجيل الخروج من جميع الأجهزة**
- إضافة `token_blacklist` في Redis
- إمكانية إنهاء جلسات محددة
4. **تشفير البيانات الحساسة**
- نقل `api_key.dart` إلى متغيرات بيئة آمنة
- استخدام Secure Enclave لتخزين التوكنات
### 9.2 تحسينات معمارية (أولوية متوسطة) 🟡
5. **إعادة هيكلة routes.dart**
- تعريف جميع المسارات (~30 مسار) في ملف routes
- استخدام `GetPage` مع `binding` و `middleware`
6. **تقسيم admin_home_page.dart**
- فصل إلى مكونات مستقلة (StatelessWidget)
- نقل كل قسم إلى ملف منفصل
7. **طبقة خدمة موحدة (Service Layer)**
- إنشاء BaseRepository/BaseService
- توحيد معالجة الـ CRUD
- إضافة retry policy و offline support
8. **استخدام Riverpod/Bloc بدلاً من GetX**
- GetX يعمل ولكن ليس الخيار الأمثل للتطبيقات الكبيرة
- تحسين فصل المسؤوليات (Separation of Concerns)
### 9.3 تحسينات وظيفية (أولوية متوسطة) 🟡
9. **لوحة معلومات قابلة للتخصيص**
- إضافة Widgets قابلة للسحب والإفلات
- حفظ تفضيلات المستخدم
10. **تقارير PDF مصدرة**
- تصدير الإحصائيات كـ PDF
- جداولة التقارير الدورية
11. **نظام تنبيهات ذكي**
- تنبيهات تلقائية عند تجاوز الحدود (عدد شكاوى، إلغاءات)
- Machine Learning للكشف عن السلوك الاحتيالي
12. **محادثة مباشرة مع السائقين والركاب**
- دمج chat داخل لوحة الإدارة
- استخدام WebSocket للتواصل الفوري
### 9.4 تحسينات تقنية (أولوية منخفضة) 🟢
13. **Pagination و Infinite Scroll**
- لجميع القوائم (السائقين، الركاب، الرحلات)
14. **WebSocket للوحة الإدارة**
- اتصال مباشر مع socket_intaleq
- تحديثات فورية بدون polling
15. **دعم الوضع المظلم/الفاتح**
- إضافة theme toggle
- دعم system theme
16. **اختبارات واجهة المستخدم**
- Widget Tests و Integration Tests
### 9.5 إضافات جديدة مقترحة (أولوية متنوعة) 💡
17. **لوحة تحليلات الأعمال (BI Dashboard)**
- رسوم بيانية تفاعلية
- مقارنة الأداء بين الفترات
- توقع الإيرادات المستقبلية
18. **نظام إدارة المناطق الجغرافية**
- رسم المناطق النشطة على الخريطة
- تحليل الكثافة والتغطية
- إدارة المناطق المحظورة
19. **نظام Gamification Dashboard**
- لوحة كاملة لنظام التحفيز (موجود API بدون UI)
- عرض تصنيفات السائقين
- إدارة المكافآت والتحديات
20. **نظام إدارة الحملات التسويقية**
- إنشاء حملات إشعارات مستهدفة
- A/B Testing للإشعارات
- قياس فعالية الحملات
21. **تكامل مع منصات خارجية**
- تصدير البيانات إلى Google Sheets
- Slack/Telegram notifications للإشعارات المهمة
- Webhook للمطورين الخارجيين
22. **نظام توثيق API تلقائي**
- Swagger/OpenAPI documentation
- Sandbox environment للاختبار
---
## 10. ملخص وتوصيات
### 10.1 ملخص الحالة الراهنة
نظام إنتلق هو نظام متكامل وقوي لتطبيقات نقل الركاب، مع:
- **بنية تحتية متقدمة**: Redis، JWT، HMAC، Rate Limiting
- **مجموعة واسعة من الميزات**: إدارة ركاب، سائقين، رحلات، مالية، شكاوى، إشعارات
- **دعم متعدد الدول**: مصر، سوريا، الأردن مع إمكانية التوسع
- **تصميم عصري**: واجهة مستخدم جذابة باللغة العربية
**نقاط القوة الرئيسية:**
1. نظام مصادقة آمن مع JWT و HMAC متعدد الطبقات
2. فصل الخدمات المالية والجغرافية في خوادم مستقلة
3. دعم التتبع الجغرافي المكاني (Spatial Index)
4. نظام V2 للوحة المعلومات بتحديث تلقائي
5. واجهة إدارة شاملة تغطي معظم جوانب النظام
**نقاط الضعف الرئيسية:**
1. عدم استخدام إطار عمل PHP (صعوبة في الصيانة والتوسع)
2. كود كبير في ملفات مفردة (1123 سطر في admin_home_page.dart)
3. نظام صلاحيات بدائي مع أرقام هواتف ثابتة
4. نقص التوثيق والاختبارات
5. طرق تنقل غير متسقة في التطبيق (مزيج بين Get.toNamed و Get.to)
### 10.2 خريطة الطريق المقترحة
```
المرحلة الأولى (شهر 1-2) - تحسينات أمنية وحرجة:
├── 2FA للمشرفين
├── تحسين نظام الصلاحيات
├── نقل المفاتيح السرية إلى بيئة آمنة
└── إصلاح الثغرات الأمنية
المرحلة الثانية (شهر 3-4) - تحسينات معمارية:
├── إعادة هيكلة routes
├── تقسيم الملفات الكبيرة
├── إضافة طبقة خدمة موحدة
└── Pagination لكل القوائم
المرحلة الثالثة (شهر 5-6) - إضافات جديدة:
├── لوحة BI Dashboard
├── WebSocket Integration
├── تقارير PDF
└── نظام إدارة المناطق
المرحلة الرابعة (شهر 7-8) - تحسينات متقدمة:
├── API Documentation (Swagger)
├── اختبارات آلية
├── Offline Support
└── تكامل مع منصات خارجية
```
### 10.3 توصيات فورية
1. **🚨 عاجل**: نقل `api_key.dart` إلى متغيرات بيئة (`.env`) وليس في الكود
2. **🚨 عاجل**: إزالة أرقام الهواتف الثابتة من كود التحقق من الصلاحيات
3. **⚠️ مهم**: إكمال ملف `routes.dart` بجميع المسارات
4. **⚠️ مهم**: إضافة `INDEX` للأعمدة المستخدمة بكثرة في قاعدة البيانات
5. **📝 موصى به**: إنشاء `README.md` شامل لكل مشروع
---
<div align="center">
---
**تم إعداد هذا التقرير بواسطة التحليل الآلي للنظام**
*تاريخ الإعداد: 6 يناير 2026*
*الإصدار: 1.0*
</div>
</div>

View File

@@ -289,7 +289,7 @@ packages:
source: hosted source: hosted
version: "0.3.5+2" version: "0.3.5+2"
crypto: crypto:
dependency: transitive dependency: "direct main"
description: description:
name: crypto name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
@@ -320,6 +320,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.6" version: "3.1.6"
dart_webrtc:
dependency: transitive
description:
name: dart_webrtc
sha256: f6d615bddea5e458ce180a914f3055c234ffb52fb7397a51b3491e76d6d7edb2
url: "https://pub.dev"
source: hosted
version: "1.8.1"
dbus: dbus:
dependency: transitive dependency: transitive
description: description:
@@ -742,6 +750,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_webrtc:
dependency: "direct main"
description:
name: flutter_webrtc
sha256: c7b0a67ca2c878575fc5c146d801cd874f58f5f1ef5fa6e8eb0c93d413beb948
url: "https://pub.dev"
source: hosted
version: "1.4.1"
flutter_widget_from_html: flutter_widget_from_html:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1324,6 +1340,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.1" version: "6.0.1"
logger:
dependency: transitive
description:
name: logger
sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c"
url: "https://pub.dev"
source: hosted
version: "2.7.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@@ -2224,6 +2248,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.3"
webrtc_interface:
dependency: transitive
description:
name: webrtc_interface
sha256: c6f100eac5057d9a817a60473126f9828c796d42884d498af4f339c97b21014f
url: "https://pub.dev"
source: hosted
version: "1.5.1"
webview_flutter: webview_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -2233,7 +2265,7 @@ packages:
source: hosted source: hosted
version: "4.9.0" version: "4.9.0"
webview_flutter_android: webview_flutter_android:
dependency: transitive dependency: "direct main"
description: description:
name: webview_flutter_android name: webview_flutter_android
sha256: "47a8da40d02befda5b151a26dba71f47df471cddd91dfdb7802d0a87c5442558" sha256: "47a8da40d02befda5b151a26dba71f47df471cddd91dfdb7802d0a87c5442558"
@@ -2249,7 +2281,7 @@ packages:
source: hosted source: hosted
version: "2.14.0" version: "2.14.0"
webview_flutter_wkwebview: webview_flutter_wkwebview:
dependency: transitive dependency: "direct main"
description: description:
name: webview_flutter_wkwebview name: webview_flutter_wkwebview
sha256: "108bd85d0ff20bff1e8b52a040f5c19b6b9fc4a78fdf3160534ff5a11a82e267" sha256: "108bd85d0ff20bff1e8b52a040f5c19b6b9fc4a78fdf3160534ff5a11a82e267"

View File

@@ -11,6 +11,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
flutter_webrtc: ^1.4.1
secure_string_operations: secure_string_operations:
path: ./secure_string_operations path: ./secure_string_operations
firebase_messaging: ^16.1.1 firebase_messaging: ^16.1.1
@@ -19,6 +20,7 @@ dependencies:
path: ^1.9.1 path: ^1.9.1
intl: ^0.20.2 intl: ^0.20.2
http: ^1.2.2 http: ^1.2.2
crypto: ^3.0.3
get: get:
path: ./packages/get path: ./packages/get
get_storage: get_storage:
@@ -56,6 +58,8 @@ dependencies:
record: ^6.2.0 record: ^6.2.0
dio: ^5.9.1 dio: ^5.9.1
webview_flutter: ^4.9.0 webview_flutter: ^4.9.0
webview_flutter_android: ^3.16.2
webview_flutter_wkwebview: ^3.14.0
just_audio: ^0.10.5 just_audio: ^0.10.5
# share: ^2.0.4 # share: ^2.0.4
google_sign_in: ^7.2.0 google_sign_in: ^7.2.0

201
scratch/benchmark_route.py Normal file
View File

@@ -0,0 +1,201 @@
#!/usr/bin/env python3
import time
import urllib.request
import urllib.error
import json
import random
from concurrent.futures import ThreadPoolExecutor, as_completed
# ==================== CONFIGURATION ====================
API_KEY = "" # Kept your actual API key
BASE_URL = "https://map-saas.intaleqapp.com/api/maps/route"
CONCURRENCY = 1000 # Number of concurrent threads
TOTAL_REQUESTS = 10000 # Total number of requests to send
TIMEOUT_SECONDS = 10 # Request timeout
# Bounding boxes for heavily populated regions inside Jordan and Syria (excluding deserts and Egypt since it is not in the map database)
REGIONS = {
"Amman (Jordan)": {
"lat_min": 31.85, "lat_max": 32.15,
"lng_min": 35.80, "lng_max": 36.00
},
"Irbid (Jordan)": {
"lat_min": 32.45, "lat_max": 32.60,
"lng_min": 35.80, "lng_max": 35.95
},
"Damascus (Syria)": {
"lat_min": 33.45, "lat_max": 33.55,
"lng_min": 36.25, "lng_max": 36.35
},
"Aleppo (Syria)": {
"lat_min": 36.15, "lat_max": 36.25,
"lng_min": 37.10, "lng_max": 37.20
}
}
# =======================================================
def generate_random_route():
"""Generates random starting and ending points inside Jordan and Syria populated cities."""
region_name = random.choice(list(REGIONS.keys()))
bbox = REGIONS[region_name]
# Pick a random starting point in the selected region
from_lat = random.uniform(bbox["lat_min"], bbox["lat_max"])
from_lng = random.uniform(bbox["lng_min"], bbox["lng_max"])
# Pick a destination within the same region (~10-15km max) to ensure a quick and valid route
to_lat = from_lat + random.uniform(-0.08, 0.08)
to_lng = from_lng + random.uniform(-0.08, 0.08)
# Clip coordinates to region bounds
to_lat = max(bbox["lat_min"], min(to_lat, bbox["lat_max"]))
to_lng = max(bbox["lng_min"], min(to_lng, bbox["lng_max"]))
return region_name, from_lat, from_lng, to_lat, to_lng
def send_request(request_id):
region_name, from_lat, from_lng, to_lat, to_lng = generate_random_route()
# Construct dynamic URL with random coordinates
url = (
f"{BASE_URL}?fromLat={from_lat:.5f}&fromLng={from_lng:.5f}"
f"&toLat={to_lat:.5f}&toLng={to_lng:.5f}&locale=ar"
)
req = urllib.request.Request(
url,
headers={
"x-api-key": API_KEY,
"User-Agent": "Benchmark-Client/1.0"
}
)
start_time = time.perf_counter()
status_code = 0
error_message = None
try:
with urllib.request.urlopen(req, timeout=TIMEOUT_SECONDS) as response:
status_code = response.status
response.read()
except urllib.error.HTTPError as e:
status_code = e.code
try:
err_body = e.read().decode('utf-8')
error_message = f"HTTP {e.code}: {json.loads(err_body).get('message', e.reason)}"
except Exception:
error_message = f"HTTP Error {e.code}: {e.reason}"
except urllib.error.URLError as e:
status_code = 0
error_message = f"URL Error: {e.reason}"
except Exception as e:
status_code = 0
error_message = f"Generic Error: {str(e)}"
end_time = time.perf_counter()
latency = (end_time - start_time) * 1000.0 # Convert to milliseconds
return {
"id": request_id,
"region": region_name,
"success": 200 <= status_code < 300,
"status_code": status_code,
"latency": latency,
"error": error_message
}
def print_report(results, elapsed_time):
latencies = [r["latency"] for r in results]
successes = [r for r in results if r["success"]]
failures = [r for r in results if not r["success"]]
latencies.sort()
total_reqs = len(results)
success_count = len(successes)
failure_count = len(failures)
avg_latency = sum(latencies) / total_reqs if total_reqs > 0 else 0
min_latency = latencies[0] if latencies else 0
max_latency = latencies[-1] if latencies else 0
def percentile(p):
if not latencies:
return 0
idx = int(len(latencies) * p)
return latencies[min(idx, len(latencies) - 1)]
rps = total_reqs / elapsed_time if elapsed_time > 0 else 0
# Calculate stats per region
region_stats = {}
for r in results:
reg = r["region"]
if reg not in region_stats:
region_stats[reg] = {"total": 0, "success": 0, "latencies": []}
region_stats[reg]["total"] += 1
if r["success"]:
region_stats[reg]["success"] += 1
region_stats[reg]["latencies"].append(r["latency"])
print("\n" + "="*50)
print(" API LOAD TESTING REPORT ")
print("="*50)
print(f"Target URL: {BASE_URL}")
print(f"Concurrency Level: {CONCURRENCY} threads")
print(f"Total Requests: {total_reqs}")
print(f"Time Taken: {elapsed_time:.3f} seconds")
print(f"Successful Requests: {success_count} ({success_count/total_reqs*100:.1f}%)")
print(f"Failed Requests: {failure_count} ({failure_count/total_reqs*100:.1f}%)")
print(f"Requests per Second: {rps:.2f} RPS")
print("-"*50)
print("PER-REGION SUMMARY:")
for region, stats in region_stats.items():
r_avg = sum(stats["latencies"]) / stats["total"] if stats["total"] > 0 else 0
print(f" {region:17}: {stats['total']} reqs, Avg: {r_avg:.1f}ms, Success: {stats['success']}/{stats['total']}")
print("-"*50)
print("LATENCY STATISTICS (ms):")
print(f" Min: {min_latency:.2f} ms")
print(f" Max: {max_latency:.2f} ms")
print(f" Average: {avg_latency:.2f} ms")
print(f" Median (50%): {percentile(0.50):.2f} ms")
print(f" 90th Percentile: {percentile(0.90):.2f} ms")
print(f" 95th Percentile: {percentile(0.95):.2f} ms")
print(f" 99th Percentile: {percentile(0.99):.2f} ms")
print("="*50)
if failure_count > 0:
print("\nERROR SUMMARY:")
errors = {}
for f in failures:
err = f["error"] or f"HTTP {f['status_code']}"
errors[err] = errors.get(err, 0) + 1
for err, count in errors.items():
print(f" - {err}: {count} occurrence(s)")
print("="*50)
def main():
print(f"Starting dynamic benchmark of: {BASE_URL}")
print(f"Sending {TOTAL_REQUESTS} randomized requests (Amman, Irbid, Damascus, Aleppo)...")
print(f"Concurrency Level: {CONCURRENCY} concurrent threads")
results = []
start_time = time.perf_counter()
with ThreadPoolExecutor(max_workers=CONCURRENCY) as executor:
futures = {executor.submit(send_request, i): i for i in range(TOTAL_REQUESTS)}
completed = 0
for future in as_completed(futures):
results.append(future.result())
completed += 1
if completed % (TOTAL_REQUESTS // 10 or 1) == 0 or completed == TOTAL_REQUESTS:
print(f"Progress: {completed}/{TOTAL_REQUESTS} requests completed...")
end_time = time.perf_counter()
elapsed_time = end_time - start_time
print_report(results, elapsed_time)
if __name__ == "__main__":
main()

17
scratch/test_api.py Normal file
View File

@@ -0,0 +1,17 @@
import urllib.request
import json
def check_country(country):
url = "https://api.intaleq.xyz/intaleq_v3/ride/kazan/get.php"
data = json.dumps({"country": country}).encode('utf-8')
req = urllib.request.Request(url, data=data, headers={'Content-Type': 'application/json'})
try:
with urllib.request.urlopen(req) as response:
res = response.read().decode('utf-8')
print(f"Country: {country} -> {res}")
except Exception as e:
print(f"Country: {country} -> Error: {e}")
check_country("Jordan")
check_country("Syria")
check_country("Egypt")