Update: 2026-07-04 15:39:18
This commit is contained in:
@@ -97,17 +97,27 @@ class LocationIntelligenceEngine {
|
|||||||
|
|
||||||
private function sendCampaignNotification($passengerId, $zone) {
|
private function sendCampaignNotification($passengerId, $zone) {
|
||||||
// Get passenger token
|
// Get passenger token
|
||||||
$sql = "SELECT users_token, country_code FROM users WHERE users_id = :pid";
|
$sql = "SELECT t.token as users_token, p.country_code
|
||||||
|
FROM passengers p
|
||||||
|
JOIN tokens t ON p.id = t.passengerID
|
||||||
|
WHERE p.id = :pid";
|
||||||
$stmt = $this->db->prepare($sql);
|
$stmt = $this->db->prepare($sql);
|
||||||
$stmt->execute([':pid' => $passengerId]);
|
$stmt->execute([':pid' => $passengerId]);
|
||||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if ($user && !empty($user['users_token'])) {
|
if ($user && !empty($user['users_token'])) {
|
||||||
$title = "مرحباً بك في " . $zone['zone_name'] . " \u{1F389}";
|
$title = "مرحباً بك في " . $zone['zone_name'] . " 🎉";
|
||||||
$body = "اطلب رحلتك الآن من " . $zone['zone_name'] . " واستمتع بتجربة سيرو!";
|
$body = "اطلب رحلتك الآن من " . $zone['zone_name'] . " واستمتع بتجربة سيرو!";
|
||||||
|
|
||||||
// Send Push
|
// Decrypt token
|
||||||
sendFCM_Internal([$user['users_token']], $title, $body, ['type' => 'geofence_promo'], '');
|
require_once __DIR__ . '/../Security/EncryptionHelper.php';
|
||||||
|
$encryptionHelper = new EncryptionHelper();
|
||||||
|
$decryptedToken = $encryptionHelper->decryptData($user['users_token']);
|
||||||
|
|
||||||
|
if ($decryptedToken) {
|
||||||
|
// Send Push
|
||||||
|
sendFCM_Internal($decryptedToken, $title, $body, ['type' => 'geofence_promo'], 'Marketing');
|
||||||
|
}
|
||||||
|
|
||||||
// Log it
|
// Log it
|
||||||
$logSql = "INSERT INTO marketing_campaigns_log (passenger_id, message_type, country_code, region_name, triggered_by)
|
$logSql = "INSERT INTO marketing_campaigns_log (passenger_id, message_type, country_code, region_name, triggered_by)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<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" />
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||||
|
|||||||
+311
@@ -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 val PIP_CHANNEL = "siro/pip"
|
||||||
private var pipEnabled = false // هل الرحلة نشطة ويجب تفعيل 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) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(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 --------
|
// -------- PiP Helper Methods --------
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import 'ui_interactions_controller.dart';
|
|||||||
import 'map_socket_controller.dart';
|
import 'map_socket_controller.dart';
|
||||||
import '../decode_polyline_isolate.dart';
|
import '../decode_polyline_isolate.dart';
|
||||||
import '../ios_live_activity_service.dart';
|
import '../ios_live_activity_service.dart';
|
||||||
|
import '../../../services/android_live_update_service.dart';
|
||||||
import '../../firebase/local_notification.dart';
|
import '../../firebase/local_notification.dart';
|
||||||
import '../../firebase/notification_service.dart';
|
import '../../firebase/notification_service.dart';
|
||||||
import '../../functions/audio_record1.dart';
|
import '../../functions/audio_record1.dart';
|
||||||
@@ -737,8 +738,10 @@ class RideLifecycleController extends GetxController {
|
|||||||
void _showAiNegotiatorDialog() {
|
void _showAiNegotiatorDialog() {
|
||||||
final double currentPrice = double.tryParse(totalPassenger) ?? 0;
|
final double currentPrice = double.tryParse(totalPassenger) ?? 0;
|
||||||
final double suggestedPrice = currentPrice * 1.05; // 5% AI suggestion
|
final double suggestedPrice = currentPrice * 1.05; // 5% AI suggestion
|
||||||
final String currency = box.read(BoxName.serverChosen)?.toString().contains('Syria') == true
|
final String currency =
|
||||||
? 'ل.س' : 'SAR';
|
box.read(BoxName.serverChosen)?.toString().contains('Syria') == true
|
||||||
|
? 'ل.س'
|
||||||
|
: 'SAR';
|
||||||
|
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
Dialog(
|
Dialog(
|
||||||
@@ -757,24 +760,30 @@ class RideLifecycleController extends GetxController {
|
|||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: BorderRadius.circular(50),
|
||||||
border: Border.all(color: const Color(0xFF0F3460), width: 2),
|
border: Border.all(color: const Color(0xFF0F3460), width: 2),
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.psychology_rounded, color: Color(0xFF4FC3F7), size: 36),
|
child: const Icon(Icons.psychology_rounded,
|
||||||
|
color: Color(0xFF4FC3F7), size: 36),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'🤖 مقترح الذكاء الاصطناعي'.tr,
|
'🤖 مقترح الذكاء الاصطناعي'.tr,
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
'لم يقبل أي سائق طلبك بالسعر الحالي. يقترح AI رفع السعر قليلاً لتسريع القبول.'.tr,
|
'لم يقبل أي سائق طلبك بالسعر الحالي. يقترح AI رفع السعر قليلاً لتسريع القبول.'
|
||||||
|
.tr,
|
||||||
style: const TextStyle(color: Color(0xFFB0BEC5), fontSize: 13),
|
style: const TextStyle(color: Color(0xFFB0BEC5), fontSize: 13),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
// Price comparison
|
// Price comparison
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF0F3460),
|
color: const Color(0xFF0F3460),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@@ -784,17 +793,28 @@ class RideLifecycleController extends GetxController {
|
|||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
Text('السعر الحالي'.tr, style: const TextStyle(color: Color(0xFF90A4AE), fontSize: 11)),
|
Text('السعر الحالي'.tr,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF90A4AE), fontSize: 11)),
|
||||||
Text('${currentPrice.toStringAsFixed(0)} $currency',
|
Text('${currentPrice.toStringAsFixed(0)} $currency',
|
||||||
style: const TextStyle(color: Colors.white70, fontSize: 15, decoration: TextDecoration.lineThrough)),
|
style: const TextStyle(
|
||||||
|
color: Colors.white70,
|
||||||
|
fontSize: 15,
|
||||||
|
decoration: TextDecoration.lineThrough)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Icon(Icons.arrow_forward_rounded, color: Color(0xFF4FC3F7)),
|
const Icon(Icons.arrow_forward_rounded,
|
||||||
|
color: Color(0xFF4FC3F7)),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
Text('السعر المقترح'.tr, style: const TextStyle(color: Color(0xFF4FC3F7), fontSize: 11)),
|
Text('السعر المقترح'.tr,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF4FC3F7), fontSize: 11)),
|
||||||
Text('${suggestedPrice.toStringAsFixed(0)} $currency',
|
Text('${suggestedPrice.toStringAsFixed(0)} $currency',
|
||||||
style: const TextStyle(color: Color(0xFF4FC3F7), fontSize: 17, fontWeight: FontWeight.bold)),
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF4FC3F7),
|
||||||
|
fontSize: 17,
|
||||||
|
fontWeight: FontWeight.bold)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -811,9 +831,11 @@ class RideLifecycleController extends GetxController {
|
|||||||
},
|
},
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
side: const BorderSide(color: Colors.red),
|
side: const BorderSide(color: Colors.red),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10)),
|
||||||
),
|
),
|
||||||
child: Text('إلغاء'.tr, style: const TextStyle(color: Colors.red)),
|
child: Text('إلغاء'.tr,
|
||||||
|
style: const TextStyle(color: Colors.red)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
@@ -825,9 +847,13 @@ class RideLifecycleController extends GetxController {
|
|||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFF4FC3F7),
|
backgroundColor: const Color(0xFF4FC3F7),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10)),
|
||||||
),
|
),
|
||||||
child: Text('قبول +5%'.tr, style: const TextStyle(color: Colors.black, fontWeight: FontWeight.bold)),
|
child: Text('قبول +5%'.tr,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -846,12 +872,14 @@ class RideLifecycleController extends GetxController {
|
|||||||
update();
|
update();
|
||||||
|
|
||||||
// تحديث قاعدة البيانات بالسعر الجديد
|
// تحديث قاعدة البيانات بالسعر الجديد
|
||||||
await CRUD().post(link: "${AppLink.server}/ride/rides/update.php", payload: {
|
await CRUD()
|
||||||
|
.post(link: "${AppLink.server}/ride/rides/update.php", payload: {
|
||||||
"id": rideId,
|
"id": rideId,
|
||||||
"price": newPrice.toStringAsFixed(2),
|
"price": newPrice.toStringAsFixed(2),
|
||||||
});
|
});
|
||||||
|
|
||||||
Log.print('[AI Negotiator Phase 2] Price accepted by passenger: $newPrice — restarting search...');
|
Log.print(
|
||||||
|
'[AI Negotiator Phase 2] Price accepted by passenger: $newPrice — restarting search...');
|
||||||
notifiedDrivers.clear();
|
notifiedDrivers.clear();
|
||||||
|
|
||||||
// إعادة البحث مع السعر الجديد (Phase 2 retry)
|
// إعادة البحث مع السعر الجديد (Phase 2 retry)
|
||||||
@@ -1016,6 +1044,12 @@ class RideLifecycleController extends GetxController {
|
|||||||
etaText: stringRemainingTimeToPassenger,
|
etaText: stringRemainingTimeToPassenger,
|
||||||
progress: currentProgress.clamp(0.0, 1.0),
|
progress: currentProgress.clamp(0.0, 1.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Android Live Update: تحديث التقدم أثناء انتظار السائق
|
||||||
|
AndroidLiveUpdateService.updateProgress(
|
||||||
|
progress: currentProgress.clamp(0.0, 1.0),
|
||||||
|
etaText: stringRemainingTimeToPassenger,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_driverEtaCountdownTicks % beginRideInterval == 0) {
|
if (_driverEtaCountdownTicks % beginRideInterval == 0) {
|
||||||
@@ -1149,6 +1183,12 @@ class RideLifecycleController extends GetxController {
|
|||||||
etaText: stringRemainingTimeRideBegin,
|
etaText: stringRemainingTimeRideBegin,
|
||||||
progress: progressTimerRideBegin.clamp(0.0, 1.0),
|
progress: progressTimerRideBegin.clamp(0.0, 1.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Android Live Update: تحديث التقدم أثناء الرحلة
|
||||||
|
AndroidLiveUpdateService.updateProgress(
|
||||||
|
progress: progressTimerRideBegin.clamp(0.0, 1.0),
|
||||||
|
etaText: stringRemainingTimeRideBegin,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remainingSeconds % 60 == 0 || remainingSeconds == 0) {
|
if (remainingSeconds % 60 == 0 || remainingSeconds == 0) {
|
||||||
@@ -2093,7 +2133,8 @@ class RideLifecycleController extends GetxController {
|
|||||||
// 🆕 Save is_destination_match so retry_search_drivers can pass it forward
|
// 🆕 Save is_destination_match so retry_search_drivers can pass it forward
|
||||||
final int isMatchFlag = (res['is_destination_match'] ?? 0) is int
|
final int isMatchFlag = (res['is_destination_match'] ?? 0) is int
|
||||||
? res['is_destination_match'] ?? 0
|
? res['is_destination_match'] ?? 0
|
||||||
: int.tryParse(res['is_destination_match']?.toString() ?? '0') ?? 0;
|
: int.tryParse(res['is_destination_match']?.toString() ?? '0') ??
|
||||||
|
0;
|
||||||
box.write(BoxName.isDestinationMatch, isMatchFlag.toString());
|
box.write(BoxName.isDestinationMatch, isMatchFlag.toString());
|
||||||
|
|
||||||
totalPassenger = totalPassengerSpeed;
|
totalPassenger = totalPassengerSpeed;
|
||||||
@@ -2135,12 +2176,15 @@ class RideLifecycleController extends GetxController {
|
|||||||
|
|
||||||
if (res is Map && res['status'] == 'success') {
|
if (res is Map && res['status'] == 'success') {
|
||||||
hasCompetitorData = res['has_competitor_data'] == true;
|
hasCompetitorData = res['has_competitor_data'] == true;
|
||||||
competitorAvgPrice = double.tryParse(res['competitor_avg_price']?.toString() ?? '');
|
competitorAvgPrice =
|
||||||
savingsPercent = double.tryParse(res['savings_percent']?.toString() ?? '');
|
double.tryParse(res['competitor_avg_price']?.toString() ?? '');
|
||||||
|
savingsPercent =
|
||||||
|
double.tryParse(res['savings_percent']?.toString() ?? '');
|
||||||
savingsLabel = res['savings_label']?.toString();
|
savingsLabel = res['savings_label']?.toString();
|
||||||
topCompetitor = res['top_competitor']?.toString();
|
topCompetitor = res['top_competitor']?.toString();
|
||||||
driverExtraLabel = res['driver_extra_label']?.toString();
|
driverExtraLabel = res['driver_extra_label']?.toString();
|
||||||
driverExtraAmount = double.tryParse(res['driver_extra_amount']?.toString() ?? '');
|
driverExtraAmount =
|
||||||
|
double.tryParse(res['driver_extra_amount']?.toString() ?? '');
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -2912,6 +2956,15 @@ class RideLifecycleController extends GetxController {
|
|||||||
statusRide = 'Arrived';
|
statusRide = 'Arrived';
|
||||||
await RideLiveNotification.showDriverArrived(driverName);
|
await RideLiveNotification.showDriverArrived(driverName);
|
||||||
|
|
||||||
|
// Android Live Update: تحديث الحالة إلى "وصل السائق"
|
||||||
|
await AndroidLiveUpdateService.updateStatus(
|
||||||
|
status: 'driver_arrived',
|
||||||
|
driverName: driverName,
|
||||||
|
etaText: 'لقد وصل السائق',
|
||||||
|
progress: 1.0,
|
||||||
|
carDetails: '$make • $model • $carColor',
|
||||||
|
);
|
||||||
|
|
||||||
uiInteractions.driverArrivePassengerDialoge();
|
uiInteractions.driverArrivePassengerDialoge();
|
||||||
startTimerDriverWaitPassenger5Minute();
|
startTimerDriverWaitPassenger5Minute();
|
||||||
|
|
||||||
@@ -2957,7 +3010,9 @@ class RideLifecycleController extends GetxController {
|
|||||||
"Please make sure not to leave any personal belongings in the car.".tr,
|
"Please make sure not to leave any personal belongings in the car.".tr,
|
||||||
'tone1',
|
'tone1',
|
||||||
);
|
);
|
||||||
|
// End both iOS and Android Live Updates
|
||||||
IosLiveActivityService.endRideActivity();
|
IosLiveActivityService.endRideActivity();
|
||||||
|
await AndroidLiveUpdateService.endLiveUpdate();
|
||||||
PipService.disablePip();
|
PipService.disablePip();
|
||||||
await RideLiveNotification.cancel();
|
await RideLiveNotification.cancel();
|
||||||
|
|
||||||
@@ -2980,6 +3035,7 @@ class RideLifecycleController extends GetxController {
|
|||||||
if (Get.isDialogOpen == true) Get.back();
|
if (Get.isDialogOpen == true) Get.back();
|
||||||
await RideLiveNotification.cancel();
|
await RideLiveNotification.cancel();
|
||||||
IosLiveActivityService.endRideActivity();
|
IosLiveActivityService.endRideActivity();
|
||||||
|
await AndroidLiveUpdateService.endLiveUpdate();
|
||||||
PipService.disablePip();
|
PipService.disablePip();
|
||||||
|
|
||||||
Get.defaultDialog(
|
Get.defaultDialog(
|
||||||
@@ -3685,7 +3741,8 @@ class RideLifecycleController extends GetxController {
|
|||||||
for (final m in newMarkers) {
|
for (final m in newMarkers) {
|
||||||
await ctrl.addMarker(m);
|
await ctrl.addMarker(m);
|
||||||
}
|
}
|
||||||
Log.print('✅ Added ${newMarkers.length} markers via controller.addMarker()');
|
Log.print(
|
||||||
|
'✅ Added ${newMarkers.length} markers via controller.addMarker()');
|
||||||
} else {
|
} else {
|
||||||
Log.print('⚠️ mapController is null, relying on declarative markers');
|
Log.print('⚠️ mapController is null, relying on declarative markers');
|
||||||
}
|
}
|
||||||
@@ -3792,6 +3849,7 @@ class RideLifecycleController extends GetxController {
|
|||||||
await getUpdatedRideForDriverApply(rideId);
|
await getUpdatedRideForDriverApply(rideId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start iOS Live Activity
|
||||||
await IosLiveActivityService.startRideActivity(
|
await IosLiveActivityService.startRideActivity(
|
||||||
rideId: rideId,
|
rideId: rideId,
|
||||||
driverName: driverName,
|
driverName: driverName,
|
||||||
@@ -3800,6 +3858,15 @@ class RideLifecycleController extends GetxController {
|
|||||||
progress: 0.0,
|
progress: 0.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Start Android Live Update (Progress Style)
|
||||||
|
await AndroidLiveUpdateService.startLiveUpdate(
|
||||||
|
rideId: rideId,
|
||||||
|
driverName: driverName,
|
||||||
|
etaText: stringRemainingTimeToPassenger,
|
||||||
|
progress: 0.0,
|
||||||
|
carDetails: '$make • $model • $carColor',
|
||||||
|
);
|
||||||
|
|
||||||
_showRideStartNotifications();
|
_showRideStartNotifications();
|
||||||
final etaText = stringRemainingTimeToPassenger;
|
final etaText = stringRemainingTimeToPassenger;
|
||||||
final carInfo = '$make • $model • $licensePlate';
|
final carInfo = '$make • $model • $licensePlate';
|
||||||
@@ -3918,6 +3985,7 @@ class RideLifecycleController extends GetxController {
|
|||||||
currentRideState.value = RideState.cancelled;
|
currentRideState.value = RideState.cancelled;
|
||||||
await RideLiveNotification.cancel();
|
await RideLiveNotification.cancel();
|
||||||
IosLiveActivityService.endRideActivity();
|
IosLiveActivityService.endRideActivity();
|
||||||
|
await AndroidLiveUpdateService.endLiveUpdate();
|
||||||
PipService.disablePip();
|
PipService.disablePip();
|
||||||
|
|
||||||
if (rideId != 'yet' && rideId != null) {
|
if (rideId != 'yet' && rideId != null) {
|
||||||
@@ -4033,6 +4101,7 @@ class RideLifecycleController extends GetxController {
|
|||||||
stopAllTimers();
|
stopAllTimers();
|
||||||
await RideLiveNotification.cancel();
|
await RideLiveNotification.cancel();
|
||||||
IosLiveActivityService.endRideActivity();
|
IosLiveActivityService.endRideActivity();
|
||||||
|
await AndroidLiveUpdateService.endLiveUpdate();
|
||||||
PipService.disablePip();
|
PipService.disablePip();
|
||||||
_isCancelProcessed = false;
|
_isCancelProcessed = false;
|
||||||
currentRideState.value = RideState.noRide;
|
currentRideState.value = RideState.noRide;
|
||||||
@@ -4066,7 +4135,8 @@ class RideLifecycleController extends GetxController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final bool isPhase2 = newPrice != null && newPrice > 0;
|
final bool isPhase2 = newPrice != null && newPrice > 0;
|
||||||
Log.print("🔄 ${isPhase2 ? '[Phase 2]' : '[Phase 1]'} Retrying search for ride ID: $rideId");
|
Log.print(
|
||||||
|
"🔄 ${isPhase2 ? '[Phase 2]' : '[Phase 1]'} Retrying search for ride ID: $rideId");
|
||||||
|
|
||||||
var payload = {
|
var payload = {
|
||||||
"ride_id": rideId.toString(),
|
"ride_id": rideId.toString(),
|
||||||
@@ -4090,7 +4160,8 @@ class RideLifecycleController extends GetxController {
|
|||||||
"price_for_driver": costForDriver.toString(),
|
"price_for_driver": costForDriver.toString(),
|
||||||
"car_type": box.read(BoxName.carType).toString(),
|
"car_type": box.read(BoxName.carType).toString(),
|
||||||
"is_wallet": Get.find<PaymentController>().isWalletChecked.toString(),
|
"is_wallet": Get.find<PaymentController>().isWalletChecked.toString(),
|
||||||
"is_destination_match": box.read(BoxName.isDestinationMatch)?.toString() ?? "0",
|
"is_destination_match":
|
||||||
|
box.read(BoxName.isDestinationMatch)?.toString() ?? "0",
|
||||||
"has_steps": Get.find<WayPointController>().wayPoints.length > 1
|
"has_steps": Get.find<WayPointController>().wayPoints.length > 1
|
||||||
? "true"
|
? "true"
|
||||||
: "false",
|
: "false",
|
||||||
@@ -4105,7 +4176,8 @@ class RideLifecycleController extends GetxController {
|
|||||||
|
|
||||||
if (response['status'] == 'success') {
|
if (response['status'] == 'success') {
|
||||||
Log.print("✅ Search reset successfully.");
|
Log.print("✅ Search reset successfully.");
|
||||||
if (!isPhase2) startSearchingTimer(); // Phase 1 يعيد العداد، Phase 2 يُدار من increasePriceAndRestartSearch
|
if (!isPhase2)
|
||||||
|
startSearchingTimer(); // Phase 1 يعيد العداد، Phase 2 يُدار من increasePriceAndRestartSearch
|
||||||
} else {
|
} else {
|
||||||
Log.print("❌ Failed to reset search: $response");
|
Log.print("❌ Failed to reset search: $response");
|
||||||
handleNoDriverFound();
|
handleNoDriverFound();
|
||||||
@@ -4139,7 +4211,8 @@ class RideLifecycleController extends GetxController {
|
|||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
if (seconds == 20 && !_aiSilentRetryFired) {
|
if (seconds == 20 && !_aiSilentRetryFired) {
|
||||||
_aiSilentRetryFired = true;
|
_aiSilentRetryFired = true;
|
||||||
Log.print("🤖 [AI Negotiator Phase 1] 20s — silent retry with driver bonus...");
|
Log.print(
|
||||||
|
"🤖 [AI Negotiator Phase 1] 20s — silent retry with driver bonus...");
|
||||||
retrySearchForDrivers();
|
retrySearchForDrivers();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -4151,7 +4224,8 @@ class RideLifecycleController extends GetxController {
|
|||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
if (seconds >= 45) {
|
if (seconds >= 45) {
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
Log.print("🤖 [AI Negotiator Phase 2] 45s — showing AI price suggestion dialog...");
|
Log.print(
|
||||||
|
"🤖 [AI Negotiator Phase 2] 45s — showing AI price suggestion dialog...");
|
||||||
_showAiNegotiatorDialog();
|
_showAiNegotiatorDialog();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import 'package:wakelock_plus/wakelock_plus.dart';
|
|||||||
import 'constant/info.dart';
|
import 'constant/info.dart';
|
||||||
import 'constant/box_name.dart';
|
import 'constant/box_name.dart';
|
||||||
import 'controller/home/ios_live_activity_service.dart';
|
import 'controller/home/ios_live_activity_service.dart';
|
||||||
|
import 'services/android_live_update_service.dart';
|
||||||
import 'controller/local/local_controller.dart';
|
import 'controller/local/local_controller.dart';
|
||||||
import 'controller/local/translations.dart';
|
import 'controller/local/translations.dart';
|
||||||
import 'controller/themes/themes.dart';
|
import 'controller/themes/themes.dart';
|
||||||
@@ -39,7 +40,7 @@ DbSql sql = DbSql.instance;
|
|||||||
Future<void> backgroundMessageHandler(RemoteMessage message) async {
|
Future<void> backgroundMessageHandler(RemoteMessage message) async {
|
||||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||||
Log.print("Handling a background message: ${message.messageId}");
|
Log.print("Handling a background message: ${message.messageId}");
|
||||||
|
|
||||||
if (message.data.isNotEmpty) {
|
if (message.data.isNotEmpty) {
|
||||||
try {
|
try {
|
||||||
await GetStorage.init();
|
await GetStorage.init();
|
||||||
@@ -51,10 +52,12 @@ Future<void> backgroundMessageHandler(RemoteMessage message) async {
|
|||||||
if (!serviceEnabled) return;
|
if (!serviceEnabled) return;
|
||||||
|
|
||||||
LocationPermission permission = await Geolocator.checkPermission();
|
LocationPermission permission = await Geolocator.checkPermission();
|
||||||
if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) return;
|
if (permission == LocationPermission.denied ||
|
||||||
|
permission == LocationPermission.deniedForever) return;
|
||||||
|
|
||||||
|
final position = await Geolocator.getCurrentPosition(
|
||||||
|
desiredAccuracy: LocationAccuracy.high);
|
||||||
|
|
||||||
final position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
|
|
||||||
|
|
||||||
final url = Uri.parse('${AppLink.server}/api/location/sync_location.php');
|
final url = Uri.parse('${AppLink.server}/api/location/sync_location.php');
|
||||||
await http.post(url, body: {
|
await http.post(url, body: {
|
||||||
'passenger_id': passengerId.toString(),
|
'passenger_id': passengerId.toString(),
|
||||||
@@ -62,9 +65,10 @@ Future<void> backgroundMessageHandler(RemoteMessage message) async {
|
|||||||
'lng': position.longitude.toString(),
|
'lng': position.longitude.toString(),
|
||||||
'source': 'silent_push',
|
'source': 'silent_push',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update geofences on device silently
|
// Update geofences on device silently
|
||||||
await SiroGeofencingService.syncZonesWithServer(position.latitude, position.longitude);
|
await SiroGeofencingService.syncZonesWithServer(
|
||||||
|
position.latitude, position.longitude);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.print("Silent push location update failed: $e");
|
Log.print("Silent push location update failed: $e");
|
||||||
}
|
}
|
||||||
@@ -89,6 +93,9 @@ void main() {
|
|||||||
// ✅ التعديل هنا: تهيئة خدمة الـ Live Activity للآيفون
|
// ✅ التعديل هنا: تهيئة خدمة الـ Live Activity للآيفون
|
||||||
IosLiveActivityService.init();
|
IosLiveActivityService.init();
|
||||||
|
|
||||||
|
// ✅ تهيئة خدمة Android Live Update (Progress Style)
|
||||||
|
await AndroidLiveUpdateService.init();
|
||||||
|
|
||||||
// Initialize Geofencing Service
|
// Initialize Geofencing Service
|
||||||
if (Platform.isAndroid || Platform.isIOS) {
|
if (Platform.isAndroid || Platform.isIOS) {
|
||||||
await SiroGeofencingService.initAndStart();
|
await SiroGeofencingService.initAndStart();
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import '../print.dart';
|
||||||
|
|
||||||
|
/// خدمة Android Live Update (Progress Style)
|
||||||
|
///
|
||||||
|
/// توفر واجهة موحدة للتحديث المباشر لحالة الرحلة على Android
|
||||||
|
/// باستخدام NotificationCompat مع Progress Style (Android 16+)
|
||||||
|
///
|
||||||
|
/// هذه الخدمة مشابهة لـ [IosLiveActivityService] ولكن لمنصة Android
|
||||||
|
class AndroidLiveUpdateService {
|
||||||
|
static const MethodChannel _channel =
|
||||||
|
MethodChannel('siro/android_live_update');
|
||||||
|
|
||||||
|
static bool _isSupported = false;
|
||||||
|
static bool _isActive = false;
|
||||||
|
|
||||||
|
/// التحقق من دعم الجهاز للخدمة
|
||||||
|
/// تعود true إذا كان Android 11+ (API 30+)
|
||||||
|
static Future<bool> init() async {
|
||||||
|
if (!Platform.isAndroid) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final supported = await _channel.invokeMethod<bool>('isSupported');
|
||||||
|
_isSupported = supported ?? false;
|
||||||
|
Log.print(
|
||||||
|
'📱 AndroidLiveUpdateService initialized: supported=$_isSupported');
|
||||||
|
return _isSupported;
|
||||||
|
} catch (e) {
|
||||||
|
Log.print('❌ AndroidLiveUpdateService init error: $e');
|
||||||
|
_isSupported = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// بدء التحديث المباشر للرحلة (عند قبول السائق)
|
||||||
|
static Future<void> startLiveUpdate({
|
||||||
|
required String rideId,
|
||||||
|
required String driverName,
|
||||||
|
required String etaText,
|
||||||
|
required double progress,
|
||||||
|
required String carDetails,
|
||||||
|
}) async {
|
||||||
|
if (!Platform.isAndroid || !_isSupported) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _channel.invokeMethod('startLiveUpdate', {
|
||||||
|
'rideId': rideId,
|
||||||
|
'driverName': driverName,
|
||||||
|
'etaText': etaText,
|
||||||
|
'progress': progress,
|
||||||
|
'carDetails': carDetails,
|
||||||
|
});
|
||||||
|
_isActive = true;
|
||||||
|
Log.print('✅ Android Live Update Started');
|
||||||
|
} catch (e) {
|
||||||
|
Log.print('❌ AndroidLiveUpdateService startLiveUpdate error: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// تحديث التقدم والوقت المتبقي
|
||||||
|
static Future<void> updateProgress({
|
||||||
|
required double progress,
|
||||||
|
required String etaText,
|
||||||
|
}) async {
|
||||||
|
if (!Platform.isAndroid || !_isSupported || !_isActive) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _channel.invokeMethod('updateProgress', {
|
||||||
|
'progress': progress,
|
||||||
|
'etaText': etaText,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
Log.print('❌ AndroidLiveUpdateService updateProgress error: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// تحديث حالة الرحلة بالكامل
|
||||||
|
static Future<void> updateStatus({
|
||||||
|
required String status,
|
||||||
|
required String driverName,
|
||||||
|
required String etaText,
|
||||||
|
required double progress,
|
||||||
|
String carDetails = '',
|
||||||
|
}) async {
|
||||||
|
if (!Platform.isAndroid || !_isSupported || !_isActive) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _channel.invokeMethod('updateStatus', {
|
||||||
|
'status': status,
|
||||||
|
'driverName': driverName,
|
||||||
|
'etaText': etaText,
|
||||||
|
'progress': progress,
|
||||||
|
'carDetails': carDetails,
|
||||||
|
});
|
||||||
|
Log.print('✅ Android Live Update Status: $status');
|
||||||
|
} catch (e) {
|
||||||
|
Log.print('❌ AndroidLiveUpdateService updateStatus error: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// إنهاء التحديث المباشر وإزالة الإشعار
|
||||||
|
static Future<void> endLiveUpdate() async {
|
||||||
|
if (!Platform.isAndroid || !_isSupported || !_isActive) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _channel.invokeMethod('endLiveUpdate');
|
||||||
|
_isActive = false;
|
||||||
|
Log.print('✅ Android Live Update Ended');
|
||||||
|
} catch (e) {
|
||||||
|
Log.print('❌ AndroidLiveUpdateService endLiveUpdate error: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// التحقق مما إذا كان التحديث نشطاً
|
||||||
|
static bool get isActive => _isActive;
|
||||||
|
|
||||||
|
/// إعادة تعيين الحالة
|
||||||
|
static void reset() {
|
||||||
|
_isActive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1297,10 +1297,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.19"
|
version: "0.12.18"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1313,10 +1313,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.0"
|
version: "1.17.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1885,10 +1885,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.11"
|
version: "0.7.9"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user