Update: 2026-08-07 01:53:09

This commit is contained in:
Hamza-Ayed
2026-08-07 01:53:09 +03:00
parent 5b864e30df
commit 9830e5f649
7 changed files with 289 additions and 26 deletions
@@ -41,9 +41,16 @@ class OrderOverlay extends StatefulWidget {
class _OrderOverlayState extends State<OrderOverlay>
with WidgetsBindingObserver {
// === State Variables ===
/// مدة مرحلة "الطلب العام" بعد انتهاء السبق الحصري.
static const int kPublicWindowSeconds = 15;
OrderData? orderData;
Timer? timer;
int remainingSeconds = 10;
int remainingSeconds = kPublicWindowSeconds;
/// هل ما زلنا داخل ثواني السبق الحصري؟ تنقلب إلى false تلقائياً حين
/// يتبقّى من العدّاد ما يساوي مدة المرحلة العامة.
bool isExclusive = false;
final AudioPlayer audioPlayer = AudioPlayer();
bool buttonsEnabled = true;
final String mapApiKey = AK.mapAPIKEY;
@@ -113,20 +120,29 @@ class _OrderOverlayState extends State<OrderOverlay>
void _processEventData(dynamic event) {
_log("Received event: $event");
if (event is List<dynamic>) {
try {
myList = event;
final newOrder = OrderData.fromList(event);
_log("Parsed OrderData: ${newOrder.toMap()}");
setState(() {
orderData = newOrder;
});
_resetAndStartTimer();
} catch (e, s) {
_log("Error parsing OrderData: $e\nStackTrace: $s");
// ‏مساران يغذّيان هذه النافذة بشكلين مختلفين:
// • FCM → قائمة مفهرسة
// • السوكِت → خريطة JSON
// ‏كان الشرط يقبل القائمة وحدها، فحمولة السوكِت تُرفض بصمت ولا تظهر
// ‏نافذة إطلاقاً. OrderData.fromAny يوحّد الشكلين.
try {
final newOrder = OrderData.fromAny(event);
if (newOrder == null) {
_log("Unexpected data format: $event");
return;
}
} else {
_log("Unexpected data format: $event");
if (event is List) myList = event;
_log("Parsed OrderData: ${newOrder.toMap()}");
setState(() {
orderData = newOrder;
isExclusive = newOrder.offerType == 'exclusive';
});
_resetAndStartTimer();
} catch (e, s) {
_log("Error parsing OrderData: $e\nStackTrace: $s");
}
}
@@ -151,13 +167,16 @@ class _OrderOverlayState extends State<OrderOverlay>
_startTimer();
}
/// المدة الكلية للنافذة = ثواني السبق الحصري + مدة الطلب العام.
///
/// في وضع الإسناد بالدفعات: ٥ ثوانٍ الطلب خاص بهذا السائق، ثم ١٥ ثانية
/// طلب عام يتنافس فيها مع غيره — أي نافذة ٢٠ ثانية إجمالاً.
/// في البثّ الحر (بلا offerType): ١٥ ثانية عامة فقط، بلا شارة.
int _calculateTimerDuration() {
if (orderData?.durationToPassengerMinutes != null &&
orderData!.durationToPassengerMinutes > 0) {
int duration = orderData!.durationToPassengerMinutes * 60;
return duration > 10 ? 10 : duration;
}
return 10;
final int head = orderData?.offerType == 'exclusive'
? (orderData!.offerExpiresIn > 0 ? orderData!.offerExpiresIn : 5)
: 0;
return head + kPublicWindowSeconds;
}
void _startTimer() {
@@ -171,6 +190,11 @@ class _OrderOverlayState extends State<OrderOverlay>
setState(() {
if (remainingSeconds > 0) {
remainingSeconds--;
// ‏انتهى السبق: الطلب صار عاماً. النافذة لا تُغلق — السائق يظل
// ‏قادراً على القبول لكنه صار يتنافس.
if (isExclusive && remainingSeconds <= kPublicWindowSeconds) {
isExclusive = false;
}
} else {
timer.cancel();
_stopAudio();
@@ -450,12 +474,22 @@ class _OrderOverlayState extends State<OrderOverlay>
children: [
Row(
children: [
Icon(Icons.drive_eta_rounded, color: AppColors.white, size: 24),
Icon(
isExclusive ? Icons.star_rounded : Icons.drive_eta_rounded,
color: isExclusive ? const Color(0xFFFFD54F) : AppColors.white,
size: 24,
),
const SizedBox(width: 8),
// ‏العنوان يحمل حالة العرض: خاص خلال السبق، عام بعده، و"طلب
// ‏جديد" في البثّ الحر حيث لا معنى للتمييز.
Text(
"طلب جديد".tr,
style: const TextStyle(
color: AppColors.white,
orderData?.offerType == null
? "طلب جديد".tr
: (isExclusive ? "⭐ طلب خاص لك".tr : "🌐 طلب عام".tr),
style: TextStyle(
color: isExclusive
? const Color(0xFFFFD54F)
: AppColors.white,
fontSize: 18,
fontWeight: FontWeight.w600),
),