Update: 2026-07-11 05:39:23

This commit is contained in:
Hamza-Ayed
2026-07-11 05:39:23 +03:00
parent 8b19adeb80
commit 06dc0aaffe
31 changed files with 845 additions and 3846 deletions
-1
View File
@@ -116,7 +116,6 @@ flutter {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' 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-safetynet:18.1.0'
implementation 'com.google.android.gms:play-services-location:21.3.0' implementation 'com.google.android.gms:play-services-location:21.3.0'
} }
@@ -7,22 +7,27 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.Icon
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
/** /**
* Android 16 Progress Style Live Update Plugin * خدمة تحديث حالة الرحلة في شريط الإشعارات على أندرويد.
* *
* يستخدم NotificationCompat مع setProgressStyle لتوفير * تعمل على ثلاثة مستويات حسب الجهاز:
* تجربة Live Update متقدمة على Android 16 (API 36) والأجهزة الأحدث. * - أندرويد 16 فما فوق (API 36+) مع موافقة المستخدم: تُستخدم
* * ميزة Live Updates الحقيقية عبر Notification.ProgressStyle
* الميزات: * (نفس الفئة والدوال التي تحققنا منها مباشرة من ملف الـSDK).
* - شريط تقدم متحرك (Progress Style) * - أندرويد 16 فما فوق بدون موافقة المستخدم، أو أي إصدار من
* - تحديث مباشر للوقت المتبقي * API 23 حتى 35: إشعار عادٍ ثابت (Ongoing) بشريط تقدّم كلاسيكي،
* - دعم الحالات المختلفة (بحث، في الطريق، وصل، رحلة جارية) * يعمل بشكل متطابق تقريباً بصرياً لكن بدون معاملة النظام الخاصة.
* - إشعار ثابت (Ongoing) لا يمكن إلغاؤه أثناء الرحلة * - أقل من API 26: لا يتم إنشاء قناة إشعارات (الكلاس غير موجود
* أصلاً في النظام قبل هذا الإصدار)، ويُستخدم مسار الإشعار
* الكلاسيكي مباشرة بدون قناة.
*/ */
class AndroidLiveUpdatePlugin(private val context: Context) { 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_NAME = "Siro Live Ride Progress"
private const val CHANNEL_DESC = "Live progress updates for your current Siro ride" private const val CHANNEL_DESC = "Live progress updates for your current Siro ride"
private const val NOTIFICATION_ID = 999 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_SEARCHING = "searching"
private const val STATUS_DRIVER_ON_WAY = "driver_on_way" private const val STATUS_DRIVER_ON_WAY = "driver_on_way"
private const val STATUS_DRIVER_ARRIVED = "driver_arrived" private const val STATUS_DRIVER_ARRIVED = "driver_arrived"
@@ -51,34 +54,66 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
private var currentBody: String = "" private var currentBody: String = ""
init { init {
createNotificationChannel() // NotificationChannel نفسها غير موجودة في النظام قبل API 26،
// فاستدعاؤها بدون هذا الشرط يتسبب بانهيار فوري للتطبيق عند
// الإقلاع على أي جهاز أقدم (Android 6/7).
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
}
} }
/** @RequiresApi(Build.VERSION_CODES.O)
* إنشاء قناة الإشعارات المخصصة للرحلة
* مع دعم Android 16 Progress Style
*/
private fun createNotificationChannel() { private fun createNotificationChannel() {
val channel = NotificationChannel( val channel = NotificationChannel(
CHANNEL_ID, CHANNEL_ID,
CHANNEL_NAME, CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW // Low importance for progress style NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
description = CHANNEL_DESC description = CHANNEL_DESC
setShowBadge(false) setShowBadge(false)
enableVibration(false) enableVibration(false)
enableLights(false) enableLights(false)
// Android 16: دعم التقدم المستمر
if (Build.VERSION.SDK_INT >= 36) {
// يمكن إضافة خصائص Android 16 هنا
}
} }
notificationManager.createNotificationChannel(channel) 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( fun startLiveUpdate(
rideId: String, rideId: String,
@@ -93,19 +128,11 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
currentProgress = (progress * 100).toInt().coerceIn(0, 100) currentProgress = (progress * 100).toInt().coerceIn(0, 100)
currentMaxProgress = 100 currentMaxProgress = 100
showNotification( render(indeterminate = false)
title = currentTitle,
body = currentBody,
progress = currentProgress,
maxProgress = currentMaxProgress,
indeterminate = false,
ongoing = true
)
} }
/** /**
* تحديث التقدم والوقت المتبقي * تحديث التقدم والوقت المتبقي
* يستخدم Progress Style مع animate للتحديثات
*/ */
fun updateProgress(progress: Double, etaText: String) { fun updateProgress(progress: Double, etaText: String) {
currentProgress = (progress * 100).toInt().coerceIn(0, 100) currentProgress = (progress * 100).toInt().coerceIn(0, 100)
@@ -122,19 +149,11 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
else -> currentBody else -> currentBody
} }
showNotification( render(indeterminate = false)
title = currentTitle,
body = currentBody,
progress = currentProgress,
maxProgress = currentMaxProgress,
indeterminate = false,
ongoing = true
)
} }
/** /**
* تحديث حالة الرحلة بالكامل * تحديث حالة الرحلة بالكامل
* يستخدم لتغيير الحالة بين (في الطريق، وصل، رحلة جارية)
*/ */
fun updateStatus( fun updateStatus(
status: String, status: String,
@@ -150,50 +169,23 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
STATUS_SEARCHING -> { STATUS_SEARCHING -> {
currentTitle = "🔍 جاري البحث عن سائق" currentTitle = "🔍 جاري البحث عن سائق"
currentBody = etaText currentBody = etaText
showNotification( render(indeterminate = true)
title = currentTitle,
body = currentBody,
progress = 0,
maxProgress = 0,
indeterminate = true,
ongoing = true
)
} }
STATUS_DRIVER_ON_WAY -> { STATUS_DRIVER_ON_WAY -> {
currentTitle = "🚗 السائق في الطريق إليك" currentTitle = "🚗 السائق في الطريق إليك"
currentBody = "$driverName • $carDetails • $etaText" currentBody = "$driverName • $carDetails • $etaText"
showNotification( render(indeterminate = false)
title = currentTitle,
body = currentBody,
progress = currentProgress,
maxProgress = 100,
indeterminate = false,
ongoing = true
)
} }
STATUS_DRIVER_ARRIVED -> { STATUS_DRIVER_ARRIVED -> {
currentTitle = "📍 السائق وصل" currentTitle = "📍 السائق وصل"
currentBody = "الرجاء التوجّه لمقابلة $driverName عند نقطة الالتقاء" currentBody = "الرجاء التوجّه لمقابلة $driverName عند نقطة الالتقاء"
showNotification( currentProgress = 100
title = currentTitle, render(indeterminate = false)
body = currentBody,
progress = 100,
maxProgress = 100,
indeterminate = false,
ongoing = true
)
} }
STATUS_IN_PROGRESS -> { STATUS_IN_PROGRESS -> {
currentTitle = "🚀 الرحلة جارية الآن" currentTitle = "🚀 الرحلة جارية الآن"
currentBody = "المتبقي تقريبًا: $etaText" currentBody = "المتبقي تقريبًا: $etaText"
showNotification( render(indeterminate = false)
title = currentTitle,
body = currentBody,
progress = currentProgress,
maxProgress = 100,
indeterminate = false,
ongoing = true
)
} }
} }
} }
@@ -208,29 +200,58 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
} }
/** /**
* إظهار الإشعار مع الإعدادات المناسبة * يختار المسار المناسب: Live Update الحقيقية إن كانت متاحة ومسموحة،
* وإلا الإشعار الكلاسيكي الآمن على كل الإصدارات من API 23.
*/ */
private fun showNotification( private fun render(indeterminate: Boolean) {
title: String, if (canUsePromotedLiveUpdate()) {
body: String, try {
progress: Int, showPromotedProgressNotification(indeterminate)
maxProgress: Int, return
indeterminate: Boolean, } catch (e: Exception) {
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)
} }
showClassicNotification(indeterminate)
}
val pendingIntent = PendingIntent.getActivity( /**
context, * المسار الجديد: Notification.ProgressStyle الحقيقية (Android 16 / API 36+).
0, * التوقيعات هنا مؤكدة مباشرة من ملف android-36/android.jar (javap)، وليست تخميناً.
intent, */
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE @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) val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(android.R.drawable.ic_dialog_info) .setSmallIcon(android.R.drawable.ic_dialog_info)
.setLargeIcon( .setLargeIcon(
@@ -239,30 +260,23 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
android.R.drawable.ic_menu_directions android.R.drawable.ic_menu_directions
) )
) )
.setContentTitle(title) .setContentTitle(currentTitle)
.setContentText(body) .setContentText(currentBody)
.setStyle( .setStyle(
NotificationCompat.BigTextStyle() NotificationCompat.BigTextStyle()
.bigText(body) .bigText(currentBody)
) )
.setPriority(NotificationCompat.PRIORITY_LOW) .setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(ongoing) .setOngoing(true)
.setAutoCancel(false) .setAutoCancel(false)
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)
.setContentIntent(pendingIntent) .setContentIntent(buildContentIntent())
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
// تطبيق Progress Style
if (indeterminate) { if (indeterminate) {
notificationBuilder.setProgress(0, 0, true) notificationBuilder.setProgress(0, 0, true)
} else { } else {
notificationBuilder.setProgress(maxProgress, progress, false) notificationBuilder.setProgress(currentMaxProgress, currentProgress, false)
}
// Android 16: إضافة إجراءات سريعة للإشعار
if (Build.VERSION.SDK_INT >= 36) {
// يمكن إضافة إجراءات Android 16 هنا
// مثل: إظهار الخريطة، مشاركة الرحلة، إلخ
} }
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()) notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
@@ -302,11 +316,13 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
result.success(true) result.success(true)
} }
"isSupported" -> { "isSupported" -> {
result.success(Build.VERSION.SDK_INT >= 30) // يدعم Android 11+ // الإشعار الكلاسيكي يعمل من API 23 وما فوق؛ الأجهزة الأحدث
// تحصل تلقائياً على Live Update الحقيقية عبر canUsePromotedLiveUpdate().
result.success(Build.VERSION.SDK_INT >= 23)
} }
else -> { else -> {
result.notImplemented() result.notImplemented()
} }
} }
} }
} }
@@ -11,7 +11,6 @@ import android.widget.LinearLayout
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.core.view.setPadding import androidx.core.view.setPadding
import com.scottyab.rootbeer.RootBeer
import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
@@ -131,7 +130,7 @@ class MainActivity : FlutterFragmentActivity() {
private fun isDeviceCompromised(): Boolean { private fun isDeviceCompromised(): Boolean {
return try { return try {
nativeRootCheck() || rootBeerCheck() // || !safetyNetCheck() nativeRootCheck() // || !safetyNetCheck()
} catch (e: Exception) { } catch (e: Exception) {
Log.e("SecurityCheck", "Error during security checks: ${e.message}", e) Log.e("SecurityCheck", "Error during security checks: ${e.message}", e)
true 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 (معلّق كما هو) // SafetyNet (معلّق كما هو)
private fun safetyNetCheck(): Boolean { private fun safetyNetCheck(): Boolean {
Log.d("SecurityCheck", "Starting SafetyNet check...") Log.d("SecurityCheck", "Starting SafetyNet check...")
+9 -9
View File
@@ -80,28 +80,28 @@ PODS:
- GoogleDataTransport (10.1.0): - GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4) - PromisesObjC (~> 2.4)
- GoogleUtilities/AppDelegateSwizzler (8.1.1): - GoogleUtilities/AppDelegateSwizzler (8.1.2):
- GoogleUtilities/Environment - GoogleUtilities/Environment
- GoogleUtilities/Logger - GoogleUtilities/Logger
- GoogleUtilities/Network - GoogleUtilities/Network
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Environment (8.1.1): - GoogleUtilities/Environment (8.1.2):
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Logger (8.1.1): - GoogleUtilities/Logger (8.1.2):
- GoogleUtilities/Environment - GoogleUtilities/Environment
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Network (8.1.1): - GoogleUtilities/Network (8.1.2):
- GoogleUtilities/Logger - GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib" - "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Reachability - GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (8.1.1)": - "GoogleUtilities/NSData+zlib (8.1.2)":
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Privacy (8.1.1) - GoogleUtilities/Privacy (8.1.2)
- GoogleUtilities/Reachability (8.1.1): - GoogleUtilities/Reachability (8.1.2):
- GoogleUtilities/Logger - GoogleUtilities/Logger
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/UserDefaults (8.1.1): - GoogleUtilities/UserDefaults (8.1.2):
- GoogleUtilities/Logger - GoogleUtilities/Logger
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GTMSessionFetcher/Core (5.3.0) - GTMSessionFetcher/Core (5.3.0)
@@ -319,7 +319,7 @@ SPEC CHECKSUMS:
flutter_webrtc: ec91d94b484ad49cf191ef93413f64a40ffd3b4c flutter_webrtc: ec91d94b484ad49cf191ef93413f64a40ffd3b4c
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 4f2618a4a1e762a1ee134a1e2323bba9843e06da GoogleUtilities: 766ace00c6b10d8148408f329d10c4f051931850
GTMSessionFetcher: 127211aeec0b1e904fc49f4f6f895dcc535b0ecf GTMSessionFetcher: 127211aeec0b1e904fc49f4f6f895dcc535b0ecf
image_cropper: 64567491beea6cd1bc4b11948e2babb590de5826 image_cropper: 64567491beea6cd1bc4b11948e2babb590de5826
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
+172
View File
@@ -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<BoxShadow> 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<BoxShadow> 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),
),
),
);
}
}
@@ -20,7 +20,7 @@ import '../../../print.dart';
import '../../../services/offline_map_service.dart'; import '../../../services/offline_map_service.dart';
import '../../functions/crud.dart'; import '../../functions/crud.dart';
import '../points_for_rider_controller.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 '../../../views/widgets/error_snakbar.dart';
import 'map_engine_controller.dart'; import 'map_engine_controller.dart';
import '../deep_link_controller.dart'; import '../deep_link_controller.dart';
@@ -29,6 +29,8 @@ class UiInteractionsController extends GetxController {
TextEditingController whatsAppLocationText = TextEditingController(); TextEditingController whatsAppLocationText = TextEditingController();
final sosFormKey = GlobalKey<FormState>(); final sosFormKey = GlobalKey<FormState>();
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@@ -15,7 +15,7 @@ import '../../views/home/map_widget.dart/ride_begin_passenger.dart';
import '../../controller/home/menu_controller.dart'; import '../../controller/home/menu_controller.dart';
import 'map_widget.dart/apply_order_widget.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/car_details_widget_to_go.dart';
import 'map_widget.dart/cash_confirm_bottom_page.dart'; import 'map_widget.dart/cash_confirm_bottom_page.dart';
import 'map_widget.dart/google_map_passenger_widget.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 'route_planner/route_planner.dart';
import 'map_widget.dart/map_menu_widget.dart'; import 'map_widget.dart/map_menu_widget.dart';
import '../../controller/functions/package_info.dart'; import '../../controller/functions/package_info.dart';
import 'map_widget.dart/passengerRideLoctionWidget.dart'; import 'map_widget.dart/passenger_ride_location_widget.dart';
import 'map_widget.dart/payment_method.page.dart'; import 'map_widget.dart/payment_method_page.dart';
import 'map_widget.dart/points_page_for_rider.dart'; import 'map_widget.dart/points_page_for_rider.dart';
import 'map_widget.dart/ride_from_start_app.dart'; import 'map_widget.dart/ride_from_start_app.dart';
import 'map_widget.dart/searching_captain_window.dart'; import 'map_widget.dart/searching_captain_window.dart';
@@ -65,35 +65,26 @@ class MapPagePassenger extends StatelessWidget {
// OsmMapPassengerWidget(), // OsmMapPassengerWidget(),
leftMainMenuIcons(), leftMainMenuIcons(),
// PaymobPackage(), // PaymobPackage(),
// ── New Route Planner ──
const RpCenterPin(), const RpCenterPin(),
// PickerAnimtionContainerFormPlaces(),
const RoutePlannerSheet(), const RoutePlannerSheet(),
// NewMainBottomSheet(),
buttomSheetMapPage(),
CarDetailsTypeToChoose(), CarDetailsTypeToChoose(),
// const HeaderDestination(), // const HeaderDestination(),
const BurcMoney(), const BurcMoney(),
// const PromoCode(), // const PromoCode(),
ApplyOrderWidget(), ApplyOrderWidget(),
const MapMenuWidget(), const MapMenuWidget(),
// hexagonClipper(),
const CancelRidePageShow(), const CancelRidePageShow(),
CashConfirmPageShown(), CashConfirmPageShown(),
const PaymentMethodPage(), const PaymentMethodPage(),
const SearchingCaptainWindow(), const SearchingCaptainWindow(),
AttributionMap(), AttributionMap(),
// timerForCancelTripFromPassenger(),
// const DriverTimeArrivePassengerPage(),
// const TimerToPassengerFromDriver(),
const PassengerRideLocationWidget(), const PassengerRideLocationWidget(),
const RideBeginPassenger(), const RideBeginPassenger(),
const VipRideBeginPassenger(), const VipRideBeginPassenger(),
const RideFromStartApp(), const RideFromStartApp(),
PointsPageForRider(),
// cancelRidePage(),
// const MenuIconMapPageWidget(),
PointsPageForRider()
], ],
), ),
), ),
@@ -171,8 +162,8 @@ class CancelRidePageShow extends StatelessWidget {
return showCancelButton return showCancelButton
? Positioned( ? Positioned(
right: box.read(BoxName.lang) != 'ar' ? 10 : null, right: box.read(BoxName.lang) == 'ar' ? 10 : null,
left: box.read(BoxName.lang) == 'ar' ? 10 : null, left: box.read(BoxName.lang) != 'ar' ? 10 : null,
top: Get.height * .013, top: Get.height * .013,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
@@ -11,6 +11,7 @@ import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../constant/design.dart';
import '../../../controller/firebase/notification_service.dart'; import '../../../controller/firebase/notification_service.dart';
import '../../../controller/functions/launch.dart'; import '../../../controller/functions/launch.dart';
import '../../../controller/functions/crud.dart'; import '../../../controller/functions/crud.dart';
@@ -46,52 +47,30 @@ class ApplyOrderWidget extends StatelessWidget {
bottom: isVisible ? 0 : -400, bottom: isVisible ? 0 : -400,
left: 0, left: 0,
right: 0, right: 0,
child: Container( child: SiroSheetSurface(
decoration: BoxDecoration( color: Theme.of(context).cardColor,
color: Theme.of(context).cardColor, padding: const EdgeInsets.fromLTRB(
borderRadius: const BorderRadius.vertical(top: Radius.circular(25)), AppSpacing.lg, AppSpacing.sm, AppSpacing.lg, AppSpacing.lg),
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: GetBuilder<RideLifecycleController>( child: GetBuilder<RideLifecycleController>(
builder: (c) { builder: (c) {
return Column( return Column(
mainAxisSize: mainAxisSize:
MainAxisSize.min, // مهم جداً: يأخذ أقل مساحة ممكنة MainAxisSize.min, // مهم جداً: يأخذ أقل مساحة ممكنة
children: [ children: [
// مقبض صغير // 1. دمج السعر مع الحالة في صف واحد لتوفير المساحة
Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.3),
borderRadius: BorderRadius.circular(10),
),
),
const SizedBox(height: 10), // تقليل المسافة
// 1. [تغيير جوهري] دمج السعر مع الحالة في صف واحد لتوفير المساحة
_buildCompactHeaderRow(context, c), _buildCompactHeaderRow(context, c),
const SizedBox(height: 10), // مسافة مضغوطة AppSpacing.vGapMd,
// 2. كرت المعلومات المضغوط // 2. كرت المعلومات المضغوط
_buildCompactInfoCard(context, c, parseColor), _buildCompactInfoCard(context, c, parseColor),
const SizedBox(height: 10), // مسافة مضغوطة AppSpacing.vGapMd,
// 3. أزرار الاتصال (Slim) // 3. أزرار الاتصال (Slim)
_buildCompactButtonsRow(context, c), _buildCompactButtonsRow(context, c),
const SizedBox(height: 10), // مسافة مضغوطة AppSpacing.vGapMd,
// 4. شريط الوقت // 4. شريط الوقت
c.currentRideState.value == RideState.driverArrived c.currentRideState.value == RideState.driverArrived
@@ -240,10 +219,10 @@ class ApplyOrderWidget extends StatelessWidget {
RideLifecycleController controller, Color Function(String) parseColor) { RideLifecycleController controller, Color Function(String) parseColor) {
return Container( return Container(
// تقليل الحواف الداخلية للكرت // تقليل الحواف الداخلية للكرت
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor, color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.circular(16), borderRadius: AppRadii.all(AppRadii.lg),
border: Border.all(color: Colors.grey.withOpacity(0.1)), border: Border.all(color: Colors.grey.withOpacity(0.1)),
), ),
child: Column( child: Column(
@@ -496,7 +475,7 @@ class ApplyOrderWidget extends StatelessWidget {
Widget _buildCompactButtonsRow( Widget _buildCompactButtonsRow(
BuildContext context, RideLifecycleController controller) { BuildContext context, RideLifecycleController controller) {
return SizedBox( return SizedBox(
height: 40, // تحديد ارتفاع ثابت وصغير للأزرار height: AppSizes.minTouch, // ارتفاع لمس قياسي 48px
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@@ -508,7 +487,7 @@ class ApplyOrderWidget extends StatelessWidget {
onTap: () => _showContactOptionsDialog(context, controller), onTap: () => _showContactOptionsDialog(context, controller),
), ),
), ),
const SizedBox(width: 10), // تقليل المسافة AppSpacing.gapMd,
Expanded( Expanded(
child: _buildSlimButton( child: _buildSlimButton(
label: 'Call'.tr, // اختصار الكلمة label: 'Call'.tr, // اختصار الكلمة
@@ -609,7 +588,8 @@ class ApplyOrderWidget extends StatelessWidget {
foregroundColor: color, foregroundColor: color,
elevation: isPrimary ? 2 : 0, elevation: isPrimary ? 2 : 0,
padding: EdgeInsets.zero, // إزالة الحواشي الداخلية padding: EdgeInsets.zero, // إزالة الحواشي الداخلية
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadii.md)),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -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<PassengerCallPage> createState() => _PassengerCallPageState();
// }
// class _PassengerCallPageState extends State<PassengerCallPage> {
// 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<ScaffoldMessengerState> scaffoldMessengerKey =
// GlobalKey<ScaffoldMessengerState>(); // 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<void> 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<MapDriverController>().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<MapPassengerController>().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: <Widget>[
// // 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,
// // );
// // }
// }
@@ -1,6 +1,5 @@
import 'package:siro_rider/constant/currency.dart'; import 'package:siro_rider/constant/currency.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:siro_rider/controller/home/map/map_engine_controller.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:siro_rider/constant/box_name.dart'; import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/constant/colors.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/home/profile/passenger_profile_page.dart';
import 'package:siro_rider/views/widgets/elevated_btn.dart'; import 'package:siro_rider/views/widgets/elevated_btn.dart';
import 'package:siro_rider/views/widgets/my_textField.dart'; import 'package:siro_rider/views/widgets/my_textField.dart';
import 'package:intl/intl.dart';
import 'dart:ui'; import 'dart:ui';
import '../../../constant/design.dart';
import '../../../constant/info.dart'; import '../../../constant/info.dart';
import '../../../controller/functions/tts.dart'; import '../../../controller/functions/tts.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart';
@@ -31,32 +30,56 @@ class CarType {
{required this.carType, required this.carDetail, required this.image}); {required this.carType, required this.carDetail, required this.image});
} }
List<CarType> carTypes = [ // ─────────────────────────────────────────────────────────────────────────────
CarType( // CAR TYPE CATALOG (per-country availability)
// ─────────────────────────────────────────────────────────────────────────────
// كل نوع سيارة معرَّف مرة واحدة هنا؛ الدولة تحدد أي الأنواع تظهر للراكب.
final Map<String, CarType> _carTypeCatalog = {
'Fixed Price': CarType(
carType: 'Fixed Price', carType: 'Fixed Price',
carDetail: 'Closest & Cheapest'.tr, carDetail: 'Closest & Cheapest'.tr,
image: 'assets/images/carspeed.png'), image: 'assets/images/carspeed.png'),
CarType( 'Comfort': CarType(
carType: 'Comfort', carType: 'Comfort',
carDetail: 'Comfort choice'.tr, carDetail: 'Comfort choice'.tr,
image: 'assets/images/blob.png'), image: 'assets/images/blob.png'),
CarType( 'Electric': CarType(
carType: 'Electric', carType: 'Electric',
carDetail: 'Quiet & Eco-Friendly'.tr, carDetail: 'Quiet & Eco-Friendly'.tr,
image: 'assets/images/electric.png'), image: 'assets/images/electric.png'),
CarType( 'Lady': CarType(
carType: 'Lady', carType: 'Lady',
carDetail: 'Lady Captain for girls'.tr, carDetail: 'Lady Captain for girls'.tr,
image: 'assets/images/lady.png'), image: 'assets/images/lady.png'),
CarType( 'Van': CarType(
carType: 'Van', carType: 'Van',
carDetail: 'Van for familly'.tr, carDetail: 'Van for familly'.tr,
image: 'assets/images/bus.png'), image: 'assets/images/bus.png'),
CarType( 'Scooter': CarType(
carType: 'Rayeh Gai', carType: 'Scooter',
carDetail: "Best choice for cities".tr, carDetail: 'Motorcycle for one'.tr,
image: 'assets/images/roundtrip.png'), 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<String, List<String>> _carTypesByCountry = {
'Jordan': ['Fixed Price', 'Comfort', 'Electric', 'Lady'],
'Egypt': ['Fixed Price', 'Comfort', 'Lady', 'Scooter', 'Awfar Car'],
'Syria': ['Fixed Price', 'Comfort', 'Electric', 'Lady', 'Van'],
};
List<CarType> _carTypesForCountry(String country) {
final keys = _carTypesByCountry[country] ?? _carTypesByCountry['Jordan']!;
return keys.map((k) => _carTypeCatalog[k]!).toList();
}
List<CarType> carTypes = _carTypesForCountry(
box.read(BoxName.countryCode) ?? 'Jordan');
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
// MAIN WIDGET // MAIN WIDGET
@@ -65,7 +88,15 @@ class CarDetailsTypeToChoose extends StatelessWidget {
CarDetailsTypeToChoose({super.key}); CarDetailsTypeToChoose({super.key});
final textToSpeechController = Get.find<TextToSpeechController>(); final textToSpeechController = Get.find<TextToSpeechController>();
static String? _lastPreparedCountry;
void _prepareCarTypes(RideLifecycleController controller) { 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 (controller.distance > 23) {
if (!carTypes.any((car) => car.carType == 'Rayeh Gai')) { if (!carTypes.any((car) => car.carType == 'Rayeh Gai')) {
carTypes.add(CarType( carTypes.add(CarType(
@@ -92,43 +123,24 @@ class CarDetailsTypeToChoose extends StatelessWidget {
left: 0, left: 0,
right: 0, right: 0,
child: ClipRRect( child: ClipRRect(
borderRadius: const BorderRadius.only( borderRadius: AppRadii.sheetTop,
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
child: BackdropFilter( child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12), filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.secondaryColor.withAlpha(240), color: AppColor.secondaryColor.withAlpha(240),
borderRadius: const BorderRadius.only( borderRadius: AppRadii.sheetTop,
topLeft: Radius.circular(24), boxShadow: AppShadows.sheet,
topRight: Radius.circular(24),
),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(30),
blurRadius: 30,
spreadRadius: 0,
offset: const Offset(0, -8),
),
],
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// ── Drag Handle ────────────────────────────────────── // ── Drag Handle ──────────────────────────────────────
Center( const Padding(
child: Container( padding: EdgeInsets.only(top: AppSpacing.sm),
width: 32, child: SiroSheetHandle(),
height: 3,
margin: const EdgeInsets.only(top: 6, bottom: 2),
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(2),
),
),
), ),
AppSpacing.vGapXs,
// ── Header ─────────────────────────────────────────── // ── Header ───────────────────────────────────────────
_buildHeader(controller), _buildHeader(controller),
@@ -250,7 +262,8 @@ class CarDetailsTypeToChoose extends StatelessWidget {
controller.update(); controller.update();
}, },
child: Container( child: Container(
padding: const EdgeInsets.all(5), padding: const EdgeInsets.all(9),
constraints: const BoxConstraints(minWidth: 36, minHeight: 36),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Get.isDarkMode color: Get.isDarkMode
? Colors.white.withOpacity(0.08) ? 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, Widget _buildCarCard(BuildContext context, RideLifecycleController controller,
CarType carType, bool isSelected, int index) { CarType carType, bool isSelected, int index) {
return GestureDetector( return _CarTypeCard(
carType: carType,
isSelected: isSelected,
priceText:
'${_getPassengerPriceText(carType, controller)} ${CurrencyHelper.currency}',
onTap: () { onTap: () {
controller.selectCarFromList(index); controller.selectCarFromList(index);
_showCarDetailsDialog( _showCarDetailsDialog(
context, controller, carType, textToSpeechController); 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( Widget _buildPromoButton(
BuildContext context, RideLifecycleController controller) { BuildContext context, RideLifecycleController controller) {
if (controller.promoTaken) return const SizedBox.shrink(); if (controller.promoTaken) return const SizedBox.shrink();
return _PromoCodeBanner(
return Padding( onTap: () => _showPromoCodeDialog(context, controller),
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),
),
],
),
),
),
),
); );
} }
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
// NEGATIVE BALANCE WARNING // NEGATIVE BALANCE WARNING (delegates rendering to _NegativeBalanceBanner)
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
Widget _buildNegativeBalanceWarning(RideLifecycleController controller) { Widget _buildNegativeBalanceWarning(RideLifecycleController controller) {
final passengerWallet = final passengerWallet =
double.tryParse(box.read(BoxName.passengerWalletTotal) ?? '0.0') ?? 0.0; double.tryParse(box.read(BoxName.passengerWalletTotal) ?? '0.0') ?? 0.0;
if (passengerWallet < 0.0) { if (passengerWallet >= 0.0) return const SizedBox.shrink();
return Container( return _NegativeBalanceBanner(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 3), message:
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), '${'You have a negative balance of'.tr} ${passengerWallet.toStringAsFixed(2)} ${CurrencyHelper.currency}.',
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();
} }
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
@@ -852,12 +652,12 @@ class CarDetailsTypeToChoose extends StatelessWidget {
case 'Electric': case 'Electric':
return 'Travel in a modern, silent electric car. A premium, eco-friendly choice for a smooth ride.' return 'Travel in a modern, silent electric car. A premium, eco-friendly choice for a smooth ride.'
.tr; .tr;
case 'Scooter':
case 'Van': case 'Van':
return "Spacious van service ideal for families and groups. Comfortable, safe, and cost-effective travel together." return "Spacious van service ideal for families and groups. Comfortable, safe, and cost-effective travel together."
.tr; .tr;
case 'Scooter':
case 'Pink Bike': case 'Pink Bike':
return 'This is for delivery or a motorcycle.'.tr; return 'A quick, affordable motorcycle ride for one.'.tr;
case 'Mishwar Vip': case 'Mishwar Vip':
return "Perfect for passengers seeking the latest car models with the freedom to choose any route they desire" return "Perfect for passengers seeking the latest car models with the freedom to choose any route they desire"
.tr; .tr;
@@ -939,6 +739,9 @@ class CarDetailsTypeToChoose extends StatelessWidget {
return mapPassengerController.totalPassengerVan; return mapPassengerController.totalPassengerVan;
case 'Lady': case 'Lady':
return mapPassengerController.totalPassengerLady; return mapPassengerController.totalPassengerLady;
case 'Scooter':
case 'Pink Bike':
return mapPassengerController.totalPassengerScooter;
default: default:
return '0'; 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) // BURC MONEY WIDGET (Floating negative balance banner)
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
@@ -6,6 +6,7 @@ import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/views/home/my_wallet/passenger_wallet.dart'; import 'package:siro_rider/views/home/my_wallet/passenger_wallet.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/design.dart';
import '../../../constant/info.dart'; import '../../../constant/info.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../controller/payment/payment_controller.dart'; import '../../../controller/payment/payment_controller.dart';
@@ -25,26 +26,12 @@ class CashConfirmPageShown extends StatelessWidget {
left: 0, left: 0,
right: 0, right: 0,
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 300), duration: AppDurations.base,
curve: Curves.easeInOut, curve: Curves.easeInOut,
// التحكم في ظهور اللوحة لم يتغير // التحكم في ظهور اللوحة لم يتغير
transform: Matrix4.translationValues( transform: Matrix4.translationValues(
0, controller.isCashConfirmPageShown ? 0 : Get.height, 0), 0, controller.isCashConfirmPageShown ? 0 : Get.height, 0),
decoration: BoxDecoration( child: SiroSheetSurface(
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: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -55,7 +42,7 @@ class CashConfirmPageShown extends StatelessWidget {
children: [ children: [
Text( Text(
'Payment Method'.tr, 'Payment Method'.tr,
style: AppStyle.headTitle.copyWith(fontSize: 24), style: AppStyle.headTitle.copyWith(fontSize: 22),
), ),
// زر الإغلاق (كان معلقاً في الكود القديم، تم تفعيله هنا) // زر الإغلاق (كان معلقاً في الكود القديم، تم تفعيله هنا)
IconButton( IconButton(
@@ -64,7 +51,7 @@ class CashConfirmPageShown extends StatelessWidget {
), ),
], ],
), ),
const SizedBox(height: 16), AppSpacing.vGapLg,
// --- 2. بطاقات اختيار الدفع --- // --- 2. بطاقات اختيار الدفع ---
GetBuilder<PaymentController>(builder: (paymentCtrl) { GetBuilder<PaymentController>(builder: (paymentCtrl) {
@@ -87,7 +74,7 @@ class CashConfirmPageShown extends StatelessWidget {
onTap: () => onTap: () =>
paymentCtrl.onChangedPaymentMethodWallet(true), paymentCtrl.onChangedPaymentMethodWallet(true),
), ),
const SizedBox(height: 12), AppSpacing.vGapMd,
// بطاقة الكاش // بطاقة الكاش
_buildPaymentOptionCard( _buildPaymentOptionCard(
icon: Icons.money_rounded, icon: Icons.money_rounded,
@@ -101,7 +88,7 @@ class CashConfirmPageShown extends StatelessWidget {
], ],
); );
}), }),
const SizedBox(height: 24), AppSpacing.vGapXl,
// --- 3. أزرار التأكيد (بنفس منطقك القديم تماماً) --- // --- 3. أزرار التأكيد (بنفس منطقك القديم تماماً) ---
GetBuilder<PaymentController>(builder: (paymentCtrl) { GetBuilder<PaymentController>(builder: (paymentCtrl) {
@@ -165,13 +152,14 @@ class CashConfirmPageShown extends StatelessWidget {
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 250), duration: AppDurations.fast,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(AppSpacing.lg),
constraints: const BoxConstraints(minHeight: AppSizes.minTouch),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected color: isSelected
? selectedColor.withOpacity(0.1) ? selectedColor.withOpacity(0.1)
: AppColor.writeColor.withOpacity(0.05), : AppColor.writeColor.withOpacity(0.05),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(AppRadii.md),
border: Border.all( border: Border.all(
color: isSelected color: isSelected
? selectedColor ? selectedColor
@@ -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<RideLifecycleController> hexagonClipper() {
return GetBuilder<RideLifecycleController>(
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()));
}
@@ -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<RideLifecycleController>(
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();
},
);
}
}
@@ -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<LocationSearchController> 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<LocationSearchController>(
id: 'destination_form',
builder: (controller) {
final mapEngine = Get.find<MapEngineController>();
final rideLifecycle = Get.find<RideLifecycleController>();
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<LocationSearchController>(
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<void> _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<void> _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<void> _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);
}
}
@@ -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<LocationSearchController> formSearchPlacesStart() {
return GetBuilder<LocationSearchController>(
id: 'start_point_form',
builder: (controller) {
final mapEngine = Get.find<MapEngineController>();
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();
}
},
);
},
),
),
],
);
},
);
}
@@ -1,52 +0,0 @@
import 'dart:math';
import 'package:flutter/material.dart';
class HexagonClipper extends CustomClipper<Path> {
@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<Path> {
@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;
}
@@ -7,12 +7,15 @@ import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'dart:ui'; import 'dart:ui';
import 'dart:io';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../controller/home/map/location_search_controller.dart'; import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart'; import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/vip_waitting_page.dart'; import '../../../controller/home/vip_waitting_page.dart';
import '../navigation/navigation_view.dart'; import '../navigation/navigation_view.dart';
import '../../../services/android_live_update_service.dart';
import '../../../controller/home/ios_live_activity_service.dart';
// --- الدالة الرئيسية بالتصميم الجديد --- // --- الدالة الرئيسية بالتصميم الجديد ---
GetBuilder<MapEngineController> leftMainMenuIcons() { GetBuilder<MapEngineController> leftMainMenuIcons() {
@@ -66,6 +69,11 @@ GetBuilder<MapEngineController> leftMainMenuIcons() {
tooltip: 'VIP Waiting Page', tooltip: 'VIP Waiting Page',
onPressed: () => Get.to(() => VipWaittingPage()), 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) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('iOS Live Activity Test'), title: const Text('Live Activity Test (iOS & Android)'),
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
@@ -116,14 +124,67 @@ class TestPage extends StatelessWidget {
children: [ children: [
MyCircularProgressIndicator(), MyCircularProgressIndicator(),
MyElevatedButton( MyElevatedButton(
title: 'title', title: 'Start Live Activity Demo',
onPressed: () {}, 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( ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.red, 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'), child: const Text('End Activity'),
), ),
], ],
File diff suppressed because it is too large Load Diff
@@ -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<MapEngineController>(
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,
),
),
),
),
));
}
}
@@ -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()} ?",
),
],
),
),
)
],
)
],
),
),
);
}
}
@@ -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<MapEngineController>();
final locationSearch = Get.find<LocationSearchController>();
final rideLifecycle = Get.find<RideLifecycleController>();
return GetBuilder<MapEngineController>(
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(),
],
),
],
),
),
));
}
}
@@ -7,6 +7,7 @@ import 'package:intl/intl.dart';
// تأكد من المسارات // تأكد من المسارات
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/design.dart';
import '../../../constant/links.dart'; import '../../../constant/links.dart';
import 'package:siro_rider/controller/functions/country_logic.dart'; import 'package:siro_rider/controller/functions/country_logic.dart';
import '../../../controller/functions/audio_record1.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 '../../../controller/profile/profile_controller.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../../views/home/profile/complaint_page.dart'; import '../../../views/home/profile/complaint_page.dart';
import '../../../controller/functions/country_logic.dart';
class RideBeginPassenger extends StatelessWidget { class RideBeginPassenger extends StatelessWidget {
const RideBeginPassenger({super.key}); const RideBeginPassenger({super.key});
@@ -28,7 +28,6 @@ class RideBeginPassenger extends StatelessWidget {
final ProfileController profileController = Get.put(ProfileController()); final ProfileController profileController = Get.put(ProfileController());
final AudioRecorderController audioController = final AudioRecorderController audioController =
Get.put(AudioRecorderController()); Get.put(AudioRecorderController());
final uiController = Get.find<UiInteractionsController>();
return Obx(() { return Obx(() {
final controller = Get.find<RideLifecycleController>(); final controller = Get.find<RideLifecycleController>();
@@ -45,64 +44,33 @@ class RideBeginPassenger extends StatelessWidget {
bottom: isVisible ? 0 : -300, bottom: isVisible ? 0 : -300,
left: 0, left: 0,
right: 0, right: 0,
child: Container( child: SiroSheetSurface(
decoration: BoxDecoration( padding: const EdgeInsets.symmetric(
color: AppColor.secondaryColor, horizontal: AppSpacing.lg, vertical: AppSpacing.md),
borderRadius: const BorderRadius.only( child: Column(
topLeft: Radius.circular(25), mainAxisSize: MainAxisSize.min,
topRight: Radius.circular(25), children: [
), // 1. هيدر المعلومات (سائق + سيارة + سعر)
boxShadow: [ _buildCompactHeader(controller),
BoxShadow(
color: Get.isDarkMode AppSpacing.vGapMd,
? Colors.black.withValues(alpha: 0.4)
: Colors.black.withValues(alpha: 0.1), // خط فاصل خفيف
blurRadius: 20, Divider(
spreadRadius: 2, height: 1,
offset: const Offset(0, -3), 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) { AudioRecorderController audioController) {
final uiController = Get.find<UiInteractionsController>(); final uiController = Get.find<UiInteractionsController>();
return SizedBox( return SizedBox(
height: 60, height: 64,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
@@ -325,19 +293,20 @@ class RideBeginPassenger extends StatelessWidget {
}) { }) {
return InkWell( return InkWell(
onTap: onTap, onTap: onTap,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(AppRadii.md),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(10),
constraints: const BoxConstraints(minWidth: 40, minHeight: 40),
decoration: BoxDecoration( decoration: BoxDecoration(
color: bgColor, color: bgColor,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon(icon, size: 20, color: color), child: Icon(icon, size: 20, color: color),
), ),
const SizedBox(height: 4), AppSpacing.vGapXs,
Text( Text(
label, label,
style: TextStyle( style: TextStyle(
@@ -1,11 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:siro_rider/constant/colors.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/constant/style.dart';
import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart'; import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart';
import 'package:siro_rider/controller/home/map/ride_state.dart'; import 'package:siro_rider/controller/home/map/ride_state.dart';
// --- الويدجت الرئيسية بالتصميم الجديد --- // --- نافذة البحث عن كابتن ---
class SearchingCaptainWindow extends StatefulWidget { class SearchingCaptainWindow extends StatefulWidget {
const SearchingCaptainWindow({super.key}); const SearchingCaptainWindow({super.key});
@@ -34,60 +35,45 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// [تعديل 1] نستخدم Obx للاستماع إلى التغييرات في حالة الرحلة // نستمع لتغييرات حالة الرحلة
return Obx(() { return Obx(() {
// ابحث عن الكنترولر مرة واحدة
final controller = Get.find<RideLifecycleController>(); final controller = Get.find<RideLifecycleController>();
// [تعديل 2] شرط الإظهار يعتمد الآن على حالة الرحلة مباشرة // شرط الإظهار يعتمد على حالة الرحلة مباشرة
final bool isVisible = final bool isVisible =
controller.currentRideState.value == RideState.searching; controller.currentRideState.value == RideState.searching;
return AnimatedPositioned( return AnimatedPositioned(
duration: const Duration(milliseconds: 300), duration: AppDurations.base,
curve: Curves.easeInOut, curve: Curves.easeInOut,
bottom: isVisible ? 0 : -Get.height * 0.45, // زيادة الارتفاع قليلاً bottom: isVisible ? 0 : -Get.height * 0.45,
left: 0, left: 0,
right: 0, right: 0,
child: Container( // لا يوجد مقبض سحب: هذه الحالة لا تُغلق بالسحب بل بزر الإلغاء فقط
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16), child: SiroSheetSurface(
decoration: BoxDecoration( showHandle: false,
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: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// --- 1. أنيميشن الرادار --- // --- 1. أنيميشن الرادار ---
_buildRadarAnimation(controller), _buildRadarAnimation(controller),
const SizedBox(height: 20), AppSpacing.vGapXl,
// --- 2. زر الإلغاء --- // --- 2. زر الإلغاء ---
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: OutlinedButton( child: OutlinedButton(
onPressed: () { onPressed: () {
// [تعديل 3] استدعاء دالة الإلغاء الموحدة // استدعاء دالة الإلغاء الموحدة
controller.changeCancelRidePageShow(); controller.changeCancelRidePageShow();
// ();
}, },
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: AppColor.writeColor, foregroundColor: AppColor.writeColor,
side: side: BorderSide(
BorderSide(color: AppColor.writeColor.withValues(alpha: 0.3)), color: AppColor.writeColor.withValues(alpha: 0.3)),
minimumSize: const Size.fromHeight(AppSizes.minTouch),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)), borderRadius: BorderRadius.circular(AppRadii.md)),
padding: const EdgeInsets.symmetric(vertical: 12),
), ),
child: Text('Cancel Search'.tr), child: Text('Cancel Search'.tr),
), ),
@@ -106,7 +92,7 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
// --- دوائر الرادار المتحركة (تبقى كما هي) --- // --- دوائر الرادار المتحركة ---
...List.generate(3, (index) { ...List.generate(3, (index) {
return FadeTransition( return FadeTransition(
opacity: Tween<double>(begin: 1.0, end: 0.0).animate( opacity: Tween<double>(begin: 1.0, end: 0.0).animate(
@@ -139,25 +125,21 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
// [تعديل 4] النص يأتي مباشرة من الكنترولر // النص يأتي مباشرة من الكنترولر
controller.driversStatusForSearchWindow, controller.driversStatusForSearchWindow,
style: AppStyle.headTitle.copyWith(fontSize: 20), style: AppStyle.headTitle.copyWith(fontSize: 20),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 8), AppSpacing.vGapSm,
Text( Text(
'Searching for the nearest captain...'.tr, 'Searching for the nearest captain...'.tr,
style: AppStyle.subtitle style: AppStyle.subtitle
.copyWith(color: AppColor.writeColor.withValues(alpha: 0.7)), .copyWith(color: AppColor.writeColor.withValues(alpha: 0.7)),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 16), AppSpacing.vGapLg,
// مؤشر دوّار: المؤقت الرئيسي في الكنترولر هو من يقرر متى يعرض
// --- [!! تعديل جوهري !!] --- // حوار زيادة الأجرة، لذا هذا الجزء "غبي" بلا منطق.
// لم نعد بحاجة لـ buildTimerForIncrease
// المؤقت الرئيسي في الكنترولر هو من يقرر متى يعرض الحوار
// وهذا الجزء من الواجهة أصبح "غبياً" (لا يحتوي على منطق)
// الكنترولر سيستدعي _showIncreaseFeeDialog مباشرة
SizedBox( SizedBox(
height: 40, height: 40,
width: 40, width: 40,
@@ -167,7 +149,8 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
CircularProgressIndicator( CircularProgressIndicator(
strokeWidth: 3, strokeWidth: 3,
color: AppColor.primaryColor, color: AppColor.primaryColor,
backgroundColor: AppColor.primaryColor.withValues(alpha: 0.2), backgroundColor:
AppColor.primaryColor.withValues(alpha: 0.2),
), ),
Center( Center(
child: Icon( child: Icon(
@@ -185,286 +168,3 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
); );
} }
} }
// --- [!! تعديل جوهري !!] ---
// تم حذف دالة `buildTimerForIncrease` بالكامل.
// تم حذف دالة `_showIncreaseFeeDialog` من هذا الملف.
// لماذا؟ لأن الكنترولر الآن هو المسؤول الوحيد عن إظهار الحوار.
// دالة `_showIncreaseFeeDialog` موجودة بالفعل داخل `map_passenger_controller.dart`
// وسيتم استدعاؤها من `_handleRideState` عند انتهاء مهلة الـ 90 ثانية.
// // --- الويدجت الرئيسية بالتصميم الجديد ---
// class SearchingCaptainWindow extends StatefulWidget {
// const SearchingCaptainWindow({super.key});
// @override
// State<SearchingCaptainWindow> createState() => _SearchingCaptainWindowState();
// }
// class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
// 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<MapPassengerController>(
// 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<double>(begin: 1.0, end: 0.0).animate(
// CurvedAnimation(
// parent: _animationController,
// curve: Interval((index) / 3, 1.0, curve: Curves.easeInOut),
// ),
// ),
// child: ScaleTransition(
// scale: Tween<double>(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<int>(
// 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(),
// ),
// ],
// );
// }
@@ -46,49 +46,43 @@ class CupertinoDriverListWidget extends StatelessWidget {
child: CupertinoListTile( child: CupertinoListTile(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 8), vertical: 4, horizontal: 8),
leading: CircleAvatar( leading: ClipOval(
radius: 25, child: SizedBox(
backgroundImage: NetworkImage( width: 50,
'${AppLink.server}/portrate_captain_image/${driver['id']}.jpg', height: 50,
), child: Image.network(
child: Builder( '${AppLink.server}/portrate_captain_image/${driver['id']}.jpg',
builder: (context) { fit: BoxFit.cover,
return Image.network( loadingBuilder: (BuildContext context,
'${AppLink.server}/portrate_captain_image/${driver['id']}.jpg', Widget child,
fit: BoxFit.cover, ImageChunkEvent? loadingProgress) {
loadingBuilder: (BuildContext context, if (loadingProgress == null) {
Widget child, return child; // Image is loaded
ImageChunkEvent? loadingProgress) { }
if (loadingProgress == null) { return Center(
return child; // Image is loaded child: CircularProgressIndicator(
} else { strokeWidth: 2,
return Center( value: loadingProgress
child: CircularProgressIndicator( .expectedTotalBytes !=
value: loadingProgress null
.expectedTotalBytes != ? loadingProgress
null .cumulativeBytesLoaded /
? loadingProgress (loadingProgress
.cumulativeBytesLoaded / .expectedTotalBytes ??
(loadingProgress 1)
.expectedTotalBytes ?? : null,
1) ),
: null, );
), },
); errorBuilder: (BuildContext context,
} Object error, StackTrace? stackTrace) {
}, return const Icon(
errorBuilder: (BuildContext context, Icons.person,
Object error, StackTrace? stackTrace) { size: 25,
return const Icon( color: AppColor.blueColor,
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
);
},
);
},
), ),
), ),
title: Row( title: Row(
@@ -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<RideLifecycleController> timerForCancelTripFromPassenger() {
return GetBuilder<RideLifecycleController>(
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: <Widget>[
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();
},
);
}
@@ -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<RideLifecycleController>(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();
}
});
}
}