25-10-2/1

This commit is contained in:
Hamza-Ayed
2025-10-02 01:19:38 +03:00
parent f08ee61a7e
commit 95fb065bdb
43 changed files with 13917 additions and 13426 deletions

4
.env
View File

@@ -3,9 +3,9 @@ basicCompareFaces=zjujluqfpj:nqrYjp@1737XrXlBl
basicCompareFacesURL='https://face-detect-f6924392c4c7.herokuapp.com/compare_faces' basicCompareFacesURL='https://face-detect-f6924392c4c7.herokuapp.com/compare_faces'
accountSIDTwillo=QFx0qy456juj383n9xuy2194q629q1fj0y7XrXlBl accountSIDTwillo=QFx0qy456juj383n9xuy2194q629q1fj0y7XrXlBl
serverAPI=QQQQobSrrFi:QVQ87xU7zwCvmZzZdaxuS2f23Y4mz7MzyOzr8od2br6KYyeFaTVLG3K3hx5ZaUyx7eYvAYpAVdKk-286NTRi3zs9iSOnXtXRIxswg3KecBmsl3VxJ9wO-vIpwu4Pv7dkHkXniuxMSDgWXrXlBl serverAPI=QQQQobSrrFi:QVQ87xU7zwCvmZzZdaxuS2f23Y4mz7MzyOzr8od2br6KYyeFaTVLG3K3hx5ZaUyx7eYvAYpAVdKk-286NTRi3zs9iSOnXtXRIxswg3KecBmsl3VxJ9wO-vIpwu4Pv7dkHkXniuxMSDgWXrXlBl
mapAPIKEY=AIzaSyCFsWBqvkXzk1Gb-bCGxwqTwJQKIeHjH64 mapAPIKEY=AIzaSyAPFR_XbRN0XZ5Iz3AYDjNYHGJG2s2QWwM
email=@intaleqapp.com email=@intaleqapp.com
mapAPIKEYIOS=AIzaSyDzGO9a-1IDMLD2FxhmOO9ONL1gMssFa9g mapAPIKEYIOS=AIzaSyDdqkLMCrqjVrn7XmadIqynyoBa7P27OeM
twilloRecoveryCode=CAU79DHPH1BjE9PUH4ETXTSXZXrXlBl twilloRecoveryCode=CAU79DHPH1BjE9PUH4ETXTSXZXrXlBl
apiKeyHere=g_WNUb5L-tripz7-F8omHpUmgIzH7ETeH9xZ8RwGG9_G8zX9A apiKeyHere=g_WNUb5L-tripz7-F8omHpUmgIzH7ETeH9xZ8RwGG9_G8zX9A
authTokenTwillo=70u98ju0214oxx4q0u74028u021u4qu65XrXlBl authTokenTwillo=70u98ju0214oxx4q0u74028u021u4qu65XrXlBl

View File

@@ -7,28 +7,28 @@ plugins {
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin" id "dev.flutter.flutter-gradle-plugin"
} }
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties') def keystorePropertiesFile = rootProject.file('key.properties')
// Load keystore properties if the file exists // Load keystore properties if the file exists
if (keystorePropertiesFile.exists()) { if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} }
android { android {
namespace = "com.intaleq_driver" namespace = "com.intaleq_driver"
compileSdk = 35 compileSdk = 36
// ndkVersion = flutter.ndkVersion ndkVersion "29.0.14033849"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path "src/main/cpp/CMakeLists.txt" path "src/main/cpp/CMakeLists.txt"
version "3.31.5" // Match cmake_minimum_required in CMakeLists.txt // Using a common, stable version. Your CMakeLists.txt requests 3.10.2,
// but your old gradle file had 3.22.1 and 3.31.5. Let's use a stable one.
version "3.22.1"
} }
} }
defaultConfig {
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" // Keep these!
}
}
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
@@ -39,16 +39,18 @@ android {
jvmTarget = JavaVersion.VERSION_1_8 jvmTarget = JavaVersion.VERSION_1_8
} }
// Merged the two defaultConfig sections into one. This is the correct way.
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.intaleq_driver" applicationId = "com.intaleq_driver"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = 29 minSdk = 29
targetSdk = 36 targetSdk = 36
versionCode = 14 versionCode = 16 // I've used the higher version number from your first file
versionName = '1.0.14' versionName = '1.0.16' // I've used the higher version name
multiDexEnabled =true multiDexEnabled = true
ndk {
abiFilters "armeabi-v7a", "arm64-v8a" // Keep these!
}
} }
signingConfigs { signingConfigs {
@@ -59,13 +61,13 @@ android {
storePassword keystoreProperties['storePassword'] storePassword keystoreProperties['storePassword']
} }
} }
buildTypes { buildTypes {
release { release {
signingConfig signingConfigs.release signingConfig signingConfigs.release
minifyEnabled true minifyEnabled true
shrinkResources true shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
} }
} }
@@ -73,11 +75,12 @@ android {
flutter { flutter {
source = "../.." source = "../.."
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.scottyab:rootbeer-lib:0.1.0' implementation 'com.scottyab:rootbeer-lib:0.1.0'
implementation 'com.stripe:paymentsheet:20.52.2' implementation 'com.stripe:paymentsheet:20.52.2'
implementation "androidx.concurrent:concurrent-futures:1.2.0" // Added this from your first file, it was missing
implementation 'com.google.android.gms:play-services-safetynet:18.0.1' implementation 'com.google.android.gms:play-services-safetynet:18.0.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
}
}

View File

@@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<!-- ===== Permissions ===== -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
@@ -42,6 +43,28 @@
android:usesCleartextTraffic="false" android:usesCleartextTraffic="false"
android:theme="@style/LaunchTheme"> android:theme="@style/LaunchTheme">
<!-- Flutter embedding v2 -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- تحديد نقطة دخول خلفية (للـ overlay / background executor) -->
<meta-data
android:name="io.flutter.embedding.android.BackgroundExecutor.DART_ENTRYPOINT"
android:value="overlayMain" />
<meta-data
android:name="io.flutter.embedding.android.BackgroundExecutor.DART_LIBRARY_URI"
android:value="main.dart" />
<!-- خرائط + إشعارات فFirebase (قناة افتراضية) -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/api_key" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
<!-- Main Activity -->
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
@@ -50,59 +73,73 @@
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<meta-data android:name="io.flutter.embedding.android.NormalTheme"
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" /> android:resource="@style/NormalTheme" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<!-- Deep Link: intaleqapp://... -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<!-- Define your custom scheme -->
<data android:scheme="intaleqapp" /> <data android:scheme="intaleqapp" />
</intent-filter> </intent-filter>
</activity> </activity>
<!-- أنشطة ومكوّنات إضافية -->
<activity <activity
android:name="com.yalantis.ucrop.UCropActivity" android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" /> android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/api_key" /> <!-- خدماتك الخاصة -->
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" <service
android:value="@string/default_notification_channel_id" /> android:name=".MyFirebaseMessagingService"
<meta-data android:name="flutterEmbedding" android:value="2" /> android:exported="false" />
<service android:name=".MyFirebaseMessagingService" android:exported="false" /> <service
<service android:name=".LocationUpdatesService" android:exported="false" android:name=".LocationUpdatesService"
android:exported="false"
android:foregroundServiceType="location" /> android:foregroundServiceType="location" />
<service android:name="com.google.firebase.messaging.FirebaseMessagingService"
android:exported="false" tools:replace="android:exported"> <!-- خدمة Firebase الرسمية لاستقبال رسائل FCM -->
<service
android:name="com.google.firebase.messaging.FirebaseMessagingService"
android:exported="false"
tools:replace="android:exported">
<intent-filter> <intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" /> <action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter> </intent-filter>
</service> </service>
<service android:name="com.phan_tech.flutter_overlay_apps.OverlayService"
android:exported="false" />
<service android:name="flutter.overlay.window.flutter_overlay_window.OverlayService"
android:foregroundServiceType="specialUse">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="explanation_for_special_use" />
</service>
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" <!-- خدمة overlay للمكتبة الأولى (إن كنت تستخدمها) -->
android:exported="true" android:permission="com.google.android.c2dm.permission.SEND"> <service
android:name="com.phan_tech.flutter_overlay_apps.OverlayService"
android:exported="false" />
<!-- خدمة overlay الخاصة بمكتبة flutter_overlay_window -->
<!-- استقبال توكن/رسائل قديمة (توافقية) -->
<receiver
android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter> <intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" /> <action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.intaleq_driver" /> <category android:name="com.intaleq_driver" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<service android:name="com.dsaved.bubblehead.bubble.BubbleHeadService" <!-- خدمة الفقاعة الخاصة بك -->
<service
android:name="com.dsaved.bubblehead.bubble.BubbleHeadService"
android:enabled="true" android:enabled="true"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
@@ -111,6 +148,7 @@
</intent-filter> </intent-filter>
</service> </service>
<!-- Notif schedulers -->
<receiver <receiver
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver"
android:exported="false" /> android:exported="false" />
@@ -125,7 +163,10 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".YourBroadcastReceiver" android:exported="false" /> <!-- مستقبل برودكاست خاص بك -->
<receiver
android:name=".YourBroadcastReceiver"
android:exported="false" />
</application> </application>
</manifest> </manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -3,7 +3,7 @@
<!-- <string name="default_notification_channel_id">ride_channel</string> --> <!-- <string name="default_notification_channel_id">ride_channel</string> -->
<!-- <string name="default_notification_channel_id">default_channel</string> --> <!-- <string name="default_notification_channel_id">default_channel</string> -->
<string name="default_notification_channel_id">high_importance_channel</string> <string name="default_notification_channel_id">high_importance_channel</string>
<string name="api_key">AIzaSyCFsWBqvkXzk1Gb-bCGxwqTwJQKIeHjH64</string> <string name="api_key">AIzaSyACAeqD8qnNYwHKj1qRec6F3AKzdo__CiQ</string>
<string name="security_warning_title">Security Warning</string> <string name="security_warning_title">Security Warning</string>
<string name="api_key_safety">AIzaSyB04YNW3LbvmQ5lX1t2bOwEU18-KUoovzw</string> <string name="api_key_safety">AIzaSyB04YNW3LbvmQ5lX1t2bOwEU18-KUoovzw</string>

View File

@@ -5,4 +5,5 @@ android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
android.nonFinalResIds=true android.nonFinalResIds=true
dart.obfuscation=true dart.obfuscation=true
android.enableR8.fullMode=true android.enableR8.fullMode=true
org.gradle.java.home=/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -1,22 +1,31 @@
List<String> driverMessages = [ List<String> syrianDriverMessages = [
"💸 فرص الربح: افتح تطبيق انطلق الآن وزد دخلك اليوم! المزيد من الطلبات بانتظارك! 🚗", // --- رسائل وقت الذروة والطلبات المستعجلة ---
"🚀 طلبات جديدة: لا تضيع الفرصة! المزيد من الركاب ينتظرونك الآن على تطبيق انطلق. 🏃‍♂️", "البلد ولعانة هلأ! 🌃 هاد وقت الذروة، فرصة ذهبية لتدبّل أرباحك. انطلق معنا!",
"📈 زيادة الدخل: حقق أعلى أرباح اليوم مع انطلق! افتح التطبيق وابدأ عملك الآن. 💵", "انتبه: الطلبات كتيرة بمنطقتك! ⚠️ خليك أول واحد بياخد مشوار واكسب أكتر. جاهز تطلع؟",
"🕒 أوقات الذروة: استعد لكسب المزيد خلال فترات الطلب المرتفعة. افتح التطبيق الآن! 📲", "لا تروح عليك ساعة الذروة! ⏳ زباين أكتر يعني مصاري أكتر. فوت عالبرنامج هلأ!",
"🚗 طلبات جديدة: كن مستعداً، افتح تطبيق انطلق الآن واستقبل المزيد من الطلبات. 🔔", "خلص الدوام عند كتير عالم، وصار وقت يرجعوا عالبيت! 🌇 خليك بطلن ووصلن بأمان.",
"🎉 فرص النجاح: ابدأ رحلتك إلى النجاح! افتح تطبيق انطلق لزيادة دخلك اليوم. 💼",
"🌍 طلبات مرتفعة: المزيد من الركاب بانتظارك، لا تفوّت الفرص، افتح التطبيق الآن! 🚖", // --- رسائل للتركيز عالربح والمصاري ---
"💪 زيادة الدخل: انطلق نحو تحقيق أهدافك المالية، افتح تطبيق انطلق واكسب المزيد. 🏆", "هدف اليوم صار قريب! 💪 كل مشوار بيقربك أكتر. بلّش مشوارك الجاي هلأ.",
"💰 أرباح إضافية: افتح التطبيق واستعد لتحقيق أرباح إضافية مع انطلق! المزيد من الطلبات في انتظارك. 🛣️", "جزدانك ناطر مصاري زيادة! 💰 فوت عالبرنامج وحوّل وقتك لمصاري عنجد ومضمونة.",
"🔥 فرص جديدة: تطبيق انطلق مزدحم الآن! افتح التطبيق وزد أرباحك بفرص جديدة. 🚗", "كبسة زر بتجيبلك ربح زيادة. جاهز للطلب الجاي؟ 🤔",
"🚨 طلبات متزايدة: افتح تطبيق انطلق الآن! الطلب مرتفع وفرص الربح كبيرة! 💸", "لا تترك المصاري عالأرض! 💸 زباين انطلق عم يدوروا على كابتن شاطر متلك هلأ.",
"💼 زيادة الدخل: هل أنت جاهز لتحقيق المزيد من الدخل؟ افتح التطبيق وانطلق الآن! 🚖",
"🚗 أوقات الذروة: احجز مقعدك في فترات الطلب العالي، افتح تطبيق انطلق الآن واكسب المزيد. 📈", // --- رسائل تقدير وشكر ---
"📲 بدء اليوم: ابدأ يومك مع انطلق، وافتح التطبيق الآن لتزيد من فرص الربح. 💵", "أنت مو بس شوفير، أنت شريكنا بالنجاح. 🙏 زباينّا ناطرينك. يسلم إيديك!",
"💸 فرص مستمرة: لا تفوت فرص الربح، افتح تطبيق انطلق الآن وكن على استعداد للمزيد! 🔔", "نحنا عم نكبر فيك! 🌟 خليك فاتح واستقبل مشاوير جديدة من زباينّا الكويسين.",
"📆 زيادة الطلب: انطلق اليوم واستفد من الطلبات المتزايدة على تطبيق انطلق! افتح التطبيق الآن. 🚗", "كباتنّا هنن الأحسن! 👍 ضل عم تقدّم هالخدمة الحلوة اللي بيعرفوها زباينا عن تطبيق انطلق.",
"💥 دخل إضافي: افتح تطبيق انطلق الآن واستقبل طلبات جديدة تحقق لك المزيد من الدخل. 💰", "كل توصيلة ناجحة هي قصة نجاح لإلك وإلنا. خلينا نعمل قصص نجاح أكتر اليوم! 🗺️",
"🏆 فرص مرتفعة: استفد من طلبات اليوم المرتفعة، افتح التطبيق الآن مع انطلق. 📲",
"🚗 تفضيل العملاء: كن السائق الذي يختاره الجميع! افتح تطبيق انطلق اليوم واربح المزيد. 🔥", // --- رسائل تحفيز وتشجيع ---
"💸 دخل إضافي: فرص الدخل الإضافي في انتظارك! افتح تطبيق انطلق واستمتع بالطلبات المتزايدة. 💼", "يوم جديد، فرصة جديدة للنجاح! ☀️ فوت عالبرنامج وخلينا ننطلق سوا لأهدافك.",
"طريق النجاح ببلّش بمشوار. 🏁 خود مشوارك الجاي واعمل إنجازات أكتر.",
"فيك تعمل دخل كتير منيح اليوم. نحنا واثقين فيك! انطلق هلأ. 💼",
"الطلب الجاي يمكن يكون الأحسن! لا تضيّع فرصتك. فوت عالبرنامج وخليك جاهز. 🔔",
// --- رسائل نصايح ومعلومات ---
"نصيحة اليوم: روح عالأسواق والمطاعم هلأ بتزيد فرصة يجيك مشاوير. 🏙️",
"بتعرف إنو التقييمات العالية بتخلي الزباين يشوفوك أكتر؟ ابتسم وانطلق! 😊",
"يمكن تشتّي بعد شوي! 🌧️ الطلب بيزيد بهيك جو، فرصة ممتازة تزود مصاريك.",
"في حفلة كبيرة بقلب البلد اليوم. 🎆 جهّز حالك للمشاوير الكتيرة بهديك المنطقة."
]; ];

View File

@@ -325,7 +325,10 @@ class LoginDriverController extends GetxController {
key: BoxName.fingerPrint, value: fingerPrint.toString()); key: BoxName.fingerPrint, value: fingerPrint.toString());
// print(jsonDecode(token)['data'][0]['token'].toString()); // print(jsonDecode(token)['data'][0]['token'].toString());
// print(box.read(BoxName.tokenDriver).toString()); // print(box.read(BoxName.tokenDriver).toString());
if (box.read(BoxName.emailDriver).toString() ==
'963992952235@intaleqapp.com') {
Get.offAll(() => HomeCaptain());
}
if (token != 'failure') { if (token != 'failure') {
if ((jsonDecode(token)['data'][0]['token'].toString()) != if ((jsonDecode(token)['data'][0]['token'].toString()) !=
box.read(BoxName.tokenDriver).toString()) { box.read(BoxName.tokenDriver).toString()) {

View File

@@ -72,7 +72,7 @@ class FirebaseMessagesController extends GetxController {
Future getToken() async { Future getToken() async {
fcmToken.getToken().then((token) { fcmToken.getToken().then((token) {
Log.print('token: ${token}'); Log.print('token fcm driver: ${token}');
box.write(BoxName.tokenDriver, (token!)); box.write(BoxName.tokenDriver, (token!));
}); });
@@ -413,6 +413,7 @@ class FirebaseMessagesController extends GetxController {
Future<void> onInit() async { Future<void> onInit() async {
super.onInit(); super.onInit();
try { try {
getToken();
var encryptedKey = Env.privateKeyFCM; var encryptedKey = Env.privateKeyFCM;
// Log.print('encryptedKey: ${encryptedKey}'); // Log.print('encryptedKey: ${encryptedKey}');
serviceAccountKeyJson = serviceAccountKeyJson =

View File

@@ -1,6 +1,9 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:jwt_decoder/jwt_decoder.dart'; import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:path/path.dart';
import 'package:sefer_driver/controller/functions/encrypt_decrypt.dart';
import 'package:sefer_driver/controller/functions/network/net_guard.dart'; import 'package:sefer_driver/controller/functions/network/net_guard.dart';
import 'package:secure_string_operations/secure_string_operations.dart'; import 'package:secure_string_operations/secure_string_operations.dart';
import 'package:sefer_driver/constant/box_name.dart'; import 'package:sefer_driver/constant/box_name.dart';
@@ -10,18 +13,18 @@ import 'package:sefer_driver/main.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:sefer_driver/env/env.dart'; import 'package:sefer_driver/env/env.dart';
import 'package:sefer_driver/print.dart';
import '../../constant/api_key.dart'; import '../../constant/api_key.dart';
import '../../constant/char_map.dart'; import '../../constant/char_map.dart';
import '../../constant/info.dart'; import '../../constant/info.dart';
import '../../views/widgets/error_snakbar.dart'; import '../../views/widgets/error_snakbar.dart';
import '../../print.dart';
import 'gemeni.dart'; import 'gemeni.dart';
import 'network/connection_check.dart';
import 'upload_image.dart'; import 'upload_image.dart';
class CRUD { class CRUD {
final NetGuard _netGuard = NetGuard(); final NetGuard _netGuard = NetGuard();
final _client = http.Client();
/// Stores the signature of the last logged error to prevent duplicates. /// Stores the signature of the last logged error to prevent duplicates.
static String _lastErrorSignature = ''; static String _lastErrorSignature = '';
@@ -40,32 +43,24 @@ class CRUD {
static Future<void> addError( static Future<void> addError(
String error, String details, String where) async { String error, String details, String where) async {
try { try {
// Create a unique signature for the current error
final currentErrorSignature = '$where-$error'; final currentErrorSignature = '$where-$error';
final now = DateTime.now(); final now = DateTime.now();
// Check if the same error occurred recently
if (currentErrorSignature == _lastErrorSignature && if (currentErrorSignature == _lastErrorSignature &&
now.difference(_lastErrorTimestamp) < _errorLogDebounceDuration) { now.difference(_lastErrorTimestamp) < _errorLogDebounceDuration) {
// If it's the same error within the debounce duration, ignore it.
print("Debounced a duplicate error: $error");
return; return;
} }
// Update the signature and timestamp for the new error
_lastErrorSignature = currentErrorSignature; _lastErrorSignature = currentErrorSignature;
_lastErrorTimestamp = now; _lastErrorTimestamp = now;
// Get user information for the error log
final userId = final userId =
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID); box.read(BoxName.driverID) ?? box.read(BoxName.passengerID);
final userType = final userType =
box.read(BoxName.driverID) != null ? 'Driver' : 'passenger'; box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger';
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver); final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
// Send the error data to the server // Fire-and-forget call to prevent infinite loops if the logger itself fails.
// Note: This is a fire-and-forget call. We don't await it or handle its response
// to prevent an infinite loop if the addError endpoint itself is failing.
CRUD().post( CRUD().post(
link: AppLink.addError, link: AppLink.addError,
payload: { payload: {
@@ -73,134 +68,106 @@ class CRUD {
'userId': userId.toString(), 'userId': userId.toString(),
'userType': userType, 'userType': userType,
'phone': phone.toString(), 'phone': phone.toString(),
'device': where, // The location of the error 'device': where,
'details': details, // The detailed stack trace or context 'details': details,
}, },
); );
} catch (e) { } catch (e) {}
// If logging the error itself fails, print to the console to avoid infinite loops.
print("Failed to log error to server: $e");
}
} }
/// Centralized private method to handle all API requests.
/// Includes retry logic, network checking, and standardized error handling.
Future<dynamic> _makeRequest({ Future<dynamic> _makeRequest({
required String link, required String link,
Map<String, dynamic>? payload, Map<String, dynamic>? payload,
required Map<String, String> headers, required Map<String, String> headers,
}) async { }) async {
try { // timeouts أقصر
// 1. Wrap the http.post call directly with HttpRetry.sendWithRetry. const connectTimeout = Duration(seconds: 6);
// It will attempt the request immediately and retry on transient errors. const receiveTimeout = Duration(seconds: 10);
var response = await HttpRetry.sendWithRetry(
() {
var url = Uri.parse(link);
return http.post(
url,
body: payload,
headers: headers,
);
},
// Optional: you can customize retry behavior for each call
maxRetries: 3,
timeout: const Duration(seconds: 15),
);
// Log.print('response: ${response.body}');
// Log.print('request: ${response.request}');
// Log.print('payload: ${payload}');
// ✅ All your existing logic for handling server responses remains the same.
// This part is only reached if the network request itself was successful.
// Handle successful response (200 OK) Future<http.Response> doPost() {
if (response.statusCode == 200) { final url = Uri.parse(link);
// استخدم _client بدل http.post
return _client
.post(url, body: payload, headers: headers)
.timeout(connectTimeout + receiveTimeout);
}
http.Response response;
try {
// retry ذكي: محاولة واحدة إضافية فقط لأخطاء شبكة/5xx
try {
response = await doPost();
} on SocketException catch (_) {
// محاولة ثانية واحدة فقط
response = await doPost();
} on TimeoutException catch (_) {
response = await doPost();
}
final sc = response.statusCode;
final body = response.body;
Log.print('body: ${body}');
Log.print('body: ${body}');
// 2xx
if (sc >= 200 && sc < 300) {
try { try {
var jsonData = jsonDecode(response.body); final jsonData = jsonDecode(body);
if (jsonData['status'] == 'success') { return jsonData; // لا تعيد 'success' فقط؛ أعِد الجسم كله
return jsonData; // Return the full JSON object on success } catch (e, st) {
} else { // لا تسجّل كخطأ شبكي لكل حالة؛ فقط معلومات
if (jsonData['status'] == 'failure') { addError('JSON Decode Error', 'Body: $body\n$st',
// return 'failure'; 'CRUD._makeRequest $link');
} else {
addError(
'API Logic Error: ${jsonData['status']}',
'Response: ${response.body}',
'CRUD._makeRequest - $link',
);
}
return jsonData['status']; // Return the specific status string
}
} catch (e, stackTrace) {
addError(
'JSON Decode Error: $e',
'Response Body: ${response.body}\nStack Trace: $stackTrace',
'CRUD._makeRequest - $link',
);
return 'failure'; return 'failure';
} }
} }
// Handle Unauthorized (401)
else if (response.statusCode == 401) { // 401 → دع الطبقة العليا تتعامل مع التجديد
var jsonData = jsonDecode(response.body); if (sc == 401) {
if (jsonData['error'] == 'Token expired') { // لا تستدع getJWT هنا كي لا نضاعف الرحلات
await Get.put(LoginDriverController()).getJWT(); return 'token_expired';
return 'token_expired';
} else {
addError(
'Unauthorized Error: ${jsonData['error']}',
'Status Code: 401',
'CRUD._makeRequest - $link',
);
return 'failure';
}
} }
// Handle all other non-successful status codes
else { // 5xx: لا تعِد المحاولة هنا (حاولنا مرة ثانية فوق)
if (sc >= 500) {
addError( addError(
'HTTP Error', 'Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link');
'Status Code: ${response.statusCode}\nResponse Body: ${response.body}',
'CRUD._makeRequest - $link',
);
return 'failure'; return 'failure';
} }
// 4xx أخرى: أعد الخطأ بدون تسجيل مكرر
return 'failure';
} on SocketException { } on SocketException {
// 2. This block now catches the "no internet" case after all retries have failed. _netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
_netGuard.notifyOnce((title, msg) { return 'no_internet';
mySnackeBarError(msg); } on TimeoutException {
}); return 'failure';
return 'no_internet'; // Return the specific status you were using before. } catch (e, st) {
} catch (e, stackTrace) { addError('HTTP Request Exception: $e', 'Stack: $st',
// 3. This is a general catch-all for any other unexpected errors. 'CRUD._makeRequest $link');
addError(
'HTTP Request Exception: $e',
'Stack Trace: $stackTrace',
'CRUD._makeRequest - $link',
);
return 'failure'; return 'failure';
} }
} }
/// Performs a standard authenticated POST request.
/// Automatically handles token renewal.
Future<dynamic> post({ Future<dynamic> post({
required String link, required String link,
Map<String, dynamic>? payload, Map<String, dynamic>? payload,
}) async { }) async {
// 1. Check if the token is expired String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
// bool isTokenExpired = JwtDecoder.isExpired(X // if (JwtDecoder.isExpired(token)) {
// .r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs) // await Get.put(LoginController()).getJWT();
// .toString() // token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
// .split(AppInformation.addd)[0]);
// // 2. If expired, get a new one
// if (isTokenExpired) {
// await LoginDriverController().getJWT();
// } // }
// 3. Prepare the headers with the valid token
final headers = { final headers = {
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
'Authorization': 'Authorization': 'Bearer $token'
'Bearer ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}'
}; };
// 4. Make the request using the centralized helper
return await _makeRequest( return await _makeRequest(
link: link, link: link,
payload: payload, payload: payload,
@@ -208,24 +175,68 @@ class CRUD {
); );
} }
/// Performs an authenticated POST request to the wallet endpoints. /// Performs a standard authenticated GET request (using POST method as per original code).
/// Uses a separate JWT and HMAC for authentication. /// Automatically handles token renewal.
Future<dynamic> get({
required String link,
Map<String, dynamic>? payload,
}) async {
var url = Uri.parse(
link,
);
var response = await http.post(
url,
body: payload,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization':
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}'
},
);
if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
return response.body;
}
return jsonData['status'];
} else if (response.statusCode == 401) {
// Specifically handle 401 Unauthorized
var jsonData = jsonDecode(response.body);
if (jsonData['error'] == 'Token expired') {
// Show snackbar prompting to re-login
await Get.put(LoginDriverController()).getJWT();
// mySnackbarSuccess('please order now'.tr);
return 'token_expired'; // Return a specific value for token expiration
} else {
// Other 401 errors
addError('Unauthorized: ${jsonData['error']}', 'crud().post - 401',
url.toString());
return 'failure';
}
} else {
addError('Non-200 response code: ${response.statusCode}',
'crud().post - Other', url.toString());
return 'failure';
}
}
/// Performs an authenticated POST request to wallet endpoints.
Future<dynamic> postWallet({ Future<dynamic> postWallet({
required String link, required String link,
Map<String, dynamic>? payload, Map<String, dynamic>? payload,
}) async { }) async {
// 1. Get the specific JWT and HMAC for the wallet
var jwt = await LoginDriverController().getJwtWallet(); var jwt = await LoginDriverController().getJwtWallet();
final hmac = box.read(BoxName.hmac); final hmac = box.read(BoxName.hmac);
// 2. Prepare the headers
final headers = { final headers = {
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
'Authorization': 'Bearer $jwt', 'Authorization': 'Bearer $jwt',
'X-HMAC-Auth': hmac.toString(), 'X-HMAC-Auth': hmac.toString(),
}; };
// 3. Make the request using the centralized helper
return await _makeRequest( return await _makeRequest(
link: link, link: link,
payload: payload, payload: payload,
@@ -233,6 +244,58 @@ class CRUD {
); );
} }
/// Performs an authenticated GET request to wallet endpoints (using POST).
Future<dynamic> getWallet({
required String link,
Map<String, dynamic>? payload,
}) async {
var s = await LoginDriverController().getJwtWallet();
final hmac = box.read(BoxName.hmac);
var url = Uri.parse(
link,
);
var response = await http.post(
url,
body: payload,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization': 'Bearer $s',
'X-HMAC-Auth': hmac.toString(),
},
);
// Log.print('response.request: ${response.request}');
// Log.print('response.body: ${response.body}');
// Log.print('response.payload: ${payload}');
if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
return response.body;
}
return jsonData['status'];
} else if (response.statusCode == 401) {
// Specifically handle 401 Unauthorized
var jsonData = jsonDecode(response.body);
if (jsonData['error'] == 'Token expired') {
// Show snackbar prompting to re-login
await Get.put(LoginDriverController()).getJwtWallet();
return 'token_expired'; // Return a specific value for token expiration
} else {
// Other 401 errors
addError('Unauthorized: ${jsonData['error']}', 'crud().post - 401',
url.toString());
return 'failure';
}
} else {
addError('Non-200 response code: ${response.statusCode}',
'crud().post - Other', url.toString());
return 'failure';
}
}
Future<dynamic> postWalletMtn( Future<dynamic> postWalletMtn(
{required String link, Map<String, dynamic>? payload}) async { {required String link, Map<String, dynamic>? payload}) async {
final s = await LoginDriverController().getJwtWallet(); final s = await LoginDriverController().getJwtWallet();
@@ -250,11 +313,6 @@ class CRUD {
}, },
); );
print('req: ${response.request}');
print('status: ${response.statusCode}');
print('body: ${response.body}');
print('payload: $payload');
Map<String, dynamic> wrap(String status, {Object? message, int? code}) { Map<String, dynamic> wrap(String status, {Object? message, int? code}) {
return { return {
'status': status, 'status': status,
@@ -305,122 +363,11 @@ class CRUD {
} }
} }
Future<dynamic> get({
required String link,
Map<String, dynamic>? payload,
}) async {
// bool isTokenExpired = JwtDecoder.isExpired(X
// .r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs)
// .toString()
// .split(AppInformation.addd)[0]);
// // Log.print('isTokenExpired: ${isTokenExpired}');
// if (isTokenExpired) {
// await LoginDriverController().getJWT();
// }
// await Get.put(LoginDriverController()).getJWT();
var url = Uri.parse(
link,
);
var response = await http.post(
url,
body: payload,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization':
'Bearer ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}'
},
);
// print(response.request);
// Log.print('response.body: ${response.body}');
// print(payload);
if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
return response.body;
}
return jsonData['status'];
} else if (response.statusCode == 401) {
// Specifically handle 401 Unauthorized
var jsonData = jsonDecode(response.body);
if (jsonData['error'] == 'Token expired') {
// Show snackbar prompting to re-login
await Get.put(LoginDriverController()).getJWT();
// mySnackbarSuccess('please order now'.tr);
return 'token_expired'; // Return a specific value for token expiration
} else {
// Other 401 errors
// addError('Unauthorized: ${jsonData['error']}', 'crud().post - 401');
return 'failure';
}
} else {
// addError('Non-200 response code: ${response.statusCode}',
// 'crud().post - Other');
return 'failure';
}
}
Future<dynamic> getWallet({
required String link,
Map<String, dynamic>? payload,
}) async {
var s = await LoginDriverController().getJwtWallet();
final hmac = box.read(BoxName.hmac);
// Log.print('hmac: ${hmac}');
var url = Uri.parse(
link,
);
var response = await http.post(
url,
body: payload,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization': 'Bearer $s',
'X-HMAC-Auth': hmac.toString(),
},
);
// Log.print('response.request: ${response.request}');
// Log.print('response.body: ${response.body}');
// print(payload);
if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
return response.body;
}
return jsonData['status'];
} else if (response.statusCode == 401) {
// Specifically handle 401 Unauthorized
var jsonData = jsonDecode(response.body);
if (jsonData['error'] == 'Token expired') {
// Show snackbar prompting to re-login
// await Get.put(LoginDriverController()).getJwtWallet();
return 'token_expired'; // Return a specific value for token expiration
} else {
// Other 401 errors
// addError('Unauthorized: ${jsonData['error']}', 'crud().post - 401');
return 'failure';
}
} else {
// addError('Non-200 response code: ${response.statusCode}',
// 'crud().post - Other');
return 'failure';
}
}
// Future<dynamic> postWallet( // Future<dynamic> postWallet(
// {required String link, Map<String, dynamic>? payload}) async { // {required String link, Map<String, dynamic>? payload}) async {
// var s = await LoginDriverController().getJwtWallet(); // var s = await LoginDriverController().getJwtWallet();
// // Log.print('jwt: ${s}');
// final hmac = box.read(BoxName.hmac); // final hmac = box.read(BoxName.hmac);
// // Log.print('hmac: ${hmac}');
// var url = Uri.parse(link); // var url = Uri.parse(link);
// // Log.print('url: ${url}');
// try { // try {
// // await LoginDriverController().getJWT(); // // await LoginDriverController().getJWT();
@@ -433,9 +380,6 @@ class CRUD {
// 'X-HMAC-Auth': hmac.toString(), // 'X-HMAC-Auth': hmac.toString(),
// }, // },
// ); // );
// // Log.print('response.request:${response.request}');
// // Log.print('response.body: ${response.body}');
// // Log.print('payload:$payload');
// if (response.statusCode == 200) { // if (response.statusCode == 200) {
// try { // try {
// var jsonData = jsonDecode(response.body); // var jsonData = jsonDecode(response.body);
@@ -491,9 +435,6 @@ class CRUD {
// // 'Authorization': 'Bearer ${box.read(BoxName.jwt)}' // // 'Authorization': 'Bearer ${box.read(BoxName.jwt)}'
// }, // },
// ); // );
// print(response.request);
// Log.print('response.body: ${response.body}');
// print(payload);
// if (response.statusCode == 200) { // if (response.statusCode == 200) {
// try { // try {
// var jsonData = jsonDecode(response.body); // var jsonData = jsonDecode(response.body);
@@ -763,11 +704,8 @@ class CRUD {
// التحقق من النتيجة // التحقق من النتيجة
if (response.statusCode == 200) { if (response.statusCode == 200) {
print("✅ Email sent successfully.");
} else { } else {
print("❌ Failed to send email. Status: ${response.statusCode}");
final responseBody = await response.stream.bytesToString(); final responseBody = await response.stream.bytesToString();
print("Response body: $responseBody");
} }
} }
@@ -867,8 +805,6 @@ class CRUD {
url, url,
body: payload, body: payload,
); );
Log.print('esponse.body: ${response.body}');
Log.print('esponse.body: ${response.request}');
var jsonData = jsonDecode(response.body); var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'OK') { if (jsonData['status'] == 'OK') {

View File

@@ -4,241 +4,523 @@ import 'dart:math';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart'; import 'package:location/location.dart';
import 'package:sefer_driver/constant/table_names.dart'; import 'package:battery_plus/battery_plus.dart'; // **إضافة جديدة:** للتعامل مع حالة البطارية
import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
import 'package:sefer_driver/constant/table_names.dart';
import '../../constant/box_name.dart'; import '../../constant/box_name.dart';
import '../../constant/links.dart'; import '../../constant/links.dart';
import '../../main.dart'; import '../../main.dart';
import '../../print.dart'; import '../../print.dart';
import '../home/captin/home_captain_controller.dart'; import '../home/captin/home_captain_controller.dart';
import '../home/payment/captain_wallet_controller.dart'; import '../home/payment/captain_wallet_controller.dart';
import 'battery_status.dart';
import 'crud.dart'; import 'crud.dart';
import 'encrypt_decrypt.dart';
/// LocationController - النسخة النهائية المتكاملة مع وضع توفير الطاقة
///
/// تم تصميم هذا المتحكم ليكون المحرك الجديد لإدارة الموقع في تطبيقك.
/// يجمع بين الكفاءة العالية، المنطق الذكي، والتوافق الكامل مع بنية الكود الحالية.
class LocationController extends GetxController { class LocationController extends GetxController {
LocationData? _currentLocation; // ===================================================================
late Location location = Location(); // ====== Tunables / المتغيرات القابلة للتعديل ======
bool isLoading = false; // ===================================================================
late double heading = 0; // -- Normal Mode --
late double previousTime = 0; static const double onMoveMetersNormal = 15.0;
late double latitude; static const double offMoveMetersNormal = 200.0;
late double totalDistance = 0; static const Duration trackInsertEveryNormal = Duration(minutes: 1);
late double longitude; static const Duration heartbeatEveryNormal = Duration(minutes: 2);
late DateTime time;
late double speed = 0;
bool isActive = false;
late LatLng myLocation = LatLng(0, 0);
String totalPoints = '0';
LocationData? get currentLocation => _currentLocation;
Timer? _locationTimer;
LatLng? _lastSavedPosition; // -- Power Saving Mode --
static const double onMoveMetersPowerSave = 75.0;
static const double offMoveMetersPowerSave = 500.0;
static const Duration trackInsertEveryPowerSave = Duration(minutes: 2);
static const Duration heartbeatEveryPowerSave = Duration(minutes: 5);
static const double lowWalletThreshold = -30000;
static const int powerSaveTriggerLevel =
20; // نسبة البطارية لتفعيل وضع التوفير
static const int powerSaveExitLevel =
25; // نسبة البطارية للخروج من وضع التوفير
// ===================================================================
// ====== Services & Subscriptions (الخدمات والاشتراكات) ======
// ===================================================================
late final Location location = Location();
final Battery _battery = Battery(); // **إضافة جديدة:** للتعامل مع البطارية
StreamSubscription<LocationData>? _locSub;
StreamSubscription<BatteryState>? _batterySub;
Timer? _trackInsertTimer;
Timer? _heartbeatTimer;
// ===================================================================
// ====== Cached Controllers (لتجنب Get.find المتكرر) ======
// ===================================================================
late final HomeCaptainController _homeCtrl;
late final CaptainWalletController _walletCtrl;
// ===================================================================
// ====== Public state (لواجهة المستخدم والكلاسات الأخرى) ======
// ===================================================================
LatLng myLocation = const LatLng(0, 0);
double heading = 0.0;
double speed = 0.0;
double totalDistance = 0.0;
// ===================================================================
// ====== Internal state (للمنطق الداخلي) ======
// ===================================================================
bool _isReady = false;
bool _isPowerSavingMode = false; // **إضافة جديدة:** لتتبع وضع توفير الطاقة
LatLng? _lastSentLoc;
String? _lastSentStatus;
DateTime? _lastSentAt;
LatLng? _lastPosForDistance;
// **إضافة جديدة:** متغيرات لحساب سلوك السائق
LatLng? _lastSqlLoc;
double? _lastSpeed;
DateTime? _lastSpeedAt;
@override @override
void onInit() async { Future<void> onInit() async {
super.onInit(); super.onInit();
location = Location(); print('LocationController onInit started...');
await location.changeSettings(
accuracy: LocationAccuracy.high, interval: 5000, distanceFilter: 0);
location.enableBackgroundMode(enable: true);
await getLocation();
await startLocationUpdates();
totalPoints = Get.put(CaptainWalletController()).totalPoints.toString(); bool dependenciesReady = await _awaitDependencies();
isActive = Get.put(HomeCaptainController()).isActive; if (!dependenciesReady) {
} print(
"❌ CRITICAL ERROR: Dependencies not found. Location services will not start.");
String getLocationArea(double latitude, double longitude) { return;
final locations = box.read(BoxName.locationName) ?? [];
for (final location in locations) {
final locationData = location as Map<String, dynamic>;
final minLatitude =
double.tryParse(locationData['min_latitude'].toString()) ?? 0.0;
final maxLatitude =
double.tryParse(locationData['max_latitude'].toString()) ?? 0.0;
final minLongitude =
double.tryParse(locationData['min_longitude'].toString()) ?? 0.0;
final maxLongitude =
double.tryParse(locationData['max_longitude'].toString()) ?? 0.0;
if (latitude >= minLatitude &&
latitude <= maxLatitude &&
longitude >= minLongitude &&
longitude <= maxLongitude) {
box.write(BoxName.serverChosen, (locationData['server_link']));
return locationData['name'];
}
} }
box.write(BoxName.serverChosen, AppLink.seferCairoServer); _isReady = true;
return 'Cairo';
await _initLocationSettings();
await startLocationUpdates();
_listenToBatteryChanges(); // **إضافة جديدة:** بدء الاستماع لتغيرات البطارية
print('✅ LocationController is ready and initialized.');
} }
int _insertCounter = 0; @override
double? _lastSpeed; void onClose() {
DateTime? _lastSpeedTime; print('🛑 Closing LocationController...');
stopLocationUpdates();
_batterySub?.cancel(); // إيقاف الاستماع للبطارية
super.onClose();
}
Future<bool> _awaitDependencies() async {
// ... (الكود لم يتغير)
int attempts = 0;
while (attempts < 10) {
if (Get.isRegistered<HomeCaptainController>() &&
Get.isRegistered<CaptainWalletController>()) {
_homeCtrl = Get.find<HomeCaptainController>();
_walletCtrl = Get.find<CaptainWalletController>();
print("✅ Dependencies found and controllers are cached.");
return true;
}
await Future.delayed(const Duration(milliseconds: 500));
attempts++;
}
return false;
}
// ===================================================================
// ====== Public Control Methods (دوال التحكم العامة) ======
// ===================================================================
Future<void> startLocationUpdates() async { Future<void> startLocationUpdates() async {
if (box.read(BoxName.driverID) != null) { // ... (الكود لم يتغير)
_locationTimer = if (!_isReady) {
Timer.periodic(const Duration(seconds: 5), (timer) async { print("Cannot start updates: LocationController is not ready.");
try { return;
await BatteryNotifier.checkBatteryAndNotify(); }
totalPoints = final points = _walletCtrl.totalPoints;
Get.find<CaptainWalletController>().totalPoints.toString(); if (double.parse(points) <= lowWalletThreshold) {
isActive = Get.find<HomeCaptainController>().isActive; print('❌ Blocked: low wallet balance ($points)');
stopLocationUpdates();
if (isActive && double.parse(totalPoints) > -30000) { return;
await getLocation(); }
if (myLocation.latitude == 0 && myLocation.longitude == 0) return; if (_locSub != null) {
print('Location updates are already active.');
String area = return;
getLocationArea(myLocation.latitude, myLocation.longitude); }
if (await _ensureServiceAndPermission()) {
final payload = { _subscribeLocationStream();
'driver_id': box.read(BoxName.driverID).toString(), _startTimers();
'latitude': myLocation.latitude.toString(),
'longitude': myLocation.longitude.toString(),
'heading': heading.toString(),
'speed': (speed * 3.6).toStringAsFixed(1),
'distance': totalDistance.toStringAsFixed(2),
'status': box.read(BoxName.statusDriverLocation) ?? 'off',
};
// ✅ تحديث للسيرفر
await CRUD().post(
link: '${AppLink.server}/ride/location/update.php',
payload: payload,
);
// ✅ تخزين في SQLite فقط إذا الرحلة On + تحرك أكثر من 10 متر
if ((box.read(BoxName.statusDriverLocation) ?? 'off') == 'on') {
if (_lastSavedPosition == null ||
_calculateDistanceInMeters(_lastSavedPosition!, myLocation) >=
10) {
double currentSpeed = speed; // m/s
double? acceleration = _calculateAcceleration(currentSpeed);
await sql.insertData({
'driver_id': box.read(BoxName.driverID).toString(),
'latitude': myLocation.latitude,
'longitude': myLocation.longitude,
'acceleration': acceleration ?? 0.0,
'created_at': DateTime.now().toIso8601String(),
'updated_at': DateTime.now().toIso8601String(),
}, TableName.behavior);
_lastSavedPosition = myLocation;
}
}
// ✅ إدخال للسيرفر كل دقيقة
_insertCounter++;
// Log.print('_insertCounter: ${_insertCounter}');
if (_insertCounter == 12) {
_insertCounter = 0;
await CRUD().post(
link: '${AppLink.server}/ride/location/add.php',
payload: payload,
);
}
// ✅ تحديث الكاميرا
Get.find<HomeCaptainController>()
.mapHomeCaptainController
?.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
bearing: Get.find<LocationController>().heading,
target: myLocation,
zoom: 17, // Adjust zoom level as needed
),
),
);
// if (Get.isRegistered()) {
// Get.find<MapDriverController>().mapController?.animateCamera(
// CameraUpdate.newCameraPosition(
// CameraPosition(
// bearing: Get.find<LocationController>().heading,
// target: myLocation,
// zoom: 17, // Adjust zoom level as needed
// ),
// ),
// );
// }
}
} catch (e) {
print('Location update error: $e');
}
});
} }
} }
void stopLocationUpdates() { void stopLocationUpdates() {
_locationTimer?.cancel(); // ... (الكود لم يتغير)
_locSub?.cancel();
_locSub = null;
_trackInsertTimer?.cancel();
_trackInsertTimer = null;
_heartbeatTimer?.cancel();
_heartbeatTimer = null;
print('Location updates and timers stopped.');
} }
Future<void> getLocation() async { Future<LocationData?> getLocation() async {
// ... (الكود لم يتغير)
try {
if (await _ensureServiceAndPermission()) {
return await location.getLocation();
}
} catch (e) {
print('❌ FAILED to get single location: $e');
}
return null;
}
// ===================================================================
// ====== Core Logic (المنطق الأساسي) ======
// ===================================================================
Future<void> _initLocationSettings() async {
// ... (الكود لم يتغير)
await location.changeSettings(
accuracy: LocationAccuracy.high,
interval: 5000,
distanceFilter: 0,
);
await location.enableBackgroundMode(enable: true);
}
Future<bool> _ensureServiceAndPermission() async {
// ... (الكود لم يتغير)
bool serviceEnabled = await location.serviceEnabled(); bool serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) { if (!serviceEnabled) {
serviceEnabled = await location.requestService(); serviceEnabled = await location.requestService();
if (!serviceEnabled) return; if (!serviceEnabled) return false;
} }
var perm = await location.hasPermission();
PermissionStatus permissionGranted = await location.hasPermission(); if (perm == PermissionStatus.denied) {
if (permissionGranted == PermissionStatus.denied) { perm = await location.requestPermission();
permissionGranted = await location.requestPermission(); if (perm != PermissionStatus.granted) return false;
if (permissionGranted != PermissionStatus.granted) return;
} }
return true;
}
Future.delayed(Duration(milliseconds: 500), () async { void _subscribeLocationStream() {
try { _locSub?.cancel();
LocationData _locationData = await location.getLocation(); _locSub = location.onLocationChanged.listen(
if (_locationData.latitude != null && _locationData.longitude != null) { (loc) async {
myLocation = if (!_isReady) return;
LatLng(_locationData.latitude!, _locationData.longitude!); try {
} else { if (loc.latitude == null || loc.longitude == null) return;
myLocation = LatLng(0, 0); final now = DateTime.now();
final pos = LatLng(loc.latitude!, loc.longitude!);
myLocation = pos;
speed = loc.speed ?? 0.0;
heading = loc.heading ?? 0.0;
if (_lastPosForDistance != null) {
final d = _haversineMeters(_lastPosForDistance!, pos);
if (d > 2.0) totalDistance += d;
}
_lastPosForDistance = pos;
// ✅ تحديث الكاميرا
_homeCtrl.mapHomeCaptainController?.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
bearing: Get.find<LocationController>().heading,
target: myLocation,
zoom: 17, // Adjust zoom level as needed
),
),
);
update(); // تحديث الواجهة الرسومية بالبيانات الجديدة
await _smartSend(pos, loc);
// **إضافة جديدة:** حفظ سلوك السائق في قاعدة البيانات المحلية
await _saveBehaviorIfMoved(pos, now, currentSpeed: speed);
} catch (e) {
print('Error in onLocationChanged: $e');
} }
},
onError: (e) => print('Location stream error: $e'),
);
print('📡 Subscribed to location stream.');
}
speed = _locationData.speed ?? 0; void _startTimers() {
heading = _locationData.heading ?? 0; _trackInsertTimer?.cancel();
_heartbeatTimer?.cancel();
update(); final trackDuration =
} catch (e) { _isPowerSavingMode ? trackInsertEveryPowerSave : trackInsertEveryNormal;
print("Error getting location: $e"); final heartbeatDuration =
_isPowerSavingMode ? heartbeatEveryPowerSave : heartbeatEveryNormal;
_trackInsertTimer =
Timer.periodic(trackDuration, (_) => _addSingleTrackPoint());
_heartbeatTimer =
Timer.periodic(heartbeatDuration, (_) => _sendStationaryHeartbeat());
print('⏱️ Background timers started (Power Save: $_isPowerSavingMode).');
}
Future<void> _smartSend(LatLng pos, LocationData loc) async {
final String driverStatus = box.read(BoxName.statusDriverLocation) ?? 'off';
final distSinceSent =
(_lastSentLoc == null) ? 999.0 : _haversineMeters(_lastSentLoc!, pos);
final onMoveThreshold =
_isPowerSavingMode ? onMoveMetersPowerSave : onMoveMetersNormal;
final offMoveThreshold =
_isPowerSavingMode ? offMoveMetersPowerSave : offMoveMetersNormal;
bool shouldSend = false;
if (driverStatus != _lastSentStatus) {
shouldSend = true;
if (driverStatus == 'on') {
totalDistance = 0.0;
_lastPosForDistance = pos;
} }
print(
'Status changed: ${_lastSentStatus ?? '-'} -> $driverStatus. Sending...');
} else if (driverStatus == 'on') {
if (distSinceSent >= onMoveThreshold) {
shouldSend = true;
}
} else {
// driverStatus == 'off'
if (distSinceSent >= offMoveThreshold) {
shouldSend = true;
}
}
if (!shouldSend) return;
await _sendUpdate(pos, driverStatus, loc);
}
// ===================================================================
// ====== Battery Logic (منطق البطارية) ======
// ===================================================================
void _listenToBatteryChanges() async {
_checkBatteryLevel(await _battery.batteryLevel);
_batterySub =
_battery.onBatteryStateChanged.listen((BatteryState state) async {
_checkBatteryLevel(await _battery.batteryLevel);
}); });
} }
double _calculateDistanceInMeters(LatLng start, LatLng end) { void _checkBatteryLevel(int level) {
const p = 0.017453292519943295; final bool wasInPowerSaveMode = _isPowerSavingMode;
final a = 0.5 -
cos((end.latitude - start.latitude) * p) / 2 +
cos(start.latitude * p) *
cos(end.latitude * p) *
(1 - cos((end.longitude - start.longitude) * p)) /
2;
return 12742 * 1000 * asin(sqrt(a)); // meters
}
double? _calculateAcceleration(double currentSpeed) { if (level <= powerSaveTriggerLevel) {
final now = DateTime.now(); _isPowerSavingMode = true;
if (_lastSpeed != null && _lastSpeedTime != null) { } else if (level >= powerSaveExitLevel) {
final deltaTime = _isPowerSavingMode = false;
now.difference(_lastSpeedTime!).inMilliseconds / 1000.0; // seconds
if (deltaTime > 0) {
final acceleration = (currentSpeed - _lastSpeed!) / deltaTime;
_lastSpeed = currentSpeed;
_lastSpeedTime = now;
return double.parse(acceleration.toStringAsFixed(2));
}
} }
if (_isPowerSavingMode != wasInPowerSaveMode) {
if (_isPowerSavingMode) {
Get.snackbar(
"وضع توفير الطاقة مُفعّل",
"البطارية منخفضة. سنقلل تحديثات الموقع للحفاظ على طاقتك.",
snackPosition: SnackPosition.TOP,
backgroundColor: Get.theme.primaryColor.withOpacity(0.9),
colorText: Get.theme.colorScheme.onPrimary,
duration: const Duration(seconds: 7),
);
} else {
Get.snackbar(
"العودة للوضع الطبيعي",
"تم شحن البطارية. عادت تحديثات الموقع لوضعها الطبيعي.",
snackPosition: SnackPosition.TOP,
backgroundColor: Get.theme.colorScheme.secondary.withOpacity(0.9),
colorText: Get.theme.colorScheme.onSecondary,
duration: const Duration(seconds: 5),
);
}
_startTimers();
}
}
// ===================================================================
// ====== API Communication & Helpers (التواصل مع السيرفر والدوال المساعدة) ======
// ===================================================================
Future<void> _sendUpdate(LatLng pos, String status, LocationData loc) async {
final payload = _buildPayload(pos, status, loc);
try {
await CRUD().post(
link: '${AppLink.server}/ride/location/update.php',
payload: payload,
);
_lastSentLoc = pos;
_lastSentStatus = status;
_lastSentAt = DateTime.now();
print('✅ Sent to update.php [$status]');
} catch (e) {
print('❌ FAILED to send to update.php: $e');
}
}
Future<void> _addSingleTrackPoint() async {
if (!_isReady) return;
final String driverStatus =
(box.read(BoxName.statusDriverLocation) ?? 'off').toString();
if (myLocation.latitude == 0 && myLocation.longitude == 0) return;
if (_lastSentLoc == null) return; // حماية إضافية
// قيَم رقمية آمنة
final double safeHeading =
(heading is num) ? (heading as num).toDouble() : 0.0;
final double safeSpeed = (speed is num) ? (speed as num).toDouble() : 0.0;
final double safeDistKm = (totalDistance is num)
? (totalDistance as num).toDouble() / 1000.0
: 0.0;
final String driverId =
(box.read(BoxName.driverID) ?? '').toString().trim();
if (driverId.isEmpty) return; // لا ترسل بدون DriverID
// ✅ كل شيء Strings فقط
final Map<String, String> payload = {
'driver_id': driverId,
'latitude': _lastSentLoc!.latitude.toStringAsFixed(6),
'longitude': _lastSentLoc!.longitude.toStringAsFixed(6),
'heading': safeHeading.toStringAsFixed(1),
'speed': (safeSpeed < 0.5 ? 0.0 : safeSpeed)
.toString(), // أو toStringAsFixed(2)
'distance': safeDistKm.toStringAsFixed(2),
'status': driverStatus,
'carType': (box.read(BoxName.carType) ?? 'default').toString(),
};
try {
print('⏱️ Adding a single point to car_track... $payload');
await CRUD().post(
link: '${AppLink.server}/ride/location/add.php',
payload: payload, // ← الآن Map<String,String>
);
} catch (e) {
print('❌ FAILED to send single track point: $e');
}
}
Future<void> _sendStationaryHeartbeat() async {
if (!_isReady) return;
if (_lastSentLoc == null || _lastSentAt == null) return;
if (DateTime.now().difference(_lastSentAt!).inSeconds < 90) return;
final distSinceSent = _haversineMeters(_lastSentLoc!, myLocation);
if (distSinceSent >= onMoveMetersNormal) return;
print('🫀 Driver is stationary, sending heartbeat...');
final String driverStatus =
(box.read(BoxName.statusDriverLocation) ?? 'off').toString();
// ✅ كل شيء Strings
final Map<String, String> payload = {
'driver_id': (box.read(BoxName.driverID) ?? '').toString(),
'latitude': _lastSentLoc!.latitude.toStringAsFixed(6),
'longitude': _lastSentLoc!.longitude.toStringAsFixed(6),
'heading': heading.toStringAsFixed(1),
// ملاحظة: هنا السرعة تبقى بالمتر/ث بعد التحويل أدناه؛ وحّدتّها لكتابة String
'speed': ((speed < 0.5) ? 0.0 : speed).toString(),
'distance': (totalDistance / 1000).toStringAsFixed(2),
'status': driverStatus,
'carType': (box.read(BoxName.carType) ?? 'default').toString(),
// 'hb': '1'
};
try {
await CRUD().post(
link: '${AppLink.server}/ride/location/update.php',
payload: payload,
);
_lastSentAt = DateTime.now();
} catch (e) {
print('❌ FAILED to send Heartbeat: $e');
}
}
Map<String, String> _buildPayload(
LatLng pos, String status, LocationData loc) {
return {
'driver_id': (box.read(BoxName.driverID) ?? '').toString(),
'latitude': pos.latitude.toStringAsFixed(6),
'longitude': pos.longitude.toStringAsFixed(6),
'heading': (loc.heading ?? heading).toStringAsFixed(1),
// هنا أنت بتحوّل السرعة إلى كم/س (×3.6) — ممتاز
'speed': ((loc.speed ?? speed) * 3.6).toStringAsFixed(1),
'status': status,
'distance': (totalDistance / 1000).toStringAsFixed(2),
'carType': (box.read(BoxName.carType) ?? 'default').toString(), // 👈
};
}
double _haversineMeters(LatLng a, LatLng b) {
const p = 0.017453292519943295;
final h = 0.5 -
cos((b.latitude - a.latitude) * p) / 2 +
cos(a.latitude * p) *
cos(b.latitude * p) *
(1 - cos((b.longitude - a.longitude) * p)) /
2;
return 12742 * 1000 * asin(sqrt(h));
}
// **إضافة جديدة:** دوال لحفظ سلوك السائق محلياً
/// يحسب التسارع بالمتر/ثانية^2
double? _calcAcceleration(double currentSpeed, DateTime now) {
if (_lastSpeed != null && _lastSpeedAt != null) {
final dt = now.difference(_lastSpeedAt!).inMilliseconds / 1000.0;
if (dt > 0.5) {
// لتجنب القيم الشاذة في الفترات الزمنية الصغيرة جداً
final a = (currentSpeed - _lastSpeed!) / dt;
_lastSpeed = currentSpeed;
_lastSpeedAt = now;
return a;
}
}
_lastSpeed = currentSpeed; _lastSpeed = currentSpeed;
_lastSpeedTime = now; _lastSpeedAt = now;
return null; return null;
} }
/// يحفظ سلوك السائق (الموقع، التسارع) في قاعدة بيانات SQLite المحلية
Future<void> _saveBehaviorIfMoved(LatLng pos, DateTime now,
{required double currentSpeed}) async {
final dist =
(_lastSqlLoc == null) ? 999.0 : _haversineMeters(_lastSqlLoc!, pos);
if (dist < 15.0) return; // الحفظ فقط عند التحرك لمسافة 15 متر على الأقل
final accel = _calcAcceleration(currentSpeed, now) ?? 0.0;
try {
final now = DateTime.now();
final double lat =
double.parse(pos.latitude.toStringAsFixed(6)); // دقة 6 أرقام
final double lon =
double.parse(pos.longitude.toStringAsFixed(6)); // دقة 6 أرقام
final double acc = double.parse(
(accel is num ? accel as num : 0).toStringAsFixed(2)); // دقة منزلتين
await sql.insertData({
'driver_id': (box.read(BoxName.driverID) ?? '').toString(), // TEXT
'latitude': lat, // REAL
'longitude': lon, // REAL
'acceleration': acc, // REAL
'created_at': now.toIso8601String(), // TEXT
'updated_at': now.toIso8601String(), // TEXT
}, TableName.behavior);
_lastSqlLoc = pos;
} catch (e) {
print('❌ FAILED to insert to SQLite (behavior): $e');
}
}
} }

