Update: 2026-06-10 18:11:50

This commit is contained in:
Hamza-Ayed
2026-06-10 18:11:50 +03:00
parent a0473a8b0f
commit 977adfe99d
27 changed files with 3946 additions and 206 deletions

973
review_diff.patch Normal file
View File

@@ -0,0 +1,973 @@
diff --git a/backend/ride/rides/acceptRide.php b/backend/ride/rides/acceptRide.php
index 85c7832..487e2bc 100755
--- a/backend/ride/rides/acceptRide.php
+++ b/backend/ride/rides/acceptRide.php
@@ -121,6 +121,8 @@ try {
c.color,
c.color_hex,
(SELECT ROUND(AVG(rating), 2) FROM ratingDriver WHERE driver_id = d.id) AS ratingDriver,
+ (SELECT COUNT(*) FROM ratingDriver WHERE driver_id = d.id) AS ratingCount,
+ (SELECT COUNT(*) FROM ride WHERE driver_id = d.id AND status IN ('Finished', 'finished')) AS completedRides,
dt.token
FROM driver d
LEFT JOIN CarRegistration c ON c.driverID = d.id
@@ -140,6 +142,16 @@ try {
}
$driverInfo['driverName'] = trim(($driverInfo['first_name'] ?? '') . ' ' . ($driverInfo['last_name'] ?? ''));
$driverInfo['ratingDriver'] = $driverInfo['ratingDriver'] ?: "5.0";
+ $ratingValue = (float) $driverInfo['ratingDriver'];
+ $ratingCount = (int) ($driverInfo['ratingCount'] ?? 0);
+ $completedRides = (int) ($driverInfo['completedRides'] ?? 0);
+ if ($ratingValue >= 4.8 && $ratingCount >= 50 && $completedRides >= 100) {
+ $driverInfo['driverTier'] = 'Professional driver';
+ } elseif ($ratingValue >= 4.5 && $ratingCount >= 15 && $completedRides >= 30) {
+ $driverInfo['driverTier'] = 'Trusted driver';
+ } else {
+ $driverInfo['driverTier'] = 'Verified driver';
+ }
}
// ═══════════════════════════════════════════════════════════
@@ -195,4 +207,4 @@ try {
} catch (PDOException $e) {
error_log("[accept_ride] CRITICAL: " . $e->getMessage());
printFailure("Server error");
-}
\ No newline at end of file
+}
diff --git a/backend/ride/rides/getRideOrderID.php b/backend/ride/rides/getRideOrderID.php
index f8532ff..7bc828e 100755
--- a/backend/ride/rides/getRideOrderID.php
+++ b/backend/ride/rides/getRideOrderID.php
@@ -96,6 +96,17 @@ try {
FROM ratingDriver
WHERE ratingDriver.driver_id = :driverID_Sub
) AS ratingDriver,
+ (
+ SELECT COUNT(*)
+ FROM ratingDriver
+ WHERE ratingDriver.driver_id = :driverID_Sub
+ ) AS ratingCount,
+ (
+ SELECT COUNT(*)
+ FROM ride
+ WHERE ride.driver_id = :driverID_Sub
+ AND ride.status IN ('Finished', 'finished')
+ ) AS completedRides,
driverToken.token AS token
@@ -143,6 +154,16 @@ try {
$finalData[$field] = $encryptionHelper->decryptData($finalData[$field]);
}
}
+ $ratingValue = (float) ($finalData['ratingDriver'] ?: 5.0);
+ $ratingCount = (int) ($finalData['ratingCount'] ?? 0);
+ $completedRides = (int) ($finalData['completedRides'] ?? 0);
+ if ($ratingValue >= 4.8 && $ratingCount >= 50 && $completedRides >= 100) {
+ $finalData['driverTier'] = 'Professional driver';
+ } elseif ($ratingValue >= 4.5 && $ratingCount >= 15 && $completedRides >= 30) {
+ $finalData['driverTier'] = 'Trusted driver';
+ } else {
+ $finalData['driverTier'] = 'Verified driver';
+ }
}
echo json_encode([
@@ -155,4 +176,4 @@ try {
http_response_code(500);
echo json_encode(["status" => "failure", "message" => "Server Error: " . $e->getMessage()]);
}
-?>
\ No newline at end of file
+?>
diff --git a/siro_driver/lib/controller/firebase/firbase_messge.dart b/siro_driver/lib/controller/firebase/firbase_messge.dart
index 9de8eae..d183550 100755
--- a/siro_driver/lib/controller/firebase/firbase_messge.dart
+++ b/siro_driver/lib/controller/firebase/firbase_messge.dart
@@ -76,15 +76,22 @@ class FirebaseMessagesController extends GetxController {
await fcmToken.subscribeToTopic("drivers"); // أو "users" حسب نوع المستخدم
print("Subscribed to 'drivers' topic ✅");
- FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message) async {
+ FirebaseMessaging.instance
+ .getInitialMessage()
+ .then((RemoteMessage? message) async {
if (message != null && message.data.isNotEmpty) {
Log.print("🔔 FCM getInitialMessage payload: ${message.data}");
String? category = message.data['category'] ?? message.data['type'];
- if (category == 'ORDER' || category == 'Order' || category == 'OrderVIP' || message.data.containsKey('DriverList')) {
+ if (category == 'ORDER' ||
+ category == 'Order' ||
+ category == 'OrderVIP' ||
+ message.data.containsKey('DriverList')) {
String? myListString = message.data['DriverList'];
if (myListString != null && myListString.isNotEmpty) {
- await storage.write(key: 'pending_driver_list', value: myListString);
- Log.print("💾 Saved pending driver list to secure storage from getInitialMessage");
+ await storage.write(
+ key: 'pending_driver_list', value: myListString);
+ Log.print(
+ "💾 Saved pending driver list to secure storage from getInitialMessage");
}
} else {
Future.delayed(const Duration(milliseconds: 1500), () {
@@ -107,7 +114,6 @@ class FirebaseMessagesController extends GetxController {
// fireBaseTitles(message);
// }
});
- FirebaseMessaging.onBackgroundMessage((RemoteMessage message) async {});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message.data.isNotEmpty) {
diff --git a/siro_driver/lib/controller/functions/background_service.dart b/siro_driver/lib/controller/functions/background_service.dart
index e112b36..5d9bec9 100644
--- a/siro_driver/lib/controller/functions/background_service.dart
+++ b/siro_driver/lib/controller/functions/background_service.dart
@@ -9,7 +9,6 @@ import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:socket_io_client/socket_io_client.dart' as IO;
import 'package:flutter_overlay_window/flutter_overlay_window.dart' as Overlay;
import 'package:get_storage/get_storage.dart';
-import 'package:geolocator/geolocator.dart' as geo;
import '../../constant/box_name.dart';
import '../firebase/local_notification.dart';
@@ -129,40 +128,21 @@ Future<bool> onStart(ServiceInstance service) async {
service.stopSelf();
});
- // 🔥 Location management in background isolate (Using Geolocator)
- geo.Position? latestPos;
-
- // Listen to location changes continuously in the background
- geo.Geolocator.getPositionStream(
- locationSettings: geo.AndroidSettings(
- accuracy: geo.LocationAccuracy.high,
- distanceFilter: 10,
- intervalDuration: const Duration(seconds: 10),
- ),
- ).listen((pos) {
- latestPos = pos;
- });
-
- // 🔥 MERCY HEARTBEAT: Send location every 2 minutes to keep driver active in 'raids'
- Timer.periodic(const Duration(minutes: 2), (timer) async {
- if (socket != null && socket.connected && latestPos != null) {
- try {
- socket.emit('update_location', {
- 'driver_id': driverId,
- 'lat': latestPos!.latitude,
- 'lng': latestPos!.longitude,
- 'heading': latestPos!.heading,
- 'speed': latestPos!.speed * 3.6,
- 'status': box.read(BoxName.statusDriverLocation) ?? 'on',
- 'source': 'background_heartbeat'
- });
- print(
- "💓 Background Mercy Heartbeat Sent: ${latestPos!.latitude}, ${latestPos!.longitude}");
- } catch (e) {
- print("❌ Background Heartbeat Error: $e");
- }
- }
- });
+ // 🚫 [Architecture Rule] NO redundant GPS stream in background service!
+ // LocationController is the SINGLE SOURCE OF TRUTH for all location GPS updates.
+ // It already uses location.enableBackgroundMode(enable: true) to keep the GPS
+ // stream alive even when the app is in the background. The main socket in
+ // LocationController handles all emitLocationToSocket() calls including heartbeat.
+ //
+ // The background service is ONLY responsible for:
+ // 1. Keeping the socket connection alive for receiving 'new_ride_request'
+ // and 'cancel_ride' events while the main isolate is paused on Android.
+ // 2. Showing the Android Overlay UI for incoming ride requests.
+ // 3. Notifications for iOS background state.
+ //
+ // Location data is not sent from the background isolate — it would conflict
+ // with LocationController's stream and cause duplicate GPS listeners,
+ // battery drain, and device freeze (as documented in driver_lifecycle.md).
Timer.periodic(const Duration(seconds: 30), (timer) async {
if (service is AndroidServiceInstance) {
diff --git a/siro_driver/lib/controller/functions/location_controller.dart b/siro_driver/lib/controller/functions/location_controller.dart
index 3367bd8..fc15ef0 100755
--- a/siro_driver/lib/controller/functions/location_controller.dart
+++ b/siro_driver/lib/controller/functions/location_controller.dart
@@ -19,6 +19,7 @@ import '../firebase/local_notification.dart';
import '../home/captin/home_captain_controller.dart';
import '../home/captin/map_driver_controller.dart';
import '../home/payment/captain_wallet_controller.dart';
+import '../home/navigation/navigation_controller.dart';
import 'background_service.dart';
import 'crud.dart';
@@ -539,6 +540,16 @@ class LocationController extends GetxController with WidgetsBindingObserver {
}
}
+ if (Get.isRegistered<MapDriverController>()) {
+ final mapCtrl = Get.find<MapDriverController>();
+ mapCtrl.handleLocationUpdateFromCentral(pos, speed, heading);
+ }
+
+ if (Get.isRegistered<NavigationController>()) {
+ final navCtrl = Get.find<NavigationController>();
+ navCtrl.handleLocationUpdateFromCentral(pos, speed, heading);
+ }
+
await _saveBehaviorIfMoved(pos, now, currentSpeed: speed);
}, onError: (e) => Log.print('❌ Location Stream Error: $e'));
}
diff --git a/siro_driver/lib/controller/home/captin/map_driver_controller.dart b/siro_driver/lib/controller/home/captin/map_driver_controller.dart
index 8aa56ae..3baad9e 100755
--- a/siro_driver/lib/controller/home/captin/map_driver_controller.dart
+++ b/siro_driver/lib/controller/home/captin/map_driver_controller.dart
@@ -2570,27 +2570,19 @@ class MapDriverController extends GetxController
}
void _startLocationListening() {
- _locationSubscription?.cancel();
- _locationSubscription = geo.Geolocator.getPositionStream(
- locationSettings: const geo.LocationSettings(
- accuracy: geo.LocationAccuracy.bestForNavigation,
- distanceFilter: 2,
- ),
- ).listen((geo.Position pos) {
- _handleLocationUpdate(pos);
- });
+ // Location stream is now centralized in LocationController to prevent device hanging.
+ // LocationController will call handleLocationUpdateFromCentral directly.
}
/// [Fix C-4] تحديث myLocation في المستمع الأساسي
- void _handleLocationUpdate(geo.Position pos) {
- final newLoc = LatLng(pos.latitude, pos.longitude);
+ void handleLocationUpdateFromCentral(LatLng newLoc, double posSpeed, double posHeading) {
myLocation = newLoc; // ← [Fix C-4] تحديث الموقع الفوري
_oldLoc = smoothedLocation ?? newLoc;
_targetLoc = newLoc;
_oldHeading = smoothedHeading;
- if (pos.speed > 0.5) {
- _targetHeading = pos.heading;
+ if (posSpeed > 0.5) {
+ _targetHeading = posHeading;
} else {
_targetHeading = _oldHeading;
}
diff --git a/siro_driver/lib/controller/home/captin/order_request_controller.dart b/siro_driver/lib/controller/home/captin/order_request_controller.dart
index f58d4f4..169ca95 100755
--- a/siro_driver/lib/controller/home/captin/order_request_controller.dart
+++ b/siro_driver/lib/controller/home/captin/order_request_controller.dart
@@ -69,6 +69,7 @@ class OrderRequestController extends GetxController
// --- الخريطة ---
Set<Polyline> polylines = {};
+ bool _hasCalculatedFullJourney = false;
// حالة التطبيق والصوت
bool isInBackground = false;
@@ -219,6 +220,11 @@ class OrderRequestController extends GetxController
// ----------------------------------------------------------------------
Future<void> _calculateFullJourney() async {
+ if (_hasCalculatedFullJourney) {
+ if (mapController != null) zoomToFitRide();
+ return;
+ }
+ _hasCalculatedFullJourney = true;
// Don't block on mapController being null - we'll draw routes
// and markers first, then zoom when controller is ready
bool canZoom = mapController != null;
@@ -281,7 +287,7 @@ class OrderRequestController extends GetxController
totalTripDistance = tripResult['distance_text'];
totalTripDuration = tripResult['duration_text'];
polylines.add(tripResult['polyline']);
-
+
// 🔥 تخزين استجابة السيرفر كاملة (بما فيها الـ points والـ instructions)
if (tripResult['raw_response'] != null) {
box.write('cached_trip_route', tripResult['raw_response']);
diff --git a/siro_driver/lib/controller/home/navigation/navigation_controller.dart b/siro_driver/lib/controller/home/navigation/navigation_controller.dart
index e607f6d..e2a4f08 100644
--- a/siro_driver/lib/controller/home/navigation/navigation_controller.dart
+++ b/siro_driver/lib/controller/home/navigation/navigation_controller.dart
@@ -476,32 +476,18 @@ class NavigationController extends GetxController
}
void _startLocationStream() {
- _locationStreamSubscription?.cancel();
- // Listen to location updates with minimum distance filter of 2 meters
- // This provides real-time updates without the 3-4 second delay
- _locationStreamSubscription = Geolocator.getPositionStream(
- locationSettings: const LocationSettings(
- accuracy: LocationAccuracy.high,
- distanceFilter: 2, // Update every 2 meters
- ),
- ).listen(
- (Position position) {
- _handleLocationUpdate(position);
- },
- onError: (error) {
- Log.print("DEBUG: Location stream error: $error");
- },
- );
+ // Location stream is now centralized in LocationController to prevent device hanging.
+ // LocationController will call handleLocationUpdateFromCentral directly.
}
bool _isProcessing = false;
- Future<void> _handleLocationUpdate(Position position) async {
+ Future<void> handleLocationUpdateFromCentral(LatLng newLoc, double locSpeed, double locHeading) async {
if (_isProcessing) return;
_isProcessing = true;
try {
- final newLoc = LatLng(position.latitude, position.longitude);
- currentSpeed = position.speed * 3.6; // Convert m/s to km/h
+ currentSpeed = locSpeed; // Convert m/s to km/h already done by location controller if needed, wait location_controller sends raw speed or km/h? It sends raw speed. So we should * 3.6
+ currentSpeed = locSpeed * 3.6;
// Skip if movement is too small
if (_lastProcessedLocation != null) {
@@ -544,7 +530,7 @@ class NavigationController extends GetxController
_targetLoc!.longitude,
);
} else {
- _targetHeading = position.heading;
+ _targetHeading = locHeading;
}
_animController?.forward(from: 0.0);
diff --git a/siro_rider/lib/controller/firebase/firbase_messge.dart b/siro_rider/lib/controller/firebase/firbase_messge.dart
index e24c3c8..5b54235 100644
--- a/siro_rider/lib/controller/firebase/firbase_messge.dart
+++ b/siro_rider/lib/controller/firebase/firbase_messge.dart
@@ -87,12 +87,6 @@ class FirebaseMessagesController extends GetxController {
fireBaseTitles(message);
}
});
- FirebaseMessaging.onBackgroundMessage((RemoteMessage message) async {
- // Handle background message
- if (message.data.isNotEmpty) {
- fireBaseTitles(message);
- }
- });
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message.data.isNotEmpty && message.notification != null) {
diff --git a/siro_rider/lib/controller/home/map/map_socket_controller.dart b/siro_rider/lib/controller/home/map/map_socket_controller.dart
index 3d73bae..95fbf21 100644
--- a/siro_rider/lib/controller/home/map/map_socket_controller.dart
+++ b/siro_rider/lib/controller/home/map/map_socket_controller.dart
@@ -283,7 +283,7 @@ class MapSocketController extends GetxController {
}
final dynamic distanceValue =
- data['distance_m'] ?? data['distance_meters'] ?? data['distance'];
+ data['distance_m'] ?? data['distance_meters'];
final double? distanceMeters =
double.tryParse(distanceValue?.toString() ?? '');
final int? etaSeconds = data['eta_seconds'] == null
diff --git a/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart b/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart
index c229ad2..c264a61 100644
--- a/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart
+++ b/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart
@@ -112,6 +112,7 @@ class RideLifecycleController extends GetxController {
late String driverId = '';
late String make = '';
late String model = '';
+ late String gender = '';
late String carColor = '';
late String licensePlate = '';
late String driverName = '';
@@ -120,6 +121,9 @@ class RideLifecycleController extends GetxController {
late String colorHex = '';
late String carYear = '';
late String driverRate = '5.0';
+ late String driverRatingCount = '0';
+ late String driverCompletedRides = '0';
+ late String driverTier = 'Verified driver';
late String driverToken = '';
double kazan = 8;
@@ -1481,7 +1485,8 @@ class RideLifecycleController extends GetxController {
// إيقاف جلب السيارات المجاورة ومسحها، باستثناء السائق الذي قبل الطلب
mapEngine.reloadStartApp = false;
- mapEngine.markers.removeWhere((marker) => marker.markerId.value != driverId.toString());
+ mapEngine.markers
+ .removeWhere((marker) => marker.markerId.value != driverId.toString());
mapEngine.update();
await getDriverCarsLocationToPassengerAfterApplied();
@@ -1490,8 +1495,7 @@ class RideLifecycleController extends GetxController {
LatLng driverPos = driverCarsLocationToPassengerAfterApplied.last;
Log.print(
'[rideAppliedFromDriver] 📍 Driver at: $driverPos, Passenger at: $passengerLocation');
- await getInitialDriverDistanceAndDuration(driverPos, passengerLocation);
- await drawDriverPathOnly(driverPos, passengerLocation);
+ await calculateDriverToPassengerRoute(driverPos, passengerLocation);
mapEngine.fitCameraToPoints(driverPos, passengerLocation);
}
@@ -1656,6 +1660,9 @@ class RideLifecycleController extends GetxController {
driverToken = data['token']?.toString() ?? '';
carYear = data['year']?.toString() ?? '';
driverRate = data['ratingDriver']?.toString() ?? '5.0';
+ driverRatingCount = data['ratingCount']?.toString() ?? '0';
+ driverCompletedRides = data['completedRides']?.toString() ?? '0';
+ driverTier = data['driverTier']?.toString() ?? 'Verified driver';
update();
}
@@ -2221,6 +2228,15 @@ class RideLifecycleController extends GetxController {
polyLines = polyLines
.where((p) => !p.polylineId.value.startsWith('driver_route'))
.toSet();
+ polyLines = {
+ ...polyLines,
+ Polyline(
+ polylineId: const PolylineId('main_route'),
+ points: decodedPoints,
+ color: const Color(0xFF2196F3),
+ width: 6,
+ )
+ };
} else {
// مسح السلمات القديمة أولاً
polyLines = polyLines
@@ -2290,7 +2306,9 @@ class RideLifecycleController extends GetxController {
_routeHeadingMismatchCount = 0;
_isRecalculatingRoute = true;
if (statusRide == 'Begin' ||
- currentRideState.value == RideState.inProgress) {
+ statusRide == 'Arrived' ||
+ currentRideState.value == RideState.inProgress ||
+ currentRideState.value == RideState.driverArrived) {
await calculateDriverToPassengerRoute(driverPos, myDestination,
isBeginPhase: true);
} else {
@@ -2504,6 +2522,8 @@ class RideLifecycleController extends GetxController {
String icon;
if (model.contains('دراجة') || make.contains('دراجة')) {
icon = mapEngine.motoIcon;
+ } else if (gender == 'Female') {
+ icon = mapEngine.ladyIcon;
} else {
icon = mapEngine.carIcon;
}
@@ -3026,6 +3046,17 @@ class RideLifecycleController extends GetxController {
mapEngine.playRouteAnimation(
mapEngine.polylineCoordinates, mapEngine.lastComputedBounds);
}
+
+ if (driverCarsLocationToPassengerAfterApplied.isNotEmpty &&
+ myDestination.latitude != 0 &&
+ myDestination.longitude != 0) {
+ await calculateDriverToPassengerRoute(
+ driverCarsLocationToPassengerAfterApplied.last,
+ myDestination,
+ isBeginPhase: true,
+ );
+ }
+
update();
}
@@ -3903,12 +3934,37 @@ class RideLifecycleController extends GetxController {
make = data['make']?.toString() ?? '';
model = data['model']?.toString() ?? '';
+ gender = data['gender']?.toString() ?? '';
carColor = data['color']?.toString() ?? '';
colorHex = data['color_hex']?.toString() ?? '';
licensePlate = data['car_plate']?.toString() ?? '';
carYear = data['year']?.toString() ?? '';
+ // المحاولة الفورية لرسم السائق إذا توفرت الإحداثيات في البيانات
+ double lat = double.tryParse(
+ data['latitude']?.toString() ?? data['lat']?.toString() ?? '0') ??
+ 0;
+ double lng = double.tryParse(data['longitude']?.toString() ??
+ data['lng']?.toString() ??
+ '0') ??
+ 0;
+ double heading = double.tryParse(data['heading']?.toString() ?? '0') ?? 0;
+
+ if (lat != 0 && lng != 0) {
+ LatLng initialPos = LatLng(lat, lng);
+ if (driverCarsLocationToPassengerAfterApplied.isEmpty) {
+ driverCarsLocationToPassengerAfterApplied.add(initialPos);
+ } else {
+ driverCarsLocationToPassengerAfterApplied[0] = initialPos;
+ }
+ // تحديث الماركر فوراً لضمان ظهوره بشكل موثوق
+ updateDriverMarker(initialPos, heading);
+ }
+
driverRate = data['ratingDriver']?.toString() ?? '5.0';
+ driverRatingCount = data['ratingCount']?.toString() ?? '0';
+ driverCompletedRides = data['completedRides']?.toString() ?? '0';
+ driverTier = data['driverTier']?.toString() ?? 'Verified driver';
driverToken = data['token']?.toString() ?? '';
update();
@@ -4185,55 +4241,6 @@ class RideLifecycleController extends GetxController {
);
}
- Future<void> getDistanceFromDriverAfterAcceptedRide(
- String origin, String destination) async {
- String apiKey = Env.mapKeyOsm;
- if (origin.isEmpty) {
- origin = '${passengerLocation.latitude},${passengerLocation.longitude}';
- }
- var uri = Uri.parse(
- '$dynamicApiUrl?origin=$origin&destination=$destination&steps=false&overview=false');
- Log.print('uri: $uri');
-
- http.Response response;
- Map<String, dynamic> responseData;
-
- try {
- response = await http.get(
- uri,
- headers: {
- 'X-API-KEY': apiKey,
- },
- ).timeout(const Duration(seconds: 20));
-
- if (response.statusCode != 200) {
- Log.print('Error from API: ${response.statusCode}');
- isLoading = false;
- update();
- return;
- }
- if (Get.isBottomSheetOpen ?? false) {
- Get.back();
- }
- isDrawingRoute = false;
-
- responseData = json.decode(response.body);
- Log.print('responseData: $responseData');
-
- if (responseData['status'] != 'ok') {
- Log.print('API returned an error: ${responseData['message']}');
- isLoading = false;
- update();
- return;
- }
- } catch (e) {
- Log.print('Failed to get directions: $e');
- isLoading = false;
- update();
- return;
- }
- }
-
Future<void> _stageNiceToHave() async {
Log.print('🚀 MapPassengerController: Starting _stageNiceToHave');
diff --git a/siro_rider/lib/controller/home/map/ui_interactions_controller.dart b/siro_rider/lib/controller/home/map/ui_interactions_controller.dart
index 388c28e..afa97d9 100644
--- a/siro_rider/lib/controller/home/map/ui_interactions_controller.dart
+++ b/siro_rider/lib/controller/home/map/ui_interactions_controller.dart
@@ -4,7 +4,6 @@ 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';
@@ -15,19 +14,13 @@ 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();
@@ -56,54 +49,54 @@ class UiInteractionsController extends GetxController {
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,
- ),
- ],
+ 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,
- )
- );
+ 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() {
@@ -114,10 +107,12 @@ class UiInteractionsController extends GetxController {
titleStyle: AppStyle.title.copyWith(color: AppColor.redColor),
content: Column(
children: [
- Icon(Icons.warning_amber_rounded, size: 50, color: AppColor.redColor),
+ 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,
+ "Do you want to send an emergency message to your SOS contact?"
+ .tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),
diff --git a/siro_rider/lib/controller/local/translations.dart b/siro_rider/lib/controller/local/translations.dart
index 5244054..d42c370 100644
--- a/siro_rider/lib/controller/local/translations.dart
+++ b/siro_rider/lib/controller/local/translations.dart
@@ -42,6 +42,7 @@ class MyTranslation extends Translations {
"Arrived": "وصلنا",
"Audio Recording": "تسجيل صوتي",
"Call": "اتصال",
+ "Call Options": "خيارات الاتصال",
"Call Connected": "تم فتح الاتصال",
"Call Support": "اتصل بالدعم",
"Call left": "مكالمات متبقية",
@@ -49,6 +50,8 @@ class MyTranslation extends Translations {
"Change Photo": "تغيير الصورة",
"Captain": "الكابتن",
"Choose from Gallery": "اختر من المعرض",
+ "Choose how you want to call the driver":
+ "اختر طريقة الاتصال بالكابتن",
"Choose from contact": "اختر من جهات الاتصال",
"Click to track the trip": "اضغط لتتبع المشوار",
"Close panel": "إغلاق اللوحة",
@@ -92,6 +95,9 @@ class MyTranslation extends Translations {
"Finished": "انتهى",
"Fixed Price": "سعر ثابت",
"Free Call": "مكالمة مجانية",
+ "Professional driver": "كابتن محترف",
+ "Trusted driver": "كابتن موثوق",
+ "Verified driver": "كابتن موثق",
"General": "عام",
"Grant": "منح الإذن",
"Have a Promo Code?": "معك كود خصم؟",
@@ -178,6 +184,7 @@ class MyTranslation extends Translations {
"Preferences": "التفضيلات",
"Profile photo updated": "تم تحديث صورة الغلاف",
"Quick Message": "رسالة سريعة",
+ "reviews": "تقييم",
"Rating is": "التقييم هو",
"Received empty route data.": "تم استلام بيانات طريق فارغة.",
"Record": "تسجيل",
@@ -211,6 +218,7 @@ class MyTranslation extends Translations {
"Set as Work": "تحديد كالشغل",
"Share": "مشاركة",
"Share Trip": "مشاركة المشوار",
+ "Standard Call": "اتصال عادي",
"Share your experience to help us improve...":
"شاركنا تجربتك لنحسن خدمتنا...",
"Something went wrong. Please try again.": "صار غلط. جرب مرة تانية.",
@@ -271,6 +279,8 @@ class MyTranslation extends Translations {
"to arrive you.": "ليوصلك.",
"unknown": "غير معروف",
"wait 1 minute to recive message": "استنى دقيقة لتستلم الرسالة",
+ "Uses cellular network": "يستخدم شبكة الهاتف",
+ "Voice call over internet": "مكالمة صوتية عبر الإنترنت",
"with license plate": "برقم اللوحة",
"witout zero": "بدون صفر",
"you must insert token code": "لازم تدخل الكود",
@@ -16885,7 +16895,8 @@ class MyTranslation extends Translations {
"Support is Away": "سپورٹ اب دستیاب نہیں ہے",
"Support is currently Online": "سپورٹ اب آن لائن ہے",
"Voice Call": "صوتی کال",
- "We're here to help you 24/7": "ہم چوبیس گھنٹے آپ کی مدد کے لیے حاضر ہیں",
+ "We're here to help you 24/7":
+ "ہم چوبیس گھنٹے آپ کی مدد کے لیے حاضر ہیں",
"Working Hours:": "کام کے اوقات:",
"1 Passenger": "1 Passenger",
"2 Passengers": "2 Passengers",
@@ -18446,7 +18457,8 @@ class MyTranslation extends Translations {
"Support is Away": "सहायता अभी उपलब्ध नहीं है",
"Support is currently Online": "सहायता अभी ऑनलाइन है",
"Voice Call": "वॉइस कॉल",
- "We're here to help you 24/7": "हम आपकी सहायता के लिए 24/7 उपलब्ध हैं",
+ "We're here to help you 24/7":
+ "हम आपकी सहायता के लिए 24/7 उपलब्ध हैं",
"Working Hours:": "कार्य समय:",
"1 Passenger": "1 Passenger",
"2 Passengers": "2 Passengers",
diff --git a/siro_rider/lib/views/home/map_widget.dart/apply_order_widget.dart b/siro_rider/lib/views/home/map_widget.dart/apply_order_widget.dart
index 8168f4f..a0689dc 100644
--- a/siro_rider/lib/views/home/map_widget.dart/apply_order_widget.dart
+++ b/siro_rider/lib/views/home/map_widget.dart/apply_order_widget.dart
@@ -250,19 +250,23 @@ class ApplyOrderWidget extends StatelessWidget {
Row(
children: [
// صورة السائق (أصغر)
- Container(
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- border: Border.all(
- color: AppColor.primaryColor.withOpacity(0.2), width: 2),
- ),
- child: CircleAvatar(
- radius: 22, // تصغير من 28 إلى 22
- backgroundColor: Colors.grey[200],
- backgroundImage: NetworkImage(
- '${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
- onBackgroundImageError: (_, __) =>
- const Icon(Icons.person, color: Colors.grey, size: 20),
+ GestureDetector(
+ onTap: () => _showDriverAvatarDialog(context, controller),
+ child: Container(
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ border: Border.all(
+ color: AppColor.primaryColor.withOpacity(0.2),
+ width: 2),
+ ),
+ child: CircleAvatar(
+ radius: 22, // تصغير من 28 إلى 22
+ backgroundColor: Colors.grey[200],
+ backgroundImage: NetworkImage(
+ '${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
+ onBackgroundImageError: (_, __) =>
+ const Icon(Icons.person, color: Colors.grey, size: 20),
+ ),
),
),
@@ -299,6 +303,32 @@ class ApplyOrderWidget extends StatelessWidget {
),
],
),
+ const SizedBox(height: 5),
+ Wrap(
+ spacing: 6,
+ runSpacing: 4,
+ children: [
+ _buildDriverBadge(
+ icon: Icons.verified_rounded,
+ text: controller.driverTier.tr,
+ color: AppColor.primaryColor,
+ ),
+ if (controller.driverCompletedRides != '0')
+ _buildDriverBadge(
+ icon: Icons.route_rounded,
+ text:
+ '${controller.driverCompletedRides} ${'rides'.tr}',
+ color: Colors.teal,
+ ),
+ if (controller.driverRatingCount != '0')
+ _buildDriverBadge(
+ icon: Icons.reviews_rounded,
+ text:
+ '${controller.driverRatingCount} ${'reviews'.tr}',
+ color: Colors.amber.shade800,
+ ),
+ ],
+ ),
],
),
),
@@ -320,6 +350,11 @@ class ApplyOrderWidget extends StatelessWidget {
Widget _buildMicroCarIcon(
RideLifecycleController controller, Color Function(String) parseColor) {
Color carColor = parseColor(controller.colorHex);
+ final String vehicleText =
+ '${controller.model} ${controller.make}'.toLowerCase();
+ final bool isBike = vehicleText.contains('scooter') ||
+ vehicleText.contains('bike') ||
+ vehicleText.contains('دراجة');
return Container(
height: 40, // تصغير من 50
width: 40,
@@ -331,7 +366,8 @@ class ApplyOrderWidget extends StatelessWidget {
child: ColorFiltered(
colorFilter: ColorFilter.mode(carColor, BlendMode.srcIn),
child: Image.asset(
- box.read(BoxName.carType) == 'Scooter' ||
+ isBike ||
+ box.read(BoxName.carType) == 'Scooter' ||
box.read(BoxName.carType) == 'Pink Bike'
? 'assets/images/moto.png'
: 'assets/images/car3.png',
@@ -341,6 +377,81 @@ class ApplyOrderWidget extends StatelessWidget {
);
}
+ Widget _buildDriverBadge({
+ required IconData icon,
+ required String text,
+ required Color color,
+ }) {
+ return Container(
+ padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3),
+ decoration: BoxDecoration(
+ color: color.withOpacity(0.1),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(icon, size: 11, color: color),
+ const SizedBox(width: 4),
+ Text(
+ text,
+ style: TextStyle(
+ color: color,
+ fontSize: 10.5,
+ fontWeight: FontWeight.w800,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ void _showDriverAvatarDialog(
+ BuildContext context, RideLifecycleController controller) {
+ final imageUrl =
+ '${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg';
+ Get.dialog(
+ Dialog(
+ insetPadding: const EdgeInsets.symmetric(horizontal: 38),
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(18, 20, 18, 18),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ CircleAvatar(
+ radius: 58,
+ backgroundColor: Colors.grey[200],
+ backgroundImage: NetworkImage(imageUrl),
+ onBackgroundImageError: (_, __) {},
+ ),
+ const SizedBox(height: 14),
+ Text(
+ controller.driverName,
+ textAlign: TextAlign.center,
+ style: AppStyle.title.copyWith(
+ fontSize: 18,
+ fontWeight: FontWeight.w900,
+ ),
+ ),
+ const SizedBox(height: 6),
+ Text(
+ '${controller.driverTier.tr} • ${controller.driverRate}',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Colors.grey[700],
+ fontSize: 13,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ barrierDismissible: true,
+ );
+ }
+
Widget _buildSlimLicensePlate(String plateNumber) {
return Container(
width: double.infinity,