Update: 2026-08-07 06:00:56
This commit is contained in:
@@ -65,7 +65,7 @@ class OrderRequestController extends GetxController
|
||||
};
|
||||
}
|
||||
|
||||
/// معلومات الحجز المسبق إن كانت الرحلة مؤجلة.
|
||||
/// معلومات الحجز المسبق إن كانت الرحلة مجدولة.
|
||||
///
|
||||
/// يصل الحقلان من buildMarketPayload عبر السوكِت (خريطة)، أو من الفهرسين
|
||||
/// 39/40 عبر FCM (قائمة). _getValueAt يوحّد الشكلين.
|
||||
|
||||
@@ -35,9 +35,17 @@ class OrderData {
|
||||
/// ثواني السبق الحصري كما حددها الخادم (DISPATCH_HEAD_START_SECONDS).
|
||||
final int offerExpiresIn;
|
||||
|
||||
/// رحلة محجوزة مسبقاً — موعد مضبوط لا طلب فوري.
|
||||
final bool isScheduled;
|
||||
|
||||
/// موعد الانطلاق كما حجزه الراكب. فارغ في الرحلات العادية.
|
||||
final String scheduledAt;
|
||||
|
||||
OrderData({
|
||||
this.offerType,
|
||||
this.offerExpiresIn = 0,
|
||||
this.isScheduled = false,
|
||||
this.scheduledAt = '',
|
||||
required this.customerName,
|
||||
required this.customerToken,
|
||||
required this.tripDistanceKm,
|
||||
@@ -151,6 +159,11 @@ class OrderData {
|
||||
: null,
|
||||
offerExpiresIn:
|
||||
list.length > 38 ? (int.tryParse(list[38].toString()) ?? 0) : 0,
|
||||
|
||||
// الفهرسان 39/40 — يضيفهما add_ride.php للرحلات المحجوزة.
|
||||
isScheduled: list.length > 39 &&
|
||||
(list[39].toString() == '1' || list[39].toString() == 'true'),
|
||||
scheduledAt: list.length > 40 ? list[40].toString() : '',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -197,6 +210,8 @@ class OrderData {
|
||||
: null,
|
||||
offerType: str('offer_type').isNotEmpty ? str('offer_type') : null,
|
||||
offerExpiresIn: int.tryParse(str('offer_expires_in', '0')) ?? 0,
|
||||
isScheduled: str('is_scheduled') == '1' || str('is_scheduled') == 'true',
|
||||
scheduledAt: str('scheduled_at'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -475,21 +475,36 @@ class _OrderOverlayState extends State<OrderOverlay>
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
isExclusive ? Icons.star_rounded : Icons.drive_eta_rounded,
|
||||
color: isExclusive ? const Color(0xFFFFD54F) : AppColors.white,
|
||||
orderData?.isScheduled == true
|
||||
? Icons.schedule
|
||||
: isExclusive
|
||||
? Icons.star_rounded
|
||||
: Icons.drive_eta_rounded,
|
||||
color: orderData?.isScheduled == true
|
||||
? const Color(0xFFB39DDB)
|
||||
: isExclusive
|
||||
? const Color(0xFFFFD54F)
|
||||
: AppColors.white,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// العنوان يحمل حالة العرض: خاص خلال السبق، عام بعده، و"طلب
|
||||
// جديد" في البثّ الحر حيث لا معنى للتمييز.
|
||||
Text(
|
||||
orderData?.offerType == null
|
||||
? "طلب جديد".tr
|
||||
: (isExclusive ? "⭐ طلب خاص لك".tr : "🌐 طلب عام".tr),
|
||||
orderData?.isScheduled == true
|
||||
// الأولوية للمجدولة: معلومة دائمة عن الرحلة تسبق حالة
|
||||
// العرض المؤقتة، والسائق يحتاجها ليقرر إن كان يستطيع
|
||||
// الالتزام بالموعد.
|
||||
? "🗓️ رحلة مجدولة".tr
|
||||
: orderData?.offerType == null
|
||||
? "طلب جديد".tr
|
||||
: (isExclusive ? "⭐ طلب خاص لك".tr : "🌐 طلب عام".tr),
|
||||
style: TextStyle(
|
||||
color: isExclusive
|
||||
? const Color(0xFFFFD54F)
|
||||
: AppColors.white,
|
||||
color: orderData?.isScheduled == true
|
||||
? const Color(0xFFB39DDB)
|
||||
: isExclusive
|
||||
? const Color(0xFFFFD54F)
|
||||
: AppColors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600),
|
||||
),
|
||||
|
||||
@@ -116,7 +116,7 @@ class OrderRequestPage extends StatelessWidget {
|
||||
OfferTypeBadge(rideInfo: controller.offerInfo!),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
// شارة الرحلة المؤجلة — موعد مضبوط لا طلب فوري
|
||||
// شارة الرحلة المجدولة — موعد مضبوط لا طلب فوري
|
||||
if (ScheduledRideBadge.isScheduled(
|
||||
controller.scheduleInfo)) ...[
|
||||
ScheduledRideBadge(
|
||||
|
||||
@@ -189,7 +189,7 @@ class RideAvailableCard extends StatelessWidget {
|
||||
.copyWith(color: AppColor.greenColor, fontSize: 13),
|
||||
),
|
||||
),
|
||||
// شارة الرحلة المؤجلة — معلومة دائمة عن الرحلة، تظهر هنا
|
||||
// شارة الرحلة المجدولة — معلومة دائمة عن الرحلة، تظهر هنا
|
||||
// وفي نافذة الطلب والأوفرلي معاً.
|
||||
if (ScheduledRideBadge.isScheduled(rideInfo)) ...[
|
||||
const SizedBox(height: 8),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
/// شارة «رحلة مؤجلة».
|
||||
/// شارة «رحلة مجدولة».
|
||||
///
|
||||
/// السائق يجب أن يعرف أن هذه ليست رحلة لحظية بل موعد مضبوط: الراكب
|
||||
/// ينتظره في وقت محدد، والتأخر عليه يختلف عن التأخر على طلب فوري.
|
||||
@@ -73,7 +73,7 @@ class ScheduledRideBadge extends StatelessWidget {
|
||||
Icon(Icons.schedule, color: color, size: compact ? 14 : 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
t.isEmpty ? 'رحلة مؤجلة'.tr : '${'رحلة مؤجلة'.tr} · $t',
|
||||
t.isEmpty ? 'رحلة مجدولة'.tr : '${'رحلة مجدولة'.tr} · $t',
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontSize: compact ? 11 : 13,
|
||||
|
||||
+36
-2
@@ -581,6 +581,35 @@ class TripOverlayService : Service() {
|
||||
}
|
||||
detailsContainer.addView(offerBadge)
|
||||
|
||||
// شارة الرحلة المجدولة — تظهر مع شارة نوع الطلب لا بدلاً منها:
|
||||
// الأولى تصف الرحلة والثانية تصف العرض، ومعلومتان مختلفتان.
|
||||
if (trip.isScheduled) {
|
||||
val scheduledBadge =
|
||||
TextView(ctx).apply {
|
||||
val color = Color.parseColor("#6A5ACD")
|
||||
text = if (trip.scheduledAt.isEmpty()) "🗓️ رحلة مجدولة"
|
||||
else "🗓️ رحلة مجدولة · " + trip.scheduledAt
|
||||
gravity = Gravity.CENTER
|
||||
textSize = 13f
|
||||
typeface = android.graphics.Typeface.DEFAULT_BOLD
|
||||
setPadding(24, 12, 24, 12)
|
||||
setTextColor(color)
|
||||
background =
|
||||
android.graphics.drawable.GradientDrawable().apply {
|
||||
setColor(Color.parseColor("#F0EDFB"))
|
||||
cornerRadius = 40f
|
||||
setStroke(2, color)
|
||||
}
|
||||
layoutParams =
|
||||
LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
.apply { bottomMargin = 24 }
|
||||
}
|
||||
detailsContainer.addView(scheduledBadge)
|
||||
}
|
||||
|
||||
// ── 4. شريط الوقت ──
|
||||
val countdownLabel =
|
||||
TextView(ctx).apply {
|
||||
@@ -925,7 +954,9 @@ class TripOverlayService : Service() {
|
||||
pickupLat = obj.getDouble("pickupLat"),
|
||||
pickupLng = obj.getDouble("pickupLng"),
|
||||
offerType = obj.optString("offerType", ""),
|
||||
offerExpiresIn = obj.optInt("offerExpiresIn", 0)
|
||||
offerExpiresIn = obj.optInt("offerExpiresIn", 0),
|
||||
isScheduled = obj.optBoolean("isScheduled", false),
|
||||
scheduledAt = obj.optString("scheduledAt", "")
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
@@ -944,6 +975,9 @@ class TripOverlayService : Service() {
|
||||
val pickupLng: Double,
|
||||
// "exclusive" خلال السبق الحصري، "public" بعده، وفارغ في البثّ الحر
|
||||
val offerType: String = "",
|
||||
val offerExpiresIn: Int = 0
|
||||
val offerExpiresIn: Int = 0,
|
||||
// رحلة محجوزة مسبقاً — موعد مضبوط لا طلب فوري
|
||||
val isScheduled: Boolean = false,
|
||||
val scheduledAt: String = ""
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ class TripData {
|
||||
/// من نفسها — النافذة لا تُغلق، فالسائق يظل قادراً على القبول.
|
||||
final int offerExpiresIn;
|
||||
|
||||
/// رحلة محجوزة مسبقاً وموعدها. السائق يجب أن يعرف أنها ليست طلباً فورياً.
|
||||
final bool isScheduled;
|
||||
final String scheduledAt;
|
||||
|
||||
TripData({
|
||||
required this.tripId,
|
||||
required this.passengerName,
|
||||
@@ -38,6 +42,8 @@ class TripData {
|
||||
this.passengerAvatarUrl,
|
||||
this.offerType,
|
||||
this.offerExpiresIn = 0,
|
||||
this.isScheduled = false,
|
||||
this.scheduledAt = '',
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
@@ -53,6 +59,8 @@ class TripData {
|
||||
'passengerAvatarUrl': passengerAvatarUrl ?? '',
|
||||
'offerType': offerType ?? '',
|
||||
'offerExpiresIn': offerExpiresIn,
|
||||
'isScheduled': isScheduled,
|
||||
'scheduledAt': scheduledAt,
|
||||
};
|
||||
|
||||
factory TripData.fromMap(Map<String, dynamic> map) => TripData(
|
||||
@@ -67,6 +75,8 @@ class TripData {
|
||||
? null
|
||||
: map['offerType'].toString(),
|
||||
offerExpiresIn: int.tryParse('${map['offerExpiresIn'] ?? 0}') ?? 0,
|
||||
isScheduled: map['isScheduled'] == true || '${map['isScheduled']}' == '1',
|
||||
scheduledAt: (map['scheduledAt'] ?? '').toString(),
|
||||
pickupLat: (map['pickupLat'] ?? 0.0).toDouble(),
|
||||
pickupLng: (map['pickupLng'] ?? 0.0).toDouble(),
|
||||
passengerAvatarUrl: map['passengerAvatarUrl'],
|
||||
|
||||
Reference in New Issue
Block a user