Update: 2026-08-07 01:35:27
This commit is contained in:
+88
-3
@@ -44,6 +44,7 @@ class TripOverlayService : Service() {
|
||||
private var windowManager: WindowManager? = null
|
||||
private var overlayView: View? = null
|
||||
private var countDownTimer: CountDownTimer? = null
|
||||
private var exclusivityTimer: CountDownTimer? = null
|
||||
private var currentTripId: String = ""
|
||||
private var currentFoodOrderId: String = ""
|
||||
// نوع النافذة المعروضة الآن — يحدّد لمن نُرسل نتيجة القبول/الرفض
|
||||
@@ -79,6 +80,7 @@ class TripOverlayService : Service() {
|
||||
override fun onDestroy() {
|
||||
removeOverlayView()
|
||||
countDownTimer?.cancel()
|
||||
exclusivityTimer?.cancel()
|
||||
stopSound() // 🔴 إيقاف الصوت عند التدمير
|
||||
isRunning = false
|
||||
super.onDestroy()
|
||||
@@ -176,6 +178,11 @@ class TripOverlayService : Service() {
|
||||
isRunning = true
|
||||
playSound() // 🔴 تشغيل الصوت
|
||||
startCountdown(autoCloseSeconds)
|
||||
// بعد العدّاد الرئيسي لا قبله: مؤقّتان مستقلان، والحصرية تنتهي
|
||||
// قبل النافذة بكثير فتقلب الشارة وحدها دون إغلاقها.
|
||||
if (tripData.offerType == "exclusive") {
|
||||
startExclusivityCountdown(tripData.offerExpiresIn)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
stopSelf()
|
||||
}
|
||||
@@ -430,6 +437,7 @@ class TripOverlayService : Service() {
|
||||
private fun onFoodOrderAccepted() {
|
||||
stopSound()
|
||||
countDownTimer?.cancel()
|
||||
exclusivityTimer?.cancel()
|
||||
TripOverlayPlugin.notifyFoodOrderAccepted(currentFoodOrderId)
|
||||
bringAppToForeground()
|
||||
removeOverlayView()
|
||||
@@ -553,6 +561,26 @@ class TripOverlayService : Service() {
|
||||
|
||||
detailsContainer.addView(divider(ctx, 30))
|
||||
|
||||
// ── 3.5 شارة نوع الطلب: خاص أم عام ──
|
||||
// تظهر فقط حين يرسل الخادم offerType (أي في وضع الإسناد بالدفعات).
|
||||
// في البثّ الحر تبقى مخفية تماماً فلا تضيف ضجيجاً بلا معنى.
|
||||
val offerBadge =
|
||||
TextView(ctx).apply {
|
||||
gravity = Gravity.CENTER
|
||||
textSize = 13f
|
||||
typeface = android.graphics.Typeface.DEFAULT_BOLD
|
||||
setPadding(24, 12, 24, 12)
|
||||
visibility = if (trip.offerType.isEmpty()) View.GONE else View.VISIBLE
|
||||
applyOfferBadgeStyle(this, trip.offerType == "exclusive")
|
||||
layoutParams =
|
||||
LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
.apply { bottomMargin = 24 }
|
||||
}
|
||||
detailsContainer.addView(offerBadge)
|
||||
|
||||
// ── 4. شريط الوقت ──
|
||||
val countdownLabel =
|
||||
TextView(ctx).apply {
|
||||
@@ -621,7 +649,12 @@ class TripOverlayService : Service() {
|
||||
buttonsRow.addView(acceptBtn)
|
||||
detailsContainer.addView(buttonsRow)
|
||||
|
||||
card.tag = mapOf("countdownLabel" to countdownLabel, "progressBar" to progressBar)
|
||||
card.tag =
|
||||
mapOf(
|
||||
"countdownLabel" to countdownLabel,
|
||||
"progressBar" to progressBar,
|
||||
"offerBadge" to offerBadge
|
||||
)
|
||||
return card
|
||||
}
|
||||
// 🔴 دالة احترافية لقراءة المفتاح المحقون من Gradle 🔴
|
||||
@@ -763,6 +796,52 @@ class TripOverlayService : Service() {
|
||||
// ==========================================================
|
||||
// 🔴 التحكم والمؤقت 🔴
|
||||
// ==========================================================
|
||||
/**
|
||||
* تنسيق شارة نوع الطلب. الحصري بالكهرماني والعام بالرمادي — الفرق
|
||||
* اللوني وحده يكفي ليعرف السائق حالته بنظرة بلا قراءة.
|
||||
*/
|
||||
private fun applyOfferBadgeStyle(view: TextView, exclusive: Boolean) {
|
||||
val color = if (exclusive) "#B8860B" else "#607D8B"
|
||||
val bg = if (exclusive) "#FFF6DC" else "#ECEFF1"
|
||||
view.text = if (exclusive) "⭐ طلب خاص لك" else "🌐 طلب عام"
|
||||
view.setTextColor(Color.parseColor(color))
|
||||
view.background =
|
||||
android.graphics.drawable.GradientDrawable().apply {
|
||||
setColor(Color.parseColor(bg))
|
||||
cornerRadius = 40f
|
||||
setStroke(2, Color.parseColor(color))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* مؤقّت انتهاء الحصرية — منفصل تماماً عن مؤقّت إغلاق النافذة.
|
||||
*
|
||||
* بانقضاء ثواني السبق تصبح الرحلة معروضة للجميع، فتنقلب الشارة إلى
|
||||
* "طلب عام". النافذة **لا تُغلق**: السائق يظل قادراً على القبول، لكنه
|
||||
* صار يتنافس. الخلط بين المؤقّتين كان سيغلق النافذة بعد خمس ثوانٍ
|
||||
* ويحرمه الرحلة كلياً.
|
||||
*/
|
||||
private fun startExclusivityCountdown(seconds: Int) {
|
||||
exclusivityTimer?.cancel()
|
||||
if (seconds <= 0) return
|
||||
|
||||
exclusivityTimer =
|
||||
object : CountDownTimer(seconds * 1000L, 1000L) {
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
val left = (millisUntilFinished / 1000).toInt()
|
||||
val tagMap = overlayView?.tag as? Map<*, *> ?: return
|
||||
val badge = tagMap["offerBadge"] as? TextView ?: return
|
||||
badge.text = "⭐ طلب خاص لك — $left ث"
|
||||
}
|
||||
override fun onFinish() {
|
||||
val tagMap = overlayView?.tag as? Map<*, *> ?: return
|
||||
val badge = tagMap["offerBadge"] as? TextView ?: return
|
||||
applyOfferBadgeStyle(badge, false)
|
||||
}
|
||||
}
|
||||
.start()
|
||||
}
|
||||
|
||||
private fun startCountdown(totalSeconds: Int) {
|
||||
countDownTimer?.cancel()
|
||||
countDownTimer =
|
||||
@@ -796,6 +875,7 @@ class TripOverlayService : Service() {
|
||||
private fun dismissOverlay(reason: String) {
|
||||
stopSound() // 🔴
|
||||
countDownTimer?.cancel()
|
||||
exclusivityTimer?.cancel()
|
||||
if (reason == "rejected" || reason == "timeout") {
|
||||
// النتيجة تذهب لقناة النوع المعروض — لا نخلط عرض توصيل بعرض رحلة
|
||||
if (isFoodOverlay) {
|
||||
@@ -843,7 +923,9 @@ class TripOverlayService : Service() {
|
||||
estimatedFare = obj.getDouble("estimatedFare"),
|
||||
estimatedMinutes = obj.getInt("estimatedMinutes"),
|
||||
pickupLat = obj.getDouble("pickupLat"),
|
||||
pickupLng = obj.getDouble("pickupLng")
|
||||
pickupLng = obj.getDouble("pickupLng"),
|
||||
offerType = obj.optString("offerType", ""),
|
||||
offerExpiresIn = obj.optInt("offerExpiresIn", 0)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
@@ -859,6 +941,9 @@ class TripOverlayService : Service() {
|
||||
val estimatedFare: Double,
|
||||
val estimatedMinutes: Int,
|
||||
val pickupLat: Double,
|
||||
val pickupLng: Double
|
||||
val pickupLng: Double,
|
||||
// "exclusive" خلال السبق الحصري، "public" بعده، وفارغ في البثّ الحر
|
||||
val offerType: String = "",
|
||||
val offerExpiresIn: Int = 0
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,14 @@ class TripData {
|
||||
final double pickupLng;
|
||||
final String? passengerAvatarUrl;
|
||||
|
||||
/// نوع الطلب: 'exclusive' خلال ثواني السبق الحصري، 'public' بعده،
|
||||
/// و null في وضع البثّ الحر (عندها لا تظهر أي شارة).
|
||||
final String? offerType;
|
||||
|
||||
/// ثواني السبق المتبقية. بعد انقضائها تقلب النافذة الشارة إلى "طلب عام"
|
||||
/// من نفسها — النافذة لا تُغلق، فالسائق يظل قادراً على القبول.
|
||||
final int offerExpiresIn;
|
||||
|
||||
TripData({
|
||||
required this.tripId,
|
||||
required this.passengerName,
|
||||
@@ -28,6 +36,8 @@ class TripData {
|
||||
required this.pickupLat,
|
||||
required this.pickupLng,
|
||||
this.passengerAvatarUrl,
|
||||
this.offerType,
|
||||
this.offerExpiresIn = 0,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
@@ -41,6 +51,8 @@ class TripData {
|
||||
'pickupLat': pickupLat,
|
||||
'pickupLng': pickupLng,
|
||||
'passengerAvatarUrl': passengerAvatarUrl ?? '',
|
||||
'offerType': offerType ?? '',
|
||||
'offerExpiresIn': offerExpiresIn,
|
||||
};
|
||||
|
||||
factory TripData.fromMap(Map<String, dynamic> map) => TripData(
|
||||
@@ -51,6 +63,10 @@ class TripData {
|
||||
distanceKm: (map['distanceKm'] ?? 0.0).toDouble(),
|
||||
estimatedFare: (map['estimatedFare'] ?? 0.0).toDouble(),
|
||||
estimatedMinutes: map['estimatedMinutes'] ?? 0,
|
||||
offerType: (map['offerType'] ?? '').toString().isEmpty
|
||||
? null
|
||||
: map['offerType'].toString(),
|
||||
offerExpiresIn: int.tryParse('${map['offerExpiresIn'] ?? 0}') ?? 0,
|
||||
pickupLat: (map['pickupLat'] ?? 0.0).toDouble(),
|
||||
pickupLng: (map['pickupLng'] ?? 0.0).toDouble(),
|
||||
passengerAvatarUrl: map['passengerAvatarUrl'],
|
||||
|
||||
Reference in New Issue
Block a user