Update: 2026-06-26 04:04:03

This commit is contained in:
Hamza-Ayed
2026-06-26 04:04:04 +03:00
parent aea0c8e44e
commit da9e6eb981
10 changed files with 325 additions and 112 deletions

View File

@@ -482,7 +482,19 @@ class LocationSearchController extends GetxController {
bool _pendingGeocode = true;
void updateCurrentLocationFromCamera(LatLng target) {
Log.print('📍 updateCurrentLocationFromCamera: $target');
// ✅ FIX P3: Guard — تجاهل التحديثات إذا كانت المسافة < 20 متر
final double distanceDelta = Geolocator.distanceBetween(
newMyLocation.latitude,
newMyLocation.longitude,
target.latitude,
target.longitude,
);
if (distanceDelta < 20.0) {
Log.print('📍 updateCurrentLocationFromCamera: Skipped (المسافة ${distanceDelta.toStringAsFixed(1)}م < 20م)');
return;
}
Log.print('📍 updateCurrentLocationFromCamera: $target (تغيير ${distanceDelta.toStringAsFixed(1)}م)');
newMyLocation = target;
if (_pendingGeocode) {
@@ -522,7 +534,16 @@ class LocationSearchController extends GetxController {
void onCameraMoveThrottled(CameraPosition pos) {
_camThrottle?.cancel();
_camThrottle = Timer(const Duration(milliseconds: 160), () {
Log.print('📸 onCameraMoveThrottled: ${pos.target}');
// ✅ FIX P3: Guard — تجاهل التحديث إذا كان التغيير ضئيلاً (< 20م)
final double distanceDelta = Geolocator.distanceBetween(
newMyLocation.latitude,
newMyLocation.longitude,
pos.target.latitude,
pos.target.longitude,
);
if (distanceDelta < 20.0) return;
Log.print('📸 onCameraMoveThrottled: ${pos.target} (تغيير ${distanceDelta.toStringAsFixed(1)}م)');
int waypointsLength = Get.find<WayPointController>().wayPoints.length;
int index = wayPointIndex;
if (waypointsLength > 0 && index < placesCoordinate.length) {

View File

@@ -1451,11 +1451,27 @@ class RideLifecycleController extends GetxController {
"has_steps": Get.find<WayPointController>().wayPoints.length > 1
? 'true'
: 'false',
"step0": placesCoordinate.length > 0 ? placesCoordinate[0] : "",
"step1": placesCoordinate.length > 1 ? placesCoordinate[1] : "",
"step2": placesCoordinate.length > 2 ? placesCoordinate[2] : "",
"step3": placesCoordinate.length > 3 ? placesCoordinate[3] : "",
"step4": placesCoordinate.length > 4 ? placesCoordinate[4] : "",
// ✅ FIX P5: إرسال step0-4 فقط عندما has_steps == true
"step0": Get.find<WayPointController>().wayPoints.length > 1 &&
placesCoordinate.length > 0
? placesCoordinate[0]
: "",
"step1": Get.find<WayPointController>().wayPoints.length > 1 &&
placesCoordinate.length > 1
? placesCoordinate[1]
: "",
"step2": Get.find<WayPointController>().wayPoints.length > 1 &&
placesCoordinate.length > 2
? placesCoordinate[2]
: "",
"step3": Get.find<WayPointController>().wayPoints.length > 1 &&
placesCoordinate.length > 3
? placesCoordinate[3]
: "",
"step4": Get.find<WayPointController>().wayPoints.length > 1 &&
placesCoordinate.length > 4
? placesCoordinate[4]
: "",
"price_token": priceToken,
};
@@ -1796,15 +1812,30 @@ class RideLifecycleController extends GetxController {
if (!promoFormKey.currentState!.validate()) return;
try {
final String pricingPassengerLat =
mapEngine.polylineCoordinates.isNotEmpty
? mapEngine.polylineCoordinates.first.latitude.toString()
: newMyLocation.latitude.toString();
final String pricingPassengerLng =
mapEngine.polylineCoordinates.isNotEmpty
? mapEngine.polylineCoordinates.first.longitude.toString()
: newMyLocation.longitude.toString();
final String pricingDestLat = mapEngine.polylineCoordinates.isNotEmpty
? mapEngine.polylineCoordinates.last.latitude.toString()
: myDestination.latitude.toString();
final String pricingDestLng = mapEngine.polylineCoordinates.isNotEmpty
? mapEngine.polylineCoordinates.last.longitude.toString()
: myDestination.longitude.toString();
final res = await CRUD().post(link: AppLink.getPrices, payload: {
'distance': distance.toString(),
'durationToRide': durationToRide.toString(),
'startNameAddress': startNameAddress,
'endNameAddress': endNameAddress,
'destLat': myDestination.latitude.toString(),
'destLng': myDestination.longitude.toString(),
'passengerLat': newMyLocation.latitude.toString(),
'passengerLng': newMyLocation.longitude.toString(),
'destLat': pricingDestLat,
'destLng': pricingDestLng,
'passengerLat': pricingPassengerLat,
'passengerLng': pricingPassengerLng,
'walletVal': box.read(BoxName.passengerWalletTotal)?.toString() ?? '0',
'activeMenuWaypointCount': activeMenuWaypointCount.toString(),
'promo_code': promo.text,
@@ -1833,6 +1864,56 @@ class RideLifecycleController extends GetxController {
totalPassengerRayehGaiBalash =
data['totalPassengerRayehGaiBalash']?.toString() ?? '0';
priceToken = res['price_token']?.toString() ?? '';
num appliedDiscount = (res['applied_discount'] ?? 0) is num
? res['applied_discount'] ?? 0
: num.tryParse(res['applied_discount']?.toString() ?? '0') ?? 0;
// ✅ FIX P4: إظهار خطأ إذا كان الخصم صفر (برومو غير صالح)
if (appliedDiscount <= 0) {
Log.print('⚠️ Promo code returned zero discount. Showing error.');
MyDialog().getDialog(
'Invalid Promo Code'.tr,
'This promo code is invalid, expired, or does not apply to your account.'
.tr,
() => Get.back(),
);
return;
}
Log.print('💰 Promo discount applied: $appliedDiscount');
String carType = box.read(BoxName.carType) ?? '';
switch (carType) {
case 'Comfort':
totalPassenger = totalPassengerComfort;
break;
case 'Fixed Price':
totalPassenger = totalPassengerSpeed;
break;
case 'Electric':
totalPassenger = totalPassengerElectric;
break;
case 'Awfar Car':
totalPassenger = totalPassengerBalash;
break;
case 'Scooter':
case 'Pink Bike':
totalPassenger = totalPassengerScooter;
break;
case 'Van':
totalPassenger = totalPassengerVan;
break;
case 'Lady':
totalPassenger = totalPassengerLady;
break;
case 'Rayeh Gai':
totalPassenger = totalPassengerRayehGai;
break;
}
totalCostPassenger = totalPassenger;
promoTaken = true;
update();
@@ -1872,15 +1953,30 @@ class RideLifecycleController extends GetxController {
newTime = currentTime.add(durationToAdd);
try {
final String pricingPassengerLat =
mapEngine.polylineCoordinates.isNotEmpty
? mapEngine.polylineCoordinates.first.latitude.toString()
: newMyLocation.latitude.toString();
final String pricingPassengerLng =
mapEngine.polylineCoordinates.isNotEmpty
? mapEngine.polylineCoordinates.first.longitude.toString()
: newMyLocation.longitude.toString();
final String pricingDestLat = mapEngine.polylineCoordinates.isNotEmpty
? mapEngine.polylineCoordinates.last.latitude.toString()
: myDestination.latitude.toString();
final String pricingDestLng = mapEngine.polylineCoordinates.isNotEmpty
? mapEngine.polylineCoordinates.last.longitude.toString()
: myDestination.longitude.toString();
final res = await CRUD().post(link: AppLink.getPrices, payload: {
'distance': distance.toString(),
'durationToRide': durationToRide.toString(),
'startNameAddress': startNameAddress,
'endNameAddress': endNameAddress,
'destLat': myDestination.latitude.toString(),
'destLng': myDestination.longitude.toString(),
'passengerLat': newMyLocation.latitude.toString(),
'passengerLng': newMyLocation.longitude.toString(),
'destLat': pricingDestLat,
'destLng': pricingDestLng,
'passengerLat': pricingPassengerLat,
'passengerLng': pricingPassengerLng,
'walletVal': box.read(BoxName.passengerWalletTotal)?.toString() ?? '0',
'activeMenuWaypointCount': activeMenuWaypointCount.toString(),
'passenger_id': box.read(BoxName.passengerID) ?? '',
@@ -2140,7 +2236,8 @@ class RideLifecycleController extends GetxController {
if (statusRide == 'Begin' ||
currentRideState.value == RideState.inProgress) {
polyLines = {
...polyLines.where((p) => !p.polylineId.value.startsWith('driver_route')),
...polyLines
.where((p) => !p.polylineId.value.startsWith('driver_route')),
Polyline(
polylineId: const PolylineId('main_route'),
points: remainingPoints,
@@ -2190,8 +2287,7 @@ class RideLifecycleController extends GetxController {
payload: {'driver_id': driverId});
if (res != 'failure' && res is Map) {
if (res['message'] != null &&
(res['message'] as List).isNotEmpty) {
if (res['message'] != null && (res['message'] as List).isNotEmpty) {
var _data = (res['message'] as List)[0];
LatLng newDriverPos = LatLng(
@@ -2225,8 +2321,7 @@ class RideLifecycleController extends GetxController {
}
}
mapEngine.clearMarkersExceptStartEndAndDriver();
reloadMarkerDriverCarsLocationToPassengerAfterApplied(
res);
reloadMarkerDriverCarsLocationToPassengerAfterApplied(res);
}
}
update();
@@ -3393,18 +3488,39 @@ class RideLifecycleController extends GetxController {
hours = durationToAdd.inHours;
minutes = (durationToAdd.inMinutes % 60).round();
if (polyLines.isNotEmpty) mapEngine.clearPolyline();
rideConfirm = false;
isMarkersShown = true;
update();
await bottomSheet();
await mapEngine.playRouteAnimation(
mapEngine.polylineCoordinates, mapEngine.lastComputedBounds);
// ✅ FIX P1: انتظار تحميل الأيقونات قبل إنشاء الـ markers
// intaleq_maps تتطلب صوراً مسجلة عبر addImage() وليس أصولاً مباشرة
if (!mapEngine.isIconsLoaded) {
Log.print('⏳ Waiting for map icons to load before placing markers...');
for (int i = 0; i < 20 && !mapEngine.isIconsLoaded; i++) {
await Future.delayed(const Duration(milliseconds: 200));
}
}
// ✅ FIX P1: استخدام fromStyleImage مع المعرّفات المسجلة مسبقاً
markers = {
Marker(
markerId: const MarkerId('start'),
position: startLoc,
icon: InlqBitmap.fromStyleImage('orange_marker'),
icon: InlqBitmap.fromStyleImage(mapEngine.startIcon),
infoWindow: const InfoWindow(title: 'A'),
anchor: const Offset(0.5, 1.0),
),
Marker(
markerId: const MarkerId('end'),
position: endLoc,
icon: InlqBitmap.fromStyleImage('violet_marker'),
icon: InlqBitmap.fromStyleImage(mapEngine.endIcon),
infoWindow: const InfoWindow(title: 'B'),
anchor: const Offset(0.5, 1.0),
),
@@ -3418,7 +3534,7 @@ class RideLifecycleController extends GetxController {
markerId: MarkerId('waypoint_$i'),
position: wp,
icon: InlqBitmap.fromStyleImage(
isFirstWaypoint ? 'orange_marker' : 'violet_marker'),
isFirstWaypoint ? mapEngine.startIcon : mapEngine.endIcon),
infoWindow:
InfoWindow(title: isFirstWaypoint ? 'Stop 1' : 'Stop 2'),
anchor: const Offset(0.5, 1.0),
@@ -3426,16 +3542,8 @@ class RideLifecycleController extends GetxController {
}
}
if (polyLines.isNotEmpty) mapEngine.clearPolyline();
rideConfirm = false;
isMarkersShown = true;
Log.print('✅ FIX P1: Markers placed — start: $startLoc, end: $endLoc');
update();
await bottomSheet();
await mapEngine.playRouteAnimation(
mapEngine.polylineCoordinates, mapEngine.lastComputedBounds);
} catch (e, stackTrace) {
if (isDrawingRoute) {
isDrawingRoute = false;
@@ -4034,23 +4142,26 @@ class RideLifecycleController extends GetxController {
await _checkAndRefreshMapStyle();
Get.put(DeepLinkController(), permanent: true);
await initilizeGetStorage();
final bool isLoggedIn = box.read(BoxName.isVerified) == '1' &&
box.read(BoxName.passengerID) != null;
final bool isLoggedIn = box.read(BoxName.isVerified) == '1' &&
box.read(BoxName.passengerID) != null;
// We intentionally DO NOT initialize data here during onInit
// because this controller is instantiated globally before the map is opened.
// Initialization will be triggered by MapPagePassenger calling initializeDataAfterLogin()
Log.print("RideLifecycleController.onInit: Waiting for MapPagePassenger to trigger initialization.");
Log.print(
"RideLifecycleController.onInit: Waiting for MapPagePassenger to trigger initialization.");
}
Future<void> initializeDataAfterLogin() async {
if (isDataInitializedAfterLogin) {
Log.print("RideLifecycleController: Already initialized, skipping duplicate initializeDataAfterLogin.");
Log.print(
"RideLifecycleController: Already initialized, skipping duplicate initializeDataAfterLogin.");
return;
}
isDataInitializedAfterLogin = true;
Log.print("RideLifecycleController: Initializing data after successful login...");
Log.print(
"RideLifecycleController: Initializing data after successful login...");
getLocationArea(passengerLocation.latitude, passengerLocation.longitude);
await _stagePricingAndState();
await _stageNiceToHave();

View File

@@ -137,7 +137,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
// ── Car Selection List ───────────────────────────────
SizedBox(
height: 120,
height: 134,
child: ListView.builder(
physics: const BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
@@ -303,32 +303,32 @@ class CarDetailsTypeToChoose extends StatelessWidget {
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
width: 104,
width: 122,
decoration: BoxDecoration(
gradient: isSelected
? LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
AppColor.primaryColor.withAlpha(18),
AppColor.primaryColor.withAlpha(8),
AppColor.primaryColor.withAlpha(22),
AppColor.primaryColor.withAlpha(10),
],
)
: null,
color: isSelected ? null : AppColor.secondaryColor,
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(18),
border: Border.all(
color: isSelected
? AppColor.primaryColor
: AppColor.grayColor.withOpacity(0.2),
width: isSelected ? 2.0 : 1.0,
: AppColor.grayColor.withOpacity(0.25),
width: isSelected ? 2.5 : 1.5,
),
boxShadow: [
BoxShadow(
color: isSelected
? AppColor.primaryColor.withAlpha(40)
: Colors.black.withAlpha(12),
blurRadius: isSelected ? 12 : 4,
? AppColor.primaryColor.withAlpha(50)
: Colors.black.withAlpha(16),
blurRadius: isSelected ? 14 : 6,
offset: const Offset(0, 3),
),
],
@@ -338,11 +338,11 @@ class CarDetailsTypeToChoose extends StatelessWidget {
// Selected indicator
if (isSelected)
Positioned(
top: 4,
right: 4,
top: 5,
right: 5,
child: Container(
width: 18,
height: 18,
width: 22,
height: 22,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
@@ -358,13 +358,13 @@ class CarDetailsTypeToChoose extends StatelessWidget {
),
],
),
child: const Icon(Icons.check, size: 11, color: Colors.white),
child: const Icon(Icons.check, size: 13, color: Colors.white),
),
),
// Card content
Padding(
padding: const EdgeInsets.fromLTRB(6, 6, 6, 6),
padding: const EdgeInsets.fromLTRB(8, 8, 8, 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@@ -374,11 +374,11 @@ class CarDetailsTypeToChoose extends StatelessWidget {
duration: const Duration(milliseconds: 300),
child: Image.asset(
carType.image,
height: 44,
height: 54,
fit: BoxFit.contain,
),
),
const SizedBox(height: 4),
const SizedBox(height: 2),
// Car name
FittedBox(
@@ -388,7 +388,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
style: TextStyle(
fontWeight:
isSelected ? FontWeight.w800 : FontWeight.w600,
fontSize: 12,
fontSize: 13,
color: isSelected
? AppColor.primaryColor
: AppColor.writeColor,
@@ -397,12 +397,12 @@ class CarDetailsTypeToChoose extends StatelessWidget {
),
),
const SizedBox(height: 3),
const SizedBox(height: 2),
// Price tag
Container(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: isSelected
? AppColor.primaryColor
@@ -417,7 +417,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
child: Text(
'${_getPassengerPriceText(carType, controller)} ${CurrencyHelper.currency}',
style: TextStyle(
fontSize: 11,
fontSize: 12,
fontWeight: FontWeight.w700,
color: isSelected
? Colors.white
@@ -653,7 +653,6 @@ class CarDetailsTypeToChoose extends StatelessWidget {
if (controller.promoFormKey.currentState!
.validate()) {
controller.applyPromoCodeToPassenger(context);
Navigator.of(context).pop();
}
},
),