25-10-2/1
4
.env
@@ -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
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 15 KiB |
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ 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
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 461 KiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 76 KiB |
@@ -1,22 +1,31 @@
|
|||||||
List<String> driverMessages = [
|
List<String> syrianDriverMessages = [
|
||||||
"💸 فرص الربح: افتح تطبيق انطلق الآن وزد دخلك اليوم! المزيد من الطلبات بانتظارك! 🚗",
|
// --- رسائل وقت الذروة والطلبات المستعجلة ---
|
||||||
"🚀 طلبات جديدة: لا تضيع الفرصة! المزيد من الركاب ينتظرونك الآن على تطبيق انطلق. 🏃♂️",
|
"البلد ولعانة هلأ! 🌃 هاد وقت الذروة، فرصة ذهبية لتدبّل أرباحك. انطلق معنا!",
|
||||||
"📈 زيادة الدخل: حقق أعلى أرباح اليوم مع انطلق! افتح التطبيق وابدأ عملك الآن. 💵",
|
"انتبه: الطلبات كتيرة بمنطقتك! ⚠️ خليك أول واحد بياخد مشوار واكسب أكتر. جاهز تطلع؟",
|
||||||
"🕒 أوقات الذروة: استعد لكسب المزيد خلال فترات الطلب المرتفعة. افتح التطبيق الآن! 📲",
|
"لا تروح عليك ساعة الذروة! ⏳ زباين أكتر يعني مصاري أكتر. فوت عالبرنامج هلأ!",
|
||||||
"🚗 طلبات جديدة: كن مستعداً، افتح تطبيق انطلق الآن واستقبل المزيد من الطلبات. 🔔",
|
"خلص الدوام عند كتير عالم، وصار وقت يرجعوا عالبيت! 🌇 خليك بطلن ووصلن بأمان.",
|
||||||
"🎉 فرص النجاح: ابدأ رحلتك إلى النجاح! افتح تطبيق انطلق لزيادة دخلك اليوم. 💼",
|
|
||||||
"🌍 طلبات مرتفعة: المزيد من الركاب بانتظارك، لا تفوّت الفرص، افتح التطبيق الآن! 🚖",
|
// --- رسائل للتركيز عالربح والمصاري ---
|
||||||
"💪 زيادة الدخل: انطلق نحو تحقيق أهدافك المالية، افتح تطبيق انطلق واكسب المزيد. 🏆",
|
"هدف اليوم صار قريب! 💪 كل مشوار بيقربك أكتر. بلّش مشوارك الجاي هلأ.",
|
||||||
"💰 أرباح إضافية: افتح التطبيق واستعد لتحقيق أرباح إضافية مع انطلق! المزيد من الطلبات في انتظارك. 🛣️",
|
"جزدانك ناطر مصاري زيادة! 💰 فوت عالبرنامج وحوّل وقتك لمصاري عنجد ومضمونة.",
|
||||||
"🔥 فرص جديدة: تطبيق انطلق مزدحم الآن! افتح التطبيق وزد أرباحك بفرص جديدة. 🚗",
|
"كبسة زر بتجيبلك ربح زيادة. جاهز للطلب الجاي؟ 🤔",
|
||||||
"🚨 طلبات متزايدة: افتح تطبيق انطلق الآن! الطلب مرتفع وفرص الربح كبيرة! 💸",
|
"لا تترك المصاري عالأرض! 💸 زباين انطلق عم يدوروا على كابتن شاطر متلك هلأ.",
|
||||||
"💼 زيادة الدخل: هل أنت جاهز لتحقيق المزيد من الدخل؟ افتح التطبيق وانطلق الآن! 🚖",
|
|
||||||
"🚗 أوقات الذروة: احجز مقعدك في فترات الطلب العالي، افتح تطبيق انطلق الآن واكسب المزيد. 📈",
|
// --- رسائل تقدير وشكر ---
|
||||||
"📲 بدء اليوم: ابدأ يومك مع انطلق، وافتح التطبيق الآن لتزيد من فرص الربح. 💵",
|
"أنت مو بس شوفير، أنت شريكنا بالنجاح. 🙏 زباينّا ناطرينك. يسلم إيديك!",
|
||||||
"💸 فرص مستمرة: لا تفوت فرص الربح، افتح تطبيق انطلق الآن وكن على استعداد للمزيد! 🔔",
|
"نحنا عم نكبر فيك! 🌟 خليك فاتح واستقبل مشاوير جديدة من زباينّا الكويسين.",
|
||||||
"📆 زيادة الطلب: انطلق اليوم واستفد من الطلبات المتزايدة على تطبيق انطلق! افتح التطبيق الآن. 🚗",
|
"كباتنّا هنن الأحسن! 👍 ضل عم تقدّم هالخدمة الحلوة اللي بيعرفوها زباينا عن تطبيق انطلق.",
|
||||||
"💥 دخل إضافي: افتح تطبيق انطلق الآن واستقبل طلبات جديدة تحقق لك المزيد من الدخل. 💰",
|
"كل توصيلة ناجحة هي قصة نجاح لإلك وإلنا. خلينا نعمل قصص نجاح أكتر اليوم! 🗺️",
|
||||||
"🏆 فرص مرتفعة: استفد من طلبات اليوم المرتفعة، افتح التطبيق الآن مع انطلق. 📲",
|
|
||||||
"🚗 تفضيل العملاء: كن السائق الذي يختاره الجميع! افتح تطبيق انطلق اليوم واربح المزيد. 🔥",
|
// --- رسائل تحفيز وتشجيع ---
|
||||||
"💸 دخل إضافي: فرص الدخل الإضافي في انتظارك! افتح تطبيق انطلق واستمتع بالطلبات المتزايدة. 💼",
|
"يوم جديد، فرصة جديدة للنجاح! ☀️ فوت عالبرنامج وخلينا ننطلق سوا لأهدافك.",
|
||||||
|
"طريق النجاح ببلّش بمشوار. 🏁 خود مشوارك الجاي واعمل إنجازات أكتر.",
|
||||||
|
"فيك تعمل دخل كتير منيح اليوم. نحنا واثقين فيك! انطلق هلأ. 💼",
|
||||||
|
"الطلب الجاي يمكن يكون الأحسن! لا تضيّع فرصتك. فوت عالبرنامج وخليك جاهز. 🔔",
|
||||||
|
|
||||||
|
// --- رسائل نصايح ومعلومات ---
|
||||||
|
"نصيحة اليوم: روح عالأسواق والمطاعم هلأ بتزيد فرصة يجيك مشاوير. 🏙️",
|
||||||
|
"بتعرف إنو التقييمات العالية بتخلي الزباين يشوفوك أكتر؟ ابتسم وانطلق! 😊",
|
||||||
|
"يمكن تشتّي بعد شوي! 🌧️ الطلب بيزيد بهيك جو، فرصة ممتازة تزود مصاريك.",
|
||||||
|
"في حفلة كبيرة بقلب البلد اليوم. 🎆 جهّز حالك للمشاوير الكتيرة بهديك المنطقة."
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
@@ -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 =
|
||||||
|
|||||||
@@ -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') {
|
||||||
|
|||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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('خطأ', 'لم يتم العثور على مسار.');
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
111
lib/main.dart
@@ -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(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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},
|
||||||
);
|
);
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 230 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 361 B After Width: | Height: | Size: 367 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 868 B After Width: | Height: | Size: 881 B |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.5 KiB |
BIN
web/favicon.png
|
Before Width: | Height: | Size: 361 B After Width: | Height: | Size: 367 B |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |