Update: 2026-07-11 05:39:23

This commit is contained in:
Hamza-Ayed
2026-07-11 05:39:23 +03:00
parent 8b19adeb80
commit 06dc0aaffe
31 changed files with 845 additions and 3846 deletions
@@ -7,22 +7,27 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.Icon
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodCall
/**
* Android 16 Progress Style Live Update Plugin
*
* يستخدم NotificationCompat مع setProgressStyle لتوفير
* تجربة Live Update متقدمة على Android 16 (API 36) والأجهزة الأحدث.
*
* الميزات:
* - شريط تقدم متحرك (Progress Style)
* - تحديث مباشر للوقت المتبقي
* - دعم الحالات المختلفة (بحث، في الطريق، وصل، رحلة جارية)
* - إشعار ثابت (Ongoing) لا يمكن إلغاؤه أثناء الرحلة
* خدمة تحديث حالة الرحلة في شريط الإشعارات على أندرويد.
*
* تعمل على ثلاثة مستويات حسب الجهاز:
* - أندرويد 16 فما فوق (API 36+) مع موافقة المستخدم: تُستخدم
* ميزة Live Updates الحقيقية عبر Notification.ProgressStyle
* (نفس الفئة والدوال التي تحققنا منها مباشرة من ملف الـSDK).
* - أندرويد 16 فما فوق بدون موافقة المستخدم، أو أي إصدار من
* API 23 حتى 35: إشعار عادٍ ثابت (Ongoing) بشريط تقدّم كلاسيكي،
* يعمل بشكل متطابق تقريباً بصرياً لكن بدون معاملة النظام الخاصة.
* - أقل من API 26: لا يتم إنشاء قناة إشعارات (الكلاس غير موجود
* أصلاً في النظام قبل هذا الإصدار)، ويُستخدم مسار الإشعار
* الكلاسيكي مباشرة بدون قناة.
*/
class AndroidLiveUpdatePlugin(private val context: Context) {
@@ -31,9 +36,7 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
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"
@@ -51,34 +54,66 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
private var currentBody: String = ""
init {
createNotificationChannel()
// NotificationChannel نفسها غير موجودة في النظام قبل API 26،
// فاستدعاؤها بدون هذا الشرط يتسبب بانهيار فوري للتطبيق عند
// الإقلاع على أي جهاز أقدم (Android 6/7).
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
}
}
/**
* إنشاء قناة الإشعارات المخصصة للرحلة
* مع دعم Android 16 Progress Style
*/
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel() {
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW // Low importance for progress style
NotificationManager.IMPORTANCE_LOW
).apply {
description = CHANNEL_DESC
setShowBadge(false)
enableVibration(false)
enableLights(false)
// Android 16: دعم التقدم المستمر
if (Build.VERSION.SDK_INT >= 36) {
// يمكن إضافة خصائص Android 16 هنا
}
}
notificationManager.createNotificationChannel(channel)
}
/**
* هل يمكن فعلياً استخدام Live Updates الحقيقية الآن؟
* تتطلب أندرويد 16+ وموافقة صريحة من المستخدم عبر إعدادات النظام
* (Settings > Apps > Siro > Notifications > Live Updates).
*/
@Suppress("NewApi")
private fun canUsePromotedLiveUpdate(): Boolean {
if (Build.VERSION.SDK_INT < 36) return false
return try {
notificationManager.canPostPromotedNotifications()
} catch (e: Exception) {
false
}
}
private fun statusColor(): Int = when (currentStatus) {
STATUS_DRIVER_ARRIVED -> Color.parseColor("#22C55E")
STATUS_IN_PROGRESS -> Color.parseColor("#1A2340")
STATUS_SEARCHING -> Color.parseColor("#F59E0B")
else -> Color.parseColor("#1A2340")
}
private fun buildContentIntent(): PendingIntent {
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtra("notification_ride", true)
}
return PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
/**
* بدء التحديث المباشر للرحلة
* يعرض إشعاراً ثابتاً مع شريط تقدم
*/
fun startLiveUpdate(
rideId: String,
@@ -93,19 +128,11 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
currentProgress = (progress * 100).toInt().coerceIn(0, 100)
currentMaxProgress = 100
showNotification(
title = currentTitle,
body = currentBody,
progress = currentProgress,
maxProgress = currentMaxProgress,
indeterminate = false,
ongoing = true
)
render(indeterminate = false)
}
/**
* تحديث التقدم والوقت المتبقي
* يستخدم Progress Style مع animate للتحديثات
*/
fun updateProgress(progress: Double, etaText: String) {
currentProgress = (progress * 100).toInt().coerceIn(0, 100)
@@ -122,19 +149,11 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
else -> currentBody
}
showNotification(
title = currentTitle,
body = currentBody,
progress = currentProgress,
maxProgress = currentMaxProgress,
indeterminate = false,
ongoing = true
)
render(indeterminate = false)
}
/**
* تحديث حالة الرحلة بالكامل
* يستخدم لتغيير الحالة بين (في الطريق، وصل، رحلة جارية)
*/
fun updateStatus(
status: String,
@@ -150,50 +169,23 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
STATUS_SEARCHING -> {
currentTitle = "🔍 جاري البحث عن سائق"
currentBody = etaText
showNotification(
title = currentTitle,
body = currentBody,
progress = 0,
maxProgress = 0,
indeterminate = true,
ongoing = true
)
render(indeterminate = true)
}
STATUS_DRIVER_ON_WAY -> {
currentTitle = "🚗 السائق في الطريق إليك"
currentBody = "$driverName • $carDetails • $etaText"
showNotification(
title = currentTitle,
body = currentBody,
progress = currentProgress,
maxProgress = 100,
indeterminate = false,
ongoing = true
)
render(indeterminate = false)
}
STATUS_DRIVER_ARRIVED -> {
currentTitle = "📍 السائق وصل"
currentBody = "الرجاء التوجّه لمقابلة $driverName عند نقطة الالتقاء"
showNotification(
title = currentTitle,
body = currentBody,
progress = 100,
maxProgress = 100,
indeterminate = false,
ongoing = true
)
currentProgress = 100
render(indeterminate = false)
}
STATUS_IN_PROGRESS -> {
currentTitle = "🚀 الرحلة جارية الآن"
currentBody = "المتبقي تقريبًا: $etaText"
showNotification(
title = currentTitle,
body = currentBody,
progress = currentProgress,
maxProgress = 100,
indeterminate = false,
ongoing = true
)
render(indeterminate = false)
}
}
}
@@ -208,29 +200,58 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
}
/**
* إظهار الإشعار مع الإعدادات المناسبة
* يختار المسار المناسب: Live Update الحقيقية إن كانت متاحة ومسموحة،
* وإلا الإشعار الكلاسيكي الآمن على كل الإصدارات من API 23.
*/
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)
private fun render(indeterminate: Boolean) {
if (canUsePromotedLiveUpdate()) {
try {
showPromotedProgressNotification(indeterminate)
return
} catch (e: Exception) {
// أي فشل غير متوقع بالمسار الجديد يرجعنا فوراً للمسار الآمن
}
}
showClassicNotification(indeterminate)
}
val pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
/**
* المسار الجديد: Notification.ProgressStyle الحقيقية (Android 16 / API 36+).
* التوقيعات هنا مؤكدة مباشرة من ملف android-36/android.jar (javap)، وليست تخميناً.
*/
@RequiresApi(36)
private fun showPromotedProgressNotification(indeterminate: Boolean) {
val trackerIcon = Icon.createWithResource(context, android.R.drawable.ic_menu_directions)
val progressStyle = Notification.ProgressStyle()
.setProgressSegments(listOf(
Notification.ProgressStyle.Segment(100).setColor(statusColor())
))
.setProgress(currentProgress)
.setProgressIndeterminate(indeterminate)
.setProgressTrackerIcon(trackerIcon)
.setStyledByProgress(true)
val notification = Notification.Builder(context, CHANNEL_ID)
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setContentTitle(currentTitle)
.setContentText(currentBody)
.setStyle(progressStyle)
.setCategory(Notification.CATEGORY_PROGRESS)
.setOngoing(true)
.setOnlyAlertOnce(true)
.setContentIntent(buildContentIntent())
.setFlag(Notification.FLAG_PROMOTED_ONGOING, true)
.build()
notificationManager.notify(NOTIFICATION_ID, notification)
}
/**
* المسار الكلاسيكي: إشعار ثابت بشريط تقدّم عادي، متوافق من API 23 وما فوق
* (وبدون قناة إشعارات على الإصدارات الأقدم من API 26).
*/
private fun showClassicNotification(indeterminate: Boolean) {
val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setLargeIcon(
@@ -239,30 +260,23 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
android.R.drawable.ic_menu_directions
)
)
.setContentTitle(title)
.setContentText(body)
.setContentTitle(currentTitle)
.setContentText(currentBody)
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(body)
.bigText(currentBody)
)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(ongoing)
.setOngoing(true)
.setAutoCancel(false)
.setOnlyAlertOnce(true)
.setContentIntent(pendingIntent)
.setContentIntent(buildContentIntent())
.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 هنا
// مثل: إظهار الخريطة، مشاركة الرحلة، إلخ
notificationBuilder.setProgress(currentMaxProgress, currentProgress, false)
}
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
@@ -302,11 +316,13 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
result.success(true)
}
"isSupported" -> {
result.success(Build.VERSION.SDK_INT >= 30) // يدعم Android 11+
// الإشعار الكلاسيكي يعمل من API 23 وما فوق؛ الأجهزة الأحدث
// تحصل تلقائياً على Live Update الحقيقية عبر canUsePromotedLiveUpdate().
result.success(Build.VERSION.SDK_INT >= 23)
}
else -> {
result.notImplemented()
}
}
}
}
}
@@ -11,7 +11,6 @@ import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.view.setPadding
import com.scottyab.rootbeer.RootBeer
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
@@ -131,7 +130,7 @@ class MainActivity : FlutterFragmentActivity() {
private fun isDeviceCompromised(): Boolean {
return try {
nativeRootCheck() || rootBeerCheck() // || !safetyNetCheck()
nativeRootCheck() // || !safetyNetCheck()
} catch (e: Exception) {
Log.e("SecurityCheck", "Error during security checks: ${e.message}", e)
true
@@ -150,14 +149,6 @@ class MainActivity : FlutterFragmentActivity() {
}
}
private fun rootBeerCheck(): Boolean {
Log.d("SecurityCheck", "Starting RootBeer root detection...")
val rootBeer = RootBeer(this)
val isRooted = rootBeer.isRooted
Log.d("SecurityCheck", "RootBeer detection result: $isRooted")
return isRooted
}
// SafetyNet (معلّق كما هو)
private fun safetyNetCheck(): Boolean {
Log.d("SecurityCheck", "Starting SafetyNet check...")