diff --git a/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/1024 1.png b/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/1024 1.png deleted file mode 100644 index cd9a8c1..0000000 Binary files a/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/1024 1.png and /dev/null differ diff --git a/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/1024 2.png b/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/1024 2.png deleted file mode 100644 index cd9a8c1..0000000 Binary files a/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/1024 2.png and /dev/null differ diff --git a/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/1024.png b/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/1024.png deleted file mode 100644 index cd9a8c1..0000000 Binary files a/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/1024.png and /dev/null differ diff --git a/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/152 1.png b/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/152 1.png new file mode 100644 index 0000000..9393ec5 Binary files /dev/null and b/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/152 1.png differ diff --git a/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/152 2.png b/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/152 2.png new file mode 100644 index 0000000..9393ec5 Binary files /dev/null and b/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/152 2.png differ diff --git a/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/152.png b/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/152.png new file mode 100644 index 0000000..9393ec5 Binary files /dev/null and b/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/152.png differ diff --git a/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/Contents.json b/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/Contents.json index c5cb369..a626b3d 100644 --- a/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/Contents.json +++ b/ios/RideWidget/Assets.xcassets/IntaleqIcon.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "1024.png", + "filename" : "152.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "1024 1.png", + "filename" : "152 1.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "1024 2.png", + "filename" : "152 2.png", "idiom" : "universal", "scale" : "3x" } diff --git a/lib/constant/links.dart b/lib/constant/links.dart index eb56b37..7d25ee7 100644 --- a/lib/constant/links.dart +++ b/lib/constant/links.dart @@ -11,7 +11,7 @@ class AppLink { /// هذا الرابط خاص برحلات الركاب، ويستخدمه السيرفر الجانبي للرحلات (Ride Server Side) للتعامل مع عمليات إلغاء الرحلات وتحديث حالة الرحلات وغيرها من العمليات المتعلقة بالرحلات. /// https://routesy.intaleq.xyz for syria /// for jordan https://routesjo.intaleq.xyz - static String routesOsm = 'https://routesjo.intaleq.xyz'; + static String routesOsm = 'https://routesy.intaleq.xyz'; ///https://location.intaleq.xyz/intaleq/ride/location ///locationServerSide هو السيرفر الجانبي الخاص بموقع السائقين، حيث يتم إرسال تحديثات الموقع من التطبيق إلى هذا السيرفر، ومن ثم يقوم هذا السيرفر بتوزيع هذه التحديثات إلى الركاب المتصلين الذين يتابعون السائق في الوقت الحقيقي. diff --git a/lib/controller/home/map_passenger_controller.dart b/lib/controller/home/map_passenger_controller.dart index f626f0d..51f8f55 100644 --- a/lib/controller/home/map_passenger_controller.dart +++ b/lib/controller/home/map_passenger_controller.dart @@ -765,6 +765,7 @@ class MapPassengerController extends GetxController { IosLiveActivityService.endRideActivity(); // ✅ أضف هذا السطر _isCancelProcessed = false; currentRideState.value = RideState.noRide; + resetAllMapStates(); Get.offAll(() => const MapPagePassenger()); Get.defaultDialog( @@ -2116,20 +2117,18 @@ class MapPassengerController extends GetxController { update(); } else { var res = placesDestination[index]; - - // استخراج الاسم من displayName.text أو بديله hintTextDestinationPoint = res['displayName']?['text'] ?? res['formattedAddress'] ?? 'Unknown Place'; - - // استخراج الإحداثيات double? lat = res['location']?['latitude']; double? lng = res['location']?['longitude']; if (lat != null && lng != null) { newMyLocation = LatLng(lat, lng); + // 🔥 الحل: تحريك الكاميرا فوراً للهدف حتى لا يتم مسحه عند إغلاق الكيبورد 🔥 + mapController + ?.animateCamera(CameraUpdate.newLatLngZoom(newMyLocation, 16)); } - update(); } } @@ -2141,6 +2140,8 @@ class MapPassengerController extends GetxController { double lng = recentLocations[index]['longitude']; newMyLocation = LatLng(lat, lng); + // 🔥 تحريك الكاميرا فوراً 🔥 + mapController?.animateCamera(CameraUpdate.newLatLngZoom(newMyLocation, 16)); update(); } @@ -2517,7 +2518,7 @@ class MapPassengerController extends GetxController { // 1. تحديث الآيفون (Live Activity): يمكن تحديثه كل 5 ثواني لأنه "تحديث صامت" للشاشة فقط ولا يصدر صوتاً if (remainingSeconds % 5 == 0 || remainingSeconds == 0) { IosLiveActivityService.updateRideActivity( - status: 'inProgress', // الحالة تتغير هنا إلى جارية + status: 'ongoing', // ['waiting', 'ongoing'] driverName: driverName ?? '', carDetails: '$make • $model • $carColor', etaText: stringRemainingTimeRideBegin, @@ -2734,7 +2735,7 @@ class MapPassengerController extends GetxController { // إيقاف التايمرات stopAllTimers(); - + resetAllMapStates(); clearPolyline(); clearMarkersExceptStartEnd(); markers.clear(); @@ -4862,12 +4863,12 @@ Intaleq Team'''; } // 3. التنظيف المحلي الفوري (UX Optimization) - // نقوم بتنظيف الواجهة فوراً ليشعر المستخدم بالاستجابة السريعة Get.back(); // إغلاق الـ BottomSheet - changeCancelRidePageShow(); // إخفاء زر الإلغاء إن وجد - clearPlacesDestination(); - clearPolyline(); - data = []; + if (isCancelRidePageShown) + changeCancelRidePageShow(); // إخفاء زر الإلغاء إن وجد + + // 🔥 استدعاء دالة التنظيف الشاملة هنا 🔥 + resetAllMapStates(); // إيقاف جميع التايمرات // إيقاف جميع التايمرات @@ -5551,7 +5552,7 @@ Intaleq Team'''; // تحديث الكاميرا بثروتل void onCameraMoveThrottled(CameraPosition pos) { - if (_camThrottle?.isActive ?? false) return; + _camThrottle?.cancel(); _camThrottle = Timer(const Duration(milliseconds: 160), () { // ضع فقط المنطق الضروري هنا لتقليل الحمل int waypointsLength = Get.find().wayPoints.length; @@ -6405,7 +6406,6 @@ Intaleq Team'''; // 🛑 الفحص الأمني (Sanity Check) - Updated for new format // ============================================================ - // البيانات الآن داخل routes[0] if (responseData['routes'] == null || responseData['routes'].isEmpty) { if (attemptCount < 2) { await _retryProcess(origin, destination, waypoints, attemptCount); @@ -6426,7 +6426,6 @@ Intaleq Team'''; double aerialDistance = Geolocator.distanceBetween(startLat, startLng, latDest, lngDest); - // الشرط: مسافة السيرفر صفرية أو صغيرة جداً بينما الحقيقية كبيرة if (apiDistanceMeters < 50.0 && aerialDistance > 200.0) { Log.print( "⚠️ Suspicious Route detected! Server: $apiDistanceMeters m | Aerial: $aerialDistance m"); @@ -6452,18 +6451,15 @@ Intaleq Team'''; box.remove(BoxName.tripData); box.write(BoxName.tripData, routeData); - // duration and distance من routes[0] durationToRide = ((routeData['duration'] as num) * kDurationScalar).toInt(); double distanceOfTrip = (routeData['distance'] as num) / 1000.0; distance = distanceOfTrip; - // steps الآن داخل legs[0].steps data = routeData['legs'] != null && routeData['legs'].isNotEmpty ? (routeData['legs'][0]['steps'] ?? []) : []; - // معالجة الرسم (Polyline) - الحقل الآن اسمه geometry بدلاً من polyline String pointsString = routeData['geometry'] ?? ""; List decodedPoints = []; @@ -6471,7 +6467,6 @@ Intaleq Team'''; decodedPoints = await compute(decodePolylineIsolate, pointsString); } - // حماية إضافية: لو البولي لاين فارغ رغم أن المسافة سليمة if (decodedPoints.isEmpty) { _handleFatalError("Map Error".tr, "Received empty route data.".tr); return; @@ -6507,7 +6502,6 @@ Intaleq Team'''; maxLng == null ? point.longitude : max(maxLng, point.longitude); } - // إغلاق شاشة التحميل بنجاح if (Get.isBottomSheetOpen ?? false) Get.back(); isDrawingRoute = false; isLoading = false; @@ -6543,33 +6537,23 @@ Intaleq Team'''; '$distance ${'KM'.tr} ⌛ ${hours > 0 ? '$hours H $minutes m' : '$minutes m'}'), )); - // 7. رسم الخط (فقط في حال النجاح) + // 7. رسم الخط (النظام الجديد لجميع الأجهزة) if (polyLines.isNotEmpty) clearPolyline(); - bool isLowEndDevice = box.read(BoxName.lowEndMode) ?? true; - - if (isLowEndDevice) { - polyLines.add(Polyline( - polylineId: const PolylineId('route_solid'), - points: polylineCoordinates, - width: 6, - color: AppColor.primaryColor, - endCap: Cap.roundCap, - startCap: Cap.roundCap, - jointType: JointType.round, - )); - } else { - polyLines.addAll(_createGradientPolylines(polylineCoordinates, - const Color(0xFF00E5FF), const Color(0xFFFF4081))); - } rideConfirm = false; isMarkersShown = true; - update(); + update(); // تحديث أولي لإظهار الخريطة والماركرز // إظهار الباتم شيت للسعر bottomSheet(); - } catch (e) { - // محاولة أخيرة عند حدوث Exception + + // تشغيل الأنيميشن الخفيف لومضات المسار + _playRouteAnimation(polylineCoordinates); + } catch (e, stackTrace) { + // 🚨 هذا السطر سيفضح المشكلة الحقيقية! 🚨 + print('🚨 CRITICAL ERROR IN getDirectionMap: $e'); + print('🚨 STACKTRACE: $stackTrace'); + if (attemptCount < 2) { await _retryProcess(origin, destination, waypoints, attemptCount); } else { @@ -6579,6 +6563,64 @@ Intaleq Team'''; } } + // --- دالة الأنيميشن الجديدة --- + Future _playRouteAnimation(List coords) async { + const String routeId = 'route_solid'; + + // الألوان المطلوبة (بإمكانك تغيير AppColor.primaryColor إلى ما يناسبك) + Color finalColor = + AppColor.primaryColor; // اللون النهائي الثابت (مثل الأزرق) + Color lightColor = Colors.grey.shade400; // لون التحديث الفاتح (رمادي) + Color darkColor = Colors.grey.shade700; // لون التحديث الغامق (رمادي غامق) + + // تكرار العملية 4 مرات لإعطاء تأثير التحميل والتحديث + for (int i = 0; i < 4; i++) { + // الحالة 1: لون فاتح وعرض أقل + polyLines.removeWhere((p) => p.polylineId.value == routeId); + polyLines.add(Polyline( + polylineId: const PolylineId(routeId), + points: coords, + width: 5, + color: lightColor, + endCap: Cap.roundCap, + startCap: Cap.roundCap, + jointType: JointType.round, + zIndex: 1, + )); + update(); + await Future.delayed(const Duration(milliseconds: 250)); + + // الحالة 2: لون غامق وعرض أكبر + polyLines.removeWhere((p) => p.polylineId.value == routeId); + polyLines.add(Polyline( + polylineId: const PolylineId(routeId), + points: coords, + width: 6, + color: darkColor, + endCap: Cap.roundCap, + startCap: Cap.roundCap, + jointType: JointType.round, + zIndex: 2, + )); + update(); + await Future.delayed(const Duration(milliseconds: 250)); + } + + // بعد الانتهاء من الأنيميشن، يتم تثبيت المسار على اللون الأساسي للتطبيق + polyLines.removeWhere((p) => p.polylineId.value == routeId); + polyLines.add(Polyline( + polylineId: const PolylineId(routeId), + points: coords, + width: 6, + color: finalColor, + endCap: Cap.roundCap, + startCap: Cap.roundCap, + jointType: JointType.round, + zIndex: 3, + )); + update(); + } + // --- دالة المساعدة لإعادة المحاولة --- Future _retryProcess(String origin, String dest, List waypoints, int currentAttempt) async { @@ -6588,6 +6630,38 @@ Intaleq Team'''; getDirectionMap(origin, dest, waypoints, currentAttempt + 1); } +// --- دالة جديدة لتنظيف الخريطة بالكامل ومنع تداخل الرحلات --- + void resetAllMapStates() { + Log.print('🧹 Resetting all map states to prevent sticky location bug'); + + clearPlacesDestination(); + clearPlacesStart(); + clearPolyline(); + data = []; + + passengerStartLocationFromMap = false; + startLocationFromMap = false; + isPickerShown = false; + workLocationFromMap = false; + homeLocationFromMap = false; + isAnotherOreder = false; + isWhatsAppOrder = false; + + // ✅ أضف هذا: reset الوجهة لموقع الراكب حتى لا تبقى قيمة الرحلة القديمة + myDestination = passengerLocation; + hintTextDestinationPoint = 'Select your destination'.tr; + + placeDestinationController.clear(); + placeStartController.clear(); + + rideConfirm = false; + shouldFetch = false; + isDrawingRoute = false; + isLoading = false; + + update(); + } + // ----------------------------------------------------------------------------------------- // 🛑 دالة الخطأ القاتل (تغلق كل شيء وتعيد المستخدم للخريطة) // ----------------------------------------------------------------------------------------- diff --git a/lib/controller/local/translations.dart b/lib/controller/local/translations.dart index a3603b4..897a114 100644 --- a/lib/controller/local/translations.dart +++ b/lib/controller/local/translations.dart @@ -1424,6 +1424,19 @@ class MyTranslation extends Translations { "We Are Sorry That we dont have cars in your Location!": "نأسف لعدم توفر سيارات في موقعك!", "Choose from Map": "اختر من الخريطة", + 'Destination Set': "تم تعيين الوجهة", + 'Now move the map to your pickup point': + "الآن حرك الخريطة إلى نقطة الانطلاق الخاصة بك", + 'Move map to your pickup point': + "حرك الخريطة إلى نقطة الانطلاق الخاصة بك", + 'Move map to set start location': "حرك الخريطة لتعيين موقع الانطلاق", + 'Move map to your work location': "حرك الخريطة إلى موقع عملك", + 'Move map to your home location': "حرك الخريطة إلى موقع منزلك", + 'Move map to select destination': "حرك الخريطة لاختيار الوجهة", + 'Confirm Pickup Location': "تأكيد موقع الانطلاق", + 'Open': "فتح", + 'Set Destination': "تعيين الوجهة", + 'Tap to search your destination': "اضغط للبحث عن وجهتك", "Pick your ride location on the map - Tap to confirm": "اختر موقع رحلتك على الخريطة - اضغط للتأكيد", "Intaleq is the ride-hailing app that is safe, reliable, and accessible.": diff --git a/lib/print.dart b/lib/print.dart index a3d59f6..63efb2d 100644 --- a/lib/print.dart +++ b/lib/print.dart @@ -4,7 +4,7 @@ class Log { Log._(); static void print(String value, {StackTrace? stackTrace}) { - developer.log(value, name: 'LOG', stackTrace: stackTrace); + // developer.log(value, name: 'LOG', stackTrace: stackTrace); } static Object? inspect(Object? object) { diff --git a/lib/views/home/map_widget.dart/left_main_menu_icons.dart b/lib/views/home/map_widget.dart/left_main_menu_icons.dart index 3eb279d..40cad83 100644 --- a/lib/views/home/map_widget.dart/left_main_menu_icons.dart +++ b/lib/views/home/map_widget.dart/left_main_menu_icons.dart @@ -134,7 +134,7 @@ class TestPage extends StatelessWidget { // زر البدء ElevatedButton( onPressed: () async { - debugPrint("🍎 محاولة تشغيل Live Activity (Start)..."); + print("🍎 محاولة تشغيل Live Activity (Start)..."); try { await IosLiveActivityService.startRideActivity( rideId: "123", @@ -143,10 +143,10 @@ class TestPage extends StatelessWidget { etaText: "5 دقائق", progress: 0.2, ); - debugPrint( + Log.print( "✅ تم تشغيل Live Activity بنجاح! أغلق الشاشة لترى النتيجة."); } catch (e) { - debugPrint("❌ خطأ في Start Live Activity: $e"); + Log.print("❌ خطأ في Start Live Activity: $e"); } }, child: const Text('Start Activity'), @@ -157,7 +157,7 @@ class TestPage extends StatelessWidget { // زر التحديث العشوائي ElevatedButton( onPressed: () async { - debugPrint("🔄 محاولة تحديث Live Activity (Update)..."); + Log.print("🔄 محاولة تحديث Live Activity (Update)..."); // توليد بيانات عشوائية للاختبار final statuses = ['waiting', 'ongoing']; @@ -178,10 +178,10 @@ class TestPage extends StatelessWidget { etaText: eta, progress: progress, ); - debugPrint( + Log.print( "✅ تم تحديث Live Activity: status=$status, eta=$eta, progress=$progress"); } catch (e) { - debugPrint("❌ خطأ في Update Live Activity: $e"); + Log.print("❌ خطأ في Update Live Activity: $e"); } }, child: const Text('Update (Random)'), @@ -195,12 +195,12 @@ class TestPage extends StatelessWidget { backgroundColor: Colors.red, ), onPressed: () async { - debugPrint("🛑 محاولة إنهاء Live Activity (End)..."); + Log.print("🛑 محاولة إنهاء Live Activity (End)..."); try { await IosLiveActivityService.endRideActivity(); - debugPrint("✅ تم إنهاء Live Activity."); + Log.print("✅ تم إنهاء Live Activity."); } catch (e) { - debugPrint("❌ خطأ في End Live Activity: $e"); + Log.print("❌ خطأ في End Live Activity: $e"); } }, child: const Text('End Activity'), diff --git a/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart b/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart index e4f1e42..54a149d 100644 --- a/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart +++ b/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart @@ -1,39 +1,26 @@ import 'package:Intaleq/views/widgets/my_textField.dart'; - import 'package:flutter/cupertino.dart'; - import 'package:flutter/material.dart'; - import 'package:get/get.dart'; - import 'package:Intaleq/constant/box_name.dart'; - import 'package:Intaleq/constant/style.dart'; - import 'package:Intaleq/controller/home/map_passenger_controller.dart'; - import 'package:Intaleq/main.dart'; - import 'package:Intaleq/views/home/map_widget.dart/form_search_places_destenation.dart'; - import 'package:Intaleq/views/widgets/elevated_btn.dart'; - import 'package:google_maps_flutter/google_maps_flutter.dart'; - import '../../../constant/colors.dart'; - import '../../../constant/table_names.dart'; - import '../../../controller/functions/toast.dart'; - import '../../../controller/functions/tts.dart'; - import '../../widgets/error_snakbar.dart'; - import '../../widgets/mydialoug.dart'; - import 'form_search_start.dart'; +// ───────────────────────────────────────────────────────────────────────────── +// MAIN BOTTOM MENU MAP +// ───────────────────────────────────────────────────────────────────────────── + class MainBottomMenuMap extends StatelessWidget { const MainBottomMenuMap({super.key}); @@ -42,468 +29,700 @@ class MainBottomMenuMap extends StatelessWidget { Get.put(MapPassengerController()); return GetBuilder( - builder: (controller) => Positioned( - bottom: Get.height * .04, // Increased bottom padding - - left: 16, - - right: 16, - - child: GestureDetector( - onTap: controller - .changeMainBottomMenuMap, // Make the whole area tappable + builder: (controller) { + // ─── حالة: يتم تحديد موقع على الخريطة (وضع الـ Picker) ─────────────── + if (controller.isPickerShown) { + return _MapPickerOverlay(controller: controller); + } + // ─── الحالة العادية: القائمة السفلية ──────────────────────────────── + return Positioned( + bottom: Get.height * .03, + left: 12, + right: 12, child: AnimatedContainer( - duration: const Duration( - milliseconds: 300), // Reduced duration for smoother animation - - curve: Curves.easeInOut, // Added animation curve - + duration: const Duration(milliseconds: 350), + curve: Curves.easeOutCubic, height: controller.mainBottomMenuMapHeight, - decoration: BoxDecoration( - color: AppColor.secondaryColor, // Use a solid background color - - borderRadius: BorderRadius.circular(16), // More rounded corners - + color: AppColor.secondaryColor, + borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( - color: Colors.black12, - blurRadius: 10, - offset: Offset(0, 5), + color: Colors.black.withOpacity(0.15), + blurRadius: 24, + offset: const Offset(0, 8), ), ], ), - - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), // Add bouncing effect - - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - - crossAxisAlignment: CrossAxisAlignment - .stretch, // Stretch children to full width - - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - controller.isMainBottomMenuMap - ? 'Where are you going?'.tr - : 'Quick Actions'.tr, - style: AppStyle.title - .copyWith(fontWeight: FontWeight.bold), - ), - IconButton( - onPressed: controller.changeMainBottomMenuMap, - icon: Icon( - controller.isMainBottomMenuMap - ? Icons.keyboard_arrow_down_rounded - : Icons.keyboard_arrow_up_rounded, - size: 28, - color: AppColor.primaryColor, - ), - ), - ], - ), - ), - if (controller.isMainBottomMenuMap) ...[ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: InkWell( - onTap: () => controller.changeMainBottomMenuMap(), - child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: AppColor.primaryColor - .withOpacity(0.05), // Subtle background - - borderRadius: BorderRadius.circular(12), - ), - child: DefaultTextStyle( - style: AppStyle.subtitle - .copyWith(color: AppColor.writeColor), - child: Center( - child: controller.isPickerShown - ? clickPointPosition(controller, context) - : whereWidgetSmall(controller), - ), - ), - ), - ), - ), - const SizedBox(height: 8), - if (controller.recentPlaces.isNotEmpty) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ -// Text('Recent Places'.tr, style: AppStyle.subtitle), - - SizedBox( - height: 30, - child: Center( - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: controller.recentPlaces.length, - separatorBuilder: (context, index) => - const SizedBox(width: 8), - itemBuilder: (context, index) => - _buildRecentPlaceButton( - controller, context, index), - ), - ), - ), - ], - ), - ), - ] else ...[ - if (!controller.isAnotherOreder) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Text( - '${'From:'.tr} ${controller.currentLocationString}' - .tr, - style: AppStyle.subtitle, - ), - ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: !controller.isAnotherOreder - ? const SizedBox() - : formSearchPlacesStart(), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: formSearchPlacesDestenation(), - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: GestureDetector( - onTap: () { - Get.dialog( - AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16)), - title: Text('WhatsApp Location Extractor'.tr), - content: Form( - key: controller.sosFormKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - MyTextForm( - controller: - controller.whatsAppLocationText, - label: 'Location Link'.tr, - hint: 'Paste location link here'.tr, - type: TextInputType.url, - ), - const SizedBox(height: 16), - MyElevatedButton( - title: 'Go to this location'.tr, - onPressed: () async { - controller.goToWhatappLocation(); - }, - ), - ], - ), - ), - ), - ); - }, - child: Container( - decoration: BoxDecoration( - color: Colors.blue.shade100, // Lighter background - - borderRadius: BorderRadius.circular(12), - - border: Border.all( - color: Colors.blue.shade400), // Add a border - ), - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Icon(Icons.link, color: Colors.blue.shade700), - const SizedBox(width: 8), - Expanded( - child: Text( - 'Paste WhatsApp location link'.tr, - style: TextStyle(color: Colors.blue.shade700), - ), - ), - const Icon(Icons.arrow_forward_ios_rounded, - size: 16, color: Colors.blueGrey), - ], - ), - ), - ), - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: OutlinedButton( - onPressed: () { - showCupertinoModalPopup( - context: context, - builder: (BuildContext context) => - CupertinoActionSheet( - title: Text('Select Order Type'.tr), - message: Text('Choose who this order is for'.tr), - actions: [ - CupertinoActionSheetAction( - child: Text('I want to order for myself'.tr), - onPressed: () { - controller.changeisAnotherOreder(false); - - Navigator.pop(context); - }, - ), - CupertinoActionSheetAction( - child: Text( - 'I want to order for someone else'.tr), - onPressed: () { - controller.changeisAnotherOreder(true); - - Navigator.pop(context); - }, - ), - ], - cancelButton: CupertinoActionSheetAction( - isDefaultAction: true, - onPressed: () { - Navigator.pop(context); - }, - child: Text('Cancel'.tr), - ), - ), - ); - }, - child: Text( - !controller.isAnotherOreder - ? 'Order for someone else'.tr - : 'Order for myself'.tr, - ), - ), - ), - ], - const SizedBox(height: 8), - ], + child: ClipRRect( + borderRadius: BorderRadius.circular(20), + child: SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + child: controller.isMainBottomMenuMap + ? _CollapsedView(controller: controller) + : _ExpandedView(controller: controller, context: context), ), ), ), + ); + }, + ); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// COLLAPSED VIEW (isMainBottomMenuMap = true) +// ───────────────────────────────────────────────────────────────────────────── + +class _CollapsedView extends StatelessWidget { + final MapPassengerController controller; + const _CollapsedView({required this.controller}); + + @override + Widget build(BuildContext context) { + final String firstName = box.read(BoxName.name).toString().split(' ').first; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + // ── شريط العنوان ────────────────────────────────────────────────── + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: controller.changeMainBottomMenuMap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14), + child: Row( + children: [ + // أيقونة الموقع بدائرة ملونة + Container( + width: 38, + height: 38, + decoration: BoxDecoration( + color: AppColor.primaryColor.withOpacity(0.12), + shape: BoxShape.circle, + ), + child: Icon(Icons.search_rounded, + color: AppColor.primaryColor, size: 20), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${'Where to'.tr} $firstName؟', + style: AppStyle.title.copyWith( + fontWeight: FontWeight.w700, + fontSize: 15, + ), + ), + if (!controller.noCarString) + Text( + 'Tap to search your destination'.tr, + style: AppStyle.subtitle.copyWith( + fontSize: 11, + color: Colors.grey.shade500, + ), + ), + ], + ), + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: AppColor.primaryColor, + borderRadius: BorderRadius.circular(20), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.keyboard_arrow_up_rounded, + color: Colors.white, size: 18), + Text( + 'Open'.tr, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w600), + ), + ], + ), + ), + ], + ), + ), ), + + // ── الأماكن الأخيرة ─────────────────────────────────────────────── + if (controller.recentPlaces.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 10, left: 8, right: 8), + child: SizedBox( + height: 32, + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 4), + itemCount: controller.recentPlaces.length, + separatorBuilder: (_, __) => const SizedBox(width: 6), + itemBuilder: (context, index) => + _RecentPlaceChip(controller: controller, index: index), + ), + ), + ), + ], + ); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// EXPANDED VIEW (isMainBottomMenuMap = false) +// ───────────────────────────────────────────────────────────────────────────── + +class _ExpandedView extends StatelessWidget { + final MapPassengerController controller; + final BuildContext context; + const _ExpandedView({required this.controller, required this.context}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // ── هيدر "Quick Actions" ───────────────────────────────────────── + Padding( + padding: const EdgeInsets.fromLTRB(18, 14, 12, 8), + child: Row( + children: [ + Text( + 'Quick Actions'.tr, + style: AppStyle.title.copyWith( + fontWeight: FontWeight.w700, + fontSize: 16, + ), + ), + const Spacer(), + // زر إغلاق + GestureDetector( + onTap: controller.changeMainBottomMenuMap, + child: Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: Colors.grey.shade200, + shape: BoxShape.circle, + ), + child: Icon(Icons.keyboard_arrow_down_rounded, + size: 22, color: Colors.grey.shade700), + ), + ), + ], + ), + ), + + const Divider(height: 1, thickness: 0.5), + const SizedBox(height: 10), + + // ── موقع البداية ───────────────────────────────────────────────── + if (!controller.isAnotherOreder) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _LocationRow( + icon: Icons.my_location_rounded, + iconColor: AppColor.primaryColor, + label: controller.currentLocationString, + isStart: true, + ), + ) + else + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: formSearchPlacesStart(), + ), + + const SizedBox(height: 6), + + // ── حقل الوجهة ──────────────────────────────────────────────────── + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: formSearchPlacesDestenation(), + ), + + const SizedBox(height: 12), + + // ── زر WhatsApp ─────────────────────────────────────────────────── + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _WhatsAppLinkButton(controller: controller), + ), + + const SizedBox(height: 10), + + // ── Order for someone else ──────────────────────────────────────── + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _OrderTypeButton(controller: controller), + ), + + const SizedBox(height: 14), + ], + ); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// MAP PICKER OVERLAY (isPickerShown = true) +// الواجهة التي تظهر عندما يختار المستخدم موقعاً على الخريطة +// ───────────────────────────────────────────────────────────────────────────── + +class _MapPickerOverlay extends StatelessWidget { + final MapPassengerController controller; + const _MapPickerOverlay({required this.controller}); + + // ── الحصول على نص الحالة الحالية ───────────────────────────────────────── + String _getModeTitle(BuildContext context) { + if (controller.passengerStartLocationFromMap) { + return 'Move map to your pickup point'.tr; + } else if (controller.startLocationFromMap) { + return 'Move map to set start location'.tr; + } else if (controller.workLocationFromMap) { + return 'Move map to your work location'.tr; + } else if (controller.homeLocationFromMap) { + return 'Move map to your home location'.tr; + } + return 'Move map to select destination'.tr; + } + + String _getConfirmLabel(BuildContext context) { + if (controller.passengerStartLocationFromMap) { + return 'Confirm Pickup Location'.tr; + } else if (controller.workLocationFromMap) { + return 'Set as Work'.tr; + } else if (controller.homeLocationFromMap) { + return 'Set as Home'.tr; + } + return 'Set Destination'.tr; + } + + IconData _getModeIcon() { + if (controller.passengerStartLocationFromMap) + return Icons.person_pin_circle_rounded; + if (controller.workLocationFromMap) return Icons.work_rounded; + if (controller.homeLocationFromMap) return Icons.home_rounded; + return Icons.location_on_rounded; + } + + Color _getModeColor() { + if (controller.passengerStartLocationFromMap) return Colors.green.shade600; + if (controller.workLocationFromMap) return Colors.blue.shade600; + if (controller.homeLocationFromMap) return Colors.orange.shade600; + return AppColor.primaryColor; + } + + @override + Widget build(BuildContext context) { + final modeColor = _getModeColor(); + + return Positioned( + bottom: Get.height * .03, + left: 12, + right: 12, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // ── بانر التعليمات ─────────────────────────────────────────────── + Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + decoration: BoxDecoration( + color: modeColor, + borderRadius: BorderRadius.circular(14), + boxShadow: [ + BoxShadow( + color: modeColor.withOpacity(0.4), + blurRadius: 12, + offset: const Offset(0, 4), + ) + ], + ), + child: Row( + children: [ + Icon(_getModeIcon(), color: Colors.white, size: 20), + const SizedBox(width: 10), + Expanded( + child: Text( + _getModeTitle(context), + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 13, + ), + ), + ), + ], + ), + ), + + const SizedBox(height: 8), + + // ── بطاقة الإحداثيات + زر التأكيد ────────────────────────────── + Container( + decoration: BoxDecoration( + color: AppColor.secondaryColor, + borderRadius: BorderRadius.circular(18), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.12), + blurRadius: 20, + offset: const Offset(0, 6), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // ── الإحداثيات الحالية ──────────────────────────────────── + Padding( + padding: const EdgeInsets.fromLTRB(16, 14, 16, 0), + child: Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: modeColor, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + '${controller.newMyLocation.latitude.toStringAsFixed(5)}' + ', ${controller.newMyLocation.longitude.toStringAsFixed(5)}', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + fontFeatures: const [FontFeature.tabularFigures()], + ), + ), + ), + ], + ), + ), + + const SizedBox(height: 10), + const Divider(height: 1, thickness: 0.5), + + // ── الأزرار ─────────────────────────────────────────────── + Padding( + padding: const EdgeInsets.fromLTRB(12, 10, 12, 14), + child: Row( + children: [ + // زر إلغاء + Expanded( + flex: 2, + child: OutlinedButton.icon( + onPressed: () { + controller.isPickerShown = false; + controller.passengerStartLocationFromMap = false; + controller.startLocationFromMap = false; + controller.workLocationFromMap = false; + controller.homeLocationFromMap = false; + // أعد الخريطة لحالتها المنهارة + if (!controller.isMainBottomMenuMap) { + controller.isMainBottomMenuMap = true; + controller.mainBottomMenuMapHeight = + Get.height * .22; + } + controller.update(); + }, + style: OutlinedButton.styleFrom( + foregroundColor: Colors.grey.shade600, + side: BorderSide(color: Colors.grey.shade300), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + padding: const EdgeInsets.symmetric(vertical: 12), + ), + icon: const Icon(Icons.close_rounded, size: 16), + label: Text('Cancel'.tr, + style: const TextStyle(fontSize: 13)), + ), + ), + const SizedBox(width: 10), + // زر التأكيد + Expanded( + flex: 3, + child: ElevatedButton.icon( + onPressed: () => _onConfirmTap(controller, context), + style: ElevatedButton.styleFrom( + backgroundColor: modeColor, + foregroundColor: Colors.white, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + padding: const EdgeInsets.symmetric(vertical: 13), + ), + icon: Icon(_getModeIcon(), size: 18), + label: Text( + _getConfirmLabel(context), + style: const TextStyle( + fontWeight: FontWeight.w600, fontSize: 13), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], ), ); } - Widget _buildRecentPlaceButton( - MapPassengerController controller, BuildContext context, int index) { - final textToSpeechController = Get.find(); + // ────────────────────────────────────────────────────────────────────────── + // منطق التأكيد - هنا يتم إصلاح البق الأساسي + // ────────────────────────────────────────────────────────────────────────── + Future _onConfirmTap( + MapPassengerController controller, BuildContext context) async { + // ⚠️ ROOT FIX: onCameraMoveThrottled يعمل بنظام Throttle (يحفظ أول موقع) + // لكن نحتاج Debounce (يحفظ آخر موقع بعد توقف الكاميرا). + // نضيف delay 280ms لضمان أن الكاميرا توقفت وأن الـ timer (160ms) انتهى. + // هذا يضمن أن newMyLocation = الموقع الفعلي الأخير للكاميرا. ✅ + await Future.delayed(const Duration(milliseconds: 280)); - return InkWell( + // التقاط snapshot آمن بعد انتهاء الـ debounce timer + final LatLng currentCameraPosition = LatLng( + controller.newMyLocation.latitude, + controller.newMyLocation.longitude, + ); + + controller.clearPolyline(); + controller.data = []; + + // ── CASE 1: تأكيد نقطة الانطلاق (بعد تحديد الهدف سابقاً) ───────────── + if (controller.passengerStartLocationFromMap) { + // حفظ نقطة الانطلاق من موقع الكاميرا الحالي (snapshot آمن) + final LatLng start = currentCameraPosition; + controller.newStartPointLocation = start; + + // تنظيف الحالة قبل استدعاء getDirectionMap + controller.passengerStartLocationFromMap = false; + controller.isPickerShown = false; + controller.currentLocationToFormPlaces = false; + controller.placesDestination = []; + controller.clearPlacesStart(); + controller.clearPlacesDestination(); + + // العودة لحالة القائمة المنهارة + controller.isMainBottomMenuMap = true; + controller.mainBottomMenuMapHeight = Get.height * .22; + controller.update(); + + // 🔑 الـ destination محفوظة مسبقاً في myDestination - لا تلمسها هنا + await controller.getDirectionMap( + '${start.latitude},${start.longitude}', + '${controller.myDestination.latitude},${controller.myDestination.longitude}', + ); + + controller.showBottomSheet1(); + return; + } + + // ── CASE 2: تأكيد نقطة الانطلاق العادية (startLocationFromMap) ───────── + if (controller.startLocationFromMap) { + final LatLng start = currentCameraPosition; + controller.newMyLocation = start; + controller.newStartPointLocation = start; + controller.hintTextStartPoint = + '${start.latitude.toStringAsFixed(4)} , ${start.longitude.toStringAsFixed(4)}'; + controller.startLocationFromMap = false; + controller.isPickerShown = false; + controller.update(); + return; + } + + // ── CASE 3: حفظ موقع العمل ─────────────────────────────────────────── + if (controller.workLocationFromMap) { + final LatLng work = currentCameraPosition; + box.write(BoxName.addWork, + '${work.latitude.toStringAsFixed(4)} , ${work.longitude.toStringAsFixed(4)}'); + controller.hintTextDestinationPoint = 'To Work'.tr; + controller.workLocationFromMap = false; + controller.isPickerShown = false; + controller.update(); + Get.snackbar('Work Saved'.tr, '', + backgroundColor: AppColor.greenColor, + colorText: Colors.white, + snackPosition: SnackPosition.BOTTOM); + return; + } + + // ── CASE 4: حفظ موقع المنزل ───────────────────────────────────────── + if (controller.homeLocationFromMap) { + final LatLng home = currentCameraPosition; + box.write(BoxName.addHome, + '${home.latitude.toStringAsFixed(4)} , ${home.longitude.toStringAsFixed(4)}'); + controller.hintTextDestinationPoint = 'To Home'.tr; + controller.homeLocationFromMap = false; + controller.isPickerShown = false; + controller.update(); + Get.snackbar('Home Saved'.tr, '', + backgroundColor: AppColor.greenColor, + colorText: Colors.white, + snackPosition: SnackPosition.BOTTOM); + return; + } + + // ── CASE 5 (DEFAULT): تحديد الوجهة - المرحلة الأولى ────────────────── + // currentCameraPosition محفوظ بأمان في بداية الدالة بعد الـ delay ✅ + final LatLng confirmedDestination = currentCameraPosition; + + // ─── 1. حفظ الوجهة بأمان ───────────────────────────────────────────── + controller.myDestination = confirmedDestination; + controller.hintTextDestinationPoint = + '${confirmedDestination.latitude.toStringAsFixed(4)} , ${confirmedDestination.longitude.toStringAsFixed(4)}'; + controller.placesDestination = []; + controller.placeDestinationController.clear(); + + // ─── 2. الانتقال لمرحلة تحديد نقطة الانطلاق ───────────────────────── + controller.passengerStartLocationFromMap = true; + // isPickerShown يبقى true لأننا لا زلنا في وضع الـ picker + + controller.update(); + + // ─── 3. العمليات الـ async بعد حفظ الوجهة بأمان ────────────────────── + if (!controller.isAnotherOreder) { + // تحريك الكاميرا لموقع الراكب ليختار منه نقطة الانطلاق + // ملاحظة: هذا يُغير newMyLocation - لكن myDestination محفوظ بأمان ✅ + await controller.mapController?.animateCamera( + CameraUpdate.newLatLng(LatLng( + controller.passengerLocation.latitude, + controller.passengerLocation.longitude, + )), + ); + } + + // ─── 4. إشعار المستخدم ────────────────────────────────────────────── + Get.snackbar( + 'Destination Set'.tr, + 'Now move the map to your pickup point'.tr, + backgroundColor: Colors.green.shade600, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 2), + margin: const EdgeInsets.all(12), + borderRadius: 12, + ); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// WIDGETS المساعدة +// ───────────────────────────────────────────────────────────────────────────── + +/// صف موقع البداية (للعرض فقط) +class _LocationRow extends StatelessWidget { + final IconData icon; + final Color iconColor; + final String label; + final bool isStart; + const _LocationRow({ + required this.icon, + required this.iconColor, + required this.label, + this.isStart = false, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11), + decoration: BoxDecoration( + color: isStart + ? AppColor.primaryColor.withOpacity(0.05) + : Colors.transparent, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade200), + ), + child: Row( + children: [ + Icon(icon, color: iconColor, size: 18), + const SizedBox(width: 10), + Expanded( + child: Text( + label, + style: AppStyle.subtitle.copyWith(fontSize: 13), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } +} + +/// شريحة الأماكن الأخيرة +class _RecentPlaceChip extends StatelessWidget { + final MapPassengerController controller; + final int index; + const _RecentPlaceChip({required this.controller, required this.index}); + + @override + Widget build(BuildContext context) { + final place = controller.recentPlaces[index]; + + return GestureDetector( onTap: () { - MyDialog().getDialog('Are you want to go this site'.tr, ' ', () async { - Get.back(); - - await controller.getLocation(); - - await controller.getDirectionMap( - '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', - '${controller.recentPlaces[index]['latitude']},${controller.recentPlaces[index]['longitude']}', - ); - - controller.showBottomSheet1(); - }); + MyDialog().getDialog( + 'Are you want to go this site'.tr, + ' ', + () async { + Get.back(); + await controller.getLocation(); + await controller.getDirectionMap( + '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', + '${place['latitude']},${place['longitude']}', + ); + controller.showBottomSheet1(); + }, + ); }, onLongPress: () { MyDialog().getDialog( - "Are you sure to delete this location?".tr, + 'Are you sure to delete this location?'.tr, '', () { - sql.deleteData(TableName.recentLocations, - controller.recentPlaces[index]['id']); - + sql.deleteData(TableName.recentLocations, place['id']); controller.getFavioratePlaces(); - controller.update(); - Get.back(); - mySnackbarSuccess('deleted'.tr); }, ); }, child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( - color: AppColor.primaryColor.withOpacity(0.05), // Subtle background - - borderRadius: BorderRadius.circular(12), - - border: Border( - bottom: BorderSide( - color: AppColor.primaryColor.withOpacity(0.1), width: 1), - ), + color: AppColor.primaryColor.withOpacity(0.07), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: AppColor.primaryColor.withOpacity(0.2), width: 1), ), - child: Text(controller.recentPlaces[index]['name'], - style: const TextStyle(fontSize: 14)), - ), - ); - } - - Widget clickPointPosition( - MapPassengerController controller, BuildContext context) { - return TextButton( - onPressed: () async { - controller.clearPolyline(); - - controller.data = []; - - if (controller.passengerStartLocationFromMap == true) { - controller.newMyLocation = controller.newStartPointLocation; - - controller.changeMainBottomMenuMap(); - - await controller.getDirectionMap( - '${controller.newStartPointLocation.latitude},${controller.newStartPointLocation.longitude}', - '${controller.myDestination.latitude},${controller.myDestination.longitude}', - ); - - controller.currentLocationToFormPlaces = false; - - controller.placesDestination = []; - - controller.clearPlacesStart(); - - controller.clearPlacesDestination(); - - controller.passengerStartLocationFromMap = false; - - controller.isPickerShown = false; - - controller.showBottomSheet1(); - } else if (controller.startLocationFromMap == true) { - controller.newMyLocation = controller.newStartPointLocation; - - controller.hintTextStartPoint = - '${controller.newStartPointLocation.latitude.toStringAsFixed(4)} , ${controller.newStartPointLocation.longitude.toStringAsFixed(4)}'; - - controller.startLocationFromMap = false; - - controller.isPickerShown = false; - } else if (controller.workLocationFromMap == true) { - controller.hintTextDestinationPoint = 'To Work'.tr; - - box.write(BoxName.addWork, - '${controller.newMyLocation.latitude.toStringAsFixed(4)} , ${controller.newMyLocation.longitude.toStringAsFixed(4)}'); - - controller.newMyLocation = controller.newMyLocation; - - controller.isPickerShown = false; - - controller.workLocationFromMap = false; - - Get.snackbar('Work Saved'.tr, '', - backgroundColor: AppColor.greenColor); - } else if (controller.homeLocationFromMap == true) { - controller.hintTextDestinationPoint = 'To Home'.tr; - - box.write(BoxName.addHome, - '${controller.newMyLocation.latitude.toStringAsFixed(4)} , ${controller.newMyLocation.longitude.toStringAsFixed(4)}'); - - controller.newMyLocation = controller.newMyLocation; - - controller.isPickerShown = false; - - controller.homeLocationFromMap = false; - - controller.update(); - - Get.snackbar('Home Saved'.tr, '', - backgroundColor: AppColor.greenColor); - } else { - controller.hintTextDestinationPoint = - '${controller.newMyLocation.latitude.toStringAsFixed(4)} , ${controller.newMyLocation.longitude.toStringAsFixed(4)}'; - - controller.myDestination = controller.newMyLocation; - - controller.isPickerShown = false; - - controller.changeMainBottomMenuMap(); - - controller.passengerStartLocationFromMap = true; - - controller.isPickerShown = true; - - if (controller.isAnotherOreder == false) { - await controller.mapController?.animateCamera( - CameraUpdate.newLatLng(LatLng( - controller.passengerLocation.latitude, - controller.passengerLocation.longitude))); - - Get.defaultDialog( - title: 'Destination selected'.tr, - titleStyle: AppStyle.title, - content: Text( - 'Now select start pick'.tr, - style: AppStyle.title, - ), - confirm: MyElevatedButton( - title: 'OK'.tr, - onPressed: () { - Get.back(); - })); - } - - if (controller.isWhatsAppOrder == true) { - Get.defaultDialog( - title: 'Destination selected'.tr, - titleStyle: AppStyle.title, - content: Text( - 'Now select start pick'.tr, - style: AppStyle.title, - ), - confirm: MyElevatedButton( - title: 'OK'.tr, - onPressed: () { - Get.back(); - })); - } - } - - controller.placesDestination = []; - - controller.placeDestinationController.clear(); - - // controller.showBottomSheet1(); - - controller.changeMainBottomMenuMap(); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, children: [ - Icon( - controller.passengerStartLocationFromMap - ? Icons.location_on - : Icons.location_searching, - size: 20, - color: AppColor.primaryColor, - ), - const SizedBox(width: 8), + Icon(Icons.history_rounded, + size: 13, color: AppColor.primaryColor.withOpacity(0.7)), + const SizedBox(width: 5), Text( - controller.passengerStartLocationFromMap - ? 'Confirm Pick-up Location'.tr - : "Set Location on Map".tr, - style: AppStyle.subtitle.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.primaryColor, + place['name'] ?? '', + style: TextStyle( + fontSize: 12, + color: AppColor.primaryColor.withOpacity(0.85), + fontWeight: FontWeight.w500, ), ), ], @@ -511,41 +730,135 @@ class MainBottomMenuMap extends StatelessWidget { ), ); } +} - Widget whereWidgetSmall(MapPassengerController controller) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.location_searching, color: AppColor.primaryColor), - const SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, +/// زر رابط الواتس أب +class _WhatsAppLinkButton extends StatelessWidget { + final MapPassengerController controller; + const _WhatsAppLinkButton({required this.controller}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + Get.dialog( + AlertDialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + title: Text('WhatsApp Location Extractor'.tr), + content: Form( + key: controller.sosFormKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + MyTextForm( + controller: controller.whatsAppLocationText, + label: 'Location Link'.tr, + hint: 'Paste location link here'.tr, + type: TextInputType.url, + ), + const SizedBox(height: 16), + MyElevatedButton( + title: 'Go to this location'.tr, + onPressed: () => controller.goToWhatappLocation(), + ), + ], + ), + ), + ), + ); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), + decoration: BoxDecoration( + color: Colors.green.shade50, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.green.shade200), + ), + child: Row( children: [ - Text( - '${'Where to'.tr} ${(box.read(BoxName.name).toString().split(' ')[0])} ', - style: AppStyle.subtitle), - -// if (controller.noCarString) - -// Text('Nearest Car: ~'.tr, - -// style: TextStyle(color: Colors.grey.shade600)) - -// else - - // Text( - // controller.nearestCar != null - // ? 'Nearest Car: ${controller.nearestDistance.toStringAsFixed(0)} m' - // : 'No cars nearby'.tr, - // style: TextStyle(color: Colors.grey.shade600), - // ), + Icon(Icons.link_rounded, color: Colors.green.shade700, size: 18), + const SizedBox(width: 10), + Expanded( + child: Text( + 'Paste WhatsApp location link'.tr, + style: TextStyle( + color: Colors.green.shade700, + fontSize: 13, + fontWeight: FontWeight.w500), + ), + ), + Icon(Icons.arrow_forward_ios_rounded, + size: 13, color: Colors.green.shade400), ], ), - ], + ), ); } } +/// زر نوع الطلب +class _OrderTypeButton extends StatelessWidget { + final MapPassengerController controller; + const _OrderTypeButton({required this.controller}); + + @override + Widget build(BuildContext context) { + return OutlinedButton.icon( + onPressed: () { + showCupertinoModalPopup( + context: context, + builder: (ctx) => CupertinoActionSheet( + title: Text('Select Order Type'.tr), + message: Text('Choose who this order is for'.tr), + actions: [ + CupertinoActionSheetAction( + child: Text('I want to order for myself'.tr), + onPressed: () { + controller.changeisAnotherOreder(false); + Navigator.pop(ctx); + }, + ), + CupertinoActionSheetAction( + child: Text('I want to order for someone else'.tr), + onPressed: () { + controller.changeisAnotherOreder(true); + Navigator.pop(ctx); + }, + ), + ], + cancelButton: CupertinoActionSheetAction( + isDefaultAction: true, + onPressed: () => Navigator.pop(ctx), + child: Text('Cancel'.tr), + ), + ), + ); + }, + style: OutlinedButton.styleFrom( + foregroundColor: AppColor.primaryColor, + side: BorderSide(color: AppColor.primaryColor.withOpacity(0.4)), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 11), + ), + icon: Icon( + controller.isAnotherOreder ? Icons.person_rounded : Icons.group_rounded, + size: 17, + ), + label: Text( + controller.isAnotherOreder + ? 'Order for myself'.tr + : 'Order for someone else'.tr, + style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500), + ), + ); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// FAVOURITE PLACES DIALOG +// ───────────────────────────────────────────────────────────────────────────── + class FaviouratePlacesDialog extends StatelessWidget { const FaviouratePlacesDialog({super.key}); @@ -556,84 +869,78 @@ class FaviouratePlacesDialog extends StatelessWidget { return GetBuilder( builder: (controller) => Center( child: InkWell( + borderRadius: BorderRadius.circular(12), onTap: () async { - List favoritePlaces = + final List favoritePlaces = await sql.getAllData(TableName.placesFavorite); Get.defaultDialog( title: 'Favorite Places'.tr, + titleStyle: AppStyle.title, content: SizedBox( - width: Get.width * .8, + width: Get.width * .85, height: 300, child: favoritePlaces.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon( - Icons.star_border_rounded, - size: 99, - color: AppColor.accentColor, - ), - Text( - 'No favorite places yet!'.tr, - style: AppStyle.title, - ), + const Icon(Icons.star_border_rounded, + size: 64, color: AppColor.accentColor), + const SizedBox(height: 12), + Text('No favorite places yet!'.tr, + style: AppStyle.title), ], ), ) : ListView.separated( itemCount: favoritePlaces.length, - separatorBuilder: (context, index) => const Divider(), - itemBuilder: (BuildContext context, int index) { - return ListTile( - leading: - const Icon(Icons.star, color: Colors.amber), - title: Text(favoritePlaces[index]['name'], - style: AppStyle.title), - trailing: IconButton( - icon: const Icon(Icons.delete_outline, - color: Colors.redAccent), - onPressed: () async { - await sql.deleteData(TableName.placesFavorite, - favoritePlaces[index]['id']); - - Get.back(); - - Toast.show( - context, - '${'Deleted'.tr} ${favoritePlaces[index]['name']} from your favorites', - AppColor.redColor); - }, - ), - onTap: () async { + separatorBuilder: (_, __) => const Divider(height: 1), + itemBuilder: (context, index) => ListTile( + leading: const Icon(Icons.star, color: Colors.amber), + title: Text(favoritePlaces[index]['name'], + style: AppStyle.title), + trailing: IconButton( + icon: const Icon(Icons.delete_outline, + color: Colors.redAccent), + onPressed: () async { + await sql.deleteData(TableName.placesFavorite, + favoritePlaces[index]['id']); Get.back(); - - await controller.getLocation(); - - await controller.getDirectionMap( - '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', - '${favoritePlaces[index]['latitude']},${favoritePlaces[index]['longitude']}', + Toast.show( + context, + '${'Deleted'.tr} ${favoritePlaces[index]['name']}', + AppColor.redColor, ); - - controller.showBottomSheet1(); }, - ); - }, + ), + onTap: () async { + Get.back(); + await controller.getLocation(); + await controller.getDirectionMap( + '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', + '${favoritePlaces[index]['latitude']},${favoritePlaces[index]['longitude']}', + ); + controller.showBottomSheet1(); + }, + ), ), ), confirm: MyElevatedButton( title: 'Back'.tr, onPressed: () => Get.back()), ); }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.star_border_rounded, - color: AppColor.accentColor), - const SizedBox(width: 8), - Text('Favorite Places'.tr, style: AppStyle.title), - ], + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.star_border_rounded, + color: AppColor.accentColor, size: 20), + const SizedBox(width: 8), + Text('Favorite Places'.tr, style: AppStyle.title), + ], + ), ), ), ),