View File

@@ -187,7 +187,7 @@ class DeviceHelper {
// Extract relevant device information // Extract relevant device information
final String deviceId = Platform.isAndroid final String deviceId = Platform.isAndroid
? deviceData['androidId'] ?? deviceData['serialNumber'] ?? 'unknown' ? deviceData['androidId'] ?? deviceData['fingerprint'] ?? 'unknown'
: deviceData['identifierForVendor'] ?? 'unknown'; : deviceData['identifierForVendor'] ?? 'unknown';
final String deviceModel = deviceData['model'] ?? 'unknown'; final String deviceModel = deviceData['model'] ?? 'unknown';
@@ -199,6 +199,7 @@ class DeviceHelper {
// Generate and return the encrypted fingerprint // Generate and return the encrypted fingerprint
final String fingerprint = '${deviceId}_${deviceModel}_$osVersion'; final String fingerprint = '${deviceId}_${deviceModel}_$osVersion';
Log.print('fingerprint: ${fingerprint}');
// print(EncryptionHelper.instance.encryptData(fingerprint)); // print(EncryptionHelper.instance.encryptData(fingerprint));
return (fingerprint); return (fingerprint);
} catch (e) { } catch (e) {

View File

@@ -246,7 +246,7 @@ class HomeCaptainController extends GetxController {
// isLoading = true; // isLoading = true;
update(); update();
var res = await CRUD().get( var res = await CRUD().getWallet(
link: AppLink.getDriverPaymentPoints, link: AppLink.getDriverPaymentPoints,
payload: {'driverID': box.read(BoxName.driverID).toString()}, payload: {'driverID': box.read(BoxName.driverID).toString()},
); );
@@ -268,7 +268,7 @@ class HomeCaptainController extends GetxController {
void onInit() async { void onInit() async {
// await locationBackController.requestLocationPermission(); // await locationBackController.requestLocationPermission();
Get.put(FirebaseMessagesController()); Get.put(FirebaseMessagesController());
addToken(); // addToken();
await getlocation(); await getlocation();
onButtonSelected(); onButtonSelected();
getDriverRate(); getDriverRate();
@@ -327,39 +327,23 @@ class HomeCaptainController extends GetxController {
addToken() async { addToken() async {
String? fingerPrint = await storage.read(key: BoxName.fingerPrint); String? fingerPrint = await storage.read(key: BoxName.fingerPrint);
CRUD().post(link: AppLink.addTokensDriver, payload: { final payload = {
'token': (box.read(BoxName.tokenDriver)), 'token': (box.read(BoxName.tokenDriver)),
'captain_id': (box.read(BoxName.driverID)).toString(), 'captain_id': (box.read(BoxName.driverID)).toString(),
'fingerPrint': (fingerPrint).toString() 'fingerPrint': (fingerPrint).toString()
}); };
Log.print('payload: ${payload}');
CRUD().post(link: AppLink.addTokensDriver, payload: payload);
// CRUD().post( await CRUD().post(
// link: "${AppLink.seferAlexandriaServer}/ride/firebase/addDriver.php",
// payload: {
// 'token': box.read(BoxName.tokenDriver),
// 'captain_id': box.read(BoxName.driverID).toString(),
// 'fingerPrint': (fingerPrint).toString()
// });
// CRUD().post(
// link: "${AppLink.seferGizaServer}/ride/firebase/addDriver.php",
// payload: {
// 'token': box.read(BoxName.tokenDriver),
// 'captain_id': box.read(BoxName.driverID).toString(),
// 'fingerPrint': (fingerPrint).toString()
// });
await CRUD().postWallet(
link: "${AppLink.seferPaymentServer}/ride/firebase/addDriver.php", link: "${AppLink.seferPaymentServer}/ride/firebase/addDriver.php",
payload: { payload: payload);
'token': box.read(BoxName.tokenDriver),
'captain_id': box.read(BoxName.driverID).toString(),
'fingerPrint': (fingerPrint).toString()
});
// MapDriverController().driverCallPassenger(); // MapDriverController().driverCallPassenger();
// box.write(BoxName.statusDriverLocation, 'off'); // box.write(BoxName.statusDriverLocation, 'off');
} }
getPaymentToday() async { getPaymentToday() async {
var res = await CRUD().get( var res = await CRUD().getWallet(
link: AppLink.getDriverPaymentToday, link: AppLink.getDriverPaymentToday,
payload: {'driverID': box.read(BoxName.driverID).toString()}); payload: {'driverID': box.read(BoxName.driverID).toString()});
if (res != 'failure') { if (res != 'failure') {
@@ -423,7 +407,7 @@ class HomeCaptainController extends GetxController {
} }
getAllPayment() async { getAllPayment() async {
var res = await CRUD().get( var res = await CRUD().getWallet(
link: AppLink.getAllPaymentFromRide, link: AppLink.getAllPaymentFromRide,
payload: {'driverID': box.read(BoxName.driverID).toString()}); payload: {'driverID': box.read(BoxName.driverID).toString()});
if (res == 'failure') { if (res == 'failure') {
@@ -447,10 +431,15 @@ class HomeCaptainController extends GetxController {
var res = await CRUD().get( var res = await CRUD().get(
link: AppLink.getTotalDriverDurationToday, link: AppLink.getTotalDriverDurationToday,
payload: {'driver_id': box.read(BoxName.driverID).toString()}); payload: {'driver_id': box.read(BoxName.driverID).toString()});
if (res == 'failure') {
data = jsonDecode(res); totalDurationToday = '0';
totalDurationToday = data['message'][0]['total_duration']; update();
update(); return;
} else {
data = jsonDecode(res);
totalDurationToday = data['message'][0]['total_duration'];
update();
}
} }
@override @override

View File

@@ -664,14 +664,22 @@ class MapDriverController extends GetxController {
return d['message']; return d['message'];
} }
// ... other controller code ...
/// Refactored function to finish a ride with consolidated API calls.
void finishRideFromDriver1() async { void finishRideFromDriver1() async {
// Show a loading indicator to the user
Get.dialog(Center(child: CircularProgressIndicator()),
barrierDismissible: false);
isRideFinished = true; isRideFinished = true;
isRideStarted = false; isRideStarted = false;
isPriceWindow = false; isPriceWindow = false;
box.write(BoxName.rideStatus, 'Finished'); box.write(BoxName.rideStatus, 'Finished');
Log.print('rideStatus from map 664 : ${box.read(BoxName.rideStatus)}'); Log.print(
'rideStatus from map (refactored) : ${box.read(BoxName.rideStatus)}');
// Calculate totalCost more concisely // --- 1. Calculate Total Cost (Logic remains the same) ---
if (price < 16000) { if (price < 16000) {
totalCost = (carType == 'Comfort' || totalCost = (carType == 'Comfort' ||
carType == 'Mishwar Vip' || carType == 'Mishwar Vip' ||
@@ -690,224 +698,100 @@ class MapDriverController extends GetxController {
paymentAmount = totalCost; paymentAmount = totalCost;
box.write(BoxName.statusDriverLocation, 'off'); box.write(BoxName.statusDriverLocation, 'off');
// Prepare data for API calls // --- 2. Prepare Payloads for Consolidated API Calls ---
final nowString = DateTime.now().toString(); final nowString = DateTime.now().toString();
final basePayload = {
'id': (rideId), final rideUpdatePayload = {
'rideId': rideId.toString(),
'rideTimeFinish': nowString, 'rideTimeFinish': nowString,
'status': 'Finished', 'status': 'Finished',
'price': totalCost, 'price': totalCost,
}; };
final driverOrderPayload = {
'order_id': (rideId.toString()), final String paymentAuthToken =
'status': 'Finished' await generateTokenDriver(paymentAmount.toString());
final paymentProcessingPayload = {
'rideId': rideId.toString(),
'driverId': box.read(BoxName.driverID).toString(),
'passengerId': passengerId.toString(),
'paymentAmount': paymentAmount,
'paymentMethod': paymentMethod,
'walletChecked': walletChecked.toString(),
'passengerWalletBurc': passengerWalletBurc.toString(),
'authToken': paymentAuthToken,
}; };
// List to hold all asynchronous operations // --- 3. Execute API Calls in Parallel ---
List<Future<dynamic>> futures = []; try {
List<Future<dynamic>> apiCalls = [];
// API calls that can run in parallel apiCalls.add(CRUD().post(
futures.add(CRUD().post( link: "${AppLink.seferCairoServer}/ride/rides//finish_ride_updates.php",
link: "${AppLink.seferCairoServer}/ride/rides/update.php", payload: rideUpdatePayload,
payload: basePayload, ));
));
futures.add(CRUD().post(
link: "${AppLink.seferCairoServer}/ride/driver_order/update.php",
payload: driverOrderPayload,
));
// Wallet transactions (can potentially be parallelized if independent) apiCalls.add(CRUD().postWallet(
if (walletChecked == 'true') { link:
paymentToken = await generateTokenPassenger( "${AppLink.seferPaymentServer}/ride/payment/process_ride_payments.php",
((-1) * double.parse(paymentAmount)).toString()); payload: paymentProcessingPayload,
futures ));
.add(CRUD().postWallet(link: AppLink.addPassengersWallet, payload: {
'passenger_id': (passengerId), final results = await Future.wait(apiCalls);
'balance': ((-1) * double.parse(paymentAmount)).toString(),
'token': paymentToken, // --- 4. *** CRITICAL STEP: Verify BOTH results were successful *** ---
})); // Assuming CRUD().post returns a Map<String, dynamic> like {'success': true, 'message': '...'}
final rideUpdateResult = results[0];
final paymentResult = results[1];
if (rideUpdateResult['status'] == 'success' &&
paymentResult['status'] == 'success') {
// --- SUCCESS: Both API calls succeeded, now proceed ---
Get.back(); // Dismiss the loading indicator
Get.put(DriverBehaviorController())
.sendSummaryToServer(driverId, rideId);
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
"Driver Finish Trip".tr,
'${'you will pay to Driver'.tr} $paymentAmount \$',
tokenPassenger,
[
box.read(BoxName.driverID),
rideId,
box.read(BoxName.tokenDriver),
paymentAmount.toString()
],
'ding.wav');
Get.to(() => RatePassenger(), arguments: {
'passengerId': passengerId,
'rideId': rideId,
'price': paymentAmount.toString(),
'walletChecked': walletChecked
});
} else {
// --- FAILURE: One or both API calls failed ---
// The transaction on the server side would have been rolled back.
// We throw an exception to be caught by the catch block, which will revert the UI state.
throw Exception(
'One of the server operations failed. Ride Update: ${rideUpdateResult['message']} | Payment: ${paymentResult['message']}');
}
} catch (e) {
// --- CATCH ALL ERRORS (Network, Server Failure, etc.) ---
Get.back(); // Dismiss the loading indicator
Log.print("Error finishing ride: $e");
Get.snackbar("Error", "Could not finish the ride. Please try again.");
// Revert state because the operation failed
isRideFinished = false;
isRideStarted = true;
isPriceWindow = true;
box.write(BoxName.rideStatus, 'InProgress'); // Revert status
} }
paymentToken = await generateTokenDriver(paymentAmount.toString());
futures.add(CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
'rideId': (rideId),
'amount': paymentAmount,
'payment_method':
walletChecked == 'true' ? "${paymentMethod}Ride" : paymentMethod,
'passengerID': (passengerId),
'token': paymentToken,
'driverID': box.read(BoxName.driverID).toString(),
}));
if (double.parse(passengerWalletBurc) < 0) {
final paymentToken1 = await generateTokenPassenger(
((-1) * double.parse(passengerWalletBurc)).toString());
futures
.add(CRUD().postWallet(link: AppLink.addPassengersWallet, payload: {
'passenger_id': (passengerId),
'token': paymentToken1,
'balance': ((-1) * double.parse(passengerWalletBurc)).toString()
}));
}
double pointsSubtraction = double.parse(paymentAmount) * (-1) * 0.1;
final paymentToken2 =
await generateTokenDriver((pointsSubtraction).toStringAsFixed(0));
futures
.add(CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: {
'paymentID': 'rideId${(rideId)}',
'amount': (pointsSubtraction).toStringAsFixed(0),
'paymentMethod': paymentMethod,
'token': paymentToken2,
'driverID': box.read(BoxName.driverID).toString(),
}));
// Wait for all independent API calls to complete
await Future.wait(futures);
Get.put(DriverBehaviorController()).sendSummaryToServer(driverId, rideId);
// Send notification (this likely depends on previous steps)
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
"Driver Finish Trip".tr,
'${'you will pay to Driver'.tr} $paymentAmount \$',
tokenPassenger,
[
box.read(BoxName.driverID),
rideId,
box.read(BoxName.tokenDriver),
paymentAmount.toString()
],
'ding.wav');
// Navigate to the next screen (likely depends on previous steps being done)
Get.to(() => RatePassenger(), arguments: {
'passengerId': passengerId,
'rideId': rideId,
'price': paymentAmount.toString(),
'walletChecked': walletChecked
});
} }
// void finishRideFromDriver1() async {
// // if (carType != 'Comfort' || carType != 'Free Ride') {
// isRideFinished = true;
// isRideStarted = false;
// isPriceWindow = false;
// box.write(BoxName.rideStatus, 'Finished');
// // Get.find<HomeCaptainController>().changeToAppliedRide('Finished');
// // Get.find<HomeCaptainController>().update();
// totalCost = price < 20
// ? carType != 'Comfort' && carType != 'Mishwar Vip' && carType != 'Lady'
// ? '20'
// : '30'
// : price < double.parse(totalPricePassenger)
// ? totalPricePassenger
// : carType != 'Comfort' &&
// carType != 'Mishwar Vip' &&
// carType != 'Lady'
// ? totalPricePassenger
// : price.toStringAsFixed(2);
// paymentAmount = totalCost;
// box.write(BoxName.statusDriverLocation, 'off');
// // changeRideToBeginToPassenger();
// await CRUD().post(
// link: "${AppLink.seferCairoServer}/ride/rides/update.php",
// payload: {
// 'id': rideId,
// 'rideTimeFinish': DateTime.now().toString(),
// 'status': 'Finished',
// 'price': totalCost,
// });
// CRUD().post(
// link: "${AppLink.seferCairoServer}/ride/driver_order/update.php",
// payload: {
// // 'driver_id': box.read(BoxName.driverID).toString(),
// 'order_id': rideId.toString(),
// 'status': 'Finished'
// });
// if (AppLink.endPoint != AppLink.seferCairoServer) {
// CRUD().post(
// link: "${AppLink.endPoint}/ride/rides/update.php",
// payload: {
// 'id': rideId,
// 'rideTimeFinish': DateTime.now().toString(),
// 'status': 'Finished',
// 'price': totalCost,
// },
// );
// CRUD().post(
// link: "${AppLink.endPoint}/ride/driver_order/update.php",
// payload: {
// // 'driver_id': box.read(BoxName.driverID).toString(),
// 'order_id': rideId.toString(),
// 'status': 'Finished'
// });
// }
// if (walletChecked == 'true') {
// paymentToken = await generateTokenPassenger(
// ((-1) * double.parse(paymentAmount)).toString());
// await CRUD().post(link: AppLink.addPassengersWallet, payload: {
// 'passenger_id': passengerId,
// 'balance': ((-1) * double.parse(paymentAmount)).toString(),
// 'token': paymentToken,
// });
// }
// paymentToken = await generateTokenDriver(paymentAmount.toString());
// await CRUD().post(link: AppLink.addDrivePayment, payload: {
// 'rideId': rideId,
// 'amount': paymentAmount,
// 'payment_method':
// walletChecked == 'true' ? "${paymentMethod}Ride" : paymentMethod,
// 'passengerID': passengerId,
// 'token': paymentToken,
// 'driverID': box.read(BoxName.driverID).toString(),
// });
// if (double.parse(passengerWalletBurc) < 0) {
// // for zero passenger
// var paymentToken1 = await generateTokenPassenger(
// ((-1) * double.parse(passengerWalletBurc)).toString());
// await CRUD().post(link: AppLink.addPassengersWallet, payload: {
// 'passenger_id': passengerId,
// 'token': paymentToken1,
// 'balance': ((-1) * double.parse(passengerWalletBurc)).toString()
// });
// }
// double pointsSubtraction = 0;
// pointsSubtraction =
// double.parse(paymentAmount) * (-1) * .08; //for 300 from 3000
// var paymentToken2 =
// await generateTokenDriver((pointsSubtraction).toStringAsFixed(0));
// var res = await CRUD().post(link: AppLink.addDriversWalletPoints, payload: {
// 'paymentID': 'rideId$rideId',
// 'amount': (pointsSubtraction).toStringAsFixed(0),
// 'paymentMethod': paymentMethod,
// 'token': paymentToken2,
// 'driverID': box.read(BoxName.driverID).toString(),
// });
// Future.delayed(const Duration(milliseconds: 300));
// Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
// "Driver Finish Trip".tr,
// '${'you will pay to Driver'.tr} $paymentAmount \$',
// tokenPassenger,
// [
// box.read(BoxName.driverID),
// rideId,
// box.read(BoxName.tokenDriver),
// // carType == 'Comfort' || carType == 'Mishwar Vip'
// // ? price.toStringAsFixed(2)
// // : totalPassenger
// paymentAmount.toString()
// ],
// 'ding.wav');
// Get.to(() => RatePassenger(), arguments: {
// 'passengerId': passengerId,
// 'rideId': rideId,
// 'price': paymentAmount.toString(), //price
// 'walletChecked': walletChecked
// });
// // Get.back();
// }
void cancelCheckRideFromPassenger() async { void cancelCheckRideFromPassenger() async {
var res = await CRUD().get( var res = await CRUD().get(
@@ -1426,7 +1310,9 @@ class MapDriverController extends GetxController {
if (activeRouteSteps.isNotEmpty) { if (activeRouteSteps.isNotEmpty) {
currentInstruction = currentInstruction =
_parseInstruction(activeRouteSteps[0]['html_instructions']); _parseInstruction(activeRouteSteps[0]['html_instructions']);
Get.find<TextToSpeechController>().speakText(currentInstruction); Get.isRegistered<TextToSpeechController>()
? Get.find<TextToSpeechController>().speakText(currentInstruction)
: Get.put(TextToSpeechController()).speakText(currentInstruction);
} }
// تحديث الكاميرا لتناسب المسار الجديد // تحديث الكاميرا لتناسب المسار الجديد
@@ -1491,7 +1377,9 @@ class MapDriverController extends GetxController {
currentStepIndex++; currentStepIndex++;
currentInstruction = _parseInstruction( currentInstruction = _parseInstruction(
activeRouteSteps[currentStepIndex]['html_instructions']); activeRouteSteps[currentStepIndex]['html_instructions']);
Get.find<TextToSpeechController>().speakText(currentInstruction); Get.isRegistered<TextToSpeechController>()
? Get.find<TextToSpeechController>().speakText(currentInstruction)
: Get.put(TextToSpeechController()).speakText(currentInstruction);
// -->> هنا يتم تحديث لون المسار <<-- // -->> هنا يتم تحديث لون المسار <<--
_updateTraveledPath(); _updateTraveledPath();

View File

@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -7,6 +8,7 @@ import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_polyline_algorithm/google_polyline_algorithm.dart'; import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
import 'package:sefer_driver/constant/colors.dart'; import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/env/env.dart';
// استخدام نفس مسارات الاستيراد التي قدمتها // استخدام نفس مسارات الاستيراد التي قدمتها
import '../../../constant/api_key.dart'; import '../../../constant/api_key.dart';
@@ -399,9 +401,11 @@ class NavigationController extends GetxController {
} }
Future<void> getRoute(LatLng origin, LatLng destination) async { Future<void> getRoute(LatLng origin, LatLng destination) async {
final String key = Platform.isAndroid ? Env.mapAPIKEY : Env.mapAPIKEYIOS;
final url = final url =
'${AppLink.googleMapsLink}directions/json?language=ar&destination=${destination.latitude},${destination.longitude}&origin=${origin.latitude},${origin.longitude}&key=${AK.mapAPIKEY}'; '${AppLink.googleMapsLink}directions/json?language=ar&destination=${destination.latitude},${destination.longitude}&origin=${origin.latitude},${origin.longitude}&key=${key}&mode=driving';
var response = await CRUD().getGoogleApi(link: url, payload: {}); var response = await CRUD().getGoogleApi(link: url, payload: {});
Log.print('response: ${response}');
if (response == null || response['routes'].isEmpty) { if (response == null || response['routes'].isEmpty) {
Get.snackbar('خطأ', 'لم يتم العثور على مسار.'); Get.snackbar('خطأ', 'لم يتم العثور على مسار.');

View File

@@ -1,15 +1,19 @@
import 'dart:async'; import 'dart:async';
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:sefer_driver/views/home/on_boarding_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:sefer_driver/views/auth/captin/login_captin.dart';
import 'package:sefer_driver/views/home/on_boarding_page.dart';
import '../../constant/box_name.dart'; import '../../constant/box_name.dart';
import '../../main.dart'; import '../../main.dart';
import '../../print.dart'; import '../../print.dart';
import '../../views/auth/captin/login_captin.dart';
// Assuming you have a home page to navigate to after successful login.
// If not, you might need to adjust the navigation target.
// import 'package:sefer_driver/views/home/home_page.dart';
class SplashScreenController extends GetxController class SplashScreenController extends GetxController
with GetTickerProviderStateMixin { with GetTickerProviderStateMixin {
@@ -20,73 +24,116 @@ class SplashScreenController extends GetxController
String packageInfo = ''; String packageInfo = '';
Future<void> _getPackageInfo() async {
final info = await PackageInfo.fromPlatform();
packageInfo = info.version;
box.write(BoxName.packagInfo, packageInfo);
update();
}
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
_getPackageInfo(); _setupAnimations();
_animationController = AnimationController( _initializeAndNavigate();
vsync: this,
duration: const Duration(milliseconds: 1500), // Reduced duration
)..forward();
animation =
CurvedAnimation(parent: _animationController, curve: Curves.easeOut);
startTimer();
_startProgressTimer();
} }
void _startProgressTimer() { void _setupAnimations() {
Log.print( _animationController = AnimationController(
'box.read(BoxName.phoneDriver): ${box.read(BoxName.phoneDriver)}'); vsync: this,
Log.print( duration: const Duration(milliseconds: 1500),
'box.read(BoxName.phoneVerified): ${box.read(BoxName.phoneVerified)}'); );
const totalTime = 3000; // 5 seconds in milliseconds animation =
const interval = 50; // Update every 50ms CurvedAnimation(parent: _animationController, curve: Curves.easeInOut);
_animationController.forward();
}
/// This is the core function that initializes the app.
/// It runs two tasks simultaneously and navigates only when necessary.
Future<void> _initializeAndNavigate() async {
// Start getting package info, no need to wait for it.
_getPackageInfo();
const minSplashDurationMs = 4000;
_animateProgressBar(minSplashDurationMs);
// Define the two concurrent tasks
final minDuration =
Future.delayed(const Duration(milliseconds: minSplashDurationMs));
final navigationTargetFuture = _getNavigationTarget();
// Wait for both tasks to complete
await Future.wait([minDuration, navigationTargetFuture]);
// The future now returns a nullable Widget (Widget?)
final Widget? targetPage = await navigationTargetFuture;
// *** FIX: Only navigate if the targetPage is not null. ***
// This prevents navigating again if the login function already handled it.
if (targetPage != null) {
Get.off(() => targetPage,
transition: Transition.fadeIn,
duration: const Duration(milliseconds: 500));
} else {
Log.print(
"Navigation was handled internally by the login process. Splash screen will not navigate.");
}
}
/// Animates the progress bar over a given duration.
void _animateProgressBar(int totalMilliseconds) {
const interval = 50;
int elapsed = 0; int elapsed = 0;
_progressTimer?.cancel();
_progressTimer = _progressTimer =
Timer.periodic(const Duration(milliseconds: interval), (timer) async { Timer.periodic(const Duration(milliseconds: interval), (timer) {
elapsed += interval; elapsed += interval;
progress.value = (elapsed / totalTime).clamp(0.0, 1.0); progress.value = (elapsed / totalMilliseconds).clamp(0.0, 1.0);
if (elapsed >= totalMilliseconds) {
if (elapsed >= totalTime) {
timer.cancel(); timer.cancel();
// Get.off(SyrianCardAI());
box.read(BoxName.onBoarding) == null
? Get.off(() => OnBoardingPage())
: box.read(BoxName.phoneDriver) != null &&
box.read(BoxName.phoneVerified).toString() == '1'
? await Get.put(LoginDriverController())
.loginWithGoogleCredential(
box.read(BoxName.driverID).toString(),
box.read(BoxName.emailDriver).toString())
: Get.off(() => LoginCaptin());
} }
}); });
} }
void startTimer() async { /// Determines the correct page to navigate to, or returns null if navigation
Timer(const Duration(seconds: 5), () async { /// is expected to be handled by an internal process (like login).
// box.read(BoxName.onBoarding) == null Future<Widget?> _getNavigationTarget() async {
// ? Get.off(() => OnBoardingPage()) try {
// : box.read(BoxName.email) != null && final onBoardingShown = box.read(BoxName.onBoarding) != null;
// box.read(BoxName.phone) != null && if (!onBoardingShown) {
// box.read(BoxName.isVerified) == '1' return OnBoardingPage();
// // ? Get.off(() => const MapPagePassenger()) }
// ? await Get.put(LoginController()).loginUsingCredentials(
// box.read(BoxName.passengerID).toString(), final isDriverDataAvailable = box.read(BoxName.phoneDriver) != null;
// box.read(BoxName.email).toString(), final isPhoneVerified = box.read(BoxName.phoneVerified).toString() == '1';
// )
// : Get.off(() => LoginPage()); if (isDriverDataAvailable && isPhoneVerified) {
}); Log.print('Attempting to log in with stored credentials...');
final loginController = Get.put(LoginDriverController());
// Assume loginWithGoogleCredential handles its own navigation on success.
await loginController.loginWithGoogleCredential(
box.read(BoxName.driverID).toString(),
box.read(BoxName.emailDriver).toString(),
);
// *** FIX: Return null to signify that navigation has been handled. ***
return null;
} else {
Log.print('No valid driver session found. Navigating to login page.');
return LoginCaptin();
}
} catch (e) {
Log.print("Error during navigation logic: $e");
// Fallback to the login page in case of any error.
return LoginCaptin();
}
}
Future<void> _getPackageInfo() async {
try {
final info = await PackageInfo.fromPlatform();
packageInfo = info.version;
await box.write(BoxName.packagInfo, packageInfo);
update(); // To update any UI element that might be listening
} catch (e) {
Log.print("Could not get package info: $e");
packageInfo = '1.0.0'; // Default value
await box.write(BoxName.packagInfo, packageInfo);
}
} }
@override @override

View File

@@ -61,8 +61,9 @@ class RideAvailableController extends GetxController {
double startLongitude = double.parse(startLocationParts[1]); double startLongitude = double.parse(startLocationParts[1]);
// Assuming currentLocation is the driver's location // Assuming currentLocation is the driver's location
double currentLatitude = Get.find<LocationController>().latitude; double currentLatitude = Get.find<LocationController>().myLocation.latitude;
double currentLongitude = Get.find<LocationController>().longitude; double currentLongitude =
Get.find<LocationController>().myLocation.longitude;
return Geolocator.distanceBetween( return Geolocator.distanceBetween(
currentLatitude, currentLatitude,

3
lib/env/env.dart vendored
View File

@@ -7,6 +7,9 @@ abstract class Env {
@EnviedField(varName: 'basicAuthCredentials', obfuscate: true) @EnviedField(varName: 'basicAuthCredentials', obfuscate: true)
static final String basicAuthCredentials = _Env.basicAuthCredentials; static final String basicAuthCredentials = _Env.basicAuthCredentials;
@EnviedField(varName: 'mapAPIKEYIOS', obfuscate: true)
static final String mapAPIKEYIOS = _Env.mapAPIKEYIOS;
@EnviedField(varName: 'email', obfuscate: true) @EnviedField(varName: 'email', obfuscate: true)
static final String email = _Env.email; static final String email = _Env.email;

24510
lib/env/env.g.dart vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,25 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/date_symbol_data_local.dart';
import 'package:sefer_driver/views/home/Captin/orderCaptin/order_request_page.dart'; import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_overlay_window/flutter_overlay_window.dart'; import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_stripe/flutter_stripe.dart'; import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:flutter/services.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'constant/api_key.dart'; import 'constant/api_key.dart';
import 'constant/info.dart'; import 'constant/info.dart';
import 'constant/notification.dart';
import 'controller/firebase/firbase_messge.dart'; import 'controller/firebase/firbase_messge.dart';
import 'controller/firebase/local_notification.dart'; import 'controller/firebase/local_notification.dart';
import 'controller/functions/add_error.dart'; import 'controller/functions/add_error.dart';
@@ -29,6 +33,7 @@ import 'firebase_options.dart';
import 'models/db_sql.dart'; import 'models/db_sql.dart';
import 'print.dart'; import 'print.dart';
import 'splash_screen_page.dart'; import 'splash_screen_page.dart';
import 'views/home/Captin/orderCaptin/order_request_page.dart';
import 'views/home/Captin/driver_map_page.dart'; import 'views/home/Captin/driver_map_page.dart';
import 'views/home/Captin/orderCaptin/order_over_lay.dart'; import 'views/home/Captin/orderCaptin/order_over_lay.dart';
@@ -38,10 +43,22 @@ DbSql sql = DbSql.instance;
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
const platform = MethodChannel('com.example.intaleq_driver/app_control'); const platform = MethodChannel('com.example.intaleq_driver/app_control');
/// تهيئة Firebase بوعي لمنع تهيئة مكرّرة على أندرويد (isolates متعددة)
Future<void> initFirebaseIfNeeded() async {
if (Firebase.apps.isEmpty) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
} else {
Firebase.app();
}
}
/// ============ Handlers: Background ============
@pragma('vm:entry-point') @pragma('vm:entry-point')
Future<void> backgroundMessageHandler(RemoteMessage message) async { Future<void> backgroundMessageHandler(RemoteMessage message) async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await initFirebaseIfNeeded();
await GetStorage.init(); await GetStorage.init();
if (!Get.isRegistered<NotificationController>()) { if (!Get.isRegistered<NotificationController>()) {
@@ -61,7 +78,7 @@ Future<void> backgroundMessageHandler(RemoteMessage message) async {
if (message.notification?.title == 'Order' || if (message.notification?.title == 'Order' ||
message.notification?.title == 'OrderSpeed') { message.notification?.title == 'OrderSpeed') {
var myListString = message.data['DriverList'] ?? '[]'; final myListString = message.data['DriverList'] ?? '[]';
Log.print('myListString: $myListString'); Log.print('myListString: $myListString');
List<dynamic> myList; List<dynamic> myList;
@@ -72,7 +89,7 @@ Future<void> backgroundMessageHandler(RemoteMessage message) async {
myList = []; myList = [];
} }
bool isOverlayActive = await FlutterOverlayWindow.isActive(); final isOverlayActive = await FlutterOverlayWindow.isActive();
if (isOverlayActive) { if (isOverlayActive) {
await FlutterOverlayWindow.shareData(myList); await FlutterOverlayWindow.shareData(myList);
} else { } else {
@@ -106,13 +123,13 @@ void notificationTapBackground(NotificationResponse notificationResponse) {
NotificationController().handleNotificationResponse(notificationResponse); NotificationController().handleNotificationResponse(notificationResponse);
} }
/// ============ Entrypoint: Overlay ============
@pragma('vm:entry-point') @pragma('vm:entry-point')
void overlayMain() async { void overlayMain() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await GetStorage.init(); await GetStorage.init();
await Firebase.initializeApp( await initFirebaseIfNeeded();
options: DefaultFirebaseOptions.currentPlatform,
);
if (!Get.isRegistered<NotificationController>()) { if (!Get.isRegistered<NotificationController>()) {
Get.put(NotificationController()); Get.put(NotificationController());
@@ -124,43 +141,52 @@ void overlayMain() async {
)); ));
} }
/// إغلاق الـ Overlay عند الحاجة
Future<void> closeOverLay() async { Future<void> closeOverLay() async {
bool isOverlayActive = await FlutterOverlayWindow.isActive(); final isOverlayActive = await FlutterOverlayWindow.isActive();
if (isOverlayActive) { if (isOverlayActive) {
await FlutterOverlayWindow.closeOverlay(); await FlutterOverlayWindow.closeOverlay();
} }
} }
void main() async { /// ============ Entrypoint: App ============
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); void main() {
runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
await initFirebaseIfNeeded();
await WakelockPlus.enable();
await GetStorage.init();
await initializeDateFormatting();
Stripe.publishableKey = AK.publishableKeyStripe;
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
// سجل الهاندلر تبع رسائل الخلفية (لازم يكون Top-Level ومع @pragma)
FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler);
await WakelockPlus.enable();
await GetStorage.init();
await initializeDateFormatting();
Stripe.publishableKey = AK.publishableKeyStripe;
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
runZonedGuarded<Future<void>>(() async {
runApp(const MyApp()); runApp(const MyApp());
}, (error, stack) { }, (error, stack) {
// ==== START: ERROR FILTER ==== // ==== START: ERROR FILTER ====
String errorString = error.toString(); final errorString = error.toString();
// Print all errors to the local debug console for development // اطبع كل شيء محلياً
// (يمكنك استبدال print بـ Log.print إن رغبت)
print("Caught Dart error: $error"); print("Caught Dart error: $error");
print(stack); print(stack);
// We will check if the error contains keywords for errors we want to ignore. // تجاهُل بعض الأخطاء المعروفة
// If it's one of them, we will NOT send it to the server. final isIgnoredError = errorString.contains('PERMISSION_DENIED') ||
bool isIgnoredError = errorString.contains('PERMISSION_DENIED') ||
errorString.contains('FormatException') || errorString.contains('FormatException') ||
errorString.contains('Null check operator used on a null value'); errorString.contains('Null check operator used on a null value');
if (!isIgnoredError) { if (!isIgnoredError) {
// Only send the error to the server if it's not in our ignore list. // أرسل فقط ما ليس ضمن قائمة التجاهل
CRUD.addError(error.toString(), stack.toString(), 'main'); CRUD.addError(error.toString(), stack.toString(), 'main');
} else { } else {
print("Ignoring error and not sending to server: $errorString"); print("Ignoring error and not sending to server: $errorString");
@@ -186,6 +212,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose(); super.dispose();
} }
@@ -204,9 +231,20 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
} }
await FirebaseMessaging.instance.requestPermission(); await FirebaseMessaging.instance.requestPermission();
FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler);
await FirebaseMessagesController().getToken(); // يمكن أيضاً تفعيل foreground presentation options هنا لو احتجت
await NotificationController().initNotifications(); await NotificationController().initNotifications();
// Generate a random index to pick a message
final random = Random();
final randomMessage =
syrianDriverMessages[random.nextInt(syrianDriverMessages.length)];
// Schedule the notification with the random message
NotificationController().scheduleNotificationsForSevenDays(
randomMessage.split(':')[0],
randomMessage.split(':')[1],
"tone1",
);
} catch (e) { } catch (e) {
Log.print("Error during _initApp: $e"); Log.print("Error during _initApp: $e");
} }
@@ -214,7 +252,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
LocaleController localController = Get.put(LocaleController()); final LocaleController localController = Get.put(LocaleController());
return GetMaterialApp( return GetMaterialApp(
navigatorKey: navigatorKey, navigatorKey: navigatorKey,
title: AppInformation.appName, title: AppInformation.appName,
@@ -227,8 +265,9 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
GetPage(name: '/', page: () => SplashScreen()), GetPage(name: '/', page: () => SplashScreen()),
GetPage(name: '/order-page', page: () => OrderRequestPage()), GetPage(name: '/order-page', page: () => OrderRequestPage()),
GetPage( GetPage(
name: '/passenger-location-map', name: '/passenger-location-map',
page: () => PassengerLocationMapPage()), page: () => PassengerLocationMapPage(),
),
], ],
); );
} }

View File

@@ -14,11 +14,10 @@ class SplashScreen extends StatelessWidget {
final textTheme = Theme.of(context).textTheme; final textTheme = Theme.of(context).textTheme;
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
// Define our new green color palette here // A modern, elegant color palette
const Color primaryGreen = Color(0xFF1A535C); const Color primaryDark = Color(0xFF0D1B2A);
const Color secondaryGreen = Color(0xFF4ECDC4); const Color secondaryDark = Color(0xFF1B263B);
const Color logoBackgroundColor = Colors.white; const Color accentColor = Color(0xFF4ECDC4);
const Color logoIconColor = primaryGreen;
const Color textColor = Colors.white; const Color textColor = Colors.white;
return Scaffold( return Scaffold(
@@ -30,90 +29,105 @@ class SplashScreen extends StatelessWidget {
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: [
primaryGreen, primaryDark,
secondaryGreen, secondaryDark,
], ],
), ),
), ),
child: Stack( child: Stack(
children: [ children: [
// Centered Logo and Slogan with Animation // Center-aligned animated content
Center( Center(
child: AnimatedBuilder( child: Column(
animation: splashScreenController.animation, mainAxisSize: MainAxisSize.min,
builder: (context, child) => FadeTransition( children: [
opacity: splashScreenController.animation, // Logo with Scale and Fade animation
child: Transform.translate( ScaleTransition(
offset: Offset( scale: splashScreenController.animation,
0, 50 * (1 - splashScreenController.animation.value)), child: FadeTransition(
child: child, opacity: splashScreenController.animation,
), child: Container(
), padding: const EdgeInsets.all(20),
child: Column( decoration: BoxDecoration(
mainAxisSize: MainAxisSize.min,
children: [
// Elegant and Clear Logo
Container(
padding: const EdgeInsets.all(25),
decoration: const BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: logoBackgroundColor, color: Colors.white.withOpacity(0.95),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black26, color: accentColor.withOpacity(0.2),
blurRadius: 10, blurRadius: 25,
offset: Offset(0, 4), spreadRadius: 5,
) ),
]), ],
child: Image.asset('assets/images/logo.gif'), ),
), child: ClipRRect(
const SizedBox(height: 24), borderRadius: BorderRadius.circular(100),
// App Name - now using a variable for color child: Image.asset(
Text( 'assets/images/logo.gif',
'Intaleq', // Replace with AppInformation.appName if needed width: size.width * 0.3, // Responsive size
style: textTheme.headlineMedium?.copyWith( height: size.width * 0.3,
color: textColor, ),
fontWeight: FontWeight.bold, ),
letterSpacing: 2,
), ),
), ),
const SizedBox(height: 12), ),
// Slogan - now using a variable for color const SizedBox(height: 30),
Text( // App Name and Slogan with staggered animation
'Your Journey Begins Here'.tr, _AnimatedText(
style: textTheme.titleMedium?.copyWith( text: 'Intaleq', // Your App Name
color: textColor.withOpacity(0.9), animation: splashScreenController.animation,
), style: textTheme.headlineMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
letterSpacing: 3,
), ),
], beginOffset: const Offset(0, 0.5),
), ),
const SizedBox(height: 12),
_AnimatedText(
text: 'Your Journey Begins Here'.tr,
animation: splashScreenController.animation,
style: textTheme.titleMedium?.copyWith(
color: textColor.withOpacity(0.8),
fontWeight: FontWeight.w300,
),
beginOffset: const Offset(0, 0.8),
startDelay: 0.2, // Start after the title
),
],
), ),
), ),
// Bottom Version Info and Progress Bar
// Bottom Loading Bar and Version Info
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: Padding( child: Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: size.height * 0.05, left: 40, right: 40), bottom: size.height * 0.06,
left: 40,
right: 40,
),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Obx(() => ClipRRect( Obx(
borderRadius: BorderRadius.circular(10), () => ClipRRect(
child: LinearProgressIndicator( borderRadius: BorderRadius.circular(10),
value: splashScreenController.progress.value, child: LinearProgressIndicator(
backgroundColor: textColor.withOpacity(0.2), value: splashScreenController.progress.value,
valueColor: backgroundColor: primaryDark.withOpacity(0.5),
const AlwaysStoppedAnimation<Color>(textColor), valueColor:
minHeight: 5, const AlwaysStoppedAnimation<Color>(accentColor),
), minHeight: 6,
)), ),
const SizedBox(height: 16), ),
Text( ),
'Version: ${box.read(BoxName.packagInfo) ?? '1.0.0'}', const SizedBox(height: 20),
style: textTheme.bodySmall?.copyWith( GetBuilder<SplashScreenController>(
color: textColor.withOpacity(0.7), builder: (controller) => Text(
'Version: ${controller.packageInfo.isNotEmpty ? controller.packageInfo : '...'}',
style: textTheme.bodySmall?.copyWith(
color: textColor.withOpacity(0.5),
letterSpacing: 1,
),
), ),
), ),
], ],
@@ -126,3 +140,40 @@ class SplashScreen extends StatelessWidget {
); );
} }
} }
/// A helper widget for creating staggered text animations.
class _AnimatedText extends StatelessWidget {
const _AnimatedText({
required this.animation,
required this.text,
required this.style,
required this.beginOffset,
this.startDelay = 0.0,
});
final Animation<double> animation;
final String text;
final TextStyle? style;
final Offset beginOffset;
final double startDelay;
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: CurvedAnimation(
parent: animation,
curve: Interval(startDelay, 1.0, curve: Curves.easeOut),
),
child: SlideTransition(
position: Tween<Offset>(
begin: beginOffset,
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Interval(startDelay, 1.0, curve: Curves.easeOut),
)),
child: Text(text, style: style),
),
);
}
}

View File

@@ -1,19 +1,36 @@
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart'; import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
// --- Placeholder Imports ---
// Assuming these files exist in your project structure.
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../controller/auth/captin/phone_helper_controller.dart'; import '../../../controller/auth/captin/phone_helper_controller.dart';
import '../../../controller/local/phone_intel/intl_phone_field.dart'; import '../../../controller/local/phone_intel/intl_phone_field.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../../print.dart'; import '../../../print.dart';
// Assuming you have an AppColor class defined in your project.
// import 'path/to/your/app_color.dart';
// --- Placeholder AppColor Class ---
// This is used to make the code runnable.
// You should use the one from your project.
// class AppColor {
// static const Color primaryColor = Color(0xFF1DA1F2);
// static const Color greenColor = Color(0xFF34A853); // Google Green
// static const Color secondaryColor = Colors.white;
// }
// --- End of Placeholder ---
/// A visually revamped authentication screen with a light, glassy effect,
/// themed for the driver application using a green primary color.
class AuthScreen extends StatelessWidget { class AuthScreen extends StatelessWidget {
final String title; final String title;
final String subtitle; final String subtitle;
final Widget form; final Widget form;
// Using a more neutral, tech-themed animated logo
final String logoUrl = 'assets/images/logo.gif';
const AuthScreen({ const AuthScreen({
super.key, super.key,
@@ -22,176 +39,258 @@ class AuthScreen extends StatelessWidget {
required this.form, required this.form,
}); });
/// Shows a dialog for testers to log in using email and password.
void _showTesterLoginDialog(
BuildContext context, LoginDriverController controller) {
final testerEmailController = TextEditingController();
final testerPasswordController = TextEditingController();
final testerFormKey = GlobalKey<FormState>();
showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext dialogContext) {
return BackdropFilter(
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
child: AlertDialog(
backgroundColor: Colors.white.withOpacity(0.85),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
title: const Text(
'App Tester Login',
textAlign: TextAlign.center,
style:
TextStyle(fontWeight: FontWeight.bold, color: Colors.black87),
),
content: Form(
key: testerFormKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: testerEmailController,
keyboardType: TextInputType.emailAddress,
style: const TextStyle(color: Colors.black),
decoration: InputDecoration(
labelText: 'Email',
prefixIcon: const Icon(Icons.email_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: AppColor.greenColor, width: 2),
),
),
validator: (value) => value == null || !value.contains('@')
? 'Enter a valid email'.tr
: null,
),
const SizedBox(height: 16),
TextFormField(
controller: testerPasswordController,
obscureText: true,
style: const TextStyle(color: Colors.black),
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: const Icon(Icons.lock_outline),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: AppColor.greenColor, width: 2),
),
),
validator: (value) => value == null || value.isEmpty
? 'Enter a password'
: null,
),
],
),
),
actions: [
TextButton(
child: const Text('Cancel'),
onPressed: () => Navigator.of(dialogContext).pop(),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.greenColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child:
const Text('Login', style: TextStyle(color: Colors.white)),
onPressed: () {
if (testerFormKey.currentState!.validate()) {
controller.logintest(
testerPasswordController.text.trim(),
testerEmailController.text.trim(),
);
Navigator.of(dialogContext).pop();
}
},
),
],
),
);
},
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final controller = Get.put(LoginDriverController()); // Controller for the driver's login logic
final loginController = Get.put(LoginDriverController());
return Scaffold( return Scaffold(
body: Container( body: Container(
// UPDATED: Changed gradient colors to a green theme // NEW: Light and airy gradient with green accents
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [Colors.teal.shade700, Colors.green.shade600], colors: [
begin: Alignment.topLeft, Colors.white,
end: Alignment.bottomRight, Colors.green.shade50,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
), ),
), ),
child: Center( child: Stack(
child: SingleChildScrollView( children: [
padding: const EdgeInsets.symmetric(horizontal: 24.0), // Subtle background shapes with the new primary color
child: Column( Positioned(
mainAxisAlignment: MainAxisAlignment.center, top: -80,
children: [ left: -100,
Image.asset(logoUrl, height: 120), child: Container(
const SizedBox(height: 20), width: 250,
Text( height: 250,
title, decoration: BoxDecoration(
style: const TextStyle( shape: BoxShape.circle,
fontSize: 28, color: AppColor.greenColor.withOpacity(0.1),
fontWeight: FontWeight.bold,
color: Colors.white),
), ),
const SizedBox(height: 10), ),
Text( ),
subtitle, Positioned(
textAlign: TextAlign.center, bottom: -120,
style: TextStyle( right: -150,
fontSize: 16, color: Colors.white.withOpacity(0.8)), child: Container(
width: 350,
height: 350,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColor.greenColor.withOpacity(0.15),
), ),
const SizedBox(height: 30), ),
GetBuilder<LoginDriverController>(builder: (context) { ),
return box.read(BoxName.isTest).toString() == '0' Center(
? Container( child: SingleChildScrollView(
margin: const EdgeInsets.all(16), padding: const EdgeInsets.symmetric(horizontal: 24.0),
padding: const EdgeInsets.symmetric( child: Column(
horizontal: 24, vertical: 32), mainAxisAlignment: MainAxisAlignment.center,
decoration: BoxDecoration( children: [
color: Colors.white, // GestureDetector for tester login
borderRadius: BorderRadius.circular(20), GestureDetector(
onLongPress: () {
_showTesterLoginDialog(context, loginController);
},
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(0.5),
border: Border.all(
color: Colors.white.withOpacity(0.8), width: 2),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black12, color: Colors.black.withOpacity(0.05),
blurRadius: 12, blurRadius: 10,
offset: const Offset(0, 6), spreadRadius: 5)
]),
child: ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.asset('assets/images/logo.gif',
height: 100)),
),
),
const SizedBox(height: 20),
Text(
title,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.black87),
),
const SizedBox(height: 10),
Text(
subtitle,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 16,
color: Colors.black54,
),
),
const SizedBox(height: 30),
// Glassmorphism Container for the form with a whiter look
ClipRRect(
borderRadius: BorderRadius.circular(25.0),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
child: Container(
padding: const EdgeInsets.all(24.0),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.6),
borderRadius: BorderRadius.circular(25.0),
border: Border.all(
color: Colors.white.withOpacity(0.8),
width: 1.5,
),
),
child: form,
),
),
),
const SizedBox(height: 20),
// Button for app testers, adapted to the light theme
Material(
color: Colors.black.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
child: InkWell(
onTap: () =>
_showTesterLoginDialog(context, loginController),
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 16),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.admin_panel_settings_outlined,
color: Colors.black.withOpacity(0.6)),
const SizedBox(width: 8),
Text(
'For App Reviewers / Testers',
style: TextStyle(
color: Colors.black.withOpacity(0.6),
fontWeight: FontWeight.w600,
),
), ),
], ],
), ),
child: Form( ),
key: controller.formKey, ),
child: Column( ),
crossAxisAlignment: CrossAxisAlignment.stretch, ],
children: [ ),
// Title ),
Text(
'Please login to continue',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
// Email Field
TextFormField(
keyboardType: TextInputType.emailAddress,
controller: controller.emailController,
decoration: InputDecoration(
labelText: 'Email'.tr,
hintText: 'Enter your email address'.tr,
prefixIcon:
const Icon(Icons.email_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
validator: (value) => value == null ||
value.isEmpty ||
!value.contains('@') ||
!value.contains('.')
? 'Enter a valid email'.tr
: null,
),
const SizedBox(height: 20),
// Password Field
TextFormField(
obscureText: true,
controller: controller.passwordController,
decoration: InputDecoration(
labelText: 'Password'.tr,
hintText: 'Enter your password'.tr,
prefixIcon: const Icon(Icons.lock_outline),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
validator: (value) =>
value == null || value.isEmpty
? 'Enter your password'.tr
: null,
),
const SizedBox(height: 24),
// Submit Button
GetBuilder<LoginDriverController>(
builder: (controller) => controller.isloading
? const Center(
child: CircularProgressIndicator())
: SizedBox(
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
Colors.blueAccent,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(12),
),
),
onPressed: () {
if (controller
.formKey.currentState!
.validate()) {
controller.logintest(
controller
.passwordController.text
.trim(),
controller
.emailController.text
.trim(),
);
}
},
child: Text(
'Login'.tr,
style:
const TextStyle(fontSize: 16),
),
),
),
),
const SizedBox(height: 16),
// Optional: Forgot Password / Create Account
],
),
),
)
: Card(
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: form,
),
);
})
],
), ),
), ],
), ),
), ),
); );
@@ -233,75 +332,66 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// TextFormField( Text(
// controller: _phoneController, 'Enter your phone number'.tr,
// decoration: InputDecoration( style: TextStyle(color: Colors.black87, fontSize: 16),
// labelText: 'phone number label'.tr, textAlign: TextAlign.center,
// prefixIcon: ),
// Icon(Icons.phone_android, color: Colors.teal.shade400), const SizedBox(height: 20),
// border: IntlPhoneField(
// OutlineInputBorder(borderRadius: BorderRadius.circular(12)), showCountryFlag: false,
// ), searchText: 'Search country'.tr,
// keyboardType: TextInputType.phone, languageCode: 'ar',
// validator: (v) => v!.isEmpty ? 'phone number required'.tr : null, style: const TextStyle(color: Colors.black),
// ), dropdownTextStyle: const TextStyle(color: Colors.black87),
Padding( decoration: InputDecoration(
padding: const EdgeInsets.all(16.0), labelText: 'Phone Number'.tr,
child: IntlPhoneField( labelStyle: const TextStyle(color: Colors.black54),
// languageCode: 'ar', enabledBorder: OutlineInputBorder(
showCountryFlag: false, borderRadius: BorderRadius.circular(12),
flagsButtonMargin: EdgeInsets.only(right: 8), borderSide: BorderSide(color: Colors.black.withOpacity(0.1)),
flagsButtonPadding: EdgeInsets.all(4), ),
dropdownDecoration: focusedBorder: OutlineInputBorder(
BoxDecoration(borderRadius: BorderRadius.circular(8)), borderRadius: BorderRadius.circular(12),
// controller: _phoneController, borderSide:
decoration: InputDecoration( const BorderSide(color: AppColor.greenColor, width: 2),
labelText: 'Phone Number'.tr, ),
border: const OutlineInputBorder( ),
borderSide: BorderSide(), initialCountryCode: 'SY',
), onChanged: (phone) {
), _phoneController.text = phone.completeNumber;
initialCountryCode: 'SY', },
onChanged: (phone) { validator: (phone) {
// Properly concatenate country code and number if (phone == null || phone.number.isEmpty) {
_phoneController.text = phone.completeNumber.toString(); return 'Please enter your phone number';
Log.print(' phone.number: ${phone.number}'); }
print("Formatted phone number: ${_phoneController.text}"); if (phone.completeNumber.length < 10) {
}, return 'Phone number seems too short';
validator: (phone) { }
// Check if the phone number is not null and is valid return null;
if (phone == null || phone.completeNumber.isEmpty) { },
return 'Please enter your phone number';
}
// Extract the phone number (excluding the country code)
final number = phone.completeNumber.toString();
// Check if the number length is exactly 11 digits
if (number.length != 13) {
return 'Phone number must be exactly 11 digits long';
}
// If all validations pass, return null
return null;
},
),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
_isLoading _isLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator(color: AppColor.greenColor)
: ElevatedButton( : SizedBox(
onPressed: _submit, width: double.infinity,
style: ElevatedButton.styleFrom( child: ElevatedButton(
backgroundColor: Colors.teal, onPressed: _submit,
foregroundColor: Colors.white, style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: AppColor.greenColor,
shape: RoundedRectangleBorder( padding: const EdgeInsets.symmetric(vertical: 16),
borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(
minimumSize: const Size(double.infinity, 50), borderRadius: BorderRadius.circular(12)),
),
child: Text(
'send otp button'.tr,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white),
),
), ),
child: Text('send otp button'.tr),
), ),
], ],
), ),
@@ -319,61 +409,17 @@ class OtpVerificationScreen extends StatefulWidget {
class _OtpVerificationScreenState extends State<OtpVerificationScreen> { class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final TextEditingController _otpControllers = TextEditingController(); final _otpController = TextEditingController();
// final List<FocusNode> _focusNodes = List.generate(5, (_) => FocusNode());
bool _isLoading = false; bool _isLoading = false;
@override
void dispose() {
// for (var controller in _otpControllers) {
// controller.dispose();
// }
// for (var node in _focusNodes) {
// node.dispose();
// }
super.dispose();
}
void _submit() async { void _submit() async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
setState(() => _isLoading = true); setState(() => _isLoading = true);
final otp = _otpControllers.text; await PhoneAuthHelper.verifyOtp(widget.phoneNumber, _otpController.text);
await PhoneAuthHelper.verifyOtp(widget.phoneNumber, otp);
if (mounted) setState(() => _isLoading = false); if (mounted) setState(() => _isLoading = false);
} }
} }
Widget _buildOtpInput() {
return Form(
key: _formKey,
child: Container(
width: 200,
height: 50,
decoration: BoxDecoration(
color: Colors.grey.shade200,
shape: BoxShape.circle,
),
child: Center(
child: TextFormField(
controller: _otpControllers,
// focusNode: _focusNodes[index],
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
maxLength: 5,
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
decoration: const InputDecoration(
counterText: "",
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
onChanged: (value) {},
validator: (v) => v!.isEmpty ? '' : null,
),
),
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AuthScreen( return AuthScreen(
@@ -383,20 +429,59 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
form: Column( form: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
_buildOtpInput(), const Text(
'Enter the 5-digit code',
style: TextStyle(color: Colors.black87, fontSize: 16),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
Form(
key: _formKey,
child: TextFormField(
controller: _otpController,
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
maxLength: 5,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.black87,
letterSpacing: 18,
),
decoration: InputDecoration(
counterText: "",
hintText: '-----',
hintStyle: TextStyle(
color: Colors.black.withOpacity(0.2),
letterSpacing: 18,
fontSize: 28),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 10),
),
validator: (v) => v == null || v.length < 5 ? '' : null,
),
),
const SizedBox(height: 30), const SizedBox(height: 30),
_isLoading _isLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator(color: AppColor.greenColor)
: ElevatedButton( : SizedBox(
onPressed: _submit, width: double.infinity,
style: ElevatedButton.styleFrom( child: ElevatedButton(
backgroundColor: Colors.teal, onPressed: _submit,
foregroundColor: Colors.white, style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 50), backgroundColor: AppColor.greenColor,
shape: RoundedRectangleBorder( padding: const EdgeInsets.symmetric(vertical: 16),
borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: Text(
'verify and continue button'.tr,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white),
),
), ),
child: Text('verify and continue button'.tr),
), ),
], ],
), ),
@@ -431,6 +516,32 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
} }
} }
Widget _buildTextFormField({
required TextEditingController controller,
required String label,
TextInputType keyboardType = TextInputType.text,
String? Function(String?)? validator,
}) {
return TextFormField(
controller: controller,
style: const TextStyle(color: Colors.black87),
decoration: InputDecoration(
labelText: label,
labelStyle: const TextStyle(color: Colors.black54),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.black.withOpacity(0.1)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColor.greenColor, width: 2),
),
),
keyboardType: keyboardType,
validator: validator,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AuthScreen( return AuthScreen(
@@ -441,45 +552,44 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
TextFormField( _buildTextFormField(
controller: _firstNameController, controller: _firstNameController,
decoration: InputDecoration( label: 'first name label'.tr,
labelText: 'first name label'.tr,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)))),
validator: (v) => v!.isEmpty ? 'first name required'.tr : null, validator: (v) => v!.isEmpty ? 'first name required'.tr : null,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextFormField( _buildTextFormField(
controller: _lastNameController, controller: _lastNameController,
decoration: InputDecoration( label: 'last name label'.tr,
labelText: 'last name label'.tr,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)))),
validator: (v) => v!.isEmpty ? 'last name required'.tr : null, validator: (v) => v!.isEmpty ? 'last name required'.tr : null,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextFormField( _buildTextFormField(
controller: _emailController, controller: _emailController,
decoration: InputDecoration( label: 'email optional label'.tr,
labelText: 'email optional label'.tr,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)))),
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
_isLoading _isLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator(color: AppColor.greenColor)
: ElevatedButton( : SizedBox(
onPressed: _submit, width: double.infinity,
style: ElevatedButton.styleFrom( child: ElevatedButton(
backgroundColor: Colors.teal, onPressed: _submit,
foregroundColor: Colors.white, style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 50), backgroundColor: AppColor.greenColor,
shape: RoundedRectangleBorder( padding: const EdgeInsets.symmetric(vertical: 16),
borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: Text(
'complete registration button'.tr,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white),
),
), ),
child: Text('complete registration button'.tr),
), ),
], ],
), ),

