Update: 2026-07-04 15:39:18

This commit is contained in:
Hamza-Ayed
2026-07-04 15:39:18 +03:00
parent c7bd644b88
commit 8ae6a2e2da
8 changed files with 580 additions and 43 deletions
@@ -18,6 +18,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
@@ -0,0 +1,311 @@
package com.siro.siro_rider
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.Build
import androidx.core.app.NotificationCompat
import io.flutter.plugin.common.MethodChannel
/**
* Android 16 Progress Style Live Update Plugin
*
* يستخدم NotificationCompat مع setProgressStyle لتوفير
* تجربة Live Update متقدمة على Android 16 (API 36) والأجهزة الأحدث.
*
* الميزات:
* - شريط تقدم متحرك (Progress Style)
* - تحديث مباشر للوقت المتبقي
* - دعم الحالات المختلفة (بحث، في الطريق، وصل، رحلة جارية)
* - إشعار ثابت (Ongoing) لا يمكن إلغاؤه أثناء الرحلة
*/
class AndroidLiveUpdatePlugin(private val context: Context) {
companion object {
private const val CHANNEL_ID = "siro_live_ride_progress"
private const val CHANNEL_NAME = "Siro Live Ride Progress"
private const val CHANNEL_DESC = "Live progress updates for your current Siro ride"
private const val NOTIFICATION_ID = 999
private const val SUMMARY_NOTIFICATION_ID = 1000
// Ride status constants
private const val STATUS_SEARCHING = "searching"
private const val STATUS_DRIVER_ON_WAY = "driver_on_way"
private const val STATUS_DRIVER_ARRIVED = "driver_arrived"
private const val STATUS_IN_PROGRESS = "in_progress"
private const val STATUS_FINISHED = "finished"
}
private val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private var currentProgress: Int = 0
private var currentMaxProgress: Int = 100
private var currentStatus: String = STATUS_SEARCHING
private var currentTitle: String = ""
private var currentBody: String = ""
init {
createNotificationChannel()
}
/**
* إنشاء قناة الإشعارات المخصصة للرحلة
* مع دعم Android 16 Progress Style
*/
private fun createNotificationChannel() {
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW // Low importance for progress style
).apply {
description = CHANNEL_DESC
setShowBadge(false)
enableVibration(false)
enableLights(false)
// Android 16: دعم التقدم المستمر
if (Build.VERSION.SDK_INT >= 36) {
// يمكن إضافة خصائص Android 16 هنا
}
}
notificationManager.createNotificationChannel(channel)
}
/**
* بدء التحديث المباشر للرحلة
* يعرض إشعاراً ثابتاً مع شريط تقدم
*/
fun startLiveUpdate(
rideId: String,
driverName: String,
etaText: String,
progress: Double,
carDetails: String
) {
currentStatus = STATUS_DRIVER_ON_WAY
currentTitle = "🚗 السائق في الطريق إليك"
currentBody = "$driverName • $carDetails • $etaText"
currentProgress = (progress * 100).toInt().coerceIn(0, 100)
currentMaxProgress = 100
showNotification(
title = currentTitle,
body = currentBody,
progress = currentProgress,
maxProgress = currentMaxProgress,
indeterminate = false,
ongoing = true
)
}
/**
* تحديث التقدم والوقت المتبقي
* يستخدم Progress Style مع animate للتحديثات
*/
fun updateProgress(progress: Double, etaText: String) {
currentProgress = (progress * 100).toInt().coerceIn(0, 100)
currentBody = when (currentStatus) {
STATUS_DRIVER_ON_WAY -> {
val parts = currentBody.split(" • ")
if (parts.size >= 3) {
"${parts[0]} • ${parts[1]} • $etaText"
} else {
"$currentBody • $etaText"
}
}
STATUS_IN_PROGRESS -> "المتبقي تقريبًا: $etaText"
else -> currentBody
}
showNotification(
title = currentTitle,
body = currentBody,
progress = currentProgress,
maxProgress = currentMaxProgress,
indeterminate = false,
ongoing = true
)
}
/**
* تحديث حالة الرحلة بالكامل
* يستخدم لتغيير الحالة بين (في الطريق، وصل، رحلة جارية)
*/
fun updateStatus(
status: String,
driverName: String,
etaText: String,
progress: Double,
carDetails: String
) {
currentStatus = status
currentProgress = (progress * 100).toInt().coerceIn(0, 100)
when (status) {
STATUS_SEARCHING -> {
currentTitle = "🔍 جاري البحث عن سائق"
currentBody = etaText
showNotification(
title = currentTitle,
body = currentBody,
progress = 0,
maxProgress = 0,
indeterminate = true,
ongoing = true
)
}
STATUS_DRIVER_ON_WAY -> {
currentTitle = "🚗 السائق في الطريق إليك"
currentBody = "$driverName • $carDetails • $etaText"
showNotification(
title = currentTitle,
body = currentBody,
progress = currentProgress,
maxProgress = 100,
indeterminate = false,
ongoing = true
)
}
STATUS_DRIVER_ARRIVED -> {
currentTitle = "📍 السائق وصل"
currentBody = "الرجاء التوجّه لمقابلة $driverName عند نقطة الالتقاء"
showNotification(
title = currentTitle,
body = currentBody,
progress = 100,
maxProgress = 100,
indeterminate = false,
ongoing = true
)
}
STATUS_IN_PROGRESS -> {
currentTitle = "🚀 الرحلة جارية الآن"
currentBody = "المتبقي تقريبًا: $etaText"
showNotification(
title = currentTitle,
body = currentBody,
progress = currentProgress,
maxProgress = 100,
indeterminate = false,
ongoing = true
)
}
}
}
/**
* إنهاء التحديث المباشر وإزالة الإشعار
*/
fun endLiveUpdate() {
notificationManager.cancel(NOTIFICATION_ID)
currentStatus = STATUS_FINISHED
currentProgress = 0
}
/**
* إظهار الإشعار مع الإعدادات المناسبة
*/
private fun showNotification(
title: String,
body: String,
progress: Int,
maxProgress: Int,
indeterminate: Boolean,
ongoing: Boolean
) {
// Intent لفتح التطبيق عند النقر على الإشعار
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtra("notification_ride", true)
}
val pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setLargeIcon(
BitmapFactory.decodeResource(
context.resources,
android.R.drawable.ic_menu_directions
)
)
.setContentTitle(title)
.setContentText(body)
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(body)
)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(ongoing)
.setAutoCancel(false)
.setOnlyAlertOnce(true)
.setContentIntent(pendingIntent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
// تطبيق Progress Style
if (indeterminate) {
notificationBuilder.setProgress(0, 0, true)
} else {
notificationBuilder.setProgress(maxProgress, progress, false)
}
// Android 16: إضافة إجراءات سريعة للإشعار
if (Build.VERSION.SDK_INT >= 36) {
// يمكن إضافة إجراءات Android 16 هنا
// مثل: إظهار الخريطة، مشاركة الرحلة، إلخ
}
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
}
/**
* معالجة استدعاءات MethodChannel
*/
fun handleMethodCall(call: MethodChannel.MethodCall, result: MethodChannel.Result) {
when (call.method) {
"startLiveUpdate" -> {
val rideId = call.argument<String>("rideId") ?: ""
val driverName = call.argument<String>("driverName") ?: ""
val etaText = call.argument<String>("etaText") ?: ""
val progress = call.argument<Double>("progress") ?: 0.0
val carDetails = call.argument<String>("carDetails") ?: ""
startLiveUpdate(rideId, driverName, etaText, progress, carDetails)
result.success(true)
}
"updateProgress" -> {
val progress = call.argument<Double>("progress") ?: 0.0
val etaText = call.argument<String>("etaText") ?: ""
updateProgress(progress, etaText)
result.success(true)
}
"updateStatus" -> {
val status = call.argument<String>("status") ?: ""
val driverName = call.argument<String>("driverName") ?: ""
val etaText = call.argument<String>("etaText") ?: ""
val progress = call.argument<Double>("progress") ?: 0.0
val carDetails = call.argument<String>("carDetails") ?: ""
updateStatus(status, driverName, etaText, progress, carDetails)
result.success(true)
}
"endLiveUpdate" -> {
endLiveUpdate()
result.success(true)
}
"isSupported" -> {
result.success(Build.VERSION.SDK_INT >= 30) // يدعم Android 11+
}
else -> {
result.notImplemented()
}
}
}
}
@@ -29,6 +29,10 @@ class MainActivity : FlutterFragmentActivity() {
private val PIP_CHANNEL = "siro/pip"
private var pipEnabled = false // هل الرحلة نشطة ويجب تفعيل PiP عند الخروج؟
// قناة Android Live Update (Android 16 Progress Style)
private val LIVE_UPDATE_CHANNEL = "siro/android_live_update"
private lateinit var liveUpdatePlugin: AndroidLiveUpdatePlugin
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
@@ -72,6 +76,14 @@ class MainActivity : FlutterFragmentActivity() {
}
}
}
// -------- 3) قناة Android Live Update (Progress Style) --------
liveUpdatePlugin = AndroidLiveUpdatePlugin(this)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, LIVE_UPDATE_CHANNEL)
.setMethodCallHandler { call, result ->
liveUpdatePlugin.handleMethodCall(call, result)
}
}
// -------- PiP Helper Methods --------