diff --git a/siro_rider/android/app/build.gradle b/siro_rider/android/app/build.gradle index edecc175..53a92dbe 100644 --- a/siro_rider/android/app/build.gradle +++ b/siro_rider/android/app/build.gradle @@ -116,7 +116,6 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' - implementation 'com.scottyab:rootbeer-lib:0.1.0' implementation 'com.google.android.gms:play-services-safetynet:18.1.0' implementation 'com.google.android.gms:play-services-location:21.3.0' } diff --git a/siro_rider/android/app/src/main/kotlin/com/siro/siro_rider/AndroidLiveUpdatePlugin.kt b/siro_rider/android/app/src/main/kotlin/com/siro/siro_rider/AndroidLiveUpdatePlugin.kt index 9ccd3776..19349820 100644 --- a/siro_rider/android/app/src/main/kotlin/com/siro/siro_rider/AndroidLiveUpdatePlugin.kt +++ b/siro_rider/android/app/src/main/kotlin/com/siro/siro_rider/AndroidLiveUpdatePlugin.kt @@ -7,22 +7,27 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.graphics.BitmapFactory +import android.graphics.Color +import android.graphics.drawable.Icon import android.os.Build +import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodCall /** - * Android 16 Progress Style Live Update Plugin - * - * يستخدم NotificationCompat مع setProgressStyle لتوفير - * تجربة Live Update متقدمة على Android 16 (API 36) والأجهزة الأحدث. - * - * الميزات: - * - شريط تقدم متحرك (Progress Style) - * - تحديث مباشر للوقت المتبقي - * - دعم الحالات المختلفة (بحث، في الطريق، وصل، رحلة جارية) - * - إشعار ثابت (Ongoing) لا يمكن إلغاؤه أثناء الرحلة + * خدمة تحديث حالة الرحلة في شريط الإشعارات على أندرويد. + * + * تعمل على ثلاثة مستويات حسب الجهاز: + * - أندرويد 16 فما فوق (API 36+) مع موافقة المستخدم: تُستخدم + * ميزة Live Updates الحقيقية عبر Notification.ProgressStyle + * (نفس الفئة والدوال التي تحققنا منها مباشرة من ملف الـSDK). + * - أندرويد 16 فما فوق بدون موافقة المستخدم، أو أي إصدار من + * API 23 حتى 35: إشعار عادٍ ثابت (Ongoing) بشريط تقدّم كلاسيكي، + * يعمل بشكل متطابق تقريباً بصرياً لكن بدون معاملة النظام الخاصة. + * - أقل من API 26: لا يتم إنشاء قناة إشعارات (الكلاس غير موجود + * أصلاً في النظام قبل هذا الإصدار)، ويُستخدم مسار الإشعار + * الكلاسيكي مباشرة بدون قناة. */ class AndroidLiveUpdatePlugin(private val context: Context) { @@ -31,9 +36,7 @@ class AndroidLiveUpdatePlugin(private val context: Context) { private const val CHANNEL_NAME = "Siro Live Ride Progress" private const val CHANNEL_DESC = "Live progress updates for your current Siro ride" private const val NOTIFICATION_ID = 999 - private const val SUMMARY_NOTIFICATION_ID = 1000 - // Ride status constants private const val STATUS_SEARCHING = "searching" private const val STATUS_DRIVER_ON_WAY = "driver_on_way" private const val STATUS_DRIVER_ARRIVED = "driver_arrived" @@ -51,34 +54,66 @@ class AndroidLiveUpdatePlugin(private val context: Context) { private var currentBody: String = "" init { - createNotificationChannel() + // NotificationChannel نفسها غير موجودة في النظام قبل API 26، + // فاستدعاؤها بدون هذا الشرط يتسبب بانهيار فوري للتطبيق عند + // الإقلاع على أي جهاز أقدم (Android 6/7). + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createNotificationChannel() + } } - /** - * إنشاء قناة الإشعارات المخصصة للرحلة - * مع دعم Android 16 Progress Style - */ + @RequiresApi(Build.VERSION_CODES.O) private fun createNotificationChannel() { val channel = NotificationChannel( CHANNEL_ID, CHANNEL_NAME, - NotificationManager.IMPORTANCE_LOW // Low importance for progress style + NotificationManager.IMPORTANCE_LOW ).apply { description = CHANNEL_DESC setShowBadge(false) enableVibration(false) enableLights(false) - // Android 16: دعم التقدم المستمر - if (Build.VERSION.SDK_INT >= 36) { - // يمكن إضافة خصائص Android 16 هنا - } } notificationManager.createNotificationChannel(channel) } + /** + * هل يمكن فعلياً استخدام Live Updates الحقيقية الآن؟ + * تتطلب أندرويد 16+ وموافقة صريحة من المستخدم عبر إعدادات النظام + * (Settings > Apps > Siro > Notifications > Live Updates). + */ + @Suppress("NewApi") + private fun canUsePromotedLiveUpdate(): Boolean { + if (Build.VERSION.SDK_INT < 36) return false + return try { + notificationManager.canPostPromotedNotifications() + } catch (e: Exception) { + false + } + } + + private fun statusColor(): Int = when (currentStatus) { + STATUS_DRIVER_ARRIVED -> Color.parseColor("#22C55E") + STATUS_IN_PROGRESS -> Color.parseColor("#1A2340") + STATUS_SEARCHING -> Color.parseColor("#F59E0B") + else -> Color.parseColor("#1A2340") + } + + private fun buildContentIntent(): PendingIntent { + val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply { + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP + putExtra("notification_ride", true) + } + return PendingIntent.getActivity( + context, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + } + /** * بدء التحديث المباشر للرحلة - * يعرض إشعاراً ثابتاً مع شريط تقدم */ fun startLiveUpdate( rideId: String, @@ -93,19 +128,11 @@ class AndroidLiveUpdatePlugin(private val context: Context) { currentProgress = (progress * 100).toInt().coerceIn(0, 100) currentMaxProgress = 100 - showNotification( - title = currentTitle, - body = currentBody, - progress = currentProgress, - maxProgress = currentMaxProgress, - indeterminate = false, - ongoing = true - ) + render(indeterminate = false) } /** * تحديث التقدم والوقت المتبقي - * يستخدم Progress Style مع animate للتحديثات */ fun updateProgress(progress: Double, etaText: String) { currentProgress = (progress * 100).toInt().coerceIn(0, 100) @@ -122,19 +149,11 @@ class AndroidLiveUpdatePlugin(private val context: Context) { else -> currentBody } - showNotification( - title = currentTitle, - body = currentBody, - progress = currentProgress, - maxProgress = currentMaxProgress, - indeterminate = false, - ongoing = true - ) + render(indeterminate = false) } /** * تحديث حالة الرحلة بالكامل - * يستخدم لتغيير الحالة بين (في الطريق، وصل، رحلة جارية) */ fun updateStatus( status: String, @@ -150,50 +169,23 @@ class AndroidLiveUpdatePlugin(private val context: Context) { STATUS_SEARCHING -> { currentTitle = "🔍 جاري البحث عن سائق" currentBody = etaText - showNotification( - title = currentTitle, - body = currentBody, - progress = 0, - maxProgress = 0, - indeterminate = true, - ongoing = true - ) + render(indeterminate = true) } STATUS_DRIVER_ON_WAY -> { currentTitle = "🚗 السائق في الطريق إليك" currentBody = "$driverName • $carDetails • $etaText" - showNotification( - title = currentTitle, - body = currentBody, - progress = currentProgress, - maxProgress = 100, - indeterminate = false, - ongoing = true - ) + render(indeterminate = false) } STATUS_DRIVER_ARRIVED -> { currentTitle = "📍 السائق وصل" currentBody = "الرجاء التوجّه لمقابلة $driverName عند نقطة الالتقاء" - showNotification( - title = currentTitle, - body = currentBody, - progress = 100, - maxProgress = 100, - indeterminate = false, - ongoing = true - ) + currentProgress = 100 + render(indeterminate = false) } STATUS_IN_PROGRESS -> { currentTitle = "🚀 الرحلة جارية الآن" currentBody = "المتبقي تقريبًا: $etaText" - showNotification( - title = currentTitle, - body = currentBody, - progress = currentProgress, - maxProgress = 100, - indeterminate = false, - ongoing = true - ) + render(indeterminate = false) } } } @@ -208,29 +200,58 @@ class AndroidLiveUpdatePlugin(private val context: Context) { } /** - * إظهار الإشعار مع الإعدادات المناسبة + * يختار المسار المناسب: Live Update الحقيقية إن كانت متاحة ومسموحة، + * وإلا الإشعار الكلاسيكي الآمن على كل الإصدارات من API 23. */ - private fun showNotification( - title: String, - body: String, - progress: Int, - maxProgress: Int, - indeterminate: Boolean, - ongoing: Boolean - ) { - // Intent لفتح التطبيق عند النقر على الإشعار - val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply { - flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP - putExtra("notification_ride", true) + private fun render(indeterminate: Boolean) { + if (canUsePromotedLiveUpdate()) { + try { + showPromotedProgressNotification(indeterminate) + return + } catch (e: Exception) { + // أي فشل غير متوقع بالمسار الجديد يرجعنا فوراً للمسار الآمن + } } + showClassicNotification(indeterminate) + } - val pendingIntent = PendingIntent.getActivity( - context, - 0, - intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) + /** + * المسار الجديد: Notification.ProgressStyle الحقيقية (Android 16 / API 36+). + * التوقيعات هنا مؤكدة مباشرة من ملف android-36/android.jar (javap)، وليست تخميناً. + */ + @RequiresApi(36) + private fun showPromotedProgressNotification(indeterminate: Boolean) { + val trackerIcon = Icon.createWithResource(context, android.R.drawable.ic_menu_directions) + val progressStyle = Notification.ProgressStyle() + .setProgressSegments(listOf( + Notification.ProgressStyle.Segment(100).setColor(statusColor()) + )) + .setProgress(currentProgress) + .setProgressIndeterminate(indeterminate) + .setProgressTrackerIcon(trackerIcon) + .setStyledByProgress(true) + + val notification = Notification.Builder(context, CHANNEL_ID) + .setSmallIcon(android.R.drawable.ic_dialog_info) + .setContentTitle(currentTitle) + .setContentText(currentBody) + .setStyle(progressStyle) + .setCategory(Notification.CATEGORY_PROGRESS) + .setOngoing(true) + .setOnlyAlertOnce(true) + .setContentIntent(buildContentIntent()) + .setFlag(Notification.FLAG_PROMOTED_ONGOING, true) + .build() + + notificationManager.notify(NOTIFICATION_ID, notification) + } + + /** + * المسار الكلاسيكي: إشعار ثابت بشريط تقدّم عادي، متوافق من API 23 وما فوق + * (وبدون قناة إشعارات على الإصدارات الأقدم من API 26). + */ + private fun showClassicNotification(indeterminate: Boolean) { val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(android.R.drawable.ic_dialog_info) .setLargeIcon( @@ -239,30 +260,23 @@ class AndroidLiveUpdatePlugin(private val context: Context) { android.R.drawable.ic_menu_directions ) ) - .setContentTitle(title) - .setContentText(body) + .setContentTitle(currentTitle) + .setContentText(currentBody) .setStyle( NotificationCompat.BigTextStyle() - .bigText(body) + .bigText(currentBody) ) .setPriority(NotificationCompat.PRIORITY_LOW) - .setOngoing(ongoing) + .setOngoing(true) .setAutoCancel(false) .setOnlyAlertOnce(true) - .setContentIntent(pendingIntent) + .setContentIntent(buildContentIntent()) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - // تطبيق Progress Style if (indeterminate) { notificationBuilder.setProgress(0, 0, true) } else { - notificationBuilder.setProgress(maxProgress, progress, false) - } - - // Android 16: إضافة إجراءات سريعة للإشعار - if (Build.VERSION.SDK_INT >= 36) { - // يمكن إضافة إجراءات Android 16 هنا - // مثل: إظهار الخريطة، مشاركة الرحلة، إلخ + notificationBuilder.setProgress(currentMaxProgress, currentProgress, false) } notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()) @@ -302,11 +316,13 @@ class AndroidLiveUpdatePlugin(private val context: Context) { result.success(true) } "isSupported" -> { - result.success(Build.VERSION.SDK_INT >= 30) // يدعم Android 11+ + // الإشعار الكلاسيكي يعمل من API 23 وما فوق؛ الأجهزة الأحدث + // تحصل تلقائياً على Live Update الحقيقية عبر canUsePromotedLiveUpdate(). + result.success(Build.VERSION.SDK_INT >= 23) } else -> { result.notImplemented() } } } -} \ No newline at end of file +} diff --git a/siro_rider/android/app/src/main/kotlin/com/siro/siro_rider/MainActivity.kt b/siro_rider/android/app/src/main/kotlin/com/siro/siro_rider/MainActivity.kt index 27fe7165..5f3edaa2 100644 --- a/siro_rider/android/app/src/main/kotlin/com/siro/siro_rider/MainActivity.kt +++ b/siro_rider/android/app/src/main/kotlin/com/siro/siro_rider/MainActivity.kt @@ -11,7 +11,6 @@ import android.widget.LinearLayout import android.widget.ProgressBar import android.widget.TextView import androidx.core.view.setPadding -import com.scottyab.rootbeer.RootBeer import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel @@ -131,7 +130,7 @@ class MainActivity : FlutterFragmentActivity() { private fun isDeviceCompromised(): Boolean { return try { - nativeRootCheck() || rootBeerCheck() // || !safetyNetCheck() + nativeRootCheck() // || !safetyNetCheck() } catch (e: Exception) { Log.e("SecurityCheck", "Error during security checks: ${e.message}", e) true @@ -150,14 +149,6 @@ class MainActivity : FlutterFragmentActivity() { } } - private fun rootBeerCheck(): Boolean { - Log.d("SecurityCheck", "Starting RootBeer root detection...") - val rootBeer = RootBeer(this) - val isRooted = rootBeer.isRooted - Log.d("SecurityCheck", "RootBeer detection result: $isRooted") - return isRooted - } - // SafetyNet (معلّق كما هو) private fun safetyNetCheck(): Boolean { Log.d("SecurityCheck", "Starting SafetyNet check...") diff --git a/siro_rider/ios/Podfile.lock b/siro_rider/ios/Podfile.lock index 34dffe36..dd83d251 100644 --- a/siro_rider/ios/Podfile.lock +++ b/siro_rider/ios/Podfile.lock @@ -80,28 +80,28 @@ PODS: - GoogleDataTransport (10.1.0): - nanopb (~> 3.30910.0) - PromisesObjC (~> 2.4) - - GoogleUtilities/AppDelegateSwizzler (8.1.1): + - GoogleUtilities/AppDelegateSwizzler (8.1.2): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - GoogleUtilities/Privacy - - GoogleUtilities/Environment (8.1.1): + - GoogleUtilities/Environment (8.1.2): - GoogleUtilities/Privacy - - GoogleUtilities/Logger (8.1.1): + - GoogleUtilities/Logger (8.1.2): - GoogleUtilities/Environment - GoogleUtilities/Privacy - - GoogleUtilities/Network (8.1.1): + - GoogleUtilities/Network (8.1.2): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Privacy - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (8.1.1)": + - "GoogleUtilities/NSData+zlib (8.1.2)": - GoogleUtilities/Privacy - - GoogleUtilities/Privacy (8.1.1) - - GoogleUtilities/Reachability (8.1.1): + - GoogleUtilities/Privacy (8.1.2) + - GoogleUtilities/Reachability (8.1.2): - GoogleUtilities/Logger - GoogleUtilities/Privacy - - GoogleUtilities/UserDefaults (8.1.1): + - GoogleUtilities/UserDefaults (8.1.2): - GoogleUtilities/Logger - GoogleUtilities/Privacy - GTMSessionFetcher/Core (5.3.0) @@ -319,7 +319,7 @@ SPEC CHECKSUMS: flutter_webrtc: ec91d94b484ad49cf191ef93413f64a40ffd3b4c geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 - GoogleUtilities: 4f2618a4a1e762a1ee134a1e2323bba9843e06da + GoogleUtilities: 766ace00c6b10d8148408f329d10c4f051931850 GTMSessionFetcher: 127211aeec0b1e904fc49f4f6f895dcc535b0ecf image_cropper: 64567491beea6cd1bc4b11948e2babb590de5826 image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 diff --git a/siro_rider/lib/constant/design.dart b/siro_rider/lib/constant/design.dart new file mode 100644 index 00000000..effbd8fc --- /dev/null +++ b/siro_rider/lib/constant/design.dart @@ -0,0 +1,172 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import 'colors.dart'; + +/// ----------------------------------------------------------------------------- +/// Siro Design Tokens +/// ----------------------------------------------------------------------------- +/// A single source of truth for spacing, radii, shadows, durations and the +/// shared bottom-sheet surface used across the passenger map overlays. +/// +/// Goal: kill the "magic numbers" scattered across every map widget +/// (radii of 15/24/25, ad-hoc shadows, handles that appear only sometimes) +/// so all sheets read as ONE consistent product — the way Uber / Careem / Bolt +/// feel like a single system rather than a pile of separate screens. +/// +/// Everything here is additive. Existing widgets keep working untouched; new +/// and refactored widgets opt in. + +/// 4pt spacing scale. Use these instead of raw `SizedBox(height: 10)`. +class AppSpacing { + AppSpacing._(); + + static const double xs = 4; + static const double sm = 8; + static const double md = 12; + static const double lg = 16; + static const double xl = 20; + static const double xxl = 24; + + // Convenience gaps (const so they can live in const child lists). + static const Widget gapXs = SizedBox(height: xs, width: xs); + static const Widget gapSm = SizedBox(height: sm, width: sm); + static const Widget gapMd = SizedBox(height: md, width: md); + static const Widget gapLg = SizedBox(height: lg, width: lg); + static const Widget gapXl = SizedBox(height: xl, width: xl); + + static const Widget vGapXs = SizedBox(height: xs); + static const Widget vGapSm = SizedBox(height: sm); + static const Widget vGapMd = SizedBox(height: md); + static const Widget vGapLg = SizedBox(height: lg); + static const Widget vGapXl = SizedBox(height: xl); +} + +/// Corner radii. `sheet` is THE radius for every bottom sheet so they all match. +class AppRadii { + AppRadii._(); + + static const double sm = 8; + static const double md = 12; + static const double lg = 16; + static const double sheet = 24; + static const double pill = 999; + + static const BorderRadius sheetTop = BorderRadius.only( + topLeft: Radius.circular(sheet), + topRight: Radius.circular(sheet), + ); + + static BorderRadius all(double r) => BorderRadius.circular(r); +} + +/// Shared elevation. One shadow for sheets, one for cards — no more per-widget +/// hand-tuned `BoxShadow`s (and no neumorphic accent-color shadows). +class AppShadows { + AppShadows._(); + + static List get sheet => [ + BoxShadow( + color: Colors.black.withValues( + alpha: Get.isDarkMode ? 0.45 : 0.12, + ), + blurRadius: 24, + spreadRadius: 1, + offset: const Offset(0, -4), + ), + ]; + + static List get card => [ + BoxShadow( + color: Colors.black.withValues( + alpha: Get.isDarkMode ? 0.30 : 0.06, + ), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ]; +} + +/// Motion durations. Keep sheet slide-in/out consistent everywhere. +class AppDurations { + AppDurations._(); + + static const Duration fast = Duration(milliseconds: 200); + static const Duration base = Duration(milliseconds: 300); + static const Duration slow = Duration(milliseconds: 450); +} + +/// Minimum touch target (Material / iOS HIG guidance is 44–48px). +class AppSizes { + AppSizes._(); + + static const double minTouch = 48; +} + +/// ----------------------------------------------------------------------------- +/// Shared bottom-sheet surface +/// ----------------------------------------------------------------------------- +/// The rounded-top card + shadow + optional drag handle that every map sheet +/// draws by hand today. Wrap your content in this and all sheets instantly +/// share the same radius, shadow, handle and horizontal rhythm. +/// +/// It does NOT own show/hide animation — each caller keeps its own +/// AnimatedPositioned / transform logic so behaviour is unchanged. +class SiroSheetSurface extends StatelessWidget { + const SiroSheetSurface({ + super.key, + required this.child, + this.padding = + const EdgeInsets.fromLTRB(AppSpacing.xl, AppSpacing.md, AppSpacing.xl, AppSpacing.lg), + this.showHandle = true, + this.color, + }); + + final Widget child; + final EdgeInsets padding; + final bool showHandle; + final Color? color; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: color ?? AppColor.cardColor, + borderRadius: AppRadii.sheetTop, + boxShadow: AppShadows.sheet, + ), + padding: padding, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (showHandle) ...[ + const SiroSheetHandle(), + AppSpacing.vGapMd, + ], + child, + ], + ), + ); + } +} + +/// The little grab handle at the top of a sheet. Consistent size everywhere. +class SiroSheetHandle extends StatelessWidget { + const SiroSheetHandle({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: AppColor.grayColor.withValues(alpha: 0.35), + borderRadius: BorderRadius.circular(AppRadii.pill), + ), + ), + ); + } +} diff --git a/siro_rider/lib/controller/home/map/location_search_controller.dart b/siro_rider/lib/controller/home/map/location_search_controller.dart index a2fa0f10..969b26b3 100644 --- a/siro_rider/lib/controller/home/map/location_search_controller.dart +++ b/siro_rider/lib/controller/home/map/location_search_controller.dart @@ -20,7 +20,7 @@ import '../../../print.dart'; import '../../../services/offline_map_service.dart'; import '../../functions/crud.dart'; import '../points_for_rider_controller.dart'; -import '../../../views/home/map_widget.dart/form_serch_multiy_point.dart'; +import '../../../views/home/map_widget.dart/waypoint_stops_list.dart'; import '../../../views/widgets/error_snakbar.dart'; import 'map_engine_controller.dart'; import '../deep_link_controller.dart'; diff --git a/siro_rider/lib/controller/home/map/ui_interactions_controller.dart b/siro_rider/lib/controller/home/map/ui_interactions_controller.dart index cd25c447..5a24127a 100644 --- a/siro_rider/lib/controller/home/map/ui_interactions_controller.dart +++ b/siro_rider/lib/controller/home/map/ui_interactions_controller.dart @@ -29,6 +29,8 @@ class UiInteractionsController extends GetxController { TextEditingController whatsAppLocationText = TextEditingController(); final sosFormKey = GlobalKey(); + + @override void onInit() { super.onInit(); diff --git a/siro_rider/lib/views/home/map_page_passenger.dart b/siro_rider/lib/views/home/map_page_passenger.dart index 3f5c9689..185c3e8a 100644 --- a/siro_rider/lib/views/home/map_page_passenger.dart +++ b/siro_rider/lib/views/home/map_page_passenger.dart @@ -15,7 +15,7 @@ import '../../views/home/map_widget.dart/ride_begin_passenger.dart'; import '../../controller/home/menu_controller.dart'; import 'map_widget.dart/apply_order_widget.dart'; -import 'map_widget.dart/buttom_sheet_map_show.dart'; +import 'map_widget.dart/legacy_destination_bottom_sheet.dart'; import 'map_widget.dart/car_details_widget_to_go.dart'; import 'map_widget.dart/cash_confirm_bottom_page.dart'; import 'map_widget.dart/google_map_passenger_widget.dart'; @@ -23,8 +23,8 @@ import 'map_widget.dart/left_main_menu_icons.dart'; import 'route_planner/route_planner.dart'; import 'map_widget.dart/map_menu_widget.dart'; import '../../controller/functions/package_info.dart'; -import 'map_widget.dart/passengerRideLoctionWidget.dart'; -import 'map_widget.dart/payment_method.page.dart'; +import 'map_widget.dart/passenger_ride_location_widget.dart'; +import 'map_widget.dart/payment_method_page.dart'; import 'map_widget.dart/points_page_for_rider.dart'; import 'map_widget.dart/ride_from_start_app.dart'; import 'map_widget.dart/searching_captain_window.dart'; @@ -65,35 +65,26 @@ class MapPagePassenger extends StatelessWidget { // OsmMapPassengerWidget(), leftMainMenuIcons(), // PaymobPackage(), + // ── New Route Planner ── const RpCenterPin(), - // PickerAnimtionContainerFormPlaces(), const RoutePlannerSheet(), - // NewMainBottomSheet(), - - buttomSheetMapPage(), CarDetailsTypeToChoose(), // const HeaderDestination(), const BurcMoney(), // const PromoCode(), ApplyOrderWidget(), const MapMenuWidget(), - // hexagonClipper(), const CancelRidePageShow(), CashConfirmPageShown(), const PaymentMethodPage(), const SearchingCaptainWindow(), AttributionMap(), - // timerForCancelTripFromPassenger(), - // const DriverTimeArrivePassengerPage(), - // const TimerToPassengerFromDriver(), const PassengerRideLocationWidget(), const RideBeginPassenger(), const VipRideBeginPassenger(), const RideFromStartApp(), + PointsPageForRider(), - // cancelRidePage(), - // const MenuIconMapPageWidget(), - PointsPageForRider() ], ), ), @@ -171,8 +162,8 @@ class CancelRidePageShow extends StatelessWidget { return showCancelButton ? Positioned( - right: box.read(BoxName.lang) != 'ar' ? 10 : null, - left: box.read(BoxName.lang) == 'ar' ? 10 : null, + right: box.read(BoxName.lang) == 'ar' ? 10 : null, + left: box.read(BoxName.lang) != 'ar' ? 10 : null, top: Get.height * .013, child: GestureDetector( onTap: () { diff --git a/siro_rider/lib/views/home/map_widget.dart/apply_order_widget.dart b/siro_rider/lib/views/home/map_widget.dart/apply_order_widget.dart index 1a6608da..d6676bd6 100644 --- a/siro_rider/lib/views/home/map_widget.dart/apply_order_widget.dart +++ b/siro_rider/lib/views/home/map_widget.dart/apply_order_widget.dart @@ -11,6 +11,7 @@ import 'package:get/get.dart'; import 'package:intl/intl.dart'; import '../../../constant/box_name.dart'; +import '../../../constant/design.dart'; import '../../../controller/firebase/notification_service.dart'; import '../../../controller/functions/launch.dart'; import '../../../controller/functions/crud.dart'; @@ -46,52 +47,30 @@ class ApplyOrderWidget extends StatelessWidget { bottom: isVisible ? 0 : -400, left: 0, right: 0, - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: const BorderRadius.vertical(top: Radius.circular(25)), - boxShadow: [ - BoxShadow( - blurRadius: 20, - spreadRadius: 1, - color: Colors.black.withOpacity(0.1), - offset: const Offset(0, -3), - ) - ], - ), - // تغيير: تقليل الحواف الخارجية بشكل كبير - padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + child: SiroSheetSurface( + color: Theme.of(context).cardColor, + padding: const EdgeInsets.fromLTRB( + AppSpacing.lg, AppSpacing.sm, AppSpacing.lg, AppSpacing.lg), child: GetBuilder( builder: (c) { return Column( mainAxisSize: MainAxisSize.min, // مهم جداً: يأخذ أقل مساحة ممكنة children: [ - // مقبض صغير - Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.grey.withOpacity(0.3), - borderRadius: BorderRadius.circular(10), - ), - ), - const SizedBox(height: 10), // تقليل المسافة - - // 1. [تغيير جوهري] دمج السعر مع الحالة في صف واحد لتوفير المساحة + // 1. دمج السعر مع الحالة في صف واحد لتوفير المساحة _buildCompactHeaderRow(context, c), - const SizedBox(height: 10), // مسافة مضغوطة + AppSpacing.vGapMd, // 2. كرت المعلومات المضغوط _buildCompactInfoCard(context, c, parseColor), - const SizedBox(height: 10), // مسافة مضغوطة + AppSpacing.vGapMd, // 3. أزرار الاتصال (Slim) _buildCompactButtonsRow(context, c), - const SizedBox(height: 10), // مسافة مضغوطة + AppSpacing.vGapMd, // 4. شريط الوقت c.currentRideState.value == RideState.driverArrived @@ -240,10 +219,10 @@ class ApplyOrderWidget extends StatelessWidget { RideLifecycleController controller, Color Function(String) parseColor) { return Container( // تقليل الحواف الداخلية للكرت - padding: const EdgeInsets.all(10), + padding: const EdgeInsets.all(AppSpacing.md), decoration: BoxDecoration( color: Theme.of(context).scaffoldBackgroundColor, - borderRadius: BorderRadius.circular(16), + borderRadius: AppRadii.all(AppRadii.lg), border: Border.all(color: Colors.grey.withOpacity(0.1)), ), child: Column( @@ -496,7 +475,7 @@ class ApplyOrderWidget extends StatelessWidget { Widget _buildCompactButtonsRow( BuildContext context, RideLifecycleController controller) { return SizedBox( - height: 40, // تحديد ارتفاع ثابت وصغير للأزرار + height: AppSizes.minTouch, // ارتفاع لمس قياسي 48px child: Row( children: [ Expanded( @@ -508,7 +487,7 @@ class ApplyOrderWidget extends StatelessWidget { onTap: () => _showContactOptionsDialog(context, controller), ), ), - const SizedBox(width: 10), // تقليل المسافة + AppSpacing.gapMd, Expanded( child: _buildSlimButton( label: 'Call'.tr, // اختصار الكلمة @@ -609,7 +588,8 @@ class ApplyOrderWidget extends StatelessWidget { foregroundColor: color, elevation: isPrimary ? 2 : 0, padding: EdgeInsets.zero, // إزالة الحواشي الداخلية - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md)), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/siro_rider/lib/views/home/map_widget.dart/call_passenger_page.dart b/siro_rider/lib/views/home/map_widget.dart/call_passenger_page.dart deleted file mode 100644 index 1f8eda5c..00000000 --- a/siro_rider/lib/views/home/map_widget.dart/call_passenger_page.dart +++ /dev/null @@ -1,258 +0,0 @@ -// import 'dart:async'; -// import 'package:SEFER/constant/box_name.dart'; -// import 'package:SEFER/controller/home/map_passenger_controller.dart'; -// import 'package:SEFER/main.dart'; -// import 'package:SEFER/views/widgets/my_scafold.dart'; -// import 'package:flutter/material.dart'; -// import 'package:get/get.dart'; -// import 'package:permission_handler/permission_handler.dart'; - -// import 'package:agora_rtc_engine/agora_rtc_engine.dart'; - -// import '../../../../constant/api_key.dart'; -// import '../../../constant/colors.dart'; -// import '../../../constant/style.dart'; -// import '../../../controller/firebase/firbase_messge.dart'; - -// String appId = AK.agoraAppId; - -// class PassengerCallPage extends StatefulWidget { -// const PassengerCallPage({ -// super.key, -// required this.channelName, -// required this.token, -// required this.remoteID, -// }); -// final String channelName, token, remoteID; -// @override -// State createState() => _PassengerCallPageState(); -// } - -// class _PassengerCallPageState extends State { -// int uid = 0; -// int? _remoteUid = 0; // uid of the remote user -// bool _isJoined = false; // Indicates if the local user has joined the channel -// late RtcEngine agoraEngine; // Agora engine instance -// String status = ''; -// final GlobalKey scaffoldMessengerKey = -// GlobalKey(); // Global key to access the scaffold - -// showMessage(String message) { -// scaffoldMessengerKey.currentState?.showSnackBar(SnackBar( -// content: Text(message), -// )); -// } - -// initAgora() async { -// await setupVoiceSDKEngine(); -// } - -// @override -// void initState() { -// super.initState(); -// _remoteUid = int.parse(widget.remoteID); -// uid = int.parse(box.read(BoxName.phone)); -// // Set up an instance of Agora engine -// initAgora(); -// } - -// Future setupVoiceSDKEngine() async { -// // retrieve or request microphone permission -// await [Permission.microphone].request(); - -// //create an instance of the Agora engine -// agoraEngine = createAgoraRtcEngine(); -// await agoraEngine.initialize(RtcEngineContext(appId: AK.agoraAppId)); -// // Register the event handler -// agoraEngine.registerEventHandler( -// RtcEngineEventHandler( -// onJoinChannelSuccess: (RtcConnection connection, int elapsed) { -// showMessage( -// "Local user uid:${connection.localUid} joined the channel"); -// setState(() { -// _isJoined = true; -// status = 'joined'.tr; -// }); -// }, -// onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) { -// showMessage("Driver joined the channel".tr); -// setState(() { -// status = "Driver joined the channel".tr; -// _remoteUid = remoteUid; -// }); -// }, -// onUserOffline: (RtcConnection connection, int? remoteUid, -// UserOfflineReasonType reason) { -// showMessage("Driver left the channel".tr); -// setState(() { -// status = "Driver left the channel".tr; -// _remoteUid = null; -// }); -// }, -// ), -// ); -// } - -// void join() async { -// // Set channel options including the client role and channel profile -// ChannelMediaOptions options = const ChannelMediaOptions( -// clientRoleType: ClientRoleType.clientRoleBroadcaster, -// channelProfile: ChannelProfileType.channelProfileCommunication, -// ); - -// await agoraEngine.joinChannel( -// token: widget.token, -// channelId: widget.channelName, -// options: options, -// uid: uid, -// ); -// } -// //https://console.agora.io/invite?sign=5e9e22d06f22caeeada9954c9e908572%253A5ba8aed978a35eab5a5113742502ded2a41478b2a81cb19c71a30776e125b58a - -// void leave() { -// setState(() { -// _isJoined = false; -// _remoteUid = null; -// }); -// agoraEngine.leaveChannel(); -// } - -// // Clean up the resources when you leave -// @override -// void dispose() async { -// await agoraEngine.leaveChannel(); -// super.dispose(); -// } - -// // Build UI -// @override -// Widget build(BuildContext context) { -// return MaterialApp( -// scaffoldMessengerKey: scaffoldMessengerKey, -// home: MyScafolld( -// // appBar: AppBar( -// // title: const Text('Get started with Voice Calling'), -// // ), -// title: 'Call Page'.tr, -// isleading: true, -// body: [ -// Positioned( -// top: Get.height * .2, -// child: Container( -// height: 100, width: Get.width, -// decoration: AppStyle.boxDecoration, -// child: Row( -// mainAxisAlignment: MainAxisAlignment.spaceEvenly, -// children: [ -// GestureDetector( -// onTap: () async { -// // await callController.initAgoraFull(); -// // callController.join(); -// // FirebaseMessagesController().sendNotificationToPassengerToken( -// // 'Call Income', -// // '${'You have call from driver'.tr} ${box.read(BoxName.nameDriver)}', -// // Get.find().tokenPassenger, -// // [ -// // callController.token, -// // callController.channelName, -// // callController.uid.toString(), -// // callController.remoteUid.toString(), -// // ], -// // ); -// join(); -// // callController.fetchToken(); -// }, -// child: Container( -// width: 50, -// height: 50, -// decoration: const BoxDecoration( -// shape: BoxShape.circle, -// color: AppColor.greenColor), -// child: const Icon( -// Icons.phone, -// size: 35, -// color: AppColor.secondaryColor, -// )), -// ), -// Column( -// children: [ -// Text( -// status, -// style: AppStyle.title, -// ), -// Text('Driver Name'), -// ], -// ), -// GestureDetector( -// onTap: () async { -// FirebaseMessagesController() -// .sendNotificationToPassengerToken( -// 'Call End'.tr, -// 'Call End', -// Get.find().driverToken, -// [], -// 'iphone_ringtone.wav', -// ); -// leave(); -// Get.back(); -// // }, -// child: Container( -// width: 50, -// height: 50, -// decoration: const BoxDecoration( -// shape: BoxShape.circle, color: AppColor.redColor), -// child: const Icon( -// Icons.phone_disabled_sharp, -// size: 35, -// color: AppColor.secondaryColor, -// )), -// ) -// ], -// ), -// // ignore: prefer_const_constructors -// ), -// ), -// // ListView( -// // padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), -// // children: [ -// // // Status text -// // Container(height: 40, child: Center(child: _status())), -// // // Button Row -// // Row( -// // children: [ -// // Expanded( -// // child: ElevatedButton( -// // child: Text("Join".tr), -// // onPressed: () => {join()}, -// // ), -// // ), -// // const SizedBox(width: 10), -// // Expanded( -// // child: ElevatedButton( -// // child: Text("Leave".tr), -// // onPressed: () => {leave()}, -// // ), -// // ), -// // ], -// // ), -// // ], -// // ), -// ]), -// ); -// } - -// // Widget _status() { -// // String statusText; -// // -// // if (!_isJoined) { -// // statusText = 'Join a channel'.tr; -// // } else if (_remoteUid == null) -// // statusText = 'Waiting for a remote user to join...'; -// // else -// // statusText = 'Connected to remote user, uid:$_remoteUid'; -// // -// // return Text( -// // statusText, -// // ); -// // } -// } diff --git a/siro_rider/lib/views/home/map_widget.dart/car_details_widget_to_go.dart b/siro_rider/lib/views/home/map_widget.dart/car_details_widget_to_go.dart index e0836332..3d4ca027 100644 --- a/siro_rider/lib/views/home/map_widget.dart/car_details_widget_to_go.dart +++ b/siro_rider/lib/views/home/map_widget.dart/car_details_widget_to_go.dart @@ -1,6 +1,5 @@ import 'package:siro_rider/constant/currency.dart'; import 'package:flutter/material.dart'; -import 'package:siro_rider/controller/home/map/map_engine_controller.dart'; import 'package:get/get.dart'; import 'package:siro_rider/constant/box_name.dart'; import 'package:siro_rider/constant/colors.dart'; @@ -9,9 +8,9 @@ import 'package:siro_rider/main.dart'; import 'package:siro_rider/views/home/profile/passenger_profile_page.dart'; import 'package:siro_rider/views/widgets/elevated_btn.dart'; import 'package:siro_rider/views/widgets/my_textField.dart'; -import 'package:intl/intl.dart'; import 'dart:ui'; +import '../../../constant/design.dart'; import '../../../constant/info.dart'; import '../../../controller/functions/tts.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart'; @@ -31,32 +30,56 @@ class CarType { {required this.carType, required this.carDetail, required this.image}); } -List carTypes = [ - CarType( +// ───────────────────────────────────────────────────────────────────────────── +// CAR TYPE CATALOG (per-country availability) +// ───────────────────────────────────────────────────────────────────────────── +// كل نوع سيارة معرَّف مرة واحدة هنا؛ الدولة تحدد أي الأنواع تظهر للراكب. +final Map _carTypeCatalog = { + 'Fixed Price': CarType( carType: 'Fixed Price', carDetail: 'Closest & Cheapest'.tr, image: 'assets/images/carspeed.png'), - CarType( + 'Comfort': CarType( carType: 'Comfort', carDetail: 'Comfort choice'.tr, image: 'assets/images/blob.png'), - CarType( + 'Electric': CarType( carType: 'Electric', carDetail: 'Quiet & Eco-Friendly'.tr, image: 'assets/images/electric.png'), - CarType( + 'Lady': CarType( carType: 'Lady', carDetail: 'Lady Captain for girls'.tr, image: 'assets/images/lady.png'), - CarType( + 'Van': CarType( carType: 'Van', carDetail: 'Van for familly'.tr, image: 'assets/images/bus.png'), - CarType( - carType: 'Rayeh Gai', - carDetail: "Best choice for cities".tr, - image: 'assets/images/roundtrip.png'), -]; + 'Scooter': CarType( + carType: 'Scooter', + carDetail: 'Motorcycle for one'.tr, + image: 'assets/images/moto.png'), + 'Awfar Car': CarType( + carType: 'Awfar Car', + carDetail: 'Old and affordable'.tr, + image: 'assets/images/balash.png'), +}; + +// ترتيب وتوفر أنواع السيارات حسب الدولة (بدون "Rayeh Gai" لأنها تُضاف +// تلقائياً حسب مسافة الرحلة بغض النظر عن الدولة). +const Map> _carTypesByCountry = { + 'Jordan': ['Fixed Price', 'Comfort', 'Electric', 'Lady'], + 'Egypt': ['Fixed Price', 'Comfort', 'Lady', 'Scooter', 'Awfar Car'], + 'Syria': ['Fixed Price', 'Comfort', 'Electric', 'Lady', 'Van'], +}; + +List _carTypesForCountry(String country) { + final keys = _carTypesByCountry[country] ?? _carTypesByCountry['Jordan']!; + return keys.map((k) => _carTypeCatalog[k]!).toList(); +} + +List carTypes = _carTypesForCountry( + box.read(BoxName.countryCode) ?? 'Jordan'); // ───────────────────────────────────────────────────────────────────────────── // MAIN WIDGET @@ -65,7 +88,15 @@ class CarDetailsTypeToChoose extends StatelessWidget { CarDetailsTypeToChoose({super.key}); final textToSpeechController = Get.find(); + static String? _lastPreparedCountry; + void _prepareCarTypes(RideLifecycleController controller) { + final String country = box.read(BoxName.countryCode) ?? 'Jordan'; + if (_lastPreparedCountry != country) { + carTypes = _carTypesForCountry(country); + _lastPreparedCountry = country; + } + if (controller.distance > 23) { if (!carTypes.any((car) => car.carType == 'Rayeh Gai')) { carTypes.add(CarType( @@ -92,43 +123,24 @@ class CarDetailsTypeToChoose extends StatelessWidget { left: 0, right: 0, child: ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(24), - topRight: Radius.circular(24), - ), + borderRadius: AppRadii.sheetTop, child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12), child: Container( decoration: BoxDecoration( color: AppColor.secondaryColor.withAlpha(240), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(24), - topRight: Radius.circular(24), - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withAlpha(30), - blurRadius: 30, - spreadRadius: 0, - offset: const Offset(0, -8), - ), - ], + borderRadius: AppRadii.sheetTop, + boxShadow: AppShadows.sheet, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // ── Drag Handle ────────────────────────────────────── - Center( - child: Container( - width: 32, - height: 3, - margin: const EdgeInsets.only(top: 6, bottom: 2), - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(2), - ), - ), + const Padding( + padding: EdgeInsets.only(top: AppSpacing.sm), + child: SiroSheetHandle(), ), + AppSpacing.vGapXs, // ── Header ─────────────────────────────────────────── _buildHeader(controller), @@ -250,7 +262,8 @@ class CarDetailsTypeToChoose extends StatelessWidget { controller.update(); }, child: Container( - padding: const EdgeInsets.all(5), + padding: const EdgeInsets.all(9), + constraints: const BoxConstraints(minWidth: 36, minHeight: 36), decoration: BoxDecoration( color: Get.isDarkMode ? Colors.white.withOpacity(0.08) @@ -294,258 +307,45 @@ class CarDetailsTypeToChoose extends StatelessWidget { } // ═══════════════════════════════════════════════════════════════════════════ - // CAR CARD + // CAR CARD (delegates rendering to _CarTypeCard) // ═══════════════════════════════════════════════════════════════════════════ Widget _buildCarCard(BuildContext context, RideLifecycleController controller, CarType carType, bool isSelected, int index) { - return GestureDetector( + return _CarTypeCard( + carType: carType, + isSelected: isSelected, + priceText: + '${_getPassengerPriceText(carType, controller)} ${CurrencyHelper.currency}', onTap: () { controller.selectCarFromList(index); _showCarDetailsDialog( context, controller, carType, textToSpeechController); }, - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - curve: Curves.easeOutCubic, - width: 88, - decoration: BoxDecoration( - gradient: isSelected - ? LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - AppColor.primaryColor.withAlpha(22), - AppColor.primaryColor.withAlpha(10), - ], - ) - : null, - color: isSelected ? null : AppColor.secondaryColor, - borderRadius: BorderRadius.circular(14), - border: Border.all( - color: isSelected - ? AppColor.primaryColor - : AppColor.grayColor.withOpacity(0.25), - width: isSelected ? 2 : 1.2, - ), - boxShadow: [ - BoxShadow( - color: isSelected - ? AppColor.primaryColor.withAlpha(50) - : Colors.black.withAlpha(12), - blurRadius: isSelected ? 10 : 4, - offset: const Offset(0, 2), - ), - ], - ), - child: Stack( - children: [ - // Selected indicator - if (isSelected) - Positioned( - top: 3, - right: 3, - child: Container( - width: 18, - height: 18, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - AppColor.primaryColor, - AppColor.primaryColor.withAlpha(200), - ], - ), - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: AppColor.primaryColor.withAlpha(60), - blurRadius: 4, - ), - ], - ), - child: const Icon(Icons.check, size: 11, color: Colors.white), - ), - ), - - // Card content - Padding( - padding: const EdgeInsets.fromLTRB(6, 6, 6, 6), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Car image - AnimatedScale( - scale: isSelected ? 1.1 : 1.0, - duration: const Duration(milliseconds: 300), - child: Image.asset( - carType.image, - height: 34, - fit: BoxFit.contain, - ), - ), - const SizedBox(height: 1), - - // Car name - FittedBox( - fit: BoxFit.scaleDown, - child: Text( - carType.carType.tr, - style: TextStyle( - fontWeight: - isSelected ? FontWeight.w800 : FontWeight.w600, - fontSize: 11, - color: isSelected - ? AppColor.primaryColor - : AppColor.writeColor, - ), - maxLines: 1, - ), - ), - - const SizedBox(height: 1), - - // Price tag - Container( - padding: - const EdgeInsets.symmetric(horizontal: 6, vertical: 1), - decoration: BoxDecoration( - color: isSelected - ? AppColor.primaryColor - : AppColor.writeColor.withOpacity(0.05), - borderRadius: BorderRadius.circular(8), - border: isSelected - ? null - : Border.all( - color: AppColor.grayColor.withOpacity(0.2)), - ), - child: FittedBox( - child: Text( - '${_getPassengerPriceText(carType, controller)} ${CurrencyHelper.currency}', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w800, - color: isSelected - ? Colors.white - : AppColor.writeColor.withOpacity(0.8), - ), - ), - ), - ), - ], - ), - ), - ], - ), - ), ); } // ═══════════════════════════════════════════════════════════════════════════ - // PROMO BUTTON + // PROMO BUTTON (delegates rendering to _PromoCodeBanner) // ═══════════════════════════════════════════════════════════════════════════ Widget _buildPromoButton( BuildContext context, RideLifecycleController controller) { if (controller.promoTaken) return const SizedBox.shrink(); - - return Padding( - padding: const EdgeInsets.fromLTRB(16, 2, 16, 0), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () => _showPromoCodeDialog(context, controller), - borderRadius: BorderRadius.circular(12), - child: Container( - padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 10), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Colors.amber.shade50, - Colors.orange.shade50, - ], - ), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.amber.shade200), - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: Colors.amber.shade100, - shape: BoxShape.circle, - ), - child: Icon(Icons.percent_rounded, - color: Colors.amber.shade800, size: 14), - ), - const SizedBox(width: 8), - Expanded( - child: Text( - 'Have a Promo Code?'.tr, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w700, - color: Colors.amber.shade900, - ), - ), - ), - Container( - padding: const EdgeInsets.all(3), - decoration: BoxDecoration( - color: Colors.amber.shade100, - borderRadius: BorderRadius.circular(6), - ), - child: Icon(Icons.arrow_forward_ios_rounded, - size: 10, color: Colors.amber.shade800), - ), - ], - ), - ), - ), - ), + return _PromoCodeBanner( + onTap: () => _showPromoCodeDialog(context, controller), ); } // ═══════════════════════════════════════════════════════════════════════════ - // NEGATIVE BALANCE WARNING + // NEGATIVE BALANCE WARNING (delegates rendering to _NegativeBalanceBanner) // ═══════════════════════════════════════════════════════════════════════════ Widget _buildNegativeBalanceWarning(RideLifecycleController controller) { final passengerWallet = double.tryParse(box.read(BoxName.passengerWalletTotal) ?? '0.0') ?? 0.0; - if (passengerWallet < 0.0) { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 3), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), - decoration: BoxDecoration( - color: Colors.red.shade50, - borderRadius: BorderRadius.circular(10), - border: Border.all(color: Colors.red.shade200), - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: Colors.red.shade100, - shape: BoxShape.circle, - ), - child: Icon(Icons.warning_amber_rounded, - color: Colors.red.shade700, size: 14), - ), - const SizedBox(width: 8), - Expanded( - child: Text( - '${'You have a negative balance of'.tr} ${passengerWallet.toStringAsFixed(2)} ${CurrencyHelper.currency}.', - style: TextStyle( - color: Colors.red.shade800, - fontWeight: FontWeight.w600, - fontSize: 11, - ), - ), - ), - ], - ), - ); - } - return const SizedBox.shrink(); + if (passengerWallet >= 0.0) return const SizedBox.shrink(); + return _NegativeBalanceBanner( + message: + '${'You have a negative balance of'.tr} ${passengerWallet.toStringAsFixed(2)} ${CurrencyHelper.currency}.', + ); } // ═══════════════════════════════════════════════════════════════════════════ @@ -852,12 +652,12 @@ class CarDetailsTypeToChoose extends StatelessWidget { case 'Electric': return 'Travel in a modern, silent electric car. A premium, eco-friendly choice for a smooth ride.' .tr; - case 'Scooter': case 'Van': return "Spacious van service ideal for families and groups. Comfortable, safe, and cost-effective travel together." .tr; + case 'Scooter': case 'Pink Bike': - return 'This is for delivery or a motorcycle.'.tr; + return 'A quick, affordable motorcycle ride for one.'.tr; case 'Mishwar Vip': return "Perfect for passengers seeking the latest car models with the freedom to choose any route they desire" .tr; @@ -939,6 +739,9 @@ class CarDetailsTypeToChoose extends StatelessWidget { return mapPassengerController.totalPassengerVan; case 'Lady': return mapPassengerController.totalPassengerLady; + case 'Scooter': + case 'Pink Bike': + return mapPassengerController.totalPassengerScooter; default: return '0'; } @@ -968,6 +771,281 @@ class CarDetailsTypeToChoose extends StatelessWidget { } } +// ───────────────────────────────────────────────────────────────────────────── +// CAR TYPE CARD (selectable ride option in the horizontal list) +// ───────────────────────────────────────────────────────────────────────────── +class _CarTypeCard extends StatelessWidget { + const _CarTypeCard({ + required this.carType, + required this.isSelected, + required this.priceText, + required this.onTap, + }); + + final CarType carType; + final bool isSelected; + final String priceText; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: AppDurations.base, + curve: Curves.easeOutCubic, + width: 88, + decoration: BoxDecoration( + gradient: isSelected + ? LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColor.primaryColor.withAlpha(22), + AppColor.primaryColor.withAlpha(10), + ], + ) + : null, + color: isSelected ? null : AppColor.secondaryColor, + borderRadius: AppRadii.all(AppRadii.lg), + border: Border.all( + color: isSelected + ? AppColor.primaryColor + : AppColor.grayColor.withOpacity(0.25), + width: isSelected ? 2 : 1.2, + ), + boxShadow: [ + BoxShadow( + color: isSelected + ? AppColor.primaryColor.withAlpha(50) + : Colors.black.withAlpha(12), + blurRadius: isSelected ? 10 : 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Stack( + children: [ + // Selected indicator + if (isSelected) + Positioned( + top: 3, + right: 3, + child: Container( + width: 18, + height: 18, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.primaryColor, + AppColor.primaryColor.withAlpha(200), + ], + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColor.primaryColor.withAlpha(60), + blurRadius: 4, + ), + ], + ), + child: const Icon(Icons.check, size: 11, color: Colors.white), + ), + ), + + // Card content + Padding( + padding: const EdgeInsets.all(6), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Car image + AnimatedScale( + scale: isSelected ? 1.1 : 1.0, + duration: AppDurations.base, + child: Image.asset( + carType.image, + height: 34, + fit: BoxFit.contain, + ), + ), + const SizedBox(height: 1), + + // Car name + FittedBox( + fit: BoxFit.scaleDown, + child: Text( + carType.carType.tr, + style: TextStyle( + fontWeight: + isSelected ? FontWeight.w800 : FontWeight.w600, + fontSize: 11, + color: isSelected + ? AppColor.primaryColor + : AppColor.writeColor, + ), + maxLines: 1, + ), + ), + + const SizedBox(height: 1), + + // Price tag + Container( + padding: + const EdgeInsets.symmetric(horizontal: 6, vertical: 1), + decoration: BoxDecoration( + color: isSelected + ? AppColor.primaryColor + : AppColor.writeColor.withOpacity(0.05), + borderRadius: AppRadii.all(AppRadii.sm), + border: isSelected + ? null + : Border.all( + color: AppColor.grayColor.withOpacity(0.2)), + ), + child: FittedBox( + child: Text( + priceText, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w800, + color: isSelected + ? Colors.white + : AppColor.writeColor.withOpacity(0.8), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// PROMO CODE BANNER +// ───────────────────────────────────────────────────────────────────────────── +class _PromoCodeBanner extends StatelessWidget { + const _PromoCodeBanner({required this.onTap}); + + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.fromLTRB(AppSpacing.lg, 2, AppSpacing.lg, 0), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + borderRadius: AppRadii.all(AppRadii.md), + child: Container( + constraints: const BoxConstraints(minHeight: 44), + padding: const EdgeInsets.symmetric( + vertical: AppSpacing.sm, horizontal: AppSpacing.md), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.amber.shade50, + Colors.orange.shade50, + ], + ), + borderRadius: AppRadii.all(AppRadii.md), + border: Border.all(color: Colors.amber.shade200), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: Colors.amber.shade100, + shape: BoxShape.circle, + ), + child: Icon(Icons.percent_rounded, + color: Colors.amber.shade800, size: 14), + ), + AppSpacing.gapSm, + Expanded( + child: Text( + 'Have a Promo Code?'.tr, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w700, + color: Colors.amber.shade900, + ), + ), + ), + Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + color: Colors.amber.shade100, + borderRadius: AppRadii.all(AppRadii.sm), + ), + child: Icon(Icons.arrow_forward_ios_rounded, + size: 10, color: Colors.amber.shade800), + ), + ], + ), + ), + ), + ), + ); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// NEGATIVE BALANCE BANNER +// ───────────────────────────────────────────────────────────────────────────── +class _NegativeBalanceBanner extends StatelessWidget { + const _NegativeBalanceBanner({required this.message}); + + final String message; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric( + horizontal: AppSpacing.lg, vertical: 3), + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.md, vertical: AppSpacing.sm), + decoration: BoxDecoration( + color: Colors.red.shade50, + borderRadius: AppRadii.all(AppRadii.sm), + border: Border.all(color: Colors.red.shade200), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Colors.red.shade100, + shape: BoxShape.circle, + ), + child: Icon(Icons.warning_amber_rounded, + color: Colors.red.shade700, size: 14), + ), + AppSpacing.gapSm, + Expanded( + child: Text( + message, + style: TextStyle( + color: Colors.red.shade800, + fontWeight: FontWeight.w600, + fontSize: 11, + ), + ), + ), + ], + ), + ); + } +} + // ───────────────────────────────────────────────────────────────────────────── // BURC MONEY WIDGET (Floating negative balance banner) // ───────────────────────────────────────────────────────────────────────────── diff --git a/siro_rider/lib/views/home/map_widget.dart/cash_confirm_bottom_page.dart b/siro_rider/lib/views/home/map_widget.dart/cash_confirm_bottom_page.dart index 5888ae8d..e6bd63dd 100644 --- a/siro_rider/lib/views/home/map_widget.dart/cash_confirm_bottom_page.dart +++ b/siro_rider/lib/views/home/map_widget.dart/cash_confirm_bottom_page.dart @@ -6,6 +6,7 @@ import 'package:siro_rider/constant/style.dart'; import 'package:siro_rider/views/home/my_wallet/passenger_wallet.dart'; import '../../../constant/colors.dart'; +import '../../../constant/design.dart'; import '../../../constant/info.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart'; import '../../../controller/payment/payment_controller.dart'; @@ -25,26 +26,12 @@ class CashConfirmPageShown extends StatelessWidget { left: 0, right: 0, child: AnimatedContainer( - duration: const Duration(milliseconds: 300), + duration: AppDurations.base, curve: Curves.easeInOut, // التحكم في ظهور اللوحة لم يتغير transform: Matrix4.translationValues( 0, controller.isCashConfirmPageShown ? 0 : Get.height, 0), - decoration: BoxDecoration( - color: AppColor.secondaryColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(24), - topRight: Radius.circular(24), - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.2), - blurRadius: 20, - ), - ], - ), - child: Padding( - padding: const EdgeInsets.all(20.0), + child: SiroSheetSurface( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -55,7 +42,7 @@ class CashConfirmPageShown extends StatelessWidget { children: [ Text( 'Payment Method'.tr, - style: AppStyle.headTitle.copyWith(fontSize: 24), + style: AppStyle.headTitle.copyWith(fontSize: 22), ), // زر الإغلاق (كان معلقاً في الكود القديم، تم تفعيله هنا) IconButton( @@ -64,7 +51,7 @@ class CashConfirmPageShown extends StatelessWidget { ), ], ), - const SizedBox(height: 16), + AppSpacing.vGapLg, // --- 2. بطاقات اختيار الدفع --- GetBuilder(builder: (paymentCtrl) { @@ -87,7 +74,7 @@ class CashConfirmPageShown extends StatelessWidget { onTap: () => paymentCtrl.onChangedPaymentMethodWallet(true), ), - const SizedBox(height: 12), + AppSpacing.vGapMd, // بطاقة الكاش _buildPaymentOptionCard( icon: Icons.money_rounded, @@ -101,7 +88,7 @@ class CashConfirmPageShown extends StatelessWidget { ], ); }), - const SizedBox(height: 24), + AppSpacing.vGapXl, // --- 3. أزرار التأكيد (بنفس منطقك القديم تماماً) --- GetBuilder(builder: (paymentCtrl) { @@ -165,13 +152,14 @@ class CashConfirmPageShown extends StatelessWidget { return GestureDetector( onTap: onTap, child: AnimatedContainer( - duration: const Duration(milliseconds: 250), - padding: const EdgeInsets.all(16), + duration: AppDurations.fast, + padding: const EdgeInsets.all(AppSpacing.lg), + constraints: const BoxConstraints(minHeight: AppSizes.minTouch), decoration: BoxDecoration( color: isSelected ? selectedColor.withOpacity(0.1) : AppColor.writeColor.withOpacity(0.05), - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(AppRadii.md), border: Border.all( color: isSelected ? selectedColor diff --git a/siro_rider/lib/views/home/map_widget.dart/driver_card_from_passenger.dart b/siro_rider/lib/views/home/map_widget.dart/driver_card_from_passenger.dart deleted file mode 100644 index 48c0dd3e..00000000 --- a/siro_rider/lib/views/home/map_widget.dart/driver_card_from_passenger.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -import '../../../constant/style.dart'; -import '../../../controller/home/map/ride_lifecycle_controller.dart'; -import 'hexegone_clipper.dart'; - -GetBuilder hexagonClipper() { - return GetBuilder( - builder: ((controller) => controller.rideConfirm - ? Positioned( - top: Get.height * .1, - left: Get.width * .1, - right: Get.width * .1, - child: ClipPath( - clipper: - HexagonClipper(), // CustomClipper to create a hexagon shape - child: AnimatedContainer( - duration: const Duration(microseconds: 300), - height: 250, - width: 250, - // decoration: AppStyle.boxDecoration, - // gradient: const LinearGradient( - // colors: [AppColor.greenColor, AppColor.secondaryColor], - // begin: Alignment.topLeft, - // end: Alignment.bottomCenter, - // ), - // border: Border.all(), - // color: AppColor.secondaryColor, - // borderRadius: BorderRadius.circular(15)), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - 'Waiting for Driver ...'.tr, - style: AppStyle.title, - ), - // IconButton( - // onPressed: () { - // }, - // icon: const Icon(Icons.add), - // ), - // Text( - // controller.dataCarsLocationByPassenger['message'] - // [controller.carsOrder]['phone'] - // .toString(), - // style: AppStyle.title, - // ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - '${controller.dataCarsLocationByPassenger['message'][controller.carsOrder]['first_name']} ${controller.dataCarsLocationByPassenger['message'][controller.carsOrder]['last_name']}', - style: AppStyle.title, - ), - Text( - 'Age is '.tr + - controller - .dataCarsLocationByPassenger['message'] - [controller.carsOrder]['age'] - .toString(), - style: AppStyle.title, - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - controller.dataCarsLocationByPassenger['message'] - [controller.carsOrder]['make'] - .toString(), - style: AppStyle.title, - ), - Text( - controller.dataCarsLocationByPassenger['message'] - [controller.carsOrder]['model'] - .toString(), - style: AppStyle.title, - ), - ], - ), - Text( - 'Rating is '.tr + - controller.dataCarsLocationByPassenger['message'] - [controller.carsOrder]['ratingDriver'] - .toString(), - style: AppStyle.title, - ), - Container( - decoration: BoxDecoration(border: Border.all(width: 2)), - child: Text( - controller.dataCarsLocationByPassenger['message'] - [controller.carsOrder]['car_plate'] - .toString(), - style: AppStyle.title, - ), - ), - ], - ), - ), - ), - ) - : const SizedBox())); -} diff --git a/siro_rider/lib/views/home/map_widget.dart/driver_time_arrive_passenger.dart b/siro_rider/lib/views/home/map_widget.dart/driver_time_arrive_passenger.dart deleted file mode 100644 index 7f24f653..00000000 --- a/siro_rider/lib/views/home/map_widget.dart/driver_time_arrive_passenger.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:intl/intl.dart'; -// import 'package:intl/intl.dart'; - -import '../../../constant/style.dart'; -import '../../../controller/home/map/ride_lifecycle_controller.dart'; - -class DriverTimeArrivePassengerPage extends StatelessWidget { - const DriverTimeArrivePassengerPage({super.key}); - - @override - Widget build(BuildContext context) { - return GetBuilder( - builder: (controller) { - return controller.remainingTime == 0 - ? Positioned( - bottom: Get.height * .35, - right: Get.width * .05, - child: Stack( - alignment: Alignment.center, - children: [ - Container( - decoration: AppStyle.boxDecoration, - // width: 50, - // height: 50, - child: Padding( - padding: const EdgeInsetsDirectional.only( - start: 5, end: 5), - child: Column( - children: [ - Text( - controller.durationByPassenger.toString() + - ' to arrive you.'.tr, - style: AppStyle.title, - ), - Text( - " ${DateFormat('h:mm a').format(controller.newTime)}", - style: AppStyle.title, - ), - ], - ), - )) - ], - ), - ) - : const SizedBox(); - }, - ); - } -} diff --git a/siro_rider/lib/views/home/map_widget.dart/form_search_places_destenation.dart b/siro_rider/lib/views/home/map_widget.dart/form_search_places_destenation.dart deleted file mode 100644 index 7b11e719..00000000 --- a/siro_rider/lib/views/home/map_widget.dart/form_search_places_destenation.dart +++ /dev/null @@ -1,533 +0,0 @@ -import 'package:siro_rider/print.dart'; -import 'package:siro_rider/views/widgets/mydialoug.dart'; -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:intaleq_maps/intaleq_maps.dart'; -import 'package:siro_rider/constant/box_name.dart'; -import 'package:siro_rider/constant/table_names.dart'; - -import '../../../constant/colors.dart'; -import '../../../constant/style.dart'; -import '../../../controller/functions/toast.dart'; -import '../../../controller/home/map/location_search_controller.dart'; -import '../../../controller/home/map/map_engine_controller.dart'; -import '../../../controller/home/map/ride_lifecycle_controller.dart'; -import '../../../main.dart'; - -// --------------------------------------------------- -// -- Widget for Destination Point Search (Optimized) -- -// --------------------------------------------------- - -GetBuilder formSearchPlacesDestenation() { - final String addWorkValue = - box.read(BoxName.addWork)?.toString() ?? 'addWork'; - final String addHomeValue = - box.read(BoxName.addHome)?.toString() ?? 'addHome'; - - if (addWorkValue.isEmpty || addHomeValue.isEmpty) { - box.write(BoxName.addWork, 'addWork'); - box.write(BoxName.addHome, 'addHome'); - } - - return GetBuilder( - id: 'destination_form', - builder: (controller) { - final mapEngine = Get.find(); - final rideLifecycle = Get.find(); - return Column( - children: [ - _SearchField( - controller: controller, - mapEngine: mapEngine, - rideLifecycle: rideLifecycle, - ), - _QuickActions( - controller: controller, - mapEngine: mapEngine, - rideLifecycle: rideLifecycle, - addWorkValue: addWorkValue, - addHomeValue: addHomeValue, - ), - _SearchResults( - controller: controller, - mapEngine: mapEngine, - rideLifecycle: rideLifecycle, - ), - ], - ); - }, - ); -} - -// --------------------------------------------------- -// -- Private Helper Widgets for Cleaner Code -- -// --------------------------------------------------- - -class _SearchField extends StatefulWidget { - final LocationSearchController controller; - final MapEngineController mapEngine; - final RideLifecycleController rideLifecycle; - - const _SearchField({ - required this.controller, - required this.mapEngine, - required this.rideLifecycle, - }); - - @override - State<_SearchField> createState() => _SearchFieldState(); -} - -class _SearchFieldState extends State<_SearchField> { - Timer? _debounce; - - void _onTextChanged() { - if (mounted) { - setState(() {}); - } - } - - @override - void initState() { - super.initState(); - widget.controller.placeDestinationController.addListener(_onTextChanged); - } - - void _onSearchChanged(String query) { - if (_debounce?.isActive ?? false) _debounce!.cancel(); - _debounce = Timer(const Duration(milliseconds: 500), () { - if (query.length > 2) { - widget.controller.getPlaces(); - widget.mapEngine.changeHeightPlaces(); - } else if (query.isEmpty) { - widget.controller.clearPlacesDestination(); - widget.mapEngine.changeHeightPlaces(); - } - }); - } - - @override - void dispose() { - _debounce?.cancel(); - widget.controller.placeDestinationController.removeListener(_onTextChanged); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - const Color accent = Color(0xFFEF4444); // matches the red destination dot - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 6.0), - child: Container( - padding: const EdgeInsets.all(6), - decoration: BoxDecoration( - gradient: LinearGradient(colors: [ - accent.withOpacity(0.08), - accent.withOpacity(0.03), - ]), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: accent.withOpacity(0.35), width: 1.4), - ), - child: Row( - children: [ - Container( - width: 34, - height: 34, - decoration: BoxDecoration( - color: accent, - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: accent.withOpacity(0.4), - blurRadius: 10, - offset: const Offset(0, 3)), - ], - ), - child: - const Icon(Icons.flag_rounded, color: Colors.white, size: 17), - ), - const SizedBox(width: 10), - Expanded( - child: TextFormField( - controller: widget.controller.placeDestinationController, - onChanged: _onSearchChanged, - style: const TextStyle( - fontSize: 14.5, fontWeight: FontWeight.w700), - decoration: InputDecoration( - hintText: widget.controller.hintTextDestinationPoint, - hintStyle: AppStyle.subtitle.copyWith( - color: accent.withOpacity(0.85), - fontWeight: FontWeight.w700, - fontSize: 14.5, - ), - isDense: true, - suffixIcon: widget - .controller.placeDestinationController.text.isNotEmpty - ? IconButton( - icon: Icon(Icons.clear, color: Colors.grey[400]), - onPressed: () { - widget.controller.placeDestinationController - .clear(); - }, - ) - : null, - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - focusedBorder: InputBorder.none, - filled: false, - ), - ), - ), - const SizedBox(width: 6.0), - InkWell( - borderRadius: BorderRadius.circular(12), - onTap: () { - widget.mapEngine.changeMainBottomMenuMap(); - widget.mapEngine.changePickerShown(); - }, - child: Tooltip( - message: widget.rideLifecycle.isAnotherOreder - ? 'Pick destination on map'.tr - : 'Pick on map'.tr, - child: Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: accent.withOpacity(0.3)), - ), - child: Icon(Icons.map_rounded, color: accent, size: 18), - ), - ), - ), - ], - ), - ), - ); - } -} - -class _QuickActions extends StatelessWidget { - final LocationSearchController controller; - final MapEngineController mapEngine; - final RideLifecycleController rideLifecycle; - final String addWorkValue; - final String addHomeValue; - - const _QuickActions({ - required this.controller, - required this.mapEngine, - required this.rideLifecycle, - required this.addWorkValue, - required this.addHomeValue, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildQuickActionButton( - icon: Icons.work_outline, - text: addWorkValue == 'addWork' ? 'Add Work'.tr : 'To Work'.tr, - onTap: () { - if (addWorkValue == 'addWork') { - controller.workLocationFromMap = true; - mapEngine.changeMainBottomMenuMap(); - mapEngine.changePickerShown(); - } else { - _handleQuickAction( - controller, - mapEngine, - rideLifecycle, - BoxName.addWork, - 'To Work', - ); - } - }, - onLongPress: () => - _showChangeLocationDialog(controller, mapEngine, 'Work'), - ), - _buildQuickActionButton( - icon: Icons.home_outlined, - text: addHomeValue == 'addHome' ? 'Add Home'.tr : 'To Home'.tr, - onTap: () { - if (addHomeValue == 'addHome') { - controller.homeLocationFromMap = true; - mapEngine.changeMainBottomMenuMap(); - mapEngine.changePickerShown(); - } else { - _handleQuickAction( - controller, - mapEngine, - rideLifecycle, - BoxName.addHome, - 'To Home', - ); - } - }, - onLongPress: () => - _showChangeLocationDialog(controller, mapEngine, 'Home'), - ), - ], - ), - ); - } -} - -class _SearchResults extends StatelessWidget { - final LocationSearchController controller; - final MapEngineController mapEngine; - final RideLifecycleController rideLifecycle; - - const _SearchResults({ - required this.controller, - required this.mapEngine, - required this.rideLifecycle, - }); - - @override - Widget build(BuildContext context) { - return GetBuilder( - id: 'places_list', - builder: (locCtrl) { - return AnimatedContainer( - duration: const Duration(milliseconds: 200), - height: locCtrl.placesDestination.isNotEmpty ? 300 : 0, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8.0), - ), - margin: const EdgeInsets.symmetric(horizontal: 16.0), - child: ListView.separated( - shrinkWrap: true, - physics: const ClampingScrollPhysics(), - itemCount: locCtrl.placesDestination.length, - separatorBuilder: (context, index) => - const Divider(height: 1, color: Colors.grey), - itemBuilder: (BuildContext context, int index) { - final res = locCtrl.placesDestination[index]; - final title = res['name_ar'] ?? res['name'] ?? 'Unknown Place'; - final address = res['address'] ?? 'Details not available'; - final latitude = res['latitude']; - final longitude = res['longitude']; - - return ListTile( - leading: const Icon(Icons.place, size: 30, color: Colors.grey), - title: Text( - title, - style: - AppStyle.subtitle.copyWith(fontWeight: FontWeight.w500), - ), - subtitle: Text( - address, - style: TextStyle(color: Colors.grey[600], fontSize: 12), - ), - trailing: IconButton( - icon: const Icon(Icons.favorite_border, color: Colors.grey), - onPressed: () => _handleAddToFavorites( - context, latitude, longitude, title), - ), - onTap: () => _handlePlaceSelection( - controller, - mapEngine, - rideLifecycle, - latitude, - longitude, - title, - index, - ), - ); - }, - ), - ); - }, - ); - } - - Future _handleAddToFavorites(BuildContext context, dynamic latitude, - dynamic longitude, String title) async { - if (latitude != null && longitude != null) { - await sql.insertMapLocation({ - 'latitude': latitude, - 'longitude': longitude, - 'name': title, - 'rate': 'N/A', - }, TableName.placesFavorite); - - Toast.show( - context, - '$title ${'Saved Successfully'.tr}', - AppColor.primaryColor, - ); - } else { - Toast.show( - context, - 'Invalid location data', - AppColor.redColor, - ); - } - } - - Future _handlePlaceSelection( - LocationSearchController controller, - MapEngineController mapEngine, - RideLifecycleController rideLifecycle, - dynamic latitude, - dynamic longitude, - String title, - int index) async { - if (latitude == null || longitude == null) { - Toast.show(Get.context!, 'Invalid location data', AppColor.redColor); - return; - } - - await sql.insertMapLocation({ - 'latitude': latitude, - 'longitude': longitude, - 'name': title, - 'rate': 'N/A', - 'createdAt': DateTime.now().toIso8601String(), - }, TableName.recentLocations); - - final destLatLng = LatLng( - double.parse(latitude.toString()), double.parse(longitude.toString())); - - if (rideLifecycle.isAnotherOreder) { - await _handleAnotherOrderSelection( - controller, mapEngine, rideLifecycle, destLatLng); - } else { - _handleRegularOrderSelection( - controller, mapEngine, rideLifecycle, destLatLng, index); - } - } - - Future _handleAnotherOrderSelection( - LocationSearchController controller, - MapEngineController mapEngine, - RideLifecycleController rideLifecycle, - LatLng destination) async { - controller.myDestination = destination; - controller.clearPlacesDestination(); - - await rideLifecycle.getDirectionMap( - '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', - '${controller.myDestination.latitude},${controller.myDestination.longitude}'); - - mapEngine.isPickerShown = false; - controller.passengerStartLocationFromMap = false; - mapEngine.changeMainBottomMenuMap(); - rideLifecycle.showBottomSheet1(); - } - - void _handleRegularOrderSelection( - LocationSearchController controller, - MapEngineController mapEngine, - RideLifecycleController rideLifecycle, - LatLng destination, - int index) { - controller.passengerLocation = controller.newMyLocation; - controller.myDestination = destination; - controller.convertHintTextDestinationNewPlaces(index); - - controller.clearPlacesDestination(); - - mapEngine.changeMainBottomMenuMap(); - controller.passengerStartLocationFromMap = true; - mapEngine.isPickerShown = true; - - rideLifecycle.getDirectionMap( - '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', - '${controller.myDestination.latitude},${controller.myDestination.longitude}'); - } -} - -Widget _buildQuickActionButton({ - required IconData icon, - required String text, - VoidCallback? onTap, - VoidCallback? onLongPress, -}) { - return InkWell( - onTap: onTap, - onLongPress: onLongPress, - child: Container( - padding: const EdgeInsets.all(8.0), - decoration: BoxDecoration( - color: AppColor.cyanBlue.withOpacity(0.1), - borderRadius: BorderRadius.circular(8.0), - border: Border.all(color: AppColor.cyanBlue.withOpacity(0.3)), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(icon, color: AppColor.cyanBlue), - const SizedBox(height: 4.0), - Text( - text, - textAlign: TextAlign.center, - style: AppStyle.title.copyWith( - color: AppColor.cyanBlue, fontWeight: FontWeight.w500), - ), - ], - ), - ), - ); -} - -void _showChangeLocationDialog(LocationSearchController controller, - MapEngineController mapEngine, String locationType) { - MyDialog().getDialog( - locationType == 'Work' - ? 'Change Work location ?'.tr - : 'Change Home location ?'.tr, - '', - () { - if (locationType == 'Work') { - controller.workLocationFromMap = true; - } else { - controller.homeLocationFromMap = true; - } - mapEngine.changeMainBottomMenuMap(); - mapEngine.changePickerShown(); - }, - ); -} - -void _handleQuickAction( - LocationSearchController controller, - MapEngineController mapEngine, - RideLifecycleController rideLifecycle, - String boxName, - String hintText) async { - try { - final locationString = box.read(boxName).toString(); - final parts = locationString.split(','); - final latLng = LatLng( - double.parse(parts[0]), - double.parse(parts[1]), - ); - - controller.hintTextDestinationPoint = hintText; - mapEngine.changeMainBottomMenuMap(); - - await rideLifecycle.getDirectionMap( - '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', - '${latLng.latitude},${latLng.longitude}', - ); - - controller.currentLocationToFormPlaces = false; - controller.clearPlacesDestination(); - controller.passengerStartLocationFromMap = false; - mapEngine.isPickerShown = false; - rideLifecycle.showBottomSheet1(); - } catch (e) { - Log.print("Error handling quick action: $e"); - Toast.show(Get.context!, "Failed to get location".tr, AppColor.redColor); - } -} diff --git a/siro_rider/lib/views/home/map_widget.dart/form_search_start.dart b/siro_rider/lib/views/home/map_widget.dart/form_search_start.dart deleted file mode 100644 index 07f079b2..00000000 --- a/siro_rider/lib/views/home/map_widget.dart/form_search_start.dart +++ /dev/null @@ -1,165 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:intaleq_maps/intaleq_maps.dart'; - -import '../../../constant/style.dart'; -import '../../../controller/home/map/location_search_controller.dart'; -import '../../../controller/home/map/map_engine_controller.dart'; - -// --------------------------------------------------- -// -- Widget for Start Point Search (Updated) -- -// --------------------------------------------------- - -GetBuilder formSearchPlacesStart() { - return GetBuilder( - id: 'start_point_form', - builder: (controller) { - final mapEngine = Get.find(); - const Color accent = Color(0xFF16A34A); // green: "their" pickup point - return Column( - children: [ - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 16.0, vertical: 6.0), - child: Container( - padding: const EdgeInsets.all(6), - decoration: BoxDecoration( - gradient: LinearGradient(colors: [ - accent.withOpacity(0.08), - accent.withOpacity(0.03), - ]), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: accent.withOpacity(0.35), width: 1.4), - ), - child: Row( - children: [ - Container( - width: 34, - height: 34, - decoration: BoxDecoration( - color: accent, - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: accent.withOpacity(0.4), - blurRadius: 10, - offset: const Offset(0, 3)), - ], - ), - child: const Icon(Icons.person_pin_circle_rounded, - color: Colors.white, size: 18), - ), - const SizedBox(width: 10), - Expanded( - child: TextFormField( - controller: controller.placeStartController, - style: const TextStyle( - fontSize: 14.5, fontWeight: FontWeight.w700), - onChanged: (value) { - if (controller.placeStartController.text.length > 2) { - controller.getPlacesStart(); - } else if (controller - .placeStartController.text.isEmpty) { - controller.clearPlacesStart(); - } - }, - decoration: InputDecoration( - hintText: 'Search for a starting point'.tr, - hintStyle: AppStyle.subtitle.copyWith( - color: accent.withOpacity(0.85), - fontWeight: FontWeight.w700, - fontSize: 14.5, - ), - isDense: true, - suffixIcon: controller - .placeStartController.text.isNotEmpty - ? IconButton( - icon: - Icon(Icons.clear, color: Colors.grey[400]), - onPressed: () { - controller.placeStartController.clear(); - controller.clearPlacesStart(); - }, - ) - : null, - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - focusedBorder: InputBorder.none, - filled: false, - ), - ), - ), - const SizedBox(width: 6.0), - InkWell( - borderRadius: BorderRadius.circular(12), - onTap: () { - controller.passengerStartLocationFromMap = true; - mapEngine.changeMainBottomMenuMap(); - mapEngine.changePickerShown(); - }, - child: Tooltip( - message: 'Pick start point on map'.tr, - child: Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: accent.withOpacity(0.3)), - ), - child: Icon(Icons.map_rounded, color: accent, size: 18), - ), - ), - ), - ], - ), - ), - ), - AnimatedContainer( - duration: const Duration(milliseconds: 200), - height: controller.placesStart.isNotEmpty ? 300 : 0, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8.0), - ), - margin: const EdgeInsets.symmetric(horizontal: 16.0), - child: ListView.separated( - shrinkWrap: true, - physics: const ClampingScrollPhysics(), - itemCount: controller.placesStart.length, - separatorBuilder: (context, index) => - const Divider(height: 1, color: Colors.grey), - itemBuilder: (BuildContext context, int index) { - var res = controller.placesStart[index]; - var title = res['name_ar'] ?? res['name'] ?? 'Unknown Place'; - var address = res['address'] ?? 'Details not available'; - - return ListTile( - leading: - const Icon(Icons.place, size: 30, color: Colors.grey), - title: Text(title, - style: AppStyle.subtitle - .copyWith(fontWeight: FontWeight.w500)), - subtitle: Text(address, - style: TextStyle(color: Colors.grey[600], fontSize: 12)), - onTap: () { - var latitude = res['latitude']; - var longitude = res['longitude']; - if (latitude != null && longitude != null) { - controller.passengerLocation = LatLng( - double.parse(latitude), double.parse(longitude)); - controller.placeStartController.text = title; - controller.clearPlacesStart(); - mapEngine.changeMainBottomMenuMap(); - controller.update(); - } - }, - ); - }, - ), - ), - ], - ); - }, - ); -} diff --git a/siro_rider/lib/views/home/map_widget.dart/hexegone_clipper.dart b/siro_rider/lib/views/home/map_widget.dart/hexegone_clipper.dart deleted file mode 100644 index 8cd05559..00000000 --- a/siro_rider/lib/views/home/map_widget.dart/hexegone_clipper.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; - -class HexagonClipper extends CustomClipper { - @override - Path getClip(Size size) { - final path = Path(); - final height = size.height; - final width = size.width; - final centerX = width / 2; - final centerY = height / 2; - final radius = width / 2; - - const angle = 2 * pi / 10; // Angle between each side of the hexagon - - // Start at the top right vertex of the hexagon - final startX = centerX + radius * cos(0); - final startY = centerY + radius * sin(0); - path.moveTo(startX, startY); - - // Draw the remaining sides of the hexagon - for (int i = 1; i < 10; i++) { - final x = centerX + radius * cos(angle * i); - final y = centerY + radius * sin(angle * i); - path.lineTo(x, y); - } - - path.close(); - - return path; - } - - @override - bool shouldReclip(HexagonClipper oldClipper) => false; -} - -class ArrowClipper extends CustomClipper { - @override - Path getClip(Size size) { - final path = Path(); - path.moveTo(0, size.height / 2); - path.lineTo(size.width / 2, 0); - path.lineTo(size.width, size.height / 2); - path.lineTo(size.width / 2, size.height); - path.close(); - return path; - } - - @override - bool shouldReclip(ArrowClipper oldClipper) => false; -} diff --git a/siro_rider/lib/views/home/map_widget.dart/left_main_menu_icons.dart b/siro_rider/lib/views/home/map_widget.dart/left_main_menu_icons.dart index a12bc273..1dfe3115 100644 --- a/siro_rider/lib/views/home/map_widget.dart/left_main_menu_icons.dart +++ b/siro_rider/lib/views/home/map_widget.dart/left_main_menu_icons.dart @@ -7,12 +7,15 @@ import 'package:flutter_font_icons/flutter_font_icons.dart'; import 'package:get/get.dart'; import 'package:intaleq_maps/intaleq_maps.dart'; import 'dart:ui'; +import 'dart:io'; import '../../../constant/colors.dart'; import '../../../controller/home/map/location_search_controller.dart'; import '../../../controller/home/map/map_engine_controller.dart'; import '../../../controller/home/vip_waitting_page.dart'; import '../navigation/navigation_view.dart'; +import '../../../services/android_live_update_service.dart'; +import '../../../controller/home/ios_live_activity_service.dart'; // --- الدالة الرئيسية بالتصميم الجديد --- GetBuilder leftMainMenuIcons() { @@ -66,6 +69,11 @@ GetBuilder leftMainMenuIcons() { tooltip: 'VIP Waiting Page', onPressed: () => Get.to(() => VipWaittingPage()), ), + _buildMapActionButton( + icon: Icons.explore, + tooltip: 'Test Page', + onPressed: () => Get.to(() => TestPage()), + ), ], ), ), @@ -107,7 +115,7 @@ class TestPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('iOS Live Activity Test'), + title: const Text('Live Activity Test (iOS & Android)'), ), body: Padding( padding: const EdgeInsets.all(16.0), @@ -116,14 +124,67 @@ class TestPage extends StatelessWidget { children: [ MyCircularProgressIndicator(), MyElevatedButton( - title: 'title', - onPressed: () {}, + title: 'Start Live Activity Demo', + onPressed: () { + if (Platform.isIOS) { + IosLiveActivityService.startRideActivity( + rideId: "demo_trip_123", + driverName: "أحمد علي", + carDetails: "تويوتا كامري - أبيض", + etaText: "5 دقائق للوصول", + progress: 0.3, + ); + } else if (Platform.isAndroid) { + AndroidLiveUpdateService.startLiveUpdate( + rideId: "demo_trip_123", + driverName: "أحمد علي", + carDetails: "تويوتا كامري - أبيض", + etaText: "5 دقائق للوصول", + progress: 0.3, + ); + } + }, ), + const SizedBox(height: 20), + MyElevatedButton( + title: 'Update Live Activity (Random)', + onPressed: () { + final random = Random(); + final int minutes = random.nextInt(15) + 1; + final double progress = random.nextDouble(); + final String newEtaText = "$minutes دقائق للوصول"; + + if (Platform.isIOS) { + IosLiveActivityService.updateRideActivity( + status: "accepted", + driverName: "أحمد علي", + carDetails: "تويوتا كامري - أبيض", + etaText: newEtaText, + progress: progress, + ); + } else if (Platform.isAndroid) { + AndroidLiveUpdateService.updateStatus( + status: "accepted", + driverName: "أحمد علي", + carDetails: "تويوتا كامري - أبيض", + etaText: newEtaText, + progress: progress, + ); + } + }, + ), + const SizedBox(height: 20), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.red, ), - onPressed: () async {}, + onPressed: () async { + if (Platform.isIOS) { + await IosLiveActivityService.endRideActivity(); + } else if (Platform.isAndroid) { + await AndroidLiveUpdateService.endLiveUpdate(); + } + }, child: const Text('End Activity'), ), ], diff --git a/siro_rider/lib/views/home/map_widget.dart/buttom_sheet_map_show.dart b/siro_rider/lib/views/home/map_widget.dart/legacy_destination_bottom_sheet.dart similarity index 100% rename from siro_rider/lib/views/home/map_widget.dart/buttom_sheet_map_show.dart rename to siro_rider/lib/views/home/map_widget.dart/legacy_destination_bottom_sheet.dart diff --git a/siro_rider/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart b/siro_rider/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart deleted file mode 100644 index 501a86fa..00000000 --- a/siro_rider/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart +++ /dev/null @@ -1,1211 +0,0 @@ -import 'dart:ui' show ImageFilter; -import 'package:siro_rider/print.dart'; -import 'package:siro_rider/views/widgets/my_textField.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:siro_rider/constant/box_name.dart'; -import 'package:siro_rider/constant/style.dart'; -import 'package:siro_rider/controller/home/map/map_engine_controller.dart'; -import 'package:siro_rider/controller/home/map/location_search_controller.dart'; -import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart'; -import 'package:siro_rider/main.dart'; -import 'package:siro_rider/views/home/map_widget.dart/form_search_places_destenation.dart'; -import 'package:siro_rider/views/widgets/elevated_btn.dart'; -import 'package:intaleq_maps/intaleq_maps.dart'; -import '../../../constant/colors.dart'; -import '../../../constant/table_names.dart'; -import '../../widgets/error_snakbar.dart'; -import '../../widgets/mydialoug.dart'; -import 'form_search_start.dart'; - -// ─── Design Tokens (Modern & Dynamic) ──────────────────────────────────────── - -class _D { - static const double radiusCard = 28; - static const double radiusChip = 20; - static const double radiusInner = 14; - static const double radiusPill = 50; - - static List get cardShadow => [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.08), - blurRadius: 40, - spreadRadius: -8, - offset: const Offset(0, 12), - ), - BoxShadow( - color: Colors.black.withValues(alpha: 0.04), - blurRadius: 16, - spreadRadius: -4, - offset: const Offset(0, 4), - ), - ]; - - static List glowShadow(Color c, {double intensity = 0.4}) => [ - BoxShadow( - color: c.withValues(alpha: intensity), - blurRadius: 24, - spreadRadius: -4, - offset: const Offset(0, 8), - ), - BoxShadow( - color: c.withValues(alpha: intensity * 0.5), - blurRadius: 12, - spreadRadius: -2, - offset: const Offset(0, 3), - ), - ]; - - static const Duration fast = Duration(milliseconds: 180); - static const Duration medium = Duration(milliseconds: 420); - - static LinearGradient primaryGradient({ - Alignment begin = Alignment.topLeft, - Alignment end = Alignment.bottomRight, - }) => - LinearGradient( - begin: begin, - end: end, - colors: [ - AppColor.primaryColor, - AppColor.primaryColor.withValues(alpha: 0.85), - AppColor.primaryColor.withValues(alpha: 0.7), - ], - stops: const [0.0, 0.5, 1.0], - ); -} - -// ───────────────────────────────────────────────────────────────────────────── -// MAIN BOTTOM MENU MAP - Scrollable Redesign -// ───────────────────────────────────────────────────────────────────────────── - -class MainBottomMenuMap extends StatelessWidget { - const MainBottomMenuMap({super.key}); - - @override - Widget build(BuildContext context) { - return GetBuilder( - builder: (controller) { - if (controller.isPickerShown) { - return const _MapPickerOverlay(); - } - - return Positioned( - bottom: Get.height * .035, - left: 16, - right: 16, - child: ClipRRect( - borderRadius: BorderRadius.circular(_D.radiusCard), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 14, sigmaY: 14), - child: AnimatedContainer( - duration: _D.medium, - curve: Curves.easeOutQuint, - constraints: BoxConstraints( - maxHeight: controller.isMainBottomMenuMap - ? Get.height * 0.4 - : Get.height * 0.75, - ), - decoration: BoxDecoration( - color: AppColor.cardColor.withValues(alpha: 0.88), - borderRadius: BorderRadius.circular(_D.radiusCard), - boxShadow: _D.cardShadow, - border: Border.all( - color: Get.isDarkMode - ? Colors.white.withValues(alpha: 0.15) - : Colors.white.withValues(alpha: 0.65), - width: 1.2, - ), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(_D.radiusCard), - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: controller.isMainBottomMenuMap - ? const _CollapsedView() - : const _ExpandedView(), - ), - ), - ), - ), - ), - ); - }, - ); - } -} - -// ───────────────────────────────────────────────────────────────────────────── -// COLLAPSED VIEW -// ───────────────────────────────────────────────────────────────────────────── - -class _CollapsedView extends StatelessWidget { - const _CollapsedView(); - - @override - Widget build(BuildContext context) { - final String firstName = box.read(BoxName.name).toString().split(' ').first; - final mapEngine = Get.find(); - final rideLifecycle = Get.find(); - - return GetBuilder( - builder: (locationSearch) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 14), - AnimatedContainer( - duration: _D.fast, - width: 44, - height: 5, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Colors.grey.shade400.withValues(alpha: 0.6), - Colors.grey.shade300, - Colors.grey.shade400.withValues(alpha: 0.6), - ], - ), - borderRadius: BorderRadius.circular(3), - ), - ), - const SizedBox(height: 16), - Semantics( - button: true, - label: 'Open destination search'.tr, - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: mapEngine.changeMainBottomMenuMap, - borderRadius: BorderRadius.circular(_D.radiusInner), - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 18, vertical: 8), - child: Row( - children: [ - AnimatedContainer( - duration: _D.medium, - width: 48, - height: 48, - decoration: BoxDecoration( - gradient: _D.primaryGradient(), - borderRadius: BorderRadius.circular(_D.radiusPill), - boxShadow: _D.glowShadow(AppColor.primaryColor), - ), - child: const Icon(Icons.search_rounded, - color: Colors.white, size: 22), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text.rich( - TextSpan( - children: [ - TextSpan( - text: '${'Where to'.tr} ', - style: AppStyle.title.copyWith( - fontWeight: FontWeight.w600, - fontSize: 16, - color: Colors.grey.shade700, - ), - ), - TextSpan( - text: firstName, - style: AppStyle.title.copyWith( - fontWeight: FontWeight.w800, - fontSize: 16.5, - color: AppColor.primaryColor, - letterSpacing: -0.3, - ), - ), - const TextSpan(text: '؟'), - ], - ), - ), - const SizedBox(height: 2), - if (!rideLifecycle.noCarString) - Text( - 'Tap to search your destination'.tr, - style: AppStyle.subtitle.copyWith( - fontSize: 12, - color: Colors.grey.shade500, - fontWeight: FontWeight.w400, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ), - ), - if (locationSearch.recentPlaces.isNotEmpty) ...[ - const SizedBox(height: 12), - Container( - height: 40, - padding: const EdgeInsets.symmetric(horizontal: 18), - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: locationSearch.recentPlaces.length, - separatorBuilder: (_, __) => const SizedBox(width: 10), - itemBuilder: (context, index) => _RecentPlaceChip( - locationSearch: locationSearch, index: index), - ), - ), - const SizedBox(height: 16), - ] else - const SizedBox(height: 20), - ], - ); - }, - ); - } -} - -// ───────────────────────────────────────────────────────────────────────────── -// EXPANDED VIEW - Grouped Layout -// ───────────────────────────────────────────────────────────────────────────── - -class _ExpandedView extends StatelessWidget { - const _ExpandedView(); - - @override - Widget build(BuildContext context) { - final mapEngine = Get.find(); - final rideLifecycle = Get.find(); - - return GetBuilder( - builder: (locationSearch) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox(height: 14), - Center( - child: AnimatedContainer( - duration: _D.fast, - width: 44, - height: 5, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Colors.grey.shade400.withValues(alpha: 0.6), - Colors.grey.shade300, - Colors.grey.shade400.withValues(alpha: 0.6), - ], - ), - borderRadius: BorderRadius.circular(3), - ), - ), - ), - - // ── Header ── - Container( - padding: const EdgeInsets.fromLTRB(20, 18, 16, 14), - child: Row( - children: [ - Text( - 'Plan Your Route'.tr, - style: AppStyle.title.copyWith( - fontWeight: FontWeight.w800, - fontSize: 18, - letterSpacing: -0.5, - ), - ), - const Spacer(), - Semantics( - button: true, - label: 'Close panel'.tr, - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: mapEngine.changeMainBottomMenuMap, - borderRadius: BorderRadius.circular(_D.radiusPill), - child: Container( - width: 38, - height: 38, - decoration: BoxDecoration( - color: Colors.grey.shade100, - shape: BoxShape.circle, - ), - child: Icon(Icons.keyboard_arrow_down_rounded, - size: 24, color: Colors.grey.shade600), - ), - ), - ), - ), - ], - ), - ), - - // ── Group 1: Core Routing ── - _buildSectionTitle('Route'.tr), - - _buildTimelineItem( - dotColor: AppColor.primaryColor, - showTopLine: false, - showBottomLine: true, - isStart: true, - child: !rideLifecycle.isAnotherOreder - ? _TimelineRow( - icon: Icons.my_location_rounded, - iconColor: AppColor.primaryColor, - bgColor: AppColor.primaryColor, - label: locationSearch.currentLocationString, - ) - : Padding( - padding: const EdgeInsets.only(right: 16), - child: formSearchPlacesStart(), - ), - ), - - ...List.generate(locationSearch.activeMenuWaypointCount, (index) { - final wpName = locationSearch.menuWaypointNames[index]; - final isSet = locationSearch.menuWaypoints[index] != null; - final Color accent = index == 0 - ? Colors.amber.shade600 - : Colors.deepPurple.shade400; - final Color soft = - index == 0 ? Colors.amber.shade50 : Colors.deepPurple.shade50; - - return _buildTimelineItem( - dotColor: accent, - showTopLine: true, - showBottomLine: true, - child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 14, vertical: 12), - decoration: BoxDecoration( - gradient: LinearGradient(colors: [ - soft.withValues(alpha: 0.9), - soft.withValues(alpha: 0.6) - ]), - borderRadius: BorderRadius.circular(_D.radiusInner), - border: Border.all( - color: isSet - ? accent.withValues(alpha: 0.35) - : Colors.grey.shade200), - ), - child: Row( - children: [ - Container( - width: 26, - height: 26, - decoration: BoxDecoration( - color: accent, shape: BoxShape.circle), - child: Center( - child: Text('${index + 1}', - style: const TextStyle( - color: Colors.white, - fontSize: 11, - fontWeight: FontWeight.w800))), - ), - const SizedBox(width: 12), - Expanded( - child: GestureDetector( - onTap: () { - mapEngine.changeMainBottomMenuMap(); - locationSearch.startPickingWaypointOnMap(index); - }, - child: Text( - isSet ? wpName : '${'Stop'.tr} ${index + 1}', - style: TextStyle( - fontSize: 13.5, - color: isSet - ? accent.withValues(alpha: 0.9) - : Colors.grey.shade400, - fontWeight: - isSet ? FontWeight.w600 : FontWeight.w400, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - )), - GestureDetector( - onTap: () => locationSearch.removeMenuWaypoint(index), - child: Container( - width: 28, - height: 28, - decoration: BoxDecoration( - color: Colors.red.shade50, - shape: BoxShape.circle), - child: Icon(Icons.close_rounded, - color: Colors.red.shade400, size: 15), - ), - ), - ], - ), - ), - ); - }), - - if (locationSearch.activeMenuWaypointCount < 2) - _buildTimelineItem( - dotColor: Colors.orange.shade300, - isDotDashed: true, - showTopLine: true, - showBottomLine: true, - child: InkWell( - onTap: () => locationSearch.addMenuWaypoint(), - borderRadius: BorderRadius.circular(_D.radiusInner), - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(_D.radiusInner), - border: - Border.all(color: Colors.orange.shade200, width: 1.5), - color: Colors.orange.shade50.withValues(alpha: 0.6), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.add_location_alt_outlined, - color: Colors.orange.shade500, size: 18), - const SizedBox(width: 10), - Text('Add a Stop'.tr, - style: TextStyle( - color: Colors.orange.shade700, - fontSize: 13.5, - fontWeight: FontWeight.w600)), - ], - ), - ), - ), - ), - - _buildTimelineItem( - dotColor: Colors.red.shade500, - showTopLine: true, - showBottomLine: false, - isEnd: true, - child: Padding( - padding: const EdgeInsets.only(right: 16), - child: formSearchPlacesDestenation(), - ), - ), - - const SizedBox(height: 16), - - // ── Group 2: Quick Access ── - _buildSectionDivider(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildSectionTitle('Quick Access'.tr), - const FaviouratePlacesDialog(), // تم نقلها هنا لتكون جزء من الوصول السريع - ], - ), - - if (locationSearch.recentPlaces.isNotEmpty) - Container( - height: 40, - margin: const EdgeInsets.only(bottom: 16), - child: ListView.separated( - padding: const EdgeInsets.symmetric(horizontal: 20), - scrollDirection: Axis.horizontal, - itemCount: locationSearch.recentPlaces.length, - separatorBuilder: (_, __) => const SizedBox(width: 10), - itemBuilder: (context, index) => _RecentPlaceChip( - locationSearch: locationSearch, index: index), - ), - ), - - // ── Group 3: Advanced Tools ── - _buildSectionDivider(), - _buildSectionTitle('Advanced Tools'.tr), - - Padding( - padding: const EdgeInsets.symmetric(horizontal: 18), - child: _WhatsAppLinkButton(locationSearch: locationSearch), - ), - const SizedBox(height: 12), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 18), - child: _OrderTypeButton(mapEngine: mapEngine), - ), - - const SizedBox(height: 24), // مساحة سفلية لضمان راحة السحب - ], - ); - }, - ); - } - - Widget _buildSectionTitle(String title) { - return Padding( - padding: const EdgeInsets.only(left: 20, right: 20, bottom: 12), - child: Text( - title, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.grey.shade400, - letterSpacing: 0.5, - ), - ), - ); - } - - Widget _buildSectionDivider() { - return Container( - height: 1, - margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), - color: Colors.grey.shade200, - ); - } - - Widget _buildTimelineItem({ - required Color dotColor, - required bool showTopLine, - required bool showBottomLine, - required Widget child, - bool isDotDashed = false, - bool isStart = false, - bool isEnd = false, - }) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 24, - child: Column( - children: [ - if (showTopLine) - Container( - width: 2.5, height: 12, color: Colors.grey.shade300), - Container( - width: 15, - height: 15, - decoration: BoxDecoration( - color: isDotDashed ? Colors.transparent : dotColor, - shape: BoxShape.circle, - border: - Border.all(color: dotColor, width: isDotDashed ? 2 : 3), - ), - ), - if (showBottomLine) - Container( - width: 2.5, height: 12, color: Colors.grey.shade300), - ], - ), - ), - const SizedBox(width: 14), - Expanded(child: child), - ], - ), - ); - } -} - -// ───────────────────────────────────────────────────────────────────────────── -// MAP PICKER OVERLAY -// ───────────────────────────────────────────────────────────────────────────── - -class _MapPickerOverlay extends StatelessWidget { - const _MapPickerOverlay(); - - String _getModeTitle( - LocationSearchController locationSearch, BuildContext context) { - if (locationSearch.isPickingWaypoint) { - return 'Move map to set stop'.tr + - ' ${locationSearch.pickingWaypointIndex + 1}'.tr; - } - if (locationSearch.passengerStartLocationFromMap) { - final rideLifecycle = Get.find(); - return rideLifecycle.isAnotherOreder - ? 'Now set the pickup point for the other person'.tr - : 'Move map to your pickup point'.tr; - } - if (locationSearch.startLocationFromMap) { - return 'Move map to set start location'.tr; - } - if (locationSearch.workLocationFromMap) { - return 'Move map to your work location'.tr; - } - if (locationSearch.homeLocationFromMap) { - return 'Move map to your home location'.tr; - } - return 'Move map to select destination'.tr; - } - - String _getConfirmLabel( - LocationSearchController locationSearch, BuildContext context) { - if (locationSearch.isPickingWaypoint) return 'Set as Stop'.tr; - if (locationSearch.passengerStartLocationFromMap) { - return 'Confirm Pickup Location'.tr; - } - if (locationSearch.workLocationFromMap) return 'Set as Work'.tr; - if (locationSearch.homeLocationFromMap) return 'Set as Home'.tr; - return 'Set Destination'.tr; - } - - IconData _getModeIcon(LocationSearchController locationSearch) { - if (locationSearch.isPickingWaypoint) return Icons.add_location_alt_rounded; - if (locationSearch.passengerStartLocationFromMap) { - return Icons.person_pin_circle_rounded; - } - if (locationSearch.workLocationFromMap) return Icons.work_rounded; - if (locationSearch.homeLocationFromMap) return Icons.home_rounded; - return Icons.location_on_rounded; - } - - Color _getModeColor(LocationSearchController locationSearch) { - if (locationSearch.isPickingWaypoint) return Colors.orange.shade600; - if (locationSearch.passengerStartLocationFromMap) - return Colors.green.shade600; - if (locationSearch.workLocationFromMap) return Colors.blue.shade600; - if (locationSearch.homeLocationFromMap) return Colors.orange.shade600; - return AppColor.primaryColor; - } - - @override - Widget build(BuildContext context) { - final mapEngine = Get.find(); - - return GetBuilder( - builder: (locationSearch) { - final modeColor = _getModeColor(locationSearch); - - return Positioned( - bottom: Get.height * .035, - left: 16, - right: 16, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: - const EdgeInsets.symmetric(horizontal: 18, vertical: 14), - decoration: BoxDecoration( - color: modeColor, - borderRadius: BorderRadius.circular(_D.radiusCard), - ), - child: Row( - children: [ - Icon(_getModeIcon(locationSearch), - color: Colors.white, size: 19), - const SizedBox(width: 14), - Expanded( - child: Text( - _getModeTitle(locationSearch, context), - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w700, - fontSize: 14), - ), - ), - ], - ), - ), - const SizedBox(height: 12), - Container( - decoration: BoxDecoration( - color: AppColor.secondaryColor, - borderRadius: BorderRadius.circular(_D.radiusCard), - boxShadow: _D.cardShadow, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(20, 18, 20, 2), - child: Row( - children: [ - Icon(Icons.gps_fixed_rounded, - color: modeColor, size: 16), - const SizedBox(width: 14), - Expanded( - child: Text( - '${locationSearch.newMyLocation.latitude.toStringAsFixed(5)}, ${locationSearch.newMyLocation.longitude.toStringAsFixed(5)}', - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade700, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.fromLTRB(16, 14, 16, 18), - child: Row( - children: [ - Expanded( - flex: 2, - child: OutlinedButton( - onPressed: () { - mapEngine.isPickerShown = false; - locationSearch.passengerStartLocationFromMap = - false; - locationSearch.startLocationFromMap = false; - locationSearch.workLocationFromMap = false; - locationSearch.homeLocationFromMap = false; - locationSearch.isPickingWaypoint = false; - locationSearch.pickingWaypointIndex = -1; - if (!mapEngine.isMainBottomMenuMap) { - mapEngine.isMainBottomMenuMap = true; - mapEngine.mainBottomMenuMapHeight = - Get.height * .22; - } - mapEngine.update(); - locationSearch.update(); - }, - child: Text('Cancel'.tr), - ), - ), - const SizedBox(width: 12), - Expanded( - flex: 3, - child: ElevatedButton( - onPressed: () => _onConfirmTap( - mapEngine, locationSearch, context), - style: ElevatedButton.styleFrom( - backgroundColor: modeColor), - child: Text( - _getConfirmLabel(locationSearch, context), - style: const TextStyle(color: Colors.white)), - ), - ), - ], - ), - ), - ], - ), - ), - ], - ), - ); - }, - ); - } - - Future _onConfirmTap(MapEngineController mapEngine, - LocationSearchController locationSearch, BuildContext context) async { - final rideLifecycle = Get.find(); - Log.print( - '🔘 _onConfirmTap: isPickingWaypoint=${locationSearch.isPickingWaypoint}, newMyLocation=${locationSearch.newMyLocation}'); - await Future.delayed(const Duration(milliseconds: 280)); - final LatLng currentCameraPosition = LatLng( - locationSearch.newMyLocation.latitude, - locationSearch.newMyLocation.longitude); - - if (locationSearch.isPickingWaypoint && - locationSearch.pickingWaypointIndex >= 0) { - locationSearch.setMenuWaypointFromMap( - locationSearch.pickingWaypointIndex, currentCameraPosition); - mySnackbarSuccess('Waypoint has been set successfully'.tr); - return; - } - - mapEngine.clearPolyline(); - rideLifecycle.data = []; - - if (locationSearch.passengerStartLocationFromMap) { - final LatLng start = currentCameraPosition; - locationSearch.newStartPointLocation = start; - locationSearch.passengerStartLocationFromMap = false; - mapEngine.isPickerShown = false; - locationSearch.currentLocationToFormPlaces = false; - locationSearch.placesDestination = []; - locationSearch.clearPlacesStart(); - locationSearch.clearPlacesDestination(); - mapEngine.isMainBottomMenuMap = true; - mapEngine.mainBottomMenuMapHeight = Get.height * .22; - mapEngine.update(); - locationSearch.update(); - await rideLifecycle.getDirectionMap( - '${start.latitude},${start.longitude}', - '${locationSearch.myDestination.latitude},${locationSearch.myDestination.longitude}'); - rideLifecycle.showBottomSheet1(); - return; - } - - if (locationSearch.startLocationFromMap) { - final LatLng start = currentCameraPosition; - locationSearch.newMyLocation = start; - locationSearch.newStartPointLocation = start; - locationSearch.hintTextStartPoint = - '${start.latitude.toStringAsFixed(4)} , ${start.longitude.toStringAsFixed(4)}'; - locationSearch.startLocationFromMap = false; - mapEngine.isPickerShown = false; - locationSearch.update(); - mapEngine.update(); - return; - } - - if (locationSearch.workLocationFromMap) { - box.write(BoxName.addWork, - '${currentCameraPosition.latitude.toStringAsFixed(4)} , ${currentCameraPosition.longitude.toStringAsFixed(4)}'); - locationSearch.hintTextDestinationPoint = 'To Work'.tr; - locationSearch.workLocationFromMap = false; - mapEngine.isPickerShown = false; - locationSearch.update(); - mapEngine.update(); - mySnackbarSuccess('Work Saved'.tr); - return; - } - - if (locationSearch.homeLocationFromMap) { - box.write(BoxName.addHome, - '${currentCameraPosition.latitude.toStringAsFixed(4)} , ${currentCameraPosition.longitude.toStringAsFixed(4)}'); - locationSearch.hintTextDestinationPoint = 'To Home'.tr; - locationSearch.homeLocationFromMap = false; - mapEngine.isPickerShown = false; - locationSearch.update(); - mapEngine.update(); - mySnackbarSuccess('Home Saved'.tr); - return; - } - - locationSearch.myDestination = currentCameraPosition; - locationSearch.hintTextDestinationPoint = - '${currentCameraPosition.latitude.toStringAsFixed(4)} , ${currentCameraPosition.longitude.toStringAsFixed(4)}'; - locationSearch.placesDestination = []; - locationSearch.placeDestinationController.clear(); - locationSearch.passengerStartLocationFromMap = true; - mapEngine.isPickerShown = true; - locationSearch.update(); - mapEngine.update(); - - try { - if (rideLifecycle.isAnotherOreder) { - await mapEngine.mapController?.animateCamera(CameraUpdate.newLatLng( - LatLng(locationSearch.newStartPointLocation.latitude, - locationSearch.newStartPointLocation.longitude))); - } else { - await mapEngine.mapController?.animateCamera(CameraUpdate.newLatLng( - LatLng(locationSearch.passengerLocation.latitude, - locationSearch.passengerLocation.longitude))); - } - } catch (e) { - Log.print("Error occurred: $e"); - } - } -} - -// ───────────────────────────────────────────────────────────────────────────── -// HELPER WIDGETS -// ───────────────────────────────────────────────────────────────────────────── - -class _TimelineRow extends StatelessWidget { - final IconData icon; - final Color iconColor; - final Color bgColor; - final String label; - const _TimelineRow( - {required this.icon, - required this.iconColor, - required this.bgColor, - required this.label}); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11), - decoration: BoxDecoration( - color: bgColor.withValues(alpha: 0.06), - borderRadius: BorderRadius.circular(_D.radiusInner)), - child: Row( - children: [ - Icon(icon, color: iconColor, size: 15), - const SizedBox(width: 12), - Expanded( - child: Text(label, - style: AppStyle.subtitle - .copyWith(fontSize: 13, fontWeight: FontWeight.w500), - maxLines: 1, - overflow: TextOverflow.ellipsis)), - ], - ), - ); - } -} - -class _RecentPlaceChip extends StatelessWidget { - final LocationSearchController locationSearch; - final int index; - const _RecentPlaceChip({required this.locationSearch, required this.index}); - - @override - Widget build(BuildContext context) { - final place = locationSearch.recentPlaces[index]; - final rideLifecycle = Get.find(); - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - MyDialog().getDialog( - 'Are you want to go this site'.tr, - ' ', - () async { - await locationSearch.getLocation(); - await rideLifecycle.getDirectionMap( - '${locationSearch.passengerLocation.latitude},${locationSearch.passengerLocation.longitude}', - '${place['latitude']},${place['longitude']}'); - rideLifecycle.showBottomSheet1(); - }, - ); - }, - borderRadius: BorderRadius.circular(_D.radiusChip), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 7), - decoration: BoxDecoration( - color: AppColor.primaryColor.withValues(alpha: 0.08), - borderRadius: BorderRadius.circular(_D.radiusChip), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.history_rounded, - size: 14, - color: AppColor.primaryColor.withValues(alpha: 0.7)), - const SizedBox(width: 7), - Text(place['name'] ?? '', - style: TextStyle( - fontSize: 12.5, - color: AppColor.primaryColor.withValues(alpha: 0.9), - fontWeight: FontWeight.w600)), - ], - ), - ), - ), - ); - } -} - -class _WhatsAppLinkButton extends StatelessWidget { - final LocationSearchController locationSearch; - const _WhatsAppLinkButton({required this.locationSearch}); - - @override - Widget build(BuildContext context) { - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - Get.dialog( - AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(22)), - title: Text('WhatsApp Location Extractor'.tr), - content: Form( - key: locationSearch.sosFormKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - MyTextForm( - controller: locationSearch.whatsAppLocationText, - label: 'Location Link'.tr, - type: TextInputType.url, - hint: 'https://maps.app.goo.gl/...'), - const SizedBox(height: 16), - MyElevatedButton( - title: 'Go to this location'.tr, - onPressed: () => locationSearch.goToWhatappLocation()), - ], - ), - ), - ), - ); - }, - borderRadius: BorderRadius.circular(_D.radiusInner), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - decoration: BoxDecoration( - color: Colors.green.shade50, - borderRadius: BorderRadius.circular(_D.radiusInner)), - child: Row( - children: [ - Icon(Icons.link_rounded, color: Colors.green.shade700, size: 18), - const SizedBox(width: 14), - Expanded( - child: Text('Paste WhatsApp location link'.tr, - style: TextStyle( - color: Colors.green.shade800, - fontSize: 13.5, - fontWeight: FontWeight.w600))), - ], - ), - ), - ), - ); - } -} - -class _OrderTypeButton extends StatelessWidget { - final MapEngineController mapEngine; - const _OrderTypeButton({required this.mapEngine}); - - @override - Widget build(BuildContext context) { - final rideLifecycle = Get.find(); - final bool isOther = mapEngine.isAnotherOreder; - const MaterialColor accent = Colors.indigo; - - void select(bool other) { - if (mapEngine.isAnotherOreder == other) return; - mapEngine.changeisAnotherOreder(other); - rideLifecycle.isAnotherOreder = other; - } - - Widget segment( - {required bool selected, - required IconData icon, - required String label, - required VoidCallback onTap}) { - return Expanded( - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(_D.radiusInner - 2), - child: AnimatedContainer( - duration: _D.fast, - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: BoxDecoration( - color: selected ? accent.shade500 : Colors.transparent, - borderRadius: BorderRadius.circular(_D.radiusInner - 2), - boxShadow: selected - ? [ - BoxShadow( - color: accent.withValues(alpha: 0.35), - blurRadius: 10, - offset: const Offset(0, 3)), - ] - : null, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, - color: selected ? Colors.white : Colors.grey.shade500, - size: 16), - const SizedBox(width: 8), - Flexible( - child: Text( - label, - textAlign: TextAlign.center, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: selected ? Colors.white : Colors.grey.shade600, - fontSize: 13, - fontWeight: - selected ? FontWeight.w700 : FontWeight.w500, - ), - ), - ), - ], - ), - ), - ), - ), - ); - } - - return Container( - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: Colors.grey.shade100, - borderRadius: BorderRadius.circular(_D.radiusInner), - ), - child: Row( - children: [ - segment( - selected: !isOther, - icon: Icons.person_rounded, - label: 'Order for myself'.tr, - onTap: () => select(false), - ), - const SizedBox(width: 4), - segment( - selected: isOther, - icon: Icons.group_rounded, - label: 'Order for someone else'.tr, - onTap: () => select(true), - ), - ], - ), - ); - } -} - -class FaviouratePlacesDialog extends StatelessWidget { - const FaviouratePlacesDialog({super.key}); - - @override - Widget build(BuildContext context) { - return GetBuilder( - builder: (locationSearch) { - final rideLifecycle = Get.find(); - return InkWell( - borderRadius: BorderRadius.circular(14), - onTap: () async { - final List favoritePlaces = - await sql.getAllData(TableName.placesFavorite); - Get.defaultDialog( - title: 'Favorite Places'.tr, - content: SizedBox( - width: Get.width * .85, - height: 300, - child: favoritePlaces.isEmpty - ? Center(child: Text('No favorite places yet!'.tr)) - : ListView.separated( - itemCount: favoritePlaces.length, - separatorBuilder: (_, __) => - Divider(height: 1, color: Colors.grey.shade100), - itemBuilder: (context, index) => ListTile( - leading: const Icon(Icons.star, - color: Colors.amber, size: 19), - title: Text(favoritePlaces[index]['name']), - trailing: IconButton( - icon: const Icon(Icons.delete_outline, - color: Colors.redAccent), - onPressed: () async { - await sql.deleteData(TableName.placesFavorite, - favoritePlaces[index]['id']); - Get.back(); - }, - ), - onTap: () async { - Get.back(); - await locationSearch.getLocation(); - await rideLifecycle.getDirectionMap( - '${locationSearch.passengerLocation.latitude},${locationSearch.passengerLocation.longitude}', - '${favoritePlaces[index]['latitude']},${favoritePlaces[index]['longitude']}'); - rideLifecycle.showBottomSheet1(); - }, - ), - ), - ), - confirm: MyElevatedButton( - title: 'Back'.tr, onPressed: () => Get.back()), - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.star_border_rounded, - color: AppColor.accentColor, size: 21), - const SizedBox(width: 10), - Text('Favorite Places'.tr, - style: AppStyle.title - .copyWith(fontWeight: FontWeight.w600, fontSize: 14)), - ], - ), - ), - ); - }, - ); - } -} diff --git a/siro_rider/lib/views/home/map_widget.dart/menu_map_page.dart b/siro_rider/lib/views/home/map_widget.dart/menu_map_page.dart deleted file mode 100644 index b600c8cd..00000000 --- a/siro_rider/lib/views/home/map_widget.dart/menu_map_page.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -import '../../../constant/box_name.dart'; -import '../../../constant/colors.dart'; -import '../../../controller/home/map/map_engine_controller.dart'; -import '../../../main.dart'; - -class MenuIconMapPageWidget extends StatelessWidget { - const MenuIconMapPageWidget({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return GetBuilder( - builder: (controller) => Positioned( - top: Get.height * .008, - left: box.read(BoxName.lang) != 'ar' ? 5 : null, - right: box.read(BoxName.lang) == 'ar' ? 5 : null, - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColor.secondaryColor, - border: Border.all(color: AppColor.accentColor)), - child: AnimatedCrossFade( - sizeCurve: Curves.bounceOut, - duration: const Duration( - milliseconds: 300), // Adjust the duration as needed - crossFadeState: controller.heightMenuBool - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: IconButton( - onPressed: () { - controller.getDrawerMenu(); - }, - icon: const Icon( - Icons.close, - color: AppColor.primaryColor, - ), - ), - secondChild: IconButton( - onPressed: () { - controller.getDrawerMenu(); - }, - icon: const Icon( - Icons.menu, - color: AppColor.accentColor, - ), - ), - ), - ), - )); - } -} diff --git a/siro_rider/lib/views/home/map_widget.dart/new_main_bottom_sheet.dart b/siro_rider/lib/views/home/map_widget.dart/new_main_bottom_sheet.dart deleted file mode 100644 index b9c0fde5..00000000 --- a/siro_rider/lib/views/home/map_widget.dart/new_main_bottom_sheet.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:siro_rider/constant/box_name.dart'; -import 'package:siro_rider/constant/colors.dart'; -import 'package:siro_rider/constant/style.dart'; -import 'package:siro_rider/main.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -class NewMainBottomSheet extends StatelessWidget { - const NewMainBottomSheet({super.key}); - - @override - Widget build(BuildContext context) { - return Positioned( - bottom: 0, - left: 5, - right: 5, - child: Container( - decoration: AppStyle.boxDecoration, - width: Get.width, - height: Get.height * .15, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Container( - decoration: BoxDecoration( - border: Border.all(), - borderRadius: BorderRadius.circular(15)), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Text('Home'.tr), - const Icon(Icons.home), - ], - ), - ), - ), - Container( - decoration: BoxDecoration( - border: Border.all(), - borderRadius: BorderRadius.circular(15)), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Text('Work'.tr), - const Icon(Icons.work_outline), - ], - ), - ), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - decoration: BoxDecoration( - border: Border.all(), - borderRadius: BorderRadius.circular(15), - color: AppColor.blueColor.withOpacity(.5), - ), - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Icon(Icons.search), - Text( - "${"Where you want go ".tr}${(box.read(BoxName.name).toString().split(' ')[0]).toString()} ?", - ), - ], - ), - ), - ) - ], - ) - ], - ), - ), - ); - } -} diff --git a/siro_rider/lib/views/home/map_widget.dart/passengerRideLoctionWidget.dart b/siro_rider/lib/views/home/map_widget.dart/passenger_ride_location_widget.dart similarity index 100% rename from siro_rider/lib/views/home/map_widget.dart/passengerRideLoctionWidget.dart rename to siro_rider/lib/views/home/map_widget.dart/passenger_ride_location_widget.dart diff --git a/siro_rider/lib/views/home/map_widget.dart/payment_method.page.dart b/siro_rider/lib/views/home/map_widget.dart/payment_method_page.dart similarity index 100% rename from siro_rider/lib/views/home/map_widget.dart/payment_method.page.dart rename to siro_rider/lib/views/home/map_widget.dart/payment_method_page.dart diff --git a/siro_rider/lib/views/home/map_widget.dart/picker_animation_container.dart b/siro_rider/lib/views/home/map_widget.dart/picker_animation_container.dart deleted file mode 100644 index 72cea187..00000000 --- a/siro_rider/lib/views/home/map_widget.dart/picker_animation_container.dart +++ /dev/null @@ -1,214 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:siro_rider/constant/table_names.dart'; - -import '../../../constant/colors.dart'; -import '../../../constant/style.dart'; -import '../../../controller/home/map/location_search_controller.dart'; -import '../../../controller/home/map/map_engine_controller.dart'; -import '../../../controller/home/map/ride_lifecycle_controller.dart'; -import '../../../main.dart'; -import '../../widgets/error_snakbar.dart'; -import '../../widgets/elevated_btn.dart'; -import 'form_search_places_destenation.dart'; - -class PickerAnimtionContainerFormPlaces extends StatelessWidget { - const PickerAnimtionContainerFormPlaces({super.key}); - - @override - Widget build(BuildContext context) { - final mapEngine = Get.find(); - final locationSearch = Get.find(); - final rideLifecycle = Get.find(); - - return GetBuilder( - builder: (controller) => Positioned( - bottom: 0, - left: 0, - right: 5, - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - height: controller.heightPickerContainer, - decoration: BoxDecoration( - boxShadow: const [ - BoxShadow( - color: AppColor.accentColor, offset: Offset(2, 2)), - BoxShadow( - color: AppColor.accentColor, offset: Offset(-2, -2)) - ], - color: AppColor.secondaryColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(15), - topRight: Radius.circular(15), - )), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - controller.isPickerShown - ? const SizedBox() - : Text( - 'Hi, Where to '.tr, - style: AppStyle.title, - ), - Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SizedBox( - height: 5, - ), - controller.isPickerShown - ? InkWell( - onTapDown: (details) { - controller.changePickerShown(); - controller.changeHeightPlaces(); - }, - child: Container( - height: 7, - width: Get.width * .3, - decoration: BoxDecoration( - color: AppColor.accentColor, - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: AppColor.accentColor, - )), - ), - ) - : const SizedBox(), - controller.isPickerShown - ? InkWell( - onTap: () {}, - child: formSearchPlacesDestenation(), - ) - : Row( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, - children: [ - TextButton( - onPressed: () { - controller.changePickerShown(); - }, - child: Text( - "Pick your destination from Map".tr, - style: AppStyle.subtitle, - ), - ), - TextButton( - onPressed: () async { - List favoritePlaces = await sql - .getAllData(TableName.placesFavorite); - Get.defaultDialog( - title: 'Favorite Places'.tr, - content: SizedBox( - width: Get.width * .8, - height: 300, - child: favoritePlaces.isEmpty - ? Center( - child: Column( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - const Icon( - Icons - .hourglass_empty_rounded, - size: 99, - color: AppColor - .primaryColor, - ), - Text( - 'You Dont Have Any places yet !' - .tr, - style: AppStyle.title, - ), - ], - ), - ) - : ListView.builder( - itemCount: - favoritePlaces.length, - itemBuilder: - (BuildContext context, - int index) { - return Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - TextButton( - onPressed: () async { - await rideLifecycle - .getDirectionMap( - '${locationSearch.passengerLocation.latitude},${locationSearch.passengerLocation.longitude}', - '${favoritePlaces[index]['latitude']},${favoritePlaces[index]['longitude']}', - ); - controller - .changePickerShown(); - controller - .changeBottomSheetShown( - forceValue: - true); - rideLifecycle - .bottomSheet(); - Get.back(); - }, - child: Text( - favoritePlaces[ - index]['name'], - style: - AppStyle.title, - ), - ), - IconButton( - onPressed: () async { - await sql.deleteData( - TableName - .placesFavorite, - favoritePlaces[ - index] - ['id']); - Get.back(); - mySnackbarInfo('${'You are Delete'.tr} ${favoritePlaces[index]['name']} from your list'); - }, - icon: const Icon(Icons - .favorite_outlined), - ), - ], - ); - }, - ), - ), - onCancel: () {}, - ); - }, - child: Text( - "Go To Favorite Places".tr, - style: AppStyle.subtitle, - ), - ), - ], - ), - if (controller.isPickerShown && - locationSearch.placesDestination.isEmpty) - MyElevatedButton( - title: 'Go to this Target'.tr, - onPressed: () async { - await rideLifecycle.getDirectionMap( - '${locationSearch.passengerLocation.latitude},${locationSearch.passengerLocation.longitude}', - '${locationSearch.newMyLocation.latitude},${locationSearch.newMyLocation.longitude}', - ); - controller.changePickerShown(); - controller.changeBottomSheetShown( - forceValue: true); - rideLifecycle.bottomSheet(); - }, - ), - if (controller.isPickerShown && - locationSearch.placesDestination.isEmpty) - const SizedBox(), - ], - ), - ], - ), - ), - )); - } -} diff --git a/siro_rider/lib/views/home/map_widget.dart/ride_begin_passenger.dart b/siro_rider/lib/views/home/map_widget.dart/ride_begin_passenger.dart index 37d5f51e..bb7cc28d 100644 --- a/siro_rider/lib/views/home/map_widget.dart/ride_begin_passenger.dart +++ b/siro_rider/lib/views/home/map_widget.dart/ride_begin_passenger.dart @@ -7,6 +7,7 @@ import 'package:intl/intl.dart'; // تأكد من المسارات import '../../../constant/box_name.dart'; import '../../../constant/colors.dart'; +import '../../../constant/design.dart'; import '../../../constant/links.dart'; import 'package:siro_rider/controller/functions/country_logic.dart'; import '../../../controller/functions/audio_record1.dart'; @@ -18,7 +19,6 @@ import '../../../controller/home/map/ride_state.dart'; import '../../../controller/profile/profile_controller.dart'; import '../../../main.dart'; import '../../../views/home/profile/complaint_page.dart'; -import '../../../controller/functions/country_logic.dart'; class RideBeginPassenger extends StatelessWidget { const RideBeginPassenger({super.key}); @@ -28,7 +28,6 @@ class RideBeginPassenger extends StatelessWidget { final ProfileController profileController = Get.put(ProfileController()); final AudioRecorderController audioController = Get.put(AudioRecorderController()); - final uiController = Get.find(); return Obx(() { final controller = Get.find(); @@ -45,64 +44,33 @@ class RideBeginPassenger extends StatelessWidget { bottom: isVisible ? 0 : -300, left: 0, right: 0, - child: Container( - decoration: BoxDecoration( - color: AppColor.secondaryColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(25), - topRight: Radius.circular(25), - ), - boxShadow: [ - BoxShadow( - color: Get.isDarkMode - ? Colors.black.withValues(alpha: 0.4) - : Colors.black.withValues(alpha: 0.1), - blurRadius: 20, - spreadRadius: 2, - offset: const Offset(0, -3), - ), + child: SiroSheetSurface( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.lg, vertical: AppSpacing.md), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 1. هيدر المعلومات (سائق + سيارة + سعر) + _buildCompactHeader(controller), + + AppSpacing.vGapMd, + + // خط فاصل خفيف + Divider( + height: 1, + thickness: 0.5, + color: AppColor.grayColor.withValues(alpha: 0.2)), + + AppSpacing.vGapMd, + + // 2. الأزرار (إجراءات) + _buildCompactActionButtons( + context, controller, profileController, audioController), + + // هامش سفلي بسيط لرفع الأزرار عن حافة الشاشة + AppSpacing.vGapXs, ], ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 1. مقبض السحب - Center( - child: Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: AppColor.grayColor.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(10), - ), - ), - ), - const SizedBox(height: 12), - - // 2. هيدر المعلومات (سائق + سيارة + سعر) - _buildCompactHeader(controller), - - const SizedBox(height: 12), - - // خط فاصل خفيف - Divider( - height: 1, - thickness: 0.5, - color: AppColor.grayColor.withValues(alpha: 0.2)), - - const SizedBox(height: 12), - - // 3. الأزرار (إجراءات) - _buildCompactActionButtons( - context, controller, profileController, audioController), - - // إضافة هامش سفلي بسيط لرفع الأزرار عن حافة الشاشة - const SizedBox(height: 5), - ], - ), - ), ), ); }); @@ -227,7 +195,7 @@ class RideBeginPassenger extends StatelessWidget { AudioRecorderController audioController) { final uiController = Get.find(); return SizedBox( - height: 60, + height: 64, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ @@ -325,19 +293,20 @@ class RideBeginPassenger extends StatelessWidget { }) { return InkWell( onTap: onTap, - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(AppRadii.md), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.all(10), + constraints: const BoxConstraints(minWidth: 40, minHeight: 40), decoration: BoxDecoration( color: bgColor, shape: BoxShape.circle, ), child: Icon(icon, size: 20, color: color), ), - const SizedBox(height: 4), + AppSpacing.vGapXs, Text( label, style: TextStyle( diff --git a/siro_rider/lib/views/home/map_widget.dart/searching_captain_window.dart b/siro_rider/lib/views/home/map_widget.dart/searching_captain_window.dart index 9a29ab50..1aa46663 100644 --- a/siro_rider/lib/views/home/map_widget.dart/searching_captain_window.dart +++ b/siro_rider/lib/views/home/map_widget.dart/searching_captain_window.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:siro_rider/constant/colors.dart'; +import 'package:siro_rider/constant/design.dart'; import 'package:siro_rider/constant/style.dart'; import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart'; import 'package:siro_rider/controller/home/map/ride_state.dart'; -// --- الويدجت الرئيسية بالتصميم الجديد --- +// --- نافذة البحث عن كابتن --- class SearchingCaptainWindow extends StatefulWidget { const SearchingCaptainWindow({super.key}); @@ -34,60 +35,45 @@ class _SearchingCaptainWindowState extends State @override Widget build(BuildContext context) { - // [تعديل 1] نستخدم Obx للاستماع إلى التغييرات في حالة الرحلة + // نستمع لتغييرات حالة الرحلة return Obx(() { - // ابحث عن الكنترولر مرة واحدة final controller = Get.find(); - // [تعديل 2] شرط الإظهار يعتمد الآن على حالة الرحلة مباشرة + // شرط الإظهار يعتمد على حالة الرحلة مباشرة final bool isVisible = controller.currentRideState.value == RideState.searching; return AnimatedPositioned( - duration: const Duration(milliseconds: 300), + duration: AppDurations.base, curve: Curves.easeInOut, - bottom: isVisible ? 0 : -Get.height * 0.45, // زيادة الارتفاع قليلاً + bottom: isVisible ? 0 : -Get.height * 0.45, left: 0, right: 0, - child: Container( - padding: const EdgeInsets.fromLTRB(20, 20, 20, 16), - decoration: BoxDecoration( - color: AppColor.secondaryColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(24), - topRight: Radius.circular(24), - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.2), - blurRadius: 20, - offset: const Offset(0, -5), - ), - ], - ), + // لا يوجد مقبض سحب: هذه الحالة لا تُغلق بالسحب بل بزر الإلغاء فقط + child: SiroSheetSurface( + showHandle: false, child: Column( mainAxisSize: MainAxisSize.min, children: [ // --- 1. أنيميشن الرادار --- _buildRadarAnimation(controller), - const SizedBox(height: 20), + AppSpacing.vGapXl, // --- 2. زر الإلغاء --- SizedBox( width: double.infinity, child: OutlinedButton( onPressed: () { - // [تعديل 3] استدعاء دالة الإلغاء الموحدة + // استدعاء دالة الإلغاء الموحدة controller.changeCancelRidePageShow(); - // (); }, style: OutlinedButton.styleFrom( foregroundColor: AppColor.writeColor, - side: - BorderSide(color: AppColor.writeColor.withValues(alpha: 0.3)), + side: BorderSide( + color: AppColor.writeColor.withValues(alpha: 0.3)), + minimumSize: const Size.fromHeight(AppSizes.minTouch), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12)), - padding: const EdgeInsets.symmetric(vertical: 12), + borderRadius: BorderRadius.circular(AppRadii.md)), ), child: Text('Cancel Search'.tr), ), @@ -106,7 +92,7 @@ class _SearchingCaptainWindowState extends State child: Stack( alignment: Alignment.center, children: [ - // --- دوائر الرادار المتحركة (تبقى كما هي) --- + // --- دوائر الرادار المتحركة --- ...List.generate(3, (index) { return FadeTransition( opacity: Tween(begin: 1.0, end: 0.0).animate( @@ -139,25 +125,21 @@ class _SearchingCaptainWindowState extends State mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - // [تعديل 4] النص يأتي مباشرة من الكنترولر + // النص يأتي مباشرة من الكنترولر controller.driversStatusForSearchWindow, style: AppStyle.headTitle.copyWith(fontSize: 20), textAlign: TextAlign.center, ), - const SizedBox(height: 8), + AppSpacing.vGapSm, Text( 'Searching for the nearest captain...'.tr, style: AppStyle.subtitle .copyWith(color: AppColor.writeColor.withValues(alpha: 0.7)), textAlign: TextAlign.center, ), - const SizedBox(height: 16), - - // --- [!! تعديل جوهري !!] --- - // لم نعد بحاجة لـ buildTimerForIncrease - // المؤقت الرئيسي في الكنترولر هو من يقرر متى يعرض الحوار - // وهذا الجزء من الواجهة أصبح "غبياً" (لا يحتوي على منطق) - // الكنترولر سيستدعي _showIncreaseFeeDialog مباشرة + AppSpacing.vGapLg, + // مؤشر دوّار: المؤقت الرئيسي في الكنترولر هو من يقرر متى يعرض + // حوار زيادة الأجرة، لذا هذا الجزء "غبي" بلا منطق. SizedBox( height: 40, width: 40, @@ -167,7 +149,8 @@ class _SearchingCaptainWindowState extends State CircularProgressIndicator( strokeWidth: 3, color: AppColor.primaryColor, - backgroundColor: AppColor.primaryColor.withValues(alpha: 0.2), + backgroundColor: + AppColor.primaryColor.withValues(alpha: 0.2), ), Center( child: Icon( @@ -185,286 +168,3 @@ class _SearchingCaptainWindowState extends State ); } } - -// --- [!! تعديل جوهري !!] --- -// تم حذف دالة `buildTimerForIncrease` بالكامل. -// تم حذف دالة `_showIncreaseFeeDialog` من هذا الملف. -// لماذا؟ لأن الكنترولر الآن هو المسؤول الوحيد عن إظهار الحوار. -// دالة `_showIncreaseFeeDialog` موجودة بالفعل داخل `map_passenger_controller.dart` -// وسيتم استدعاؤها من `_handleRideState` عند انتهاء مهلة الـ 90 ثانية. - -// // --- الويدجت الرئيسية بالتصميم الجديد --- -// class SearchingCaptainWindow extends StatefulWidget { -// const SearchingCaptainWindow({super.key}); - -// @override -// State createState() => _SearchingCaptainWindowState(); -// } - -// class _SearchingCaptainWindowState extends State -// with SingleTickerProviderStateMixin { -// late AnimationController _animationController; - -// @override -// void initState() { -// super.initState(); -// _animationController = AnimationController( -// vsync: this, -// duration: const Duration(seconds: 2), -// )..repeat(); -// } - -// @override -// void dispose() { -// _animationController.dispose(); -// super.dispose(); -// } - -// @override -// Widget build(BuildContext context) { -// return GetBuilder( -// builder: (controller) { -// return AnimatedPositioned( -// duration: const Duration(milliseconds: 300), -// curve: Curves.easeInOut, -// bottom: controller.isSearchingWindow ? 0 : -Get.height * 0.4, -// left: 0, -// right: 0, -// child: Container( -// padding: const EdgeInsets.fromLTRB(20, 20, 20, 16), -// decoration: BoxDecoration( -// color: AppColor.secondaryColor, -// borderRadius: const BorderRadius.only( -// topLeft: Radius.circular(24), -// topRight: Radius.circular(24), -// ), -// boxShadow: [ -// BoxShadow( -// color: Colors.black.withOpacity(0.2), -// blurRadius: 20, -// offset: const Offset(0, -5), -// ), -// ], -// ), -// child: Column( -// mainAxisSize: MainAxisSize.min, -// children: [ -// // --- 1. أنيميشن الرادار --- -// _buildRadarAnimation(controller), -// const SizedBox(height: 20), - -// // --- 2. زر الإلغاء --- -// SizedBox( -// width: double.infinity, -// child: OutlinedButton( -// onPressed: () { -// // --- نفس منطقك للإلغاء --- -// controller.changeCancelRidePageShow(); -// }, -// style: OutlinedButton.styleFrom( -// foregroundColor: AppColor.writeColor, -// side: BorderSide( -// color: AppColor.writeColor.withOpacity(0.3)), -// shape: RoundedRectangleBorder( -// borderRadius: BorderRadius.circular(12)), -// padding: const EdgeInsets.symmetric(vertical: 12), -// ), -// child: Text('Cancel Search'.tr), -// ), -// ), -// ], -// ), -// ), -// ); -// }, -// ); -// } - -// // --- ويدجت بناء أنيميشن الرادار --- -// Widget _buildRadarAnimation(MapPassengerController controller) { -// return SizedBox( -// height: 180, // ارتفاع ثابت لمنطقة الأنيميشن -// child: Stack( -// alignment: Alignment.center, -// children: [ -// // --- دوائر الرادار المتحركة --- -// ...List.generate(3, (index) { -// return FadeTransition( -// opacity: Tween(begin: 1.0, end: 0.0).animate( -// CurvedAnimation( -// parent: _animationController, -// curve: Interval((index) / 3, 1.0, curve: Curves.easeInOut), -// ), -// ), -// child: ScaleTransition( -// scale: Tween(begin: 0.3, end: 1.0).animate( -// CurvedAnimation( -// parent: _animationController, -// curve: Interval((index) / 3, 1.0, curve: Curves.easeInOut), -// ), -// ), -// child: Container( -// decoration: BoxDecoration( -// shape: BoxShape.circle, -// border: Border.all( -// color: AppColor.primaryColor.withOpacity(0.7), -// width: 2, -// ), -// ), -// ), -// ), -// ); -// }), -// // --- المحتوى في المنتصف --- -// Column( -// mainAxisAlignment: MainAxisAlignment.center, -// children: [ -// Text( -// controller.driversStatusForSearchWindow, -// style: AppStyle.headTitle.copyWith(fontSize: 20), -// textAlign: TextAlign.center, -// ), -// const SizedBox(height: 8), -// Text( -// 'Searching for the nearest captain...'.tr, -// style: AppStyle.subtitle -// .copyWith(color: AppColor.writeColor.withOpacity(0.7)), -// textAlign: TextAlign.center, -// ), -// const SizedBox(height: 16), -// // --- استدعاء نفس دالة المؤقت الخاصة بك --- -// buildTimerForIncrease(controller), -// ], -// ), -// ], -// ), -// ); -// } -// } - -// // --- نفس دالة المؤقت الخاصة بك مع تعديلات شكلية بسيطة --- -// Widget buildTimerForIncrease(MapPassengerController mapPassengerController) { -// return StreamBuilder( -// stream: Stream.periodic(const Duration(seconds: 1)) -// .map((_) => ++mapPassengerController.currentTimeSearchingCaptainWindow), -// initialData: 0, -// builder: (context, snapshot) { -// if (snapshot.hasData && snapshot.data! > 45) { -// // --- عرض زر زيادة الأجرة بنفس منطقك القديم --- -// return TextButton( -// onPressed: () => -// _showIncreaseFeeDialog(context, mapPassengerController), -// child: Text( -// "No one accepted? Try increasing the fare.".tr, -// style: AppStyle.title.copyWith( -// color: AppColor.primaryColor, -// decoration: TextDecoration.underline), -// textAlign: TextAlign.center, -// ), -// ); -// } - -// final double progress = (snapshot.data ?? 0).toDouble() / 30.0; - -// return SizedBox( -// height: 40, -// width: 40, -// child: Stack( -// fit: StackFit.expand, -// children: [ -// CircularProgressIndicator( -// value: progress, -// strokeWidth: 3, -// color: AppColor.primaryColor, -// backgroundColor: AppColor.primaryColor.withOpacity(0.2), -// ), -// Center( -// child: Text( -// '${snapshot.data ?? 0}', -// style: AppStyle.title.copyWith( -// color: AppColor.writeColor, fontWeight: FontWeight.bold), -// ), -// ), -// ], -// ), -// ); -// }, -// ); -// } - -// // --- دالة لعرض نافذة زيادة الأجرة (مأخوذة من منطقك القديم) --- -// void _showIncreaseFeeDialog( -// BuildContext context, MapPassengerController mapPassengerController) { -// Get.defaultDialog( -// barrierDismissible: false, -// title: "Increase Your Trip Fee (Optional)".tr, -// titleStyle: AppStyle.title, -// content: Column( -// children: [ -// Text( -// "We haven't found any drivers yet. Consider increasing your trip fee to make your offer more attractive to drivers." -// .tr, -// style: AppStyle.subtitle, -// textAlign: TextAlign.center, -// ), -// const SizedBox(height: 16), -// Row( -// mainAxisAlignment: MainAxisAlignment.center, -// children: [ -// IconButton( -// onPressed: () { -// mapPassengerController.increasFeeFromPassenger.text = -// (mapPassengerController.totalPassenger + 3) -// .toStringAsFixed(1); -// mapPassengerController.update(); -// }, -// icon: const Icon(Icons.add_circle, -// size: 40, color: AppColor.greenColor), -// ), -// SizedBox( -// width: 100, -// child: Form( -// key: mapPassengerController.increaseFeeFormKey, -// child: MyTextForm( -// controller: mapPassengerController.increasFeeFromPassenger, -// label: -// mapPassengerController.totalPassenger.toStringAsFixed(2), -// hint: -// mapPassengerController.totalPassenger.toStringAsFixed(2), -// type: TextInputType.number, -// ), -// ), -// ), -// IconButton( -// onPressed: () { -// mapPassengerController.increasFeeFromPassenger.text = -// (mapPassengerController.totalPassenger - 3) -// .toStringAsFixed(1); -// mapPassengerController.update(); -// }, -// icon: const Icon(Icons.remove_circle, -// size: 40, color: AppColor.redColor), -// ), -// ], -// ), -// ], -// ), -// actions: [ -// TextButton( -// child: Text("No, thanks".tr, -// style: const TextStyle(color: AppColor.redColor)), -// onPressed: () { -// Get.back(); -// // mapPassengerController.cancelRide(); -// mapPassengerController.changeCancelRidePageShow(); -// }, -// ), -// ElevatedButton( -// style: ElevatedButton.styleFrom(backgroundColor: AppColor.greenColor), -// child: Text("Increase Fee".tr), -// onPressed: () => -// mapPassengerController.increaseFeeByPassengerAndReOrder(), -// ), -// ], -// ); -// } diff --git a/siro_rider/lib/views/home/map_widget.dart/select_driver_mishwari.dart b/siro_rider/lib/views/home/map_widget.dart/select_driver_mishwari.dart index 6acec0d2..b509be20 100644 --- a/siro_rider/lib/views/home/map_widget.dart/select_driver_mishwari.dart +++ b/siro_rider/lib/views/home/map_widget.dart/select_driver_mishwari.dart @@ -46,49 +46,43 @@ class CupertinoDriverListWidget extends StatelessWidget { child: CupertinoListTile( padding: const EdgeInsets.symmetric( vertical: 4, horizontal: 8), - leading: CircleAvatar( - radius: 25, - backgroundImage: NetworkImage( - '${AppLink.server}/portrate_captain_image/${driver['id']}.jpg', - ), - child: Builder( - builder: (context) { - return Image.network( - '${AppLink.server}/portrate_captain_image/${driver['id']}.jpg', - fit: BoxFit.cover, - loadingBuilder: (BuildContext context, - Widget child, - ImageChunkEvent? loadingProgress) { - if (loadingProgress == null) { - return child; // Image is loaded - } else { - return Center( - child: CircularProgressIndicator( - value: loadingProgress - .expectedTotalBytes != - null - ? loadingProgress - .cumulativeBytesLoaded / - (loadingProgress - .expectedTotalBytes ?? - 1) - : null, - ), - ); - } - }, - errorBuilder: (BuildContext context, - Object error, StackTrace? stackTrace) { - return const Icon( - Icons - .person, // Icon to show when image fails to load - size: 25, // Adjust the size as needed - color: AppColor - .blueColor, // Color for the error icon - ); - }, - ); - }, + leading: ClipOval( + child: SizedBox( + width: 50, + height: 50, + child: Image.network( + '${AppLink.server}/portrate_captain_image/${driver['id']}.jpg', + fit: BoxFit.cover, + loadingBuilder: (BuildContext context, + Widget child, + ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; // Image is loaded + } + return Center( + child: CircularProgressIndicator( + strokeWidth: 2, + value: loadingProgress + .expectedTotalBytes != + null + ? loadingProgress + .cumulativeBytesLoaded / + (loadingProgress + .expectedTotalBytes ?? + 1) + : null, + ), + ); + }, + errorBuilder: (BuildContext context, + Object error, StackTrace? stackTrace) { + return const Icon( + Icons.person, + size: 25, + color: AppColor.blueColor, + ); + }, + ), ), ), title: Row( diff --git a/siro_rider/lib/views/home/map_widget.dart/timer_for_cancell_trip_from_passenger.dart b/siro_rider/lib/views/home/map_widget.dart/timer_for_cancell_trip_from_passenger.dart deleted file mode 100644 index 9282d385..00000000 --- a/siro_rider/lib/views/home/map_widget.dart/timer_for_cancell_trip_from_passenger.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -import '../../../constant/style.dart'; -import '../../../controller/home/map/ride_lifecycle_controller.dart'; - -GetBuilder timerForCancelTripFromPassenger() { - return GetBuilder( - builder: (controller) { - final isNearEnd = - controller.remainingTime <= 5; // Define a threshold for "near end" - - return controller.remainingTime > 0 && controller.remainingTime != 25 - ? Positioned( - bottom: 5, - left: 10, - right: 10, - child: Container( - height: 180, - decoration: AppStyle.boxDecoration, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Stack( - alignment: Alignment.center, - children: [ - CircularProgressIndicator( - value: controller.progress, - // Set the color based on the "isNearEnd" condition - color: isNearEnd ? Colors.red : Colors.blue, - ), - Text( - '${controller.remainingTime}', - style: AppStyle.number, - ), - ], - ), - const SizedBox( - width: 30, - ), - Text( - 'You can cancel Ride now'.tr, - style: AppStyle.title, - ) - ], - ), - Text( - 'After this period\nYou can\'t cancel!'.tr, - style: AppStyle.title, - ) - ], - ), - ), - ), - ) - : const SizedBox(); - }, - ); -} diff --git a/siro_rider/lib/views/home/map_widget.dart/timer_to_passenger_from_driver.dart b/siro_rider/lib/views/home/map_widget.dart/timer_to_passenger_from_driver.dart deleted file mode 100644 index cbbbb914..00000000 --- a/siro_rider/lib/views/home/map_widget.dart/timer_to_passenger_from_driver.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:siro_rider/views/widgets/elevated_btn.dart'; - -import '../../../constant/colors.dart'; -import '../../../constant/style.dart'; -import '../../../controller/home/map/ride_lifecycle_controller.dart'; -import 'ride_begin_passenger.dart'; - -class TimerToPassengerFromDriver extends StatelessWidget { - const TimerToPassengerFromDriver({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return GetBuilder(builder: (controller) { - if (controller.remainingTime == 0 && - (controller.isDriverInPassengerWay == true || - controller.timeToPassengerFromDriverAfterApplied > 0)) { - // ) { - return Positioned( - left: 10, - right: 10, - bottom: 5, - child: Container( - decoration: AppStyle.boxDecoration, - height: controller.remainingTime == 0 && - (controller.isDriverInPassengerWay == true || - controller.timeToPassengerFromDriverAfterApplied > 0) - ? 200 - : 0, - // width: 100, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - Text( - 'You Can cancel Ride After Captain did not come in the time' - .tr, - style: AppStyle.title, - textAlign: TextAlign.center, - ), - Stack( - children: [ - LinearProgressIndicator( - backgroundColor: AppColor.accentColor, - color: controller - .remainingTimeToPassengerFromDriverAfterApplied < - 60 - ? AppColor.redColor - : AppColor.greenColor, - minHeight: 25, - borderRadius: BorderRadius.circular(15), - value: controller - .progressTimerToPassengerFromDriverAfterApplied - .toDouble(), - ), - Center( - child: Text( - controller.stringRemainingTimeToPassenger, - style: AppStyle.title, - ), - ) - ], - ), - IconButton( - onPressed: () {}, - icon: const Icon( - Icons.phone, - color: AppColor.blueColor, - ), - ), - controller.remainingTimeToPassengerFromDriverAfterApplied < 60 - ? MyElevatedButton( - title: 'You can cancel trip'.tr, - onPressed: () async { - await controller - .calculateDistanceBetweenPassengerAndDriverBeforeCancelRide(); - }) - : const SizedBox() - ], - ), - ), - ), - ); - } else if (controller.remainingTime == 0 && - controller.isDriverArrivePassenger == true) { - return Positioned( - left: 10, - right: 10, - bottom: 5, - child: Container( - decoration: AppStyle.boxDecoration, - height: controller.remainingTime == 0 && - controller.isDriverArrivePassenger == true - ? 150 - : 0, - // width: 100, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - Text( - 'The driver waiting you in picked location .'.tr, - style: AppStyle.title, - textAlign: TextAlign.center, - ), - Stack( - children: [ - LinearProgressIndicator( - backgroundColor: AppColor.accentColor, - color: - controller.remainingTimeDriverWaitPassenger5Minute < - 60 - ? AppColor.redColor - : AppColor.greenColor, - minHeight: 50, - borderRadius: BorderRadius.circular(15), - value: controller - .progressTimerDriverWaitPassenger5Minute - .toDouble(), - ), - Center( - child: Text( - controller - .stringRemainingTimeDriverWaitPassenger5Minute, - style: AppStyle.title, - ), - ) - ], - ), - Text( - 'Please go to Car now '.tr, - style: AppStyle.title, - textAlign: TextAlign.center, - ), - ], - ), - ), - ), - ); - } else { - return const RideBeginPassenger(); - } - }); - } -} diff --git a/siro_rider/lib/views/home/map_widget.dart/form_serch_multiy_point.dart b/siro_rider/lib/views/home/map_widget.dart/waypoint_stops_list.dart similarity index 100% rename from siro_rider/lib/views/home/map_widget.dart/form_serch_multiy_point.dart rename to siro_rider/lib/views/home/map_widget.dart/waypoint_stops_list.dart