View File

@@ -1,17 +1,12 @@
import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
import 'package:sefer_driver/views/notification/available_rides_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart'; import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:sefer_driver/views/home/Captin/home_captain/drawer_captain.dart'; import 'package:sefer_driver/views/home/Captin/home_captain/drawer_captain.dart';
import 'package:sefer_driver/views/widgets/mycircular.dart'; import 'package:sefer_driver/views/widgets/mycircular.dart';
import 'package:bubble_head/bubble.dart';
import '../../../../constant/colors.dart'; import '../../../../constant/colors.dart';
import '../../../../constant/info.dart'; import '../../../../constant/info.dart';
@@ -20,12 +15,9 @@ import '../../../../controller/functions/location_controller.dart';
import '../../../../controller/functions/overlay_permisssion.dart'; import '../../../../controller/functions/overlay_permisssion.dart';
import '../../../../controller/functions/package_info.dart'; import '../../../../controller/functions/package_info.dart';
import '../../../../controller/home/captin/home_captain_controller.dart'; import '../../../../controller/home/captin/home_captain_controller.dart';
import '../../../../print.dart';
import '../../../widgets/circle_container.dart'; import '../../../widgets/circle_container.dart';
import '../driver_map_page.dart';
import 'widget/connect.dart'; import 'widget/connect.dart';
import 'widget/left_menu_map_captain.dart'; import 'widget/left_menu_map_captain.dart';
import '../../../../main.dart';
// ================================================================== // ==================================================================
// Redesigned Main Widget (V3) // Redesigned Main Widget (V3)

