Food: single-source tracking, mode exclusivity, in-app navigation

This commit is contained in:
Hamza-Ayed
2026-08-04 02:41:54 +03:00
parent 788d3dcbe5
commit d695834d19
14 changed files with 607 additions and 83 deletions
@@ -100,7 +100,9 @@ class FoodDeliveryHomePage extends StatelessWidget {
Text(
c.isDeliveryModeEnabled
? (c.isSocketConnected
? 'متصل — ستصلك عروض التوصيل فوراً'
// صريح عمداً: الوضع استعداد لا حجز. أول مهمة تصل —
// رحلة كانت أو توصيلاً — تجعله مشغولاً عن الأخرى.
? 'متصل — ستصلك عروض التوصيل، وقد تصلك رحلة أيضاً'
: 'إعادة الاتصال… العروض تصل بالتحديث الدوري')
: 'فعّله لاستقبال عروض توصيل الطعام',
style: AppStyle.subtitle,
@@ -7,6 +7,7 @@ import 'package:url_launcher/url_launcher.dart';
import '../../constant/colors.dart';
import '../../constant/style.dart';
import '../../controller/food_delivery/food_delivery_controller.dart';
import '../../controller/food_delivery/food_navigation.dart';
import '../../controller/food_delivery/food_delivery_models.dart';
import '../../controller/food_delivery/food_delivery_service.dart';
import '../widgets/elevated_btn.dart';
@@ -41,14 +42,13 @@ class _FoodTaskDetailsPageState extends State<FoodTaskDetailsPage> {
if (!res.success) mySnackbarWarning(res.message);
}
Future<void> _openMaps(double lat, double lng) async {
// نفتح تطبيق الخرائط الافتراضي على الجهاز — لا نضمّن مفاتيح ولا نمرّر
// بيانات الزبون في الرابط سوى الإحداثيات اللازمة للملاحة.
final uri = Uri.parse('https://www.google.com/maps/dir/?api=1&destination=$lat,$lng');
if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
mySnackbarWarning('تعذّر فتح تطبيق الخرائط');
}
}
/// الملاحة الداخلية هي الافتراضي: تُبقي السائق داخل التطبيق فيظل موقعه
/// مرصوداً ويستمر بثّه للزبون. جوجل يبقى خياراً ثانوياً بضغطة مطوّلة.
Future<void> _navigate(double lat, double lng, String title) =>
FoodNavigation.startInApp(lat: lat, lng: lng, title: title);
Future<void> _navigateExternal(double lat, double lng) =>
FoodNavigation.startExternal(lat, lng);
Future<void> _call(String phone) async {
final uri = Uri(scheme: 'tel', path: phone);
@@ -89,7 +89,10 @@ class _FoodTaskDetailsPageState extends State<FoodTaskDetailsPage> {
title: t.merchantNameAr,
subtitle: t.merchantAddress ?? 'مطعم',
onNavigate: (t.merchantLat != null && t.merchantLng != null)
? () => _openMaps(t.merchantLat!, t.merchantLng!)
? () => _navigate(t.merchantLat!, t.merchantLng!, t.merchantNameAr)
: null,
onNavigateExternal: (t.merchantLat != null && t.merchantLng != null)
? () => _navigateExternal(t.merchantLat!, t.merchantLng!)
: null,
onCall: d.merchantPhone == null ? null : () => _call(d.merchantPhone!),
),
@@ -99,7 +102,12 @@ class _FoodTaskDetailsPageState extends State<FoodTaskDetailsPage> {
color: AppColor.greenColor,
title: 'عنوان الزبون',
subtitle: t.deliveryAddress ?? 'غير متاح',
onNavigate: t.deliveryLat == 0 ? null : () => _openMaps(t.deliveryLat, t.deliveryLng),
onNavigate: t.deliveryLat == 0
? null
: () => _navigate(t.deliveryLat, t.deliveryLng, 'عنوان الزبون'),
onNavigateExternal: t.deliveryLat == 0
? null
: () => _navigateExternal(t.deliveryLat, t.deliveryLng),
// مكالمة مقنّعة: لا رقم هاتف للزبون يظهر أو يُخزَّن — اتصال صوتي
// عبر جلسة مؤقتة، ومتاح فقط ما دامت المهمة نشطة.
onCall: () => Get.find<FoodDeliveryController>().callCustomer(t.id),
@@ -196,6 +204,7 @@ class _FoodTaskDetailsPageState extends State<FoodTaskDetailsPage> {
required String title,
required String subtitle,
VoidCallback? onNavigate,
VoidCallback? onNavigateExternal,
VoidCallback? onCall,
IconData callIcon = Icons.call_rounded,
String callTooltip = 'اتصال',
@@ -232,8 +241,10 @@ class _FoodTaskDetailsPageState extends State<FoodTaskDetailsPage> {
if (onNavigate != null)
IconButton(
onPressed: onNavigate,
// ضغطة مطوّلة = ملاحة جوجل، لمن اعتاد عليها
onLongPress: onNavigateExternal,
icon: Icon(Icons.navigation_rounded, color: AppColor.accentColor),
tooltip: 'الملاحة',
tooltip: 'ملاحة سيرو (اضغط مطوّلاً لجوجل)',
),
],
),