Food: background location + native delivery overlay

This commit is contained in:
Hamza-Ayed
2026-08-04 03:02:04 +03:00
parent d695834d19
commit ca55aaebe8
11 changed files with 772 additions and 31 deletions
@@ -62,6 +62,45 @@ class TripData {
String toJson() => jsonEncode(toMap());
}
/// بيانات طلب توصيل الطعام المعروضة في النافذة العائمة.
/// المبالغ تصل **منسّقة نصّاً** عمداً: التنسيق والعملة يخصّان التطبيق لا
/// الطبقة الأصلية، فلا نكرّر منطق قسمة الوحدة الصغرى داخل Kotlin.
class FoodOrderOverlayData {
final String orderId;
final String merchantName;
final String merchantAddress;
final String deliveryFeeText;
final String cashToCollectText;
final String distanceText;
final int itemsCount;
final bool isCash;
FoodOrderOverlayData({
required this.orderId,
required this.merchantName,
required this.deliveryFeeText,
this.merchantAddress = '',
this.cashToCollectText = '',
this.distanceText = '',
this.itemsCount = 0,
this.isCash = false,
});
Map<String, dynamic> toMap() => {
'orderId': orderId,
'merchantName': merchantName,
'merchantAddress': merchantAddress,
'deliveryFeeText': deliveryFeeText,
'cashToCollectText': cashToCollectText,
'distanceText': distanceText,
'itemsCount': itemsCount,
'isCash': isCash,
};
String toJson() => jsonEncode(toMap());
}
/// Result returned when the driver accepts a trip
class TripAcceptedResult {
final String tripId;
@@ -82,6 +121,18 @@ class TripOverlayPlugin {
static final StreamController<String> _tripRejectedController =
StreamController<String>.broadcast();
// قناة طلبات التوصيل منفصلة عن قناة الرحلات — لا يخلط المستمع بين النوعين
static final StreamController<String> _foodAcceptedController =
StreamController<String>.broadcast();
static final StreamController<String> _foodRejectedController =
StreamController<String>.broadcast();
/// معرّف طلب التوصيل الذي قبله السائق من النافذة العائمة
static Stream<String> get onFoodOrderAccepted => _foodAcceptedController.stream;
/// معرّف طلب التوصيل الذي رفضه السائق أو انتهت مهلته
static Stream<String> get onFoodOrderRejected => _foodRejectedController.stream;
static bool _isInitialized = false;
/// Stream that fires when the driver taps "Accept" in the overlay
@@ -113,6 +164,14 @@ class TripOverlayPlugin {
_tripRejectedController.add(tripId);
break;
case 'onFoodOrderAccepted':
_foodAcceptedController.add(call.arguments['orderId'].toString());
break;
case 'onFoodOrderRejected':
_foodRejectedController.add(call.arguments['orderId'].toString());
break;
default:
throw PlatformException(
code: 'UNKNOWN_METHOD',
@@ -187,6 +246,34 @@ class TripOverlayPlugin {
}
}
/// نافذة عرض توصيل طعام. تعيد false ولا ترمي أبداً — المُنادي يكمل مساره
/// (إشعار محلي، حفظ العرض) حتى لو تعذّرت النافذة أو نقصت الصلاحية.
static Future<bool> showFoodOverlay(
FoodOrderOverlayData data, {
int autoCloseSeconds = 20,
}) async {
if (!isSupported) return false;
final granted = await isPermissionGranted();
if (!granted) {
await requestPermission();
return false;
}
try {
final result = await _channel.invokeMethod<bool>('showFoodOverlay', {
'foodData': data.toJson(),
'autoCloseSeconds': autoCloseSeconds,
});
return result ?? false;
} on PlatformException catch (e) {
debugPrint('[TripOverlay] showFoodOverlay failed: ${e.message}');
return false;
} on MissingPluginException {
return false;
}
}
/// Programmatically close the overlay (e.g. if trip was cancelled)
static Future<void> hideOverlay() async {
if (!isSupported) return;