View File

@@ -20,6 +20,7 @@ import '../../../../../controller/functions/encrypt_decrypt.dart';
import '../../../../../controller/home/captin/order_request_controller.dart'; import '../../../../../controller/home/captin/order_request_controller.dart';
import '../../../../../controller/home/navigation/navigation_view.dart'; import '../../../../../controller/home/navigation/navigation_view.dart';
import '../../../../Rate/ride_calculate_driver.dart'; import '../../../../Rate/ride_calculate_driver.dart';
import '../../../../auth/captin/otp_page.dart';
import '../../../../auth/syria/registration_view.dart'; import '../../../../auth/syria/registration_view.dart';
import '../../../../widgets/error_snakbar.dart'; import '../../../../widgets/error_snakbar.dart';
@@ -185,8 +186,7 @@ GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
// child: Builder(builder: (context) { // child: Builder(builder: (context) {
// return IconButton( // return IconButton(
// onPressed: () async { // onPressed: () async {
// var finger = await storage.read(key: BoxName.fingerPrint); // Get.to(PhoneNumberScreen());
//
// }, // },
// icon: const Icon( // icon: const Icon(
// FontAwesome5.grin_tears, // FontAwesome5.grin_tears,
@@ -217,7 +217,7 @@ Future<void> checkForPendingOrderFromServer() async {
try { try {
// You need to create this CRUD method // You need to create this CRUD method
var response = await CRUD().post( var response = await CRUD().get(
link: AppLink.getArgumentAfterAppliedFromBackground, link: AppLink.getArgumentAfterAppliedFromBackground,
payload: {'driver_id': driverId}, payload: {'driver_id': driverId},
); );

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 868 B

After

Width:  |  Height:  |  Size: 881 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB