Update codebase
This commit is contained in:
+43
@@ -46,6 +46,18 @@ class TripOverlayPlugin :
|
||||
methodChannel?.invokeMethod("onTripRejected", mapOf("tripId" to tripId))
|
||||
Log.d(TAG, "notifyTripRejected: $tripId")
|
||||
}
|
||||
|
||||
/** Called by TripOverlayService when the driver accepts a food-delivery offer. */
|
||||
fun notifyFoodOrderAccepted(orderId: String) {
|
||||
methodChannel?.invokeMethod("onFoodOrderAccepted", mapOf("orderId" to orderId))
|
||||
Log.d(TAG, "notifyFoodOrderAccepted: $orderId")
|
||||
}
|
||||
|
||||
/** Called by TripOverlayService when a food offer is rejected or expires. */
|
||||
fun notifyFoodOrderRejected(orderId: String) {
|
||||
methodChannel?.invokeMethod("onFoodOrderRejected", mapOf("orderId" to orderId))
|
||||
Log.d(TAG, "notifyFoodOrderRejected: $orderId")
|
||||
}
|
||||
}
|
||||
|
||||
// ─── FlutterPlugin ───────────────────────────────────────────────────────
|
||||
@@ -85,6 +97,17 @@ class TripOverlayPlugin :
|
||||
val success = showOverlay(tripDataJson, autoCloseSeconds)
|
||||
result.success(success)
|
||||
}
|
||||
"showFoodOverlay" -> {
|
||||
val foodDataJson =
|
||||
call.argument<String>("foodData")
|
||||
?: run {
|
||||
result.error("INVALID_ARGS", "foodData is required", null)
|
||||
return
|
||||
}
|
||||
val autoCloseSeconds = call.argument<Int>("autoCloseSeconds") ?: 20
|
||||
val success = showFoodOverlay(foodDataJson, autoCloseSeconds)
|
||||
result.success(success)
|
||||
}
|
||||
"hideOverlay" -> {
|
||||
hideOverlay()
|
||||
result.success(null)
|
||||
@@ -141,6 +164,26 @@ class TripOverlayPlugin :
|
||||
return true
|
||||
}
|
||||
|
||||
private fun showFoodOverlay(foodDataJson: String, autoCloseSeconds: Int): Boolean {
|
||||
if (!isOverlayPermissionGranted()) {
|
||||
Log.w(TAG, "Overlay permission not granted")
|
||||
return false
|
||||
}
|
||||
val intent =
|
||||
Intent(context, TripOverlayService::class.java).apply {
|
||||
action = TripOverlayService.ACTION_SHOW
|
||||
putExtra(TripOverlayService.EXTRA_FOOD_DATA, foodDataJson)
|
||||
putExtra(TripOverlayService.EXTRA_AUTO_CLOSE_SECONDS, autoCloseSeconds)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
Log.d(TAG, "showFoodOverlay called with foodData: $foodDataJson")
|
||||
return true
|
||||
}
|
||||
|
||||
private fun hideOverlay() {
|
||||
val intent =
|
||||
Intent(context, TripOverlayService::class.java).apply {
|
||||
|
||||
+265
-13
@@ -30,7 +30,10 @@ class TripOverlayService : Service() {
|
||||
const val ACTION_SHOW = "SHOW"
|
||||
const val ACTION_HIDE = "HIDE"
|
||||
const val EXTRA_TRIP_DATA = "trip_data"
|
||||
const val EXTRA_FOOD_DATA = "food_data"
|
||||
const val EXTRA_AUTO_CLOSE_SECONDS = "auto_close_seconds"
|
||||
const val MODE_TRIP = 0
|
||||
const val MODE_FOOD = 1
|
||||
private const val NOTIFICATION_ID = 9900
|
||||
private const val CHANNEL_ID = "trip_overlay_service_channel"
|
||||
|
||||
@@ -43,6 +46,7 @@ class TripOverlayService : Service() {
|
||||
private var overlayView: View? = null
|
||||
private var countDownTimer: CountDownTimer? = null
|
||||
private var currentTripId: String = ""
|
||||
private var overlayMode: Int = MODE_TRIP
|
||||
private var mediaPlayer: MediaPlayer? = null // 🔴 مشغل الصوت
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
@@ -56,10 +60,18 @@ class TripOverlayService : Service() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
when (intent?.action) {
|
||||
ACTION_SHOW -> {
|
||||
val tripDataJson = intent.getStringExtra(EXTRA_TRIP_DATA) ?: return START_NOT_STICKY
|
||||
val autoClose = intent.getIntExtra(EXTRA_AUTO_CLOSE_SECONDS, 15)
|
||||
startForegroundWithNotification()
|
||||
showTripOverlay(tripDataJson, autoClose)
|
||||
val foodDataJson = intent.getStringExtra(EXTRA_FOOD_DATA)
|
||||
if (foodDataJson != null) {
|
||||
overlayMode = MODE_FOOD
|
||||
startForegroundWithNotification(isFood = true)
|
||||
showFoodOverlay(foodDataJson, autoClose)
|
||||
} else {
|
||||
val tripDataJson = intent.getStringExtra(EXTRA_TRIP_DATA) ?: return START_NOT_STICKY
|
||||
overlayMode = MODE_TRIP
|
||||
startForegroundWithNotification(isFood = false)
|
||||
showTripOverlay(tripDataJson, autoClose)
|
||||
}
|
||||
}
|
||||
ACTION_HIDE -> dismissOverlay(reason = "programmatic")
|
||||
}
|
||||
@@ -112,11 +124,13 @@ class TripOverlayService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun startForegroundWithNotification() {
|
||||
private fun startForegroundWithNotification(isFood: Boolean = false) {
|
||||
val notification =
|
||||
NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle("طلب رحلة جديد")
|
||||
.setContentText("يوجد طلب رحلة في انتظارك")
|
||||
.setContentTitle(if (isFood) "عرض توصيل جديد" else "طلب رحلة جديد")
|
||||
.setContentText(
|
||||
if (isFood) "🍔 يوجد عرض توصيل في انتظارك" else "يوجد طلب رحلة في انتظارك"
|
||||
)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_map)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build()
|
||||
@@ -137,7 +151,24 @@ class TripOverlayService : Service() {
|
||||
}
|
||||
currentTripId = tripData.tripId
|
||||
|
||||
val view = buildOverlayView(tripData, autoCloseSeconds)
|
||||
showOverlayWindow(buildTripOverlayView(tripData, autoCloseSeconds), autoCloseSeconds)
|
||||
}
|
||||
|
||||
private fun showFoodOverlay(foodDataJson: String, autoCloseSeconds: Int) {
|
||||
removeOverlayView()
|
||||
|
||||
val foodData =
|
||||
parseFoodData(foodDataJson)
|
||||
?: run {
|
||||
stopSelf()
|
||||
return
|
||||
}
|
||||
currentTripId = foodData.orderId
|
||||
|
||||
showOverlayWindow(buildFoodOverlayView(foodData, autoCloseSeconds), autoCloseSeconds)
|
||||
}
|
||||
|
||||
private fun showOverlayWindow(view: View, autoCloseSeconds: Int) {
|
||||
overlayView = view
|
||||
|
||||
val type =
|
||||
@@ -158,7 +189,7 @@ class TripOverlayService : Service() {
|
||||
.apply {
|
||||
gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
|
||||
val metrics = resources.displayMetrics
|
||||
y = (metrics.heightPixels * 0.15).toInt() // النزول للأسفل بنسبة 15%
|
||||
y = (metrics.heightPixels * 0.15).toInt()
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -171,7 +202,7 @@ class TripOverlayService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildOverlayView(trip: TripInfo, autoCloseSeconds: Int): View {
|
||||
private fun buildTripOverlayView(trip: TripInfo, autoCloseSeconds: Int): View {
|
||||
val ctx = this
|
||||
|
||||
// الخلفية الرئيسية بستايل الكروت الحديثة (حواف دائرية كبيرة)
|
||||
@@ -329,6 +360,204 @@ class TripOverlayService : Service() {
|
||||
card.tag = mapOf("countdownLabel" to countdownLabel, "progressBar" to progressBar)
|
||||
return card
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// 🔴 نافذة عرض التوصيل (🍔 Food Overlay) 🔴
|
||||
// ==========================================================
|
||||
private fun buildFoodOverlayView(food: FoodInfo, autoCloseSeconds: Int): View {
|
||||
val ctx = this
|
||||
|
||||
val card =
|
||||
FrameLayout(ctx).apply {
|
||||
background =
|
||||
GradientDrawable().apply {
|
||||
cornerRadius = 60f
|
||||
setColor(Color.WHITE)
|
||||
setStroke(2, Color.parseColor("#E0E0E0"))
|
||||
}
|
||||
elevation = 40f
|
||||
layoutParams =
|
||||
FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply { setMargins(40, 0, 40, 0) }
|
||||
}
|
||||
|
||||
val root = LinearLayout(ctx).apply { orientation = LinearLayout.VERTICAL }
|
||||
card.addView(root)
|
||||
|
||||
// ── 1. رأس البطاقة: 🍔 + اسم المطعم ──
|
||||
val header = LinearLayout(ctx).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
setPadding(40, 34, 40, 20)
|
||||
}
|
||||
header.addView(
|
||||
TextView(ctx).apply {
|
||||
text = "🍔"
|
||||
textSize = 34f
|
||||
layoutParams =
|
||||
LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply { rightMargin = 20 }
|
||||
}
|
||||
)
|
||||
header.addView(
|
||||
LinearLayout(ctx).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
addView(
|
||||
TextView(ctx).apply {
|
||||
text = food.merchantName
|
||||
textSize = 20f
|
||||
setTextColor(Color.parseColor("#2C3E50"))
|
||||
typeface = android.graphics.Typeface.DEFAULT_BOLD
|
||||
}
|
||||
)
|
||||
if (food.merchantAddress.isNotBlank()) {
|
||||
addView(
|
||||
TextView(ctx).apply {
|
||||
text = food.merchantAddress
|
||||
textSize = 13f
|
||||
setTextColor(Color.parseColor("#7F8C8D"))
|
||||
maxLines = 1
|
||||
ellipsize = android.text.TextUtils.TruncateAt.END
|
||||
}
|
||||
)
|
||||
}
|
||||
addView(
|
||||
TextView(ctx).apply {
|
||||
text = if (food.itemsCount > 0) "عدد الأصناف: ${food.itemsCount}" else ""
|
||||
textSize = 13f
|
||||
setTextColor(Color.parseColor("#95A5A6"))
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
root.addView(header)
|
||||
|
||||
// ── 2. رسوم التوصيل + المحتوى المستحق ──
|
||||
val feeRow = LinearLayout(ctx).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
setPadding(40, 0, 40, 10)
|
||||
}
|
||||
feeRow.addView(
|
||||
TextView(ctx).apply {
|
||||
text = food.deliveryFeeText.ifBlank { "—" }
|
||||
textSize = 24f
|
||||
setTextColor(Color.parseColor("#27AE60"))
|
||||
typeface = android.graphics.Typeface.DEFAULT_BOLD
|
||||
layoutParams =
|
||||
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||
}
|
||||
)
|
||||
if (food.isCash) {
|
||||
feeRow.addView(
|
||||
TextView(ctx).apply {
|
||||
text = "💵 ${food.cashToCollectText.ifBlank { "—" }} قبض"
|
||||
textSize = 14f
|
||||
setTextColor(Color.parseColor("#E74C3C"))
|
||||
typeface = android.graphics.Typeface.DEFAULT_BOLD
|
||||
}
|
||||
)
|
||||
}
|
||||
if (food.distanceText.isNotBlank()) {
|
||||
feeRow.addView(
|
||||
TextView(ctx).apply {
|
||||
text = food.distanceText
|
||||
textSize = 14f
|
||||
setTextColor(Color.parseColor("#7F8C8D"))
|
||||
typeface = android.graphics.Typeface.DEFAULT_BOLD
|
||||
}
|
||||
)
|
||||
}
|
||||
root.addView(feeRow)
|
||||
|
||||
// ── 3. شريط الوقت ──
|
||||
val countdownLabel =
|
||||
TextView(ctx).apply {
|
||||
text = "ينتهي خلال $autoCloseSeconds ثانية"
|
||||
textSize = 12f
|
||||
setTextColor(Color.parseColor("#E74C3C"))
|
||||
gravity = Gravity.CENTER
|
||||
id = android.R.id.text1
|
||||
}
|
||||
val progressBar =
|
||||
ProgressBar(ctx, null, android.R.attr.progressBarStyleHorizontal).apply {
|
||||
max = autoCloseSeconds
|
||||
progress = autoCloseSeconds
|
||||
id = android.R.id.progress
|
||||
progressTintList =
|
||||
android.content.res.ColorStateList.valueOf(Color.parseColor("#E74C3C"))
|
||||
layoutParams =
|
||||
LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 15).apply {
|
||||
topMargin = 10
|
||||
bottomMargin = 30
|
||||
}
|
||||
}
|
||||
root.addView(countdownLabel)
|
||||
root.addView(progressBar)
|
||||
|
||||
// ── 4. الأزرار ──
|
||||
val buttonsRow = LinearLayout(ctx).apply { orientation = LinearLayout.HORIZONTAL }
|
||||
|
||||
val rejectBtn =
|
||||
Button(ctx).apply {
|
||||
text = "رفض"
|
||||
textSize = 16f
|
||||
setTextColor(Color.parseColor("#E74C3C"))
|
||||
background =
|
||||
GradientDrawable().apply {
|
||||
cornerRadius = 100f
|
||||
setColor(Color.parseColor("#FDEDEC"))
|
||||
}
|
||||
layoutParams =
|
||||
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||
.apply { rightMargin = 20 }
|
||||
setOnClickListener { dismissOverlay(reason = "rejected") }
|
||||
}
|
||||
|
||||
val acceptBtn =
|
||||
Button(ctx).apply {
|
||||
text = "قبول الطلب"
|
||||
textSize = 16f
|
||||
setTextColor(Color.WHITE)
|
||||
background =
|
||||
GradientDrawable().apply {
|
||||
cornerRadius = 100f
|
||||
setColor(Color.parseColor("#27AE60"))
|
||||
}
|
||||
layoutParams =
|
||||
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1.5f)
|
||||
setOnClickListener { onFoodOrderAccepted() }
|
||||
}
|
||||
|
||||
buttonsRow.addView(rejectBtn)
|
||||
buttonsRow.addView(acceptBtn)
|
||||
root.addView(buttonsRow)
|
||||
|
||||
card.tag = mapOf("countdownLabel" to countdownLabel, "progressBar" to progressBar)
|
||||
return card
|
||||
}
|
||||
|
||||
private fun parseFoodData(json: String): FoodInfo? {
|
||||
return try {
|
||||
val obj = JSONObject(json)
|
||||
FoodInfo(
|
||||
orderId = obj.getString("orderId"),
|
||||
merchantName = obj.getString("merchantName"),
|
||||
merchantAddress = obj.optString("merchantAddress"),
|
||||
deliveryFeeText = obj.optString("deliveryFeeText"),
|
||||
cashToCollectText = obj.optString("cashToCollectText"),
|
||||
distanceText = obj.optString("distanceText"),
|
||||
itemsCount = obj.optInt("itemsCount"),
|
||||
isCash = obj.optBoolean("isCash")
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
// 🔴 دالة احترافية لقراءة المفتاح المحقون من Gradle 🔴
|
||||
private fun getGoogleMapsApiKey(): String {
|
||||
return try {
|
||||
@@ -493,7 +722,15 @@ class TripOverlayService : Service() {
|
||||
private fun onTripAccepted() {
|
||||
stopSound() // 🔴
|
||||
TripOverlayPlugin.notifyTripAccepted(currentTripId)
|
||||
bringAppToForeground()
|
||||
bringAppToForeground(extraKey = "acceptedTripId", extraValue = currentTripId)
|
||||
removeOverlayView()
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
private fun onFoodOrderAccepted() {
|
||||
stopSound() // 🔴
|
||||
TripOverlayPlugin.notifyFoodOrderAccepted(currentTripId)
|
||||
bringAppToForeground(extraKey = "acceptedFoodOrderId", extraValue = currentTripId)
|
||||
removeOverlayView()
|
||||
stopSelf()
|
||||
}
|
||||
@@ -502,13 +739,17 @@ class TripOverlayService : Service() {
|
||||
stopSound() // 🔴
|
||||
countDownTimer?.cancel()
|
||||
if (reason == "rejected" || reason == "timeout") {
|
||||
TripOverlayPlugin.notifyTripRejected(currentTripId)
|
||||
if (overlayMode == MODE_FOOD) {
|
||||
TripOverlayPlugin.notifyFoodOrderRejected(currentTripId)
|
||||
} else {
|
||||
TripOverlayPlugin.notifyTripRejected(currentTripId)
|
||||
}
|
||||
}
|
||||
removeOverlayView()
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
private fun bringAppToForeground() {
|
||||
private fun bringAppToForeground(extraKey: String, extraValue: String) {
|
||||
val launchIntent = packageManager.getLaunchIntentForPackage(packageName) ?: return
|
||||
launchIntent.apply {
|
||||
addFlags(
|
||||
@@ -516,7 +757,7 @@ class TripOverlayService : Service() {
|
||||
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
)
|
||||
putExtra("acceptedTripId", currentTripId)
|
||||
putExtra(extraKey, extraValue)
|
||||
}
|
||||
startActivity(launchIntent)
|
||||
}
|
||||
@@ -557,4 +798,15 @@ class TripOverlayService : Service() {
|
||||
val pickupLat: Double,
|
||||
val pickupLng: Double
|
||||
)
|
||||
|
||||
data class FoodInfo(
|
||||
val orderId: String,
|
||||
val merchantName: String,
|
||||
val merchantAddress: String,
|
||||
val deliveryFeeText: String,
|
||||
val cashToCollectText: String,
|
||||
val distanceText: String,
|
||||
val itemsCount: Int,
|
||||
val isCash: Boolean
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user