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