Update: 2026-07-02 17:44:58
This commit is contained in:
@@ -734,32 +734,107 @@ class RideLifecycleController extends GetxController {
|
||||
mapSocket.initConnectionWithSocket();
|
||||
}
|
||||
|
||||
void _showIncreaseFeeDialog() {
|
||||
void _showAiNegotiatorDialog() {
|
||||
final double currentPrice = double.tryParse(totalPassenger) ?? 0;
|
||||
final double suggestedPrice = currentPrice * 1.05; // 5% AI suggestion
|
||||
final String currency = box.read(BoxName.serverChosen)?.toString().contains('Syria') == true
|
||||
? 'ل.س' : 'SAR';
|
||||
|
||||
Get.dialog(
|
||||
CupertinoAlertDialog(
|
||||
title: Text("No drivers accepted your request yet".tr),
|
||||
content: Text(
|
||||
"Increasing the fare might attract more drivers. Would you like to increase the price?"
|
||||
.tr),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text("Cancel Ride".tr,
|
||||
style: TextStyle(color: AppColor.redColor)),
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
mapEngine.changeCancelRidePageShow();
|
||||
},
|
||||
Dialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
backgroundColor: const Color(0xFF1A1A2E),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// AI Icon
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF16213E),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
border: Border.all(color: const Color(0xFF0F3460), width: 2),
|
||||
),
|
||||
child: const Icon(Icons.psychology_rounded, color: Color(0xFF4FC3F7), size: 36),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'🤖 مقترح الذكاء الاصطناعي'.tr,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'لم يقبل أي سائق طلبك بالسعر الحالي. يقترح AI رفع السعر قليلاً لتسريع القبول.'.tr,
|
||||
style: const TextStyle(color: Color(0xFFB0BEC5), fontSize: 13),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Price comparison
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF0F3460),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Text('السعر الحالي'.tr, style: const TextStyle(color: Color(0xFF90A4AE), fontSize: 11)),
|
||||
Text('${currentPrice.toStringAsFixed(0)} $currency',
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 15, decoration: TextDecoration.lineThrough)),
|
||||
],
|
||||
),
|
||||
const Icon(Icons.arrow_forward_rounded, color: Color(0xFF4FC3F7)),
|
||||
Column(
|
||||
children: [
|
||||
Text('السعر المقترح'.tr, style: const TextStyle(color: Color(0xFF4FC3F7), fontSize: 11)),
|
||||
Text('${suggestedPrice.toStringAsFixed(0)} $currency',
|
||||
style: const TextStyle(color: Color(0xFF4FC3F7), fontSize: 17, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
mapEngine.changeCancelRidePageShow();
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: const BorderSide(color: Colors.red),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
child: Text('إلغاء'.tr, style: const TextStyle(color: Colors.red)),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
increasePriceAndRestartSearch(suggestedPrice);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF4FC3F7),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
child: Text('قبول +5%'.tr, style: const TextStyle(color: Colors.black, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: Text("Increase Fare".tr,
|
||||
style: TextStyle(color: AppColor.greenColor)),
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
double newPrice = double.parse(totalPassenger) * 1.10;
|
||||
increasePriceAndRestartSearch(newPrice);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
barrierDismissible: false,
|
||||
);
|
||||
@@ -767,23 +842,20 @@ class RideLifecycleController extends GetxController {
|
||||
|
||||
Future<void> increasePriceAndRestartSearch(double newPrice) async {
|
||||
totalPassenger = newPrice.toStringAsFixed(2);
|
||||
costForDriver = newPrice * 0.86; // حفاظ على نفس نسبة السائق
|
||||
update();
|
||||
|
||||
await CRUD()
|
||||
.post(link: "${AppLink.server}/ride/rides/update.php", payload: {
|
||||
// تحديث قاعدة البيانات بالسعر الجديد
|
||||
await CRUD().post(link: "${AppLink.server}/ride/rides/update.php", payload: {
|
||||
"id": rideId,
|
||||
"price": newPrice.toStringAsFixed(2),
|
||||
});
|
||||
|
||||
Log.print(
|
||||
'[increasePrice] Price changed. Clearing notified list to resend.');
|
||||
Log.print('[AI Negotiator Phase 2] Price accepted by passenger: $newPrice — restarting search...');
|
||||
notifiedDrivers.clear();
|
||||
|
||||
_searchStartTime = DateTime.now();
|
||||
_currentSearchPhase = 0;
|
||||
isSearchingWindow = true;
|
||||
update();
|
||||
startMasterTimer();
|
||||
// إعادة البحث مع السعر الجديد (Phase 2 retry)
|
||||
retrySearchForDrivers(newPrice: newPrice);
|
||||
}
|
||||
|
||||
void _stopWaitPassengerTimer({bool resetUI = false}) {
|
||||
@@ -2018,6 +2090,12 @@ class RideLifecycleController extends GetxController {
|
||||
// Save price_token from server response
|
||||
priceToken = res['price_token']?.toString() ?? '';
|
||||
|
||||
// 🆕 Save is_destination_match so retry_search_drivers can pass it forward
|
||||
final int isMatchFlag = (res['is_destination_match'] ?? 0) is int
|
||||
? res['is_destination_match'] ?? 0
|
||||
: int.tryParse(res['is_destination_match']?.toString() ?? '0') ?? 0;
|
||||
box.write(BoxName.isDestinationMatch, isMatchFlag.toString());
|
||||
|
||||
totalPassenger = totalPassengerSpeed;
|
||||
totalCostPassenger = totalPassenger;
|
||||
}
|
||||
@@ -3979,7 +4057,7 @@ class RideLifecycleController extends GetxController {
|
||||
dataCarsLocationByPassenger['message'] != null;
|
||||
}
|
||||
|
||||
void retrySearchForDrivers() async {
|
||||
void retrySearchForDrivers({double? newPrice}) async {
|
||||
_isCancelProcessed = false;
|
||||
isSearchingWindow = true;
|
||||
currentRideState.value = RideState.searching;
|
||||
@@ -3987,7 +4065,8 @@ class RideLifecycleController extends GetxController {
|
||||
update();
|
||||
|
||||
try {
|
||||
Log.print("🔄 Retrying search for ride ID: $rideId");
|
||||
final bool isPhase2 = newPrice != null && newPrice > 0;
|
||||
Log.print("🔄 ${isPhase2 ? '[Phase 2]' : '[Phase 1]'} Retrying search for ride ID: $rideId");
|
||||
|
||||
var payload = {
|
||||
"ride_id": rideId.toString(),
|
||||
@@ -4011,9 +4090,12 @@ class RideLifecycleController extends GetxController {
|
||||
"price_for_driver": costForDriver.toString(),
|
||||
"car_type": box.read(BoxName.carType).toString(),
|
||||
"is_wallet": Get.find<PaymentController>().isWalletChecked.toString(),
|
||||
"is_destination_match": box.read(BoxName.isDestinationMatch)?.toString() ?? "0",
|
||||
"has_steps": Get.find<WayPointController>().wayPoints.length > 1
|
||||
? "true"
|
||||
: "false",
|
||||
// 🤖 Phase 2: سعر جديد وافق عليه الراكب
|
||||
if (isPhase2) "new_price": newPrice!.toStringAsFixed(2),
|
||||
};
|
||||
|
||||
var response = await CRUD().post(
|
||||
@@ -4023,7 +4105,7 @@ class RideLifecycleController extends GetxController {
|
||||
|
||||
if (response['status'] == 'success') {
|
||||
Log.print("✅ Search reset successfully.");
|
||||
startSearchingTimer();
|
||||
if (!isPhase2) startSearchingTimer(); // Phase 1 يعيد العداد، Phase 2 يُدار من increasePriceAndRestartSearch
|
||||
} else {
|
||||
Log.print("❌ Failed to reset search: $response");
|
||||
handleNoDriverFound();
|
||||
@@ -4037,8 +4119,9 @@ class RideLifecycleController extends GetxController {
|
||||
Future<void> startSearchingTimer() async {
|
||||
_searchTimer?.cancel();
|
||||
int seconds = 0;
|
||||
bool _aiSilentRetryFired = false;
|
||||
|
||||
Log.print("⏳ Search Timer Started (90s)...");
|
||||
Log.print("⏳ Search Timer Started...");
|
||||
await RideLiveNotification.showSearching(driversStatusForSearchWindow);
|
||||
|
||||
_searchTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
@@ -4049,9 +4132,27 @@ class RideLifecycleController extends GetxController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (seconds >= 90) {
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 🤖 AI Negotiator Phase 1 — T=20s: Silent retry
|
||||
// النظام يزيد حصة السائق من عمولة التطبيق كجسر مؤقت
|
||||
// الراكب لا يرى شيئاً — فقط السائق التالي يرى سعراً أفضل
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
if (seconds == 20 && !_aiSilentRetryFired) {
|
||||
_aiSilentRetryFired = true;
|
||||
Log.print("🤖 [AI Negotiator Phase 1] 20s — silent retry with driver bonus...");
|
||||
retrySearchForDrivers();
|
||||
return;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 🤖 AI Negotiator Phase 2 — T=45s: Suggest passenger price bump
|
||||
// يقترح الذكاء الاصطناعي على الراكب زيادة 5% لتسريع القبول
|
||||
// الراكب يختار: يقبل الزيادة أو يلغي الرحلة
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
if (seconds >= 45) {
|
||||
timer.cancel();
|
||||
handleNoDriverFound();
|
||||
Log.print("🤖 [AI Negotiator Phase 2] 45s — showing AI price suggestion dialog...");
|
||||
_showAiNegotiatorDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user