diff --git a/.env b/.env index 00e0d01..60ffc61 100644 --- a/.env +++ b/.env @@ -5,6 +5,7 @@ accountSIDTwillo=QFx0qy456juj3839xuy2194q629q1fj0y7XrXlBl serverAPI=QQQQobSrrFi:QVQ87xU7zwCvmZzZdaxuS2f23Y4mz7MzyOzr8od2br6KYyeFaTVLG3K3hx5ZaUyx7eYvAYpAVdKk-286NTRi3zs9iSOnXtXRIxswg3KecBmsl3VxJ9wO-vIpwu4Pv7dkHkXniuxMSDgWXrXlBl mapAPIKEY=QOmqZsFsutLDCtZCRIUAZAkB5v6AMkKEPMbJGa3XrXlBl twilloRecoveryCode=CAU79DHPH1BE9PUH4ETXTSXZXrXlBl +apiKeyHere=g_WNUb5L-7-F8oHpUmgIzH7ETeH9xZ8RwGG9_G8zX9A authTokenTwillo=70u98ju0214xx4q0u74028u021u4qu65XrXlBl chatGPTkey=zg-4C26q4SYBKQeHZDqkWowC9XrxgUEfUy9JRw2rm6Q2adb3kjwXrXlBl transactionCloude=Qhcwilomqcoib:QVO_JNYED2XWA26YXKC2TP:YK1DVH6SJB31N3PE1UXrXlBl @@ -18,9 +19,9 @@ serverPHP=https://api.sefer.live/sefer seferAlexandriaServer=https://seferalexandria.site/sefer seferPaymentServer=https://seferpw.shop/sefer seferCairoServer=https://sefer.click/sefer -seferGizaServer=https://sefergiza.site/sefer +seferGizaServer=https://gizasefer.online/sefer whatappID=369939736211879 -whatsapp=EEAAOtbZBSUK74BO93qYEsBTetiT5qGCHCdxDRXEH1cGUrlbHN2ZB4bVrFCR1ZC8xEVDtHeLUH6yHej2RPMpmoLD69AlqBJJDtQ7nrsmbeIRZCBGPoKueLQUaEMWPC2R6EJdZArqdFy1rv4ZAGJZBV9ifxvwwTAlw7dbzA3WEZBvWKPXswUkPP9UWM1fWEPL86buyH0IvEd6j9grk6l7rG6CAZD +whatsapp=EAAOtbZBSUK74BO6yE1QwIBsRCjPDANdum66xap0ZA7OZA8LqEu8MZAts1kwr12eRiNXtvpJ2ZAFSY5dw3KVSyrUuH8boLjynxdFI4Gh1Q7BCHx275X2uZBwKWZCSrsVN17i6mZAFNYYd25sQv0ZBomeTk02ZCIJot4UqWxK9ZBvxsq1k2yS7lD2NsjZB5EHbpaYGLzxFJ2FCCSX6iHyKXab6ckfK7m19wo77in7Dl3YZD cohere=Aulwd8y5SPWos0hJhG0toUf8gOhUUrpf5Q2TPmVGXrXlBl claudeAiAPI=zg-qbc-qvo39-xWOxIGwWTOzCFBnIYSKKhfyz_KVAvrH-6_4ZEJL68G_QBH26oeTOMMoQug9KuOjjKSP_A4S3SUDlbxR9duVzoQ-MkX_UQQQXrXlBl payPalClientId=QALymfNI5Tzt4s-ysoz6vD4_nqX0SUtkC_qYV-Ugk5gaM_8Z-kg4L53k8Uux_4jEWXDkNpXGSWPpIzDFXrXlBl diff --git a/Archive.zip b/Archive.zip new file mode 100644 index 0000000..e9eafde Binary files /dev/null and b/Archive.zip differ diff --git a/android/app/build.gradle b/android/app/build.gradle index e3cd72a..f30629a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,18 +26,24 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' // apply plugin: 'com.google.gms.google-services' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') - +if (keystorePropertiesFile.exists()) { + keystorePropertiesFile.withReader('UTF-8') { reader -> + keystoreProperties.load(reader) + } +} android { namespace "com.mobileapp.store.ride" - compileSdkVersion 34 - ndkVersion flutter.ndkVersion + compileSdk 35 + ndkVersion "26.1.10909125" compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 + coreLibraryDesugaringEnabled true } kotlinOptions { @@ -49,33 +55,45 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.mobileapp.store.ride" + // Specify your unique Application ID + applicationId = "com.mobileapp.store.ride" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. +<<<<<<< HEAD minSdkVersion 23 targetSdkVersion 34 versionCode 65 versionName '1.5.65' multiDexEnabled =true // manifestPlaceholders = [mapsApiKey: 'android/app/src/main/AndroidManifest.xml'] +======= + minSdk = 23 + targetSdk = flutter.targetSdkVersion + versionCode = 108 + versionName = '1.6.108' + multiDexEnabled =true + + // manifestPlaceholders can be specified here if needed +>>>>>>> 8813b4d } - signingConfigs { - release { - keyAlias keystoreProperties['keyAlias'] - keyPassword keystoreProperties['keyPassword'] - storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null - storePassword keystoreProperties['storePassword'] - } - } - buildTypes { - release { - signingConfig signingConfigs.release - } - } + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } - + buildTypes { + release { + signingConfig signingConfigs.release +// minifyEnabled true +// shrinkResources true +// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } } flutter { @@ -84,6 +102,14 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2' + // Optional dependencies like Firebase can be uncommented if needed // implementation platform('com.google.firebase:firebase-bom:32.1.1') - -} + implementation "com.stripe:stripe-android:20.47.0" + implementation 'com.stripe:paymentsheet:20.47.0' + + + // If push provisioning is needed, make sure you have the correct version: +// implementation "com.stripe:stripe-android-pushprovisioning:VERSION" // Replace VERSION with the correct one. + +} \ No newline at end of file diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..ef62132 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,30 @@ +# Suppress warnings for specific Google ML Kit and Stripe classes +-dontwarn com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions$Builder +-dontwarn com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions +-dontwarn com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions$Builder +-dontwarn com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions +-dontwarn com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions$Builder +-dontwarn com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions +-dontwarn com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions$Builder +-dontwarn com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions +-dontwarn com.stripe.android.pushProvisioning.PushProvisioningActivity$g +-dontwarn com.stripe.android.pushProvisioning.PushProvisioningActivityStarter$Args +-dontwarn com.stripe.android.pushProvisioning.PushProvisioningActivityStarter$Error +-dontwarn com.stripe.android.pushProvisioning.PushProvisioningActivityStarter +-dontwarn com.stripe.android.pushProvisioning.PushProvisioningEphemeralKeyProvider + +# Keep rules for Google ML Kit +-keep class com.google.mlkit.vision.** { *; } +-keep class com.google.mlkit.vision.text.** { *; } +-keep class com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions { *; } +-keep class com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions$Builder { *; } +-keep class com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions { *; } +-keep class com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions$Builder { *; } +-keep class com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions { *; } +-keep class com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions$Builder { *; } +-keep class com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions { *; } +-keep class com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions$Builder { *; } + +# Keep rules for Stripe +-keep class com.stripe.android.** { *; } +-keep class com.stripe.android.pushProvisioning.** { *; } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index fedcf91..cbffd52 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -10,6 +10,12 @@ + + + + + + @@ -25,6 +31,7 @@ android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" + android:enableOnBackInvokedCallback="true" android:windowSoftInputMode="adjustResize"> + + + + + + + + + My App - default_channel + + high_importance_channel AIzaSyCyfwRXTwSTLOFQSQgN5p7QZgGJVZnEKq0 \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 6f00cdb..eccf533 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -12,7 +12,7 @@ buildscript { // classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.google.gms:google-services:4.3.15' // END: FlutterFire Configuration - classpath 'com.android.tools.build:gradle:7.3.0' + classpath 'com.android.tools.build:gradle:8.4.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/android/gradle.properties b/android/gradle.properties index 4de64f2..91ed4fe 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,6 @@ org.gradle.jvmargs=-Xmx4096M android.useAndroidX=true android.enableJetifier=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 2443e72..684137a 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -4,4 +4,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip diff --git a/assets/images/car3.png b/assets/images/car3.png new file mode 100644 index 0000000..41627ae Binary files /dev/null and b/assets/images/car3.png differ diff --git a/assets/images/moto.png b/assets/images/moto.png index 0cb9559..8257812 100644 Binary files a/assets/images/moto.png and b/assets/images/moto.png differ diff --git a/assets/images/pinkBike.png b/assets/images/pinkBike.png new file mode 100644 index 0000000..0ed4ec5 Binary files /dev/null and b/assets/images/pinkBike.png differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2de2cf2..7975ed8 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -9,54 +9,61 @@ PODS: - Flutter - device_info_plus (0.0.1): - Flutter - - Firebase/Auth (10.28.0): + - Firebase/Auth (11.0.0): - Firebase/CoreOnly - - FirebaseAuth (~> 10.28.0) - - Firebase/CoreOnly (10.28.0): - - FirebaseCore (= 10.28.0) - - Firebase/Messaging (10.28.0): + - FirebaseAuth (~> 11.0.0) + - Firebase/CoreOnly (11.0.0): + - FirebaseCore (= 11.0.0) + - Firebase/Messaging (11.0.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 10.28.0) - - firebase_auth (5.1.2): - - Firebase/Auth (= 10.28.0) + - FirebaseMessaging (~> 11.0.0) + - firebase_auth (5.2.0): + - Firebase/Auth (= 11.0.0) - firebase_core - Flutter - - firebase_core (3.2.0): - - Firebase/CoreOnly (= 10.28.0) + - firebase_core (3.4.0): + - Firebase/CoreOnly (= 11.0.0) - Flutter - - firebase_messaging (15.0.3): - - Firebase/Messaging (= 10.28.0) + - firebase_messaging (15.1.0): + - Firebase/Messaging (= 11.0.0) - firebase_core - Flutter - - FirebaseAppCheckInterop (10.29.0) - - FirebaseAuth (10.28.0): - - FirebaseAppCheckInterop (~> 10.17) - - FirebaseCore (~> 10.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.8) - - GoogleUtilities/Environment (~> 7.8) - - GTMSessionFetcher/Core (< 4.0, >= 2.1) + - FirebaseAppCheckInterop (11.5.0) + - FirebaseAuth (11.0.0): + - FirebaseAppCheckInterop (~> 11.0) + - FirebaseAuthInterop (~> 11.0) + - FirebaseCore (~> 11.0) + - FirebaseCoreExtension (~> 11.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.0) + - GoogleUtilities/Environment (~> 8.0) + - GTMSessionFetcher/Core (~> 3.4) - RecaptchaInterop (~> 100.0) - - FirebaseCore (10.28.0): - - FirebaseCoreInternal (~> 10.0) - - GoogleUtilities/Environment (~> 7.12) - - GoogleUtilities/Logger (~> 7.12) - - FirebaseCoreInternal (10.29.0): - - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseInstallations (10.29.0): - - FirebaseCore (~> 10.0) - - GoogleUtilities/Environment (~> 7.8) - - GoogleUtilities/UserDefaults (~> 7.8) - - PromisesObjC (~> 2.1) - - FirebaseMessaging (10.28.0): - - FirebaseCore (~> 10.0) - - FirebaseInstallations (~> 10.0) - - GoogleDataTransport (~> 9.3) - - GoogleUtilities/AppDelegateSwizzler (~> 7.8) - - GoogleUtilities/Environment (~> 7.8) - - GoogleUtilities/Reachability (~> 7.8) - - GoogleUtilities/UserDefaults (~> 7.8) - - nanopb (< 2.30911.0, >= 2.30908.0) + - FirebaseAuthInterop (11.5.0) + - FirebaseCore (11.0.0): + - FirebaseCoreInternal (~> 11.0) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/Logger (~> 8.0) + - FirebaseCoreExtension (11.4.1): + - FirebaseCore (~> 11.0) + - FirebaseCoreInternal (11.5.0): + - "GoogleUtilities/NSData+zlib (~> 8.0)" + - FirebaseInstallations (11.4.0): + - FirebaseCore (~> 11.0) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/UserDefaults (~> 8.0) + - PromisesObjC (~> 2.4) + - FirebaseMessaging (11.0.0): + - FirebaseCore (~> 11.0) + - FirebaseInstallations (~> 11.0) + - GoogleDataTransport (~> 10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.0) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/Reachability (~> 8.0) + - GoogleUtilities/UserDefaults (~> 8.0) + - nanopb (~> 3.30910.0) - Flutter (1.0.0) + - flutter_contacts (0.0.1): + - Flutter - flutter_local_notifications (0.0.1): - Flutter - flutter_secure_storage (6.0.0): @@ -65,8 +72,11 @@ PODS: - Flutter - geolocator_apple (1.2.0): - Flutter + - Google-Maps-iOS-Utils (5.0.0): + - GoogleMaps (~> 8.0) - google_maps_flutter_ios (0.0.1): - Flutter + - Google-Maps-iOS-Utils (< 7.0, >= 5.0) - GoogleMaps (< 10.0, >= 8.4) - google_sign_in_ios (0.0.1): - AppAuth (>= 1.7.4) @@ -74,10 +84,9 @@ PODS: - FlutterMacOS - GoogleSignIn (~> 7.1) - GTMSessionFetcher (>= 3.4.0) - - GoogleDataTransport (9.4.1): - - GoogleUtilities/Environment (~> 7.7) - - nanopb (< 2.30911.0, >= 2.30908.0) - - PromisesObjC (< 3.0, >= 1.2) + - GoogleDataTransport (10.1.0): + - nanopb (~> 3.30910.0) + - PromisesObjC (~> 2.4) - GoogleMaps (8.4.0): - GoogleMaps/Maps (= 8.4.0) - GoogleMaps/Base (8.4.0) @@ -87,29 +96,28 @@ PODS: - AppAuth (< 2.0, >= 1.7.3) - GTMAppAuth (< 5.0, >= 4.1.1) - GTMSessionFetcher/Core (~> 3.3) - - GoogleUtilities/AppDelegateSwizzler (7.13.3): + - GoogleUtilities/AppDelegateSwizzler (8.0.2): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - GoogleUtilities/Privacy - - GoogleUtilities/Environment (7.13.3): + - GoogleUtilities/Environment (8.0.2): - GoogleUtilities/Privacy - - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.13.3): + - GoogleUtilities/Logger (8.0.2): - GoogleUtilities/Environment - GoogleUtilities/Privacy - - GoogleUtilities/Network (7.13.3): + - GoogleUtilities/Network (8.0.2): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Privacy - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.13.3)": + - "GoogleUtilities/NSData+zlib (8.0.2)": - GoogleUtilities/Privacy - - GoogleUtilities/Privacy (7.13.3) - - GoogleUtilities/Reachability (7.13.3): + - GoogleUtilities/Privacy (8.0.2) + - GoogleUtilities/Reachability (8.0.2): - GoogleUtilities/Logger - GoogleUtilities/Privacy - - GoogleUtilities/UserDefaults (7.13.3): + - GoogleUtilities/UserDefaults (8.0.2): - GoogleUtilities/Logger - GoogleUtilities/Privacy - GTMAppAuth (4.1.1): @@ -129,13 +137,14 @@ PODS: - Flutter - local_auth_darwin (0.0.1): - Flutter + - FlutterMacOS - location (0.0.1): - Flutter - - nanopb (2.30910.0): - - nanopb/decode (= 2.30910.0) - - nanopb/encode (= 2.30910.0) - - nanopb/decode (2.30910.0) - - nanopb/encode (2.30910.0) + - nanopb (3.30910.0): + - nanopb/decode (= 3.30910.0) + - nanopb/encode (= 3.30910.0) + - nanopb/decode (3.30910.0) + - nanopb/encode (3.30910.0) - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): @@ -155,42 +164,42 @@ PODS: - sqflite (0.0.3): - Flutter - FlutterMacOS - - Stripe (23.27.6): - - StripeApplePay (= 23.27.6) - - StripeCore (= 23.27.6) - - StripePayments (= 23.27.6) - - StripePaymentsUI (= 23.27.6) - - StripeUICore (= 23.27.6) + - Stripe (23.28.3): + - StripeApplePay (= 23.28.3) + - StripeCore (= 23.28.3) + - StripePayments (= 23.28.3) + - StripePaymentsUI (= 23.28.3) + - StripeUICore (= 23.28.3) - stripe_ios (0.0.1): - Flutter - - Stripe (~> 23.27.0) - - StripeApplePay (~> 23.27.0) - - StripeFinancialConnections (~> 23.27.0) - - StripePayments (~> 23.27.0) - - StripePaymentSheet (~> 23.27.0) - - StripePaymentsUI (~> 23.27.0) - - StripeApplePay (23.27.6): - - StripeCore (= 23.27.6) - - StripeCore (23.27.6) - - StripeFinancialConnections (23.27.6): - - StripeCore (= 23.27.6) - - StripeUICore (= 23.27.6) - - StripePayments (23.27.6): - - StripeCore (= 23.27.6) - - StripePayments/Stripe3DS2 (= 23.27.6) - - StripePayments/Stripe3DS2 (23.27.6): - - StripeCore (= 23.27.6) - - StripePaymentSheet (23.27.6): - - StripeApplePay (= 23.27.6) - - StripeCore (= 23.27.6) - - StripePayments (= 23.27.6) - - StripePaymentsUI (= 23.27.6) - - StripePaymentsUI (23.27.6): - - StripeCore (= 23.27.6) - - StripePayments (= 23.27.6) - - StripeUICore (= 23.27.6) - - StripeUICore (23.27.6): - - StripeCore (= 23.27.6) + - Stripe (~> 23.28.0) + - StripeApplePay (~> 23.28.0) + - StripeFinancialConnections (~> 23.28.0) + - StripePayments (~> 23.28.0) + - StripePaymentSheet (~> 23.28.0) + - StripePaymentsUI (~> 23.28.0) + - StripeApplePay (23.28.3): + - StripeCore (= 23.28.3) + - StripeCore (23.28.3) + - StripeFinancialConnections (23.28.3): + - StripeCore (= 23.28.3) + - StripeUICore (= 23.28.3) + - StripePayments (23.28.3): + - StripeCore (= 23.28.3) + - StripePayments/Stripe3DS2 (= 23.28.3) + - StripePayments/Stripe3DS2 (23.28.3): + - StripeCore (= 23.28.3) + - StripePaymentSheet (23.28.3): + - StripeApplePay (= 23.28.3) + - StripeCore (= 23.28.3) + - StripePayments (= 23.28.3) + - StripePaymentsUI (= 23.28.3) + - StripePaymentsUI (23.28.3): + - StripeCore (= 23.28.3) + - StripePayments (= 23.28.3) + - StripeUICore (= 23.28.3) + - StripeUICore (23.28.3): + - StripeCore (= 23.28.3) - TOCropViewController (2.7.4) - uni_links (0.0.1): - Flutter @@ -205,6 +214,7 @@ PODS: - Flutter - webview_flutter_wkwebview (0.0.1): - Flutter + - FlutterMacOS DEPENDENCIES: - audio_session (from `.symlinks/plugins/audio_session/ios`) @@ -213,6 +223,7 @@ DEPENDENCIES: - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - Flutter (from `Flutter`) + - flutter_contacts (from `.symlinks/plugins/flutter_contacts/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_tts (from `.symlinks/plugins/flutter_tts/ios`) @@ -237,7 +248,7 @@ DEPENDENCIES: - vibration (from `.symlinks/plugins/vibration/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) SPEC REPOS: trunk: @@ -245,10 +256,13 @@ SPEC REPOS: - Firebase - FirebaseAppCheckInterop - FirebaseAuth + - FirebaseAuthInterop - FirebaseCore + - FirebaseCoreExtension - FirebaseCoreInternal - FirebaseInstallations - FirebaseMessaging + - Google-Maps-iOS-Utils - GoogleDataTransport - GoogleMaps - GoogleSignIn @@ -281,6 +295,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_messaging/ios" Flutter: :path: Flutter + flutter_contacts: + :path: ".symlinks/plugins/flutter_contacts/ios" flutter_local_notifications: :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_secure_storage: @@ -330,41 +346,45 @@ EXTERNAL SOURCES: wakelock_plus: :path: ".symlinks/plugins/wakelock_plus/ios" webview_flutter_wkwebview: - :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" + :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa audio_session: 088d2483ebd1dc43f51d253d4a1c517d9a2e7207 device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d - Firebase: 5121c624121af81cbc81df3bda414b3c28c4f3c3 - firebase_auth: e778ee89483b86fe4200d1f8e9a1c52aa5fb64a8 - firebase_core: a9d0180d5285527884d07a41eb4a9ec9ed12cdb6 - firebase_messaging: ccc82a143a74de75f440a4e413dbbb37ec3fddbc - FirebaseAppCheckInterop: 6a1757cfd4067d8e00fccd14fcc1b8fd78cfac07 - FirebaseAuth: 3d872fbbfc4223edeb72769e488f325fa8b0a4a9 - FirebaseCore: 857dc1c6dd1255675047404d8466f7dfaac5d779 - FirebaseCoreInternal: df84dd300b561c27d5571684f389bf60b0a5c934 - FirebaseInstallations: 913cf60d0400ebd5d6b63a28b290372ab44590dd - FirebaseMessaging: 087a7c7cadef7b9239f005bc4db823894844f323 + Firebase: 9f574c08c2396885b5e7e100ed4293d956218af9 + firebase_auth: 16ac5db3d064db837ecd845080d7e18e4be7c66d + firebase_core: ceec591a66629daaee82d3321551692c4a871493 + firebase_messaging: 15d8b557010f3bb7b98d0302e1c7c8fbcd244425 + FirebaseAppCheckInterop: d265d9f4484e7ec1c591086408840fdd383d1213 + FirebaseAuth: d5cf28be74d7e82257f6a3f717509eff70d3cf4a + FirebaseAuthInterop: 1219bee9b23e6ebe84c256a0d95adab53d11c331 + FirebaseCore: 3cf438f431f18c12cdf2aaf64434648b63f7e383 + FirebaseCoreExtension: f1bc67a4702931a7caa097d8e4ac0a1b0d16720e + FirebaseCoreInternal: f47dd28ae7782e6a4738aad3106071a8fe0af604 + FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414 + FirebaseMessaging: d2d1d9c62c46dd2db49a952f7deb5b16ad2c9742 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_contacts: edb1c5ce76aa433e20e6cb14c615f4c0b66e0983 flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 flutter_tts: 0f492aab6accf87059b72354fcb4ba934304771d geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450 - google_maps_flutter_ios: 5bc2be60ad012e79b182ce0fb0ef5030a50fb03e + Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321 + google_maps_flutter_ios: e31555a04d1986ab130f2b9f24b6cdc861acc6d3 google_sign_in_ios: 07375bfbf2620bc93a602c0e27160d6afc6ead38 - GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a + GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d GoogleSignIn: d4281ab6cf21542b1cfaff85c191f230b399d2db - GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 + GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 image_cropper: 37d40f62177c101ff4c164906d259ea2c3aa70cf image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa - local_auth_darwin: 4d56c90c2683319835a61274b57620df9c4520ab + local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3 location: d5cf8598915965547c3f36761ae9cc4f4e87d22e - nanopb: 438bc412db1928dac798aa6fd75726007be04262 + nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 @@ -374,22 +394,22 @@ SPEC CHECKSUMS: share: 0b2c3e82132f5888bccca3351c504d0003b3b410 sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440 sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - Stripe: 9fec845645e39f371e6898926d096fd9c2feb5a5 - stripe_ios: 03c617acee72e48a2d055d096a4b0ed2afebb256 - StripeApplePay: 5f017e8dfe259fafbab70137776189deef754bb2 - StripeCore: 01ec57f0dddfe742054dc6a322f811426c25313d - StripeFinancialConnections: 56698cb6274bf89fb8c76b934f6156f368e97765 - StripePayments: 6adf11faf1b7038e77aa97019410305c6adca79d - StripePaymentSheet: 3eaf870c4388e44b0cc37e4c69d00b6957fd8bd7 - StripePaymentsUI: 59ccddeacad592b09fa67e8d641340820ddb4751 - StripeUICore: 879bbf5889265db13f52fac8aad7a176ba62481f + Stripe: cdf416cf2efe286f532a6306de0fcaa0ecc8c71a + stripe_ios: 91946e5c07e0a0dc0e1484ee6659e1f90a302cf3 + StripeApplePay: efb62ffc08e6cd4f161d77ddb45de2451075c54e + StripeCore: 9731f05e327c3dcaf7d7abd116840ceaa9482bbe + StripeFinancialConnections: 46c0049aaab3a179193502bce4a8096eb7b73f55 + StripePayments: dd1867a620b0b8b5e294e9ff2f1f7b7770765f47 + StripePaymentSheet: d155dfde74e90784d054deffb4f561a1f6dd638f + StripePaymentsUI: c24f990b03a68a7f6fe704b15dd487e7bb6b603e + StripeUICore: f2d514e900c37436dc5427fdf2c29d68ab1c2935 TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654 uni_links: d97da20c7701486ba192624d99bffaaffcfc298a url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe vibration: 7d883d141656a1c1a6d8d238616b2042a51a1241 video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 - webview_flutter_wkwebview: 2a23822e9039b7b1bc52e5add778e5d89ad488d1 + webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4 PODFILE CHECKSUM: d9271c147dd54ffd9ca5d77bf00ca21a1c9a5961 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 0f46ccb..382ae34 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -467,6 +467,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -594,6 +595,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -651,6 +653,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 265f9be..3966f9f 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -4,7 +4,7 @@ import FirebaseCore import GoogleMaps // import Constants -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index bbb234a..76cee0b 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,10 @@ + NSContactsUsageDescription + This app requires contacts access to function properly. + LSMinimumSystemVersion + 12.0 CFBundleURLTypes @@ -37,11 +41,21 @@ CFBundlePackageType APPL CFBundleShortVersionString +<<<<<<< HEAD 51 CFBundleSignature ???? CFBundleVersion 4.3.51 +======= + 78 + CFBundleSignature + ???? + CFBundleVersion + 4.3.78 + NSHumanReadableCopyright + +>>>>>>> 8813b4d FirebaseAppDelegateProxyEnabled NO GMSApiKey diff --git a/lib.zip b/lib.zip new file mode 100644 index 0000000..0dd6247 Binary files /dev/null and b/lib.zip differ diff --git a/lib/constant/api_key.dart b/lib/constant/api_key.dart index 6433459..f0e2f77 100644 --- a/lib/constant/api_key.dart +++ b/lib/constant/api_key.dart @@ -51,6 +51,7 @@ class AK { X.r(X.r(X.r(Env.payMobApikey, cn), cC), cs); static final String integrationIdPayMobWallet = X.r(X.r(X.r(Env.integrationIdPayMobWallet, cn), cC), cs); + static final String apiKeyHere = Env.apiKeyHere; static final String smsPasswordEgypt = X.r(X.r(X.r(Env.smsPasswordEgypt, cn), cC), cs); static final String ocpApimSubscriptionKey = Env.ocpApimSubscriptionKey; diff --git a/lib/constant/box_name.dart b/lib/constant/box_name.dart index 63d9eb5..e2fbaa7 100644 --- a/lib/constant/box_name.dart +++ b/lib/constant/box_name.dart @@ -10,14 +10,21 @@ class BoxName { static const String carType = "carType"; static const String carPlate = "carPlate"; static const String packagInfo = "packagInfo"; - static const String isVerified = '0'; + static const String isVerified = 'isVerified'; + static const String isFirstTime = 'isFirstTime'; + static const String isSavedPhones = 'isSavedPhones'; static const String statusDriverLocation = "statusDriverLocation"; + static const String isTest = "isTest"; static const String password = "password"; + static const String validity = "validity"; + static const String promo = "promo"; + static const String discount = "discount"; static const String arrivalTime = "arrivalTime"; static const String passwordDriver = "passwordDriver"; static const String agreeTerms = "agreeTerms"; static const String addWork = 'addWork'; static const String addHome = 'addHome'; + static const String placesDestination = 'placesDestination'; static const String tipPercentage = 'tipPercentage'; static const String accountIdStripeConnect = "accountIdStripeConnect"; static const String faceDetectTimes = "faceDetectTimes"; @@ -25,6 +32,11 @@ class BoxName { static const String sosPhoneDriver = "sosPhoneDriver"; static const String passengerID = "pasengerID"; static const String phone = "phone"; + static const String package = "package"; + static const String isInstall = "isInstall"; + static const String isGiftToken = "isGiftToken"; + static const String inviteCode = "inviteCode"; + static const String phoneWallet = "phoneWallet"; static const String phoneDriver = "phoneDriver"; static const String dobDriver = "dobDriver"; static const String sexDriver = "sexDriver"; @@ -68,7 +80,6 @@ class BoxName { static const String vin = "vin"; static const String isvibrate = "isvibrate"; static const String make = "make"; - static const String hourWait = "hourWait"; static const String model = "model"; static const String year = "year"; static const String expirationDate = "expirationDate"; diff --git a/lib/constant/links.dart b/lib/constant/links.dart index a661486..bb2c553 100644 --- a/lib/constant/links.dart +++ b/lib/constant/links.dart @@ -14,6 +14,8 @@ class AppLink { // static final String server = Env.serverPHP; static String googleMapsLink = 'https://maps.googleapis.com/maps/api/'; + static String searcMaps = + 'https://autosuggest.search.hereapi.com/v1/autosuggest'; static String llama = 'https://api.llama-api.com/chat/completions'; static String gemini = 'https://generativelanguage.googleapis.com/v1beta3/models/text-bison-001:generateText'; @@ -51,16 +53,20 @@ class AppLink { //=======================promo===================ride.mobile-app.store/ride/promo/get.php static String promo = '$server/ride/promo'; static String getPassengersPromo = "$promo/get.php"; + static String getPromoFirst = "$promo/getPromoFirst.php"; static String getPromoBytody = "$promo/getPromoBytody.php"; static String addPassengersPromo = "$promo/add.php"; static String deletePassengersPromo = "$promo/delete.php"; static String updatePassengersPromo = "$promo/update.php"; + //===============contact========================== + static String savePhones = "$server/ride/egyptPhones/add.php"; + static String getPhones = "$server/ride/egyptPhones/get.php"; + ////=======================cancelRide=================== static String ride = '$server/ride'; - static String addCancelRideFromPassenger = - "$endPoint/ride/cancelRide/add.php"; - static String cancelRide = "$endPoint/ride/cancelRide/get.php"; + static String addCancelRideFromPassenger = "$server/ride/cancelRide/add.php"; + static String cancelRide = "$server/ride/cancelRide/get.php"; //-----------------ridessss------------------ static String addRides = "$ride/rides/add.php"; static String getRides = "$endPoint/ride/rides/get.php"; @@ -70,10 +76,10 @@ class AppLink { "$endPoint/ride/rides/getRideStatusBegin.php"; static String getRideStatusFromStartApp = "$ride/rides/getRideStatusFromStartApp.php"; - static String updateRides = "$endPoint/ride/rides/update.php"; + static String updateRides = "$server/ride/rides/update.php"; static String updateStausFromSpeed = - "$endPoint/ride/rides/updateStausFromSpeed.php"; - static String deleteRides = "$endPoint/ride/rides/delete.php"; + "$server/ride/rides/updateStausFromSpeed.php"; + static String deleteRides = "$server/ride/rides/delete.php"; //-----------------DriverPayment------------------ static String adddriverScam = "$ride/driver_scam/add.php"; @@ -105,13 +111,28 @@ class AppLink { "$ride/notificationPassenger/update.php"; //-----------------Driver NotificationCaptain------------------ static String addNotificationCaptain = "$ride/notificationCaptain/add.php"; - static String addWaitingRide = "$ride/notificationCaptain/addWaitingRide.php"; - static String getRideWaiting = "$ride/notificationCaptain/getRideWaiting.php"; + static String addWaitingRide = + "$server/ride/notificationCaptain/addWaitingRide.php"; + static String updateWaitingTrip = + "$server/ride/notificationCaptain/updateWaitingTrip.php"; + static String getRideWaiting = + "$endPoint/ride/notificationCaptain/getRideWaiting.php"; static String getNotificationCaptain = "$ride/notificationCaptain/get.php"; static String updateNotificationCaptain = "$ride/notificationCaptain/update.php"; static String deleteNotificationCaptain = "$ride/notificationCaptain/delete.php"; + //-----------------invitor------------------ + + static String addInviteDriver = "$server/ride/invitor/add.php"; + static String addInvitationPassenger = + "$server/ride/invitor/addInvitationPassenger.php"; + static String getInviteDriver = "$server/ride/invitor/get.php"; + static String getDriverInvitationToPassengers = + "$server/ride/invitor/getDriverInvitationToPassengers.php"; + static String updateInviteDriver = "$server/ride/invitor/update.php"; + static String updatePassengerGift = + "$server/ride/invitor/updatePassengerGift.php"; //-----------------Api Key------------------ static String addApiKey = "$ride/apiKey/add.php"; static String getApiKey = "$ride/apiKey/get.php"; @@ -121,6 +142,7 @@ class AppLink { //-----------------Feed Back------------------ static String addFeedBack = "$ride/feedBack/add.php"; + static String uploadAudio = "$ride/feedBack/upload_audio.php"; static String getFeedBack = "$ride/feedBack/get.php"; static String updateFeedBack = "$ride/feedBack/updateFeedBack.php"; @@ -142,7 +164,8 @@ class AppLink { static String updateLicense = "$ride/license/updateFeedBack.php"; //-----------------RegisrationCar------------------ static String addRegisrationCar = "$ride/RegisrationCar/add.php"; - static String getRegisrationCar = "$endPoint/ride/RegisrationCar/get.php"; + static String getRegisrationCar = + "${box.read(BoxName.serverChosen)}/ride/RegisrationCar/get.php"; static String selectDriverAndCarForMishwariTrip = "$ride/RegisrationCar/selectDriverAndCarForMishwariTrip.php"; static String updateRegisrationCar = "$ride/RegisrationCar/update.php"; @@ -150,6 +173,7 @@ class AppLink { //-----------------mishwari------------------ static String addMishwari = "$ride/mishwari/add.php"; + static String cancelMishwari = "$ride/mishwari/cancel.php"; static String getMishwari = "$ride/mishwari/get.php"; //-----------------DriverOrder------------------ @@ -163,6 +187,8 @@ class AppLink { // ===================================== static String addRateToPassenger = "$ride/rate/add.php"; + static String savePlacesServer = "$ride/places/add.php"; + static String getapiKey = "$ride/apiKey/get.php"; static String addRateToDriver = "$ride/rate/addRateToDriver.php"; static String getDriverRate = "$ride/rate/getDriverRate.php"; static String getPassengerRate = "$ride/rate/getPassengerRate.php"; @@ -183,12 +209,14 @@ class AppLink { static String uploadEgypt = "$server/uploadEgypt.php"; //==================certifcate========== - static String location = '$endPoint/ride/location'; + static String location = '${box.read(BoxName.serverChosen)}/ride/location'; static String getCarsLocationByPassenger = "$location/get.php"; static String addpassengerLocation = "$location/addpassengerLocation.php"; static String getCarsLocationByPassengerSpeed = "$location/getSpeed.php"; static String getCarsLocationByPassengerComfort = "$location/getComfort.php"; static String getCarsLocationByPassengerBalash = "$location/getBalash.php"; + static String getCarsLocationByPassengerPinkBike = + "$location/getPinkBike.php"; static String getCarsLocationByPassengerDelivery = "$location/getDelivery.php"; static String getLocationParents = "$location/getLocationParents.php"; @@ -239,7 +267,10 @@ class AppLink { static String deletecaptainAccounr = "$authCaptin/deletecaptainAccounr.php"; static String updateAccountBank = "$authCaptin/updateAccountBank.php"; static String getAccount = "$authCaptin/getAccount.php"; - + static String updatePassengersInvitation = + "$server/ride/invitor/updatePassengersInvitation.php"; + static String updateDriverInvitationDirectly = + "$server/ride/invitor/updateDriverInvitationDirectly.php"; //===================Admin Captin============ static String getPassengerDetailsByPassengerID = @@ -248,6 +279,7 @@ class AppLink { static String getPassengerbyEmail = "$server/Admin/getPassengerbyEmail.php"; static String addAdminUser = "$server/Admin/adminUser/add.php"; static String getAdminUser = "$server/Admin/adminUser/get.php"; + static String addError = "$server/Admin/errorApp.php"; static String getCaptainDetailsByEmailOrIDOrPhone = "$server/Admin/AdminCaptain/getCaptainDetailsByEmailOrIDOrPhone.php"; static String getCaptainDetails = "$server/Admin/AdminCaptain/get.php"; diff --git a/lib/constant/notification.dart b/lib/constant/notification.dart new file mode 100644 index 0000000..7ede7a2 --- /dev/null +++ b/lib/constant/notification.dart @@ -0,0 +1,22 @@ +List messages = [ + "🚗 عروض مميزة: استمتع بأقل الأسعار وأفضل العروض! افتح تطبيق سفر الآن لتحصل على المزيد من الخيارات. 🌟", + "💸 وفر الآن: وفر مع تطبيق سفر! عروض مستمرة وخيارات متعددة تناسب احتياجاتك. 🔥", + "🔒 أمان وراحة: مع تطبيق سفر، احصل على أمان وراحة بأفضل الأسعار! 🚕", + "💼 خيارات متنوعة: استفد من خيارات متنوعة وأسعار تنافسية على تطبيق سفر، الأفضل دائماً. 🌐", + "💵 توفير مضمون: حافظ على ميزانيتك وسافر بأمان مع تطبيق سفر – العروض لا تتوقف! 🎉", + "🌍 وجهات مميزة: أفضل وجهات السفر، بأقل الأسعار مع تطبيق سفر – تابعنا الآن! 🛤️", + "🛣️ سهولة وراحة: رحلاتك أصبحت أسهل وأرخص – سافر معنا وتمتع بأفضل التجارب. 🎊", + "📲 حجز سهل: احجز رحلتك بسهولة وأمان مع سفر – المزيد من الخصومات في انتظارك! 🎁", + "👑 فئة مميزة: خليك من الفئة المميزة واستفد بأفضل الأسعار مع تطبيق سفر. 💯", + "💡 خيارات متعددة: نوفر لك خيارات متعددة وسعر مناسب – جرب تطبيق سفر الآن! 🚖", + "✨ عروض متجددة: العروض لا تتوقف على تطبيق سفر – احجز رحلتك الآن وتمتع بالمزيد! 📅", + "🚀 سهولة الوصول: السفر أصبح أسهل وأسرع مع تطبيق سفر – كن مستعدًا لأفضل التجارب! 🌠", + "🧳 راحة وأمان: تطبيق سفر يقدم لك أمان وراحة بأقل الأسعار! 📉", + "🔥 عروض فورية: احجز الآن واستمتع بعروض لا تُفوّت على تطبيق سفر! 🚘", + "🚖 أسعار تنافسية: اختر رحلتك الآن بأسعار تنافسية وتمتع بالراحة والأمان مع تطبيق سفر. ✅", + "💥 أسعار خاصة: أسعار خاصة بانتظارك على تطبيق سفر! افتح التطبيق الآن واحجز رحلتك. 🌐", + "🌟 راحة البال: انطلق بأمان وراحة مع تطبيق سفر – استمتع بأفضل الأسعار. 💸", + "📍 خصومات حصرية: استفد من الخصومات الحصرية والعروض المستمرة على تطبيق سفر! 🛤️", + "🛫 تجربة سهلة: رحلاتك أصبحت أفضل وأسهل مع تطبيق سفر – افتح التطبيق واستمتع بالتجربة. ✨", + "🔔 عروض لا مثيل لها: كن جاهزًا لعروض لا مثيل لها! تطبيق سفر يقدم لك أفضل الخيارات بأقل الأسعار. 🎉", +]; diff --git a/lib/constant/style.dart b/lib/constant/style.dart index 9d13b27..a0993aa 100644 --- a/lib/constant/style.dart +++ b/lib/constant/style.dart @@ -5,35 +5,39 @@ import 'package:google_fonts/google_fonts.dart'; import 'colors.dart'; class AppStyle { - static TextStyle headTitle = TextStyle( - fontWeight: FontWeight.bold, - fontSize: 40, - color: AppColor.accentColor, - fontFamily: box.read(BoxName.lang) == 'ar' - // ?GoogleFonts.notoNaskhArabic().fontFamily - ? GoogleFonts.notoNaskhArabic().fontFamily - : GoogleFonts.roboto().fontFamily); - static TextStyle headTitle2 = TextStyle( - fontWeight: FontWeight.bold, - fontSize: 26, - color: AppColor.writeColor, - fontFamily: box.read(BoxName.lang) == 'ar' - ? GoogleFonts.notoNaskhArabic().fontFamily - : GoogleFonts.roboto().fontFamily); + static TextStyle headTitle = const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 40, + color: AppColor.accentColor, + // fontFamily: box.read(BoxName.lang) == 'ar' + // // ?GoogleFonts.notoNaskhArabic().fontFamily + // ? GoogleFonts.notoNaskhArabic().fontFamily + // : GoogleFonts.roboto().fontFamily, + ); + static TextStyle headTitle2 = const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 22, + color: AppColor.writeColor, + // fontFamily: box.read(BoxName.lang) == 'ar' + // ? GoogleFonts.notoNaskhArabic().fontFamily + // : GoogleFonts.roboto().fontFamily + ); static TextStyle title = TextStyle( - fontWeight: FontWeight.normal, - fontSize: box.read(BoxName.lang) == 'ar' ? 14 : 16, - color: AppColor.writeColor, - fontFamily: box.read(BoxName.lang) == 'ar' - ? GoogleFonts.notoNaskhArabic().fontFamily - : GoogleFonts.roboto().fontFamily); - static TextStyle subtitle = TextStyle( - fontWeight: FontWeight.bold, - fontSize: 13, - color: AppColor.writeColor, - fontFamily: box.read(BoxName.lang) == 'ar' - ? GoogleFonts.notoNaskhArabic().fontFamily - : GoogleFonts.roboto().fontFamily); + fontWeight: FontWeight.normal, + fontSize: box.read(BoxName.lang) == 'ar' ? 14 : 16, + color: AppColor.writeColor, + // fontFamily: box.read(BoxName.lang) == 'ar' + // ? GoogleFonts.notoNaskhArabic().fontFamily + // : GoogleFonts.roboto().fontFamily + ); + static TextStyle subtitle = const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 13, + color: AppColor.writeColor, + // fontFamily: box.read(BoxName.lang) == 'ar' + // ? GoogleFonts.notoNaskhArabic().fontFamily + // : GoogleFonts.roboto().fontFamily + ); static TextStyle number = const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, @@ -43,7 +47,9 @@ class AppStyle { static BoxDecoration boxDecoration = const BoxDecoration( boxShadow: [ BoxShadow( - color: AppColor.accentColor, blurRadius: 5, offset: Offset(2, 4)), + color: Color.fromARGB(255, 218, 218, 255), + blurRadius: 5, + offset: Offset(2, 4)), BoxShadow( color: AppColor.accentColor, blurRadius: 5, offset: Offset(-2, -2)) ], diff --git a/lib/constant/univeries_polygon.dart b/lib/constant/univeries_polygon.dart new file mode 100644 index 0000000..8358b9f --- /dev/null +++ b/lib/constant/univeries_polygon.dart @@ -0,0 +1,83 @@ +import 'package:google_maps_flutter/google_maps_flutter.dart'; + +class UniversitiesPolygons { + // AUC polygon points + static const List> universityPolygons = [ + // AUC Polygon + [ + LatLng(30.013431, 31.502572), + LatLng(30.018469, 31.497478), + LatLng(30.023158, 31.495870), + LatLng(30.025084, 31.496781), + LatLng(30.018701, 31.511393), + LatLng(30.015312, 31.508310), + ], + // Example polygon for University 'German University in Cairo (GUC)' + [ + LatLng(29.984554, 31.437829), + LatLng(29.990363, 31.438390), + LatLng(29.990560, 31.445643), + LatLng(29.984436, 31.445825), + ], + //Future University in Egypt (FUE) + [ + LatLng(30.025794, 31.490946), + LatLng(30.028341, 31.491014), + LatLng(30.028341, 31.492586), + LatLng(30.025844, 31.492491), + ], + //'British University in Egypt (BUE)' + [ + LatLng(30.117423, 31.605834), + LatLng(30.118224, 31.605543), + LatLng(30.118649, 31.607361), + LatLng(30.118932, 31.608033), + LatLng(30.119592, 31.612159), + LatLng(30.119372, 31.612958), + LatLng(30.120017, 31.617102), + LatLng(30.119435, 31.617193), + ], + //Misr International University (MIU) + [ + LatLng(30.166498, 31.491663), + LatLng(30.171956, 31.491060), + LatLng(30.172212, 31.495754), + LatLng(30.167112, 31.496108), + ], + // Canadian International College (CIC) + [ + LatLng(30.034312, 31.428963), + LatLng(30.035661, 31.429037), + LatLng(30.036074, 31.430522), + LatLng(30.036017, 31.431146), + LatLng(30.034580, 31.431117), + ], + // October 6 University (O6U) + [ + LatLng(29.974102, 30.946934), + LatLng(29.976620, 30.944925), + LatLng(29.979848, 30.949832), + LatLng(29.977372, 30.951950), + ], + [ + LatLng(30.029312, 31.210046), + LatLng(30.027124, 31.201393), + LatLng(30.014523, 31.205087), + LatLng(30.015416, 31.212218), + LatLng(30.027325, 31.210661), + ], + // Add polygons for 8 more universities... + ]; + + static const List universityNames = [ + "American University in Cairo (AUC)", + 'German University in Cairo (GUC)', + 'Future University in Egypt (FUE)', + 'British University in Egypt (BUE)', + 'Misr International University (MIU)', + 'Canadian International College (CIC)', + 'October 6 University (O6U)', + "Cairo University", + // Add names for 8 more universities... + ]; +} diff --git a/lib/controller/auth/google_sign.dart b/lib/controller/auth/google_sign.dart index b46f940..f02d2e6 100644 --- a/lib/controller/auth/google_sign.dart +++ b/lib/controller/auth/google_sign.dart @@ -1,10 +1,17 @@ +import 'dart:io'; + import 'package:SEFER/constant/box_name.dart'; import 'package:SEFER/controller/auth/login_controller.dart'; import 'package:SEFER/main.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:google_sign_in/google_sign_in.dart'; +import 'package:url_launcher/url_launcher.dart'; +import '../../constant/links.dart'; import '../../onbording_page.dart'; +import '../functions/crud.dart'; class GoogleSignInHelper { static final GoogleSignIn _googleSignIn = GoogleSignIn( @@ -32,26 +39,114 @@ class GoogleSignInHelper { } } - static Future signInFromLogin() async { + Future signInFromLogin() async { try { final GoogleSignInAccount? googleUser = await _googleSignIn.signIn(); if (googleUser != null) { await _handleSignUp(googleUser); - // if (box.read(BoxName.countryCode) == 'Egypt') { await Get.put(LoginController()).loginUsingCredentials( box.read(BoxName.passengerID).toString(), box.read(BoxName.email).toString(), ); - // } else if (box.read(BoxName.countryCode) == 'Jordan') { - // // Get.to(() => AiPage()); - // } } return googleUser; } catch (error) { + // if (error is GoogleSignInAuthenticationException) { + // // Handle authentication errors from Google Sign-In + // addError("Google sign-in authentication error: ${error.message}", + // ' signInFromLogin()'); + // } else if (error is GoogleSignInAccountNotFoundException) { + // // Handle the case where the user is not found (if applicable) + // addError("Google sign-in account not found error: ${error.message}", + // ' signInFromLogin()'); + // } + // else + if (error is SocketException) { + // Handle network issues, like SSL certificate issues + addError("Network error (SSL certificate issue): ${error.message}", + ' signInFromLogin()'); + } else if (error is PlatformException) { + // Handle platform-specific errors, like Google Play Services issues + if (error.code == 'sign_in_required') { + // Google Play Services are required but not installed or outdated + showGooglePlayServicesError(); + } else { + addError("Platform error: ${error.message}", + ' signInFromLogin()'); + } + } else { + // Catch all other unknown errors + addError("Unknown error: ${error.toString()}", + ' signInFromLogin()'); + } return null; } } + void showGooglePlayServicesError() async { + const playStoreUrl = + 'https://play.google.com/store/apps/details?id=com.google.android.gms&hl=en_US'; + + if (await canLaunchUrl(Uri.parse(playStoreUrl))) { + await launchUrl(Uri.parse(playStoreUrl)); + } else { + // Fallback if the URL can't be opened + showDialog( + context: Get.context!, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Error'.tr), + content: Text( + 'Could not open the Google Play Store. Please update Google Play Services manually.' + .tr), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text('Close'.tr), + ), + ], + ); + }, + ); + } + } + + // Future signInFromLogin() async { + // try { + // final GoogleSignInAccount? googleUser = await _googleSignIn.signIn(); + // if (googleUser != null) { + // await _handleSignUp(googleUser); + // // if (box.read(BoxName.countryCode) == 'Egypt') { + // await Get.put(LoginController()).loginUsingCredentials( + // box.read(BoxName.passengerID).toString(), + // box.read(BoxName.email).toString(), + // ); + // // } else if (box.read(BoxName.countryCode) == 'Jordan') { + // // // Get.to(() => AiPage()); + // // } + // } + // return googleUser; + // } catch (error) { + // addError(error.toString(), ' signInFromLogin()'); + // return null; + // } + // } + + addError(String error, where) async { + CRUD().post(link: AppLink.addError, payload: { + 'error': error.toString(), // Example error description + 'userId': box.read(BoxName.driverID) ?? + box.read(BoxName.passengerID), // Example user ID + 'userType': box.read(BoxName.driverID) != null + ? 'Driver' + : 'passenger', // Example user type + 'phone': box.read(BoxName.phone) ?? + box.read(BoxName.phoneDriver), // Example phone number + + 'device': where + }); + } + // Method to handle Google Sign-Out static Future signOut() async { try { diff --git a/lib/controller/auth/login_controller.dart b/lib/controller/auth/login_controller.dart index bef43cf..d2f4745 100644 --- a/lib/controller/auth/login_controller.dart +++ b/lib/controller/auth/login_controller.dart @@ -1,9 +1,12 @@ import 'dart:convert'; +import 'dart:io'; import 'package:SEFER/constant/info.dart'; import 'package:SEFER/controller/firebase/firbase_messge.dart'; +import 'package:SEFER/controller/functions/add_error.dart'; import 'package:SEFER/views/auth/login_page.dart'; import 'package:SEFER/views/auth/sms_verfy_page.dart'; +import 'package:SEFER/views/widgets/my_dialog.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:SEFER/constant/box_name.dart'; @@ -13,6 +16,9 @@ import 'package:SEFER/main.dart'; import 'package:SEFER/views/home/map_page_passenger.dart'; import 'package:location/location.dart'; +import '../../print.dart'; +import '../functions/package_info.dart'; + class LoginController extends GetxController { final formKey = GlobalKey(); final formKeyAdmin = GlobalKey(); @@ -29,9 +35,20 @@ class LoginController extends GetxController { update(); } - getAppTester(String appPlatform) async { - var res = await CRUD() - .get(link: AppLink.getTesterApp, payload: {'appPlatform': appPlatform}); + @override + void onInit() async { + box.read(BoxName.isTest) == null || + box.read(BoxName.isTest).toString() == '0' + ? await getAppTester() + : null; + + super.onInit(); + } + + getAppTester() async { + var res = await CRUD().get( + link: AppLink.getTesterApp, + payload: {'appPlatform': AppInformation.appName}); if (res != 'failure') { var d = jsonDecode(res); @@ -64,6 +81,8 @@ class LoginController extends GetxController { await CRUD().get(link: AppLink.loginFromGooglePassenger, payload: { 'email': email, 'id': passengerID, + "platform": Platform.isAndroid ? 'android' : 'ios', + "appName": AppInformation.appName, }); if (res == 'Failure') { Get.offAll(SmsSignupEgypt()); @@ -73,12 +92,22 @@ class LoginController extends GetxController { } else { var jsonDecoeded = jsonDecode(res); if (jsonDecoeded.isNotEmpty) { + var d = jsonDecoeded['data'][0]; if (jsonDecoeded['status'] == 'success' && - jsonDecoeded['data'][0]['verified'].toString() == '1') { + d['verified'].toString() == '1') { // + box.write(BoxName.isVerified, '1'); - box.write(BoxName.email, jsonDecoeded['data'][0]['email']); - box.write(BoxName.phone, jsonDecoeded['data'][0]['phone']); + box.write(BoxName.email, d['email']); + box.write(BoxName.phone, d['phone']); + box.write(BoxName.isTest, '1'); + box.write(BoxName.package, d['package']); + box.write(BoxName.promo, d['promo']); + box.write(BoxName.discount, d['discount']); + box.write(BoxName.validity, d['validity']); + box.write(BoxName.isInstall, d['isInstall'] ?? 'none'); + box.write(BoxName.isGiftToken, d['isGiftToken'] ?? 'none'); + box.write(BoxName.inviteCode, d['inviteCode'] ?? 'none'); var token = await CRUD().get(link: AppLink.getTokens, payload: { 'passengerID': box.read(BoxName.passengerID).toString() @@ -86,34 +115,85 @@ class LoginController extends GetxController { if (token != 'failure') { if (jsonDecode(token)['data'][0]['token'] != box.read(BoxName.tokenFCM)) { - Get.put(FirebaseMessagesController()) - .sendNotificationToAnyWithoutData( + Get.put(FirebaseMessagesController()).sendNotificationToDriverMAP( 'token change'.tr, 'change device'.tr, jsonDecode(token)['data'][0]['token'].toString(), + [], 'cancel.wav', ); Future.delayed(const Duration(seconds: 1)); - await CRUD().post(link: AppLink.addTokens, payload: { - 'token': box.read(BoxName.tokenFCM), - 'passengerID': box.read(BoxName.passengerID).toString() - }); - // Get.defaultDialog( - // title: 'Device Change Detected'.tr, - // middleText: - // 'You can only use one device at a time. This device will now be set as your active device.' - // .tr, - // textConfirm: 'OK'.tr, - // confirmTextColor: Colors.white, - // onConfirm: () { - // Get.back(); - // Get.offAll(() => const MapPagePassenger()); - // }, - // ); - // Get.snackbar('title', 'message'); + await CRUD().post( + link: "${AppLink.server}/ride/firebase/add.php", + payload: { + 'token': box.read(BoxName.tokenFCM), + 'passengerID': box.read(BoxName.passengerID).toString() + }); + CRUD().post( + link: + "${AppLink.seferAlexandriaServer}/ride/firebase/add.php", + payload: { + 'token': box.read(BoxName.tokenFCM), + 'passengerID': box.read(BoxName.passengerID).toString() + }); + CRUD().post( + link: "${AppLink.seferGizaServer}/ride/firebase/add.php", + payload: { + 'token': box.read(BoxName.tokenFCM), + 'passengerID': box.read(BoxName.passengerID).toString() + }); + Get.defaultDialog( + title: 'Device Change Detected'.tr, + middleText: + 'You can only use one device at a time. This device will now be set as your active device.' + .tr, + textConfirm: 'OK'.tr, + confirmTextColor: Colors.white, + onConfirm: () { + Get.back(); + Get.offAll(() => const MapPagePassenger()); + }, + ); + } else { + print('same'); } + } // Logging to check if inviteCode is written correctly + + if (box.read(BoxName.inviteCode).toString() != 'none' && + box.read(BoxName.isInstall).toString() != '1') { + await CRUD() + .post(link: AppLink.updatePassengersInvitation, payload: { + "inviteCode": box.read(BoxName.inviteCode).toString(), + "passengerID": box.read(BoxName.passengerID).toString(), + }); + Get.defaultDialog( + title: 'Invitation Used' + .tr, // Automatically translates based on the current locale + middleText: "Your invite code was successfully applied!" + .tr, // Automatically translates based on the current locale + onConfirm: () { + try { + CRUD().post(link: AppLink.addPassengersPromo, payload: { + "promoCode": + 'S-${box.read(BoxName.name).toString().split(' ')[0]}', + "amount": '25', + "passengerID": box.read(BoxName.passengerID).toString(), + "description": 'promo first' + }); + } catch (e) { + addError(e.toString(), + 'passenger Invitation Used dialogu as promo line 185 login_controller'); + } finally { + // Continue with the rest of your flow, regardless of errors + // For example, navigate to the next page + Get.offAll(() => const MapPagePassenger()); + } + }, + textConfirm: "OK".tr, // Confirm button text + ); + } else { + Get.offAll(() => const MapPagePassenger()); } - Get.offAll(() => const MapPagePassenger()); } else { Get.offAll(() => SmsSignupEgypt()); // Get.snackbar(jsonDecoeded['status'], jsonDecoeded['data'], @@ -128,34 +208,6 @@ class LoginController extends GetxController { } } - // void adminDashboardOpen() async { - // if (formKeyAdmin.currentState!.validate()) { - // await DeviceInfoPlus.getDeviceInfo(); - // if (Platform.isAndroid) { - // // var res = await CRUD().get(link: AppLink.getAdminUser, payload: { - // // // 'device_number': DeviceInfoPlus.deviceData['serialNumber'].toString(), - // // }); - // // var d = jsonDecode(res); - // // // if (DeviceInfoPlus.deviceData['serialNumber'] == - // // d['message']['device_number']) { - // Get.back(); - // Get.to(() => const AdminHomePage()); - // // } - // } - // if (Platform.isIOS) { - // // var res = await CRUD().get(link: AppLink.getAdminUser, payload: { - // // 'device_number': DeviceInfoPlus.deviceData['identifierForVendor'].toString(), - // // }); - // // var d = jsonDecode(res); - // // if (DeviceInfoPlus.deviceData['serialNumber'] == - // // d['message']['device_number']) { - // Get.back(); - // Get.to(() => const AdminHomePage()); - // // } - // } - // } - // } - void login() async { isloading = true; update(); @@ -204,6 +256,12 @@ class LoginController extends GetxController { } } + goToMapPage() { + if (box.read(BoxName.email) != null) { + Get.offAll(() => const MapPagePassenger()); + } + } + final location = Location(); // late PermissionStatus permissionGranted = PermissionStatus.denied; @@ -236,15 +294,4 @@ class LoginController extends GetxController { } update(); } - - @override - void onInit() async { - // permissionLocation = await Permission.locationWhenInUse.isGranted; - await getAppTester(AppInformation.appName); - // if (isTest == 0 && box.read(BoxName.passengerID) != null) { - // // await loginUsingCredentials( - // // box.read(BoxName.passengerID), box.read(BoxName.email)); - // } - super.onInit(); - } } diff --git a/lib/controller/auth/onboarding_controller.dart b/lib/controller/auth/onboarding_controller.dart index 1edc952..2746e84 100644 --- a/lib/controller/auth/onboarding_controller.dart +++ b/lib/controller/auth/onboarding_controller.dart @@ -22,7 +22,7 @@ class OnBoardingControllerImp extends OnBoardingController { if (currentPage > onBoardingList.length - 1) { box.write(BoxName.onBoarding, 'yes'); - Get.offAll(LoginPage()); + Get.offAll(() => LoginPage()); } else { pageController.animateToPage(currentPage, duration: const Duration(milliseconds: 900), curve: Curves.easeInOut); diff --git a/lib/controller/auth/register_controller.dart b/lib/controller/auth/register_controller.dart index 6edfad7..d01ceee 100644 --- a/lib/controller/auth/register_controller.dart +++ b/lib/controller/auth/register_controller.dart @@ -3,7 +3,11 @@ import 'dart:convert'; import 'dart:math'; import 'package:SEFER/constant/colors.dart'; +import 'package:SEFER/controller/auth/login_controller.dart'; +import 'package:SEFER/controller/functions/add_error.dart'; +import 'package:SEFER/controller/local/phone_intel/phone_number.dart'; import 'package:SEFER/views/home/map_page_passenger.dart'; +import 'package:SEFER/views/widgets/my_dialog.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:SEFER/constant/links.dart'; @@ -14,14 +18,14 @@ import 'package:SEFER/views/widgets/elevated_btn.dart'; import '../../constant/box_name.dart'; import '../../main.dart'; +import '../../print.dart'; import '../../views/auth/verify_email_page.dart'; import '../functions/sms_controller.dart'; class RegisterController extends GetxController { final formKey = GlobalKey(); final formKey3 = GlobalKey(); - List countryCodes = ['+1', '+91', '+44']; - String selectedCountryCode = '+1'; + TextEditingController firstNameController = TextEditingController(); TextEditingController lastNameController = TextEditingController(); TextEditingController emailController = TextEditingController(); @@ -41,11 +45,6 @@ class RegisterController extends GetxController { super.onInit(); } - void updateCountryCode(String newCode) { - selectedCountryCode = newCode; - update(); - } - void startTimer() { _timer?.cancel(); // Cancel any existing timer _timer = Timer.periodic(const Duration(seconds: 1), (timer) { @@ -119,166 +118,207 @@ class RegisterController extends GetxController { } sendOtpMessage() async { - SmsEgyptController smsEgyptController = Get.put(SmsEgyptController()); - - int randomNumber = Random().nextInt(100000) + 1; + SmsEgyptController smsEgyptController; isLoading = true; update(); - if (formKey3.currentState!.validate()) { - if (box.read(BoxName.countryCode) == 'Egypt') { - if (isValidEgyptianPhoneNumber(phoneController.text) == true) { - var responseCheker = await CRUD() - .post(link: AppLink.checkPhoneNumberISVerfiedPassenger, payload: { - 'phone_number': '+2${phoneController.text}', - 'email': box.read(BoxName.email), - }); - if (responseCheker != 'failure') { - var d = jsonDecode(responseCheker); - if (d['message'][0]['verified'].toString() == '1') { + try { + // Initialize SmsEgyptController + smsEgyptController = Get.put(SmsEgyptController()); + + // Generate a random OTP + int randomNumber = Random().nextInt(100000) + 1; + + isLoading = true; + update(); + + // Get phone number from controller + String phoneNumber = phoneController.text; + + // Check if the phone number is from Egypt (Assuming Egyptian numbers start with +20) + + if (phoneController.text.isNotEmpty) { + bool isEgyptianNumber = phoneNumber.startsWith('+20'); + if (isEgyptianNumber && phoneNumber.length == 13) { + // Check if the phone number is already verified + var responseChecker = await CRUD().post( + link: AppLink.checkPhoneNumberISVerfiedPassenger, + payload: { + 'phone_number': phoneNumber, + 'email': box.read(BoxName.email), + }, + ); + + if (responseChecker != 'failure') { + var data = jsonDecode(responseChecker); + + // If the phone number is already verified + if (data['message'][0]['verified'].toString() == '1') { Get.snackbar('Phone number is verified before'.tr, '', backgroundColor: AppColor.greenColor); box.write(BoxName.isVerified, '1'); - box.write(BoxName.phone, '+2${phoneController.text}'); + box.write(BoxName.phone, phoneNumber); Get.offAll(const MapPagePassenger()); } else { - await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: { - 'phone_number': '+2${phoneController.text}', - 'token': randomNumber.toString(), - }); - - await smsEgyptController.sendSmsEgypt( - phoneController.text.toString(), randomNumber.toString()); - isSent = true; - remainingTime = 300; // Reset to 5 minutes - startTimer(); - isLoading = false; - update(); + await sendOtp(phoneNumber, randomNumber, isEgyptianNumber, + smsEgyptController); } } else { - await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: { - 'phone_number': '+2${phoneController.text}', - 'token': randomNumber.toString(), - }); - - await smsEgyptController.sendSmsEgypt( - phoneController.text.toString(), randomNumber.toString()); - isSent = true; - remainingTime = 300; // Reset to 5 minutes - startTimer(); - isLoading = false; - update(); - - // Get.snackbar(responseCheker, 'message'); + await sendOtp(phoneNumber, randomNumber, isEgyptianNumber, + smsEgyptController); } - } else if (isValidPhoneNumber(phoneController.text)) { - await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: { - 'phone_number': '+${phoneController.text}', - 'token': randomNumber.toString(), - }); - await smsEgyptController.sendWhatsAppAuth( - phoneController.text, randomNumber.toString()); - // await smsEgyptController.sendSmsEgypt( - // phoneController.text.toString(), randomNumber.toString()); - isSent = true; - remainingTime = 300; // Reset to 5 minutes - startTimer(); - isLoading = false; - update(); - } else { - Get.snackbar('Phone Number wrong'.tr, '', - backgroundColor: AppColor.redColor, - duration: const Duration(seconds: 5)); + } else if (phoneNumber.length > 9) { + sendOtp( + phoneNumber, randomNumber, isEgyptianNumber, smsEgyptController); } + } else { + MyDialog().getDialog( + 'Error'.tr, 'Phone number must be exactly 11 digits long'.tr, () { + Get.back(); + }); + // sendOtp( + // phoneNumber, randomNumber, isEgyptianNumber, smsEgyptController); } + } catch (e) { + // Handle error + } finally { + isLoading = false; + update(); } } - verifySMSCode() async { - // if (formKey3.currentState!.validate()) { - if (isValidEgyptianPhoneNumber(phoneController.text)) { - var res = await CRUD().post(link: AppLink.verifyOtpMessage, payload: { - 'phone_number': '+2${phoneController.text}', - 'token': verifyCode.text.toString(), +// Helper function to send OTP or WhatsApp message based on phone number location + Future sendOtp(String phoneNumber, int otp, bool isEgyptian, + SmsEgyptController controller) async { + // Trim any leading or trailing whitespace from the phone number + phoneNumber = phoneNumber.trim(); + var dd = await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: { + 'phone_number': phoneNumber, + 'token': otp.toString(), + }); + Log.print('dd: ${dd}'); + if (isEgyptian) { + await CRUD().post(link: AppLink.updatePhoneInvalidSMSPassenger, payload: { + "phone_number": Get.find().phoneController.text }); - if (res != 'failure') { - // var dec = jsonDecode(res); - box.write(BoxName.phoneDriver, '+2${phoneController.text}'); - var payload = { - 'id': box.read(BoxName.passengerID), - 'phone': '+2${phoneController.text}', - 'email': box.read(BoxName.email), - 'password': 'unknown', - 'gender': 'unknown', - 'birthdate': '2002-01-01', - 'site': 'unknown', - 'first_name': box.read(BoxName.name).toString().split(' ')[0], - 'last_name': box.read(BoxName.name).toString().split(' ')[1], - }; + box.write(BoxName.phoneDriver, phoneController.text); + var nameParts = box.read(BoxName.name).toString().split(' '); + var firstName = nameParts.isNotEmpty ? nameParts[0] : 'unknown'; + var lastName = nameParts.length > 1 ? nameParts[1] : 'unknown'; - var res1 = await CRUD().post( - link: AppLink.signUp, + var payload = { + 'id': box.read(BoxName.passengerID), + 'phone': phoneController.text, + 'email': box.read(BoxName.email), + 'password': 'unknown', + 'gender': 'unknown', + 'birthdate': '2002-01-01', + 'site': box.read(BoxName.passengerPhotoUrl) ?? 'unknown', + 'first_name': firstName, + 'last_name': lastName, + }; + + var res1 = await CRUD().post( + link: AppLink.signUp, + payload: payload, + ); + + if (res1 != 'failure') { + await CRUD().post( + link: '${AppLink.seferAlexandriaServer}/auth/signup.php', payload: payload, ); - if (res1 != 'failure') { - CRUD().post( - link: '${AppLink.seferAlexandriaServer}/auth/signup.php', - payload: payload, - ); - CRUD().post( - link: '${AppLink.seferGizaServer}/auth/signup.php', - payload: payload, - ); - box.write(BoxName.isVerified, '1'); - box.write(BoxName.phone, '+2${phoneController.text}'); - Get.offAll(const MapPagePassenger()); - } - } else { - Get.snackbar( - 'Error'.tr, "The email or phone number is already registered.".tr, - backgroundColor: Colors.redAccent); + await CRUD().post( + link: '${AppLink.seferGizaServer}/auth/signup.php', + payload: payload, + ); + + box.write(BoxName.isVerified, '1'); + box.write(BoxName.isFirstTime, '0'); + box.write(BoxName.phone, phoneController.text); + + Get.put(LoginController()).loginUsingCredentials( + box.read(BoxName.passengerID).toString(), + box.read(BoxName.email).toString(), + ); } + // await controller.sendSmsEgypt(phoneNumber, otp.toString()); } else { - var res = await CRUD().post(link: AppLink.verifyOtpMessage, payload: { - 'phone_number': '+${phoneController.text}', - 'token': verifyCode.text.toString(), - }); - if (res != 'failure') { - // var dec = jsonDecode(res); - box.write(BoxName.phoneDriver, '+${phoneController.text}'); - var payload = { - 'id': box.read(BoxName.passengerID), - 'phone': '+${phoneController.text}', - 'email': box.read(BoxName.email), - 'password': 'unknown', - 'gender': 'unknown', - 'birthdate': '2002-01-01', - 'site': 'unknown', - 'first_name': box.read(BoxName.name).toString().split(' ')[0], - 'last_name': box.read(BoxName.name).toString().split(' ')[1], - }; + await CRUD().sendWhatsAppAuth(phoneNumber, otp.toString()); + } + isLoading = false; - var res1 = await CRUD().post( - link: AppLink.signUp, - payload: payload, - ); - if (res1 != 'failure') { - CRUD().post( - link: '${AppLink.seferAlexandriaServer}/auth/signup.php', + isSent = true; + remainingTime = 300; + update(); // Reset to 5 minutes + startTimer(); + } + + verifySMSCode() async { + try { + if (formKey3.currentState!.validate()) { + var res = await CRUD().post(link: AppLink.verifyOtpMessage, payload: { + 'phone_number': phoneController.text, + 'token': verifyCode.text.toString(), + }); + + if (res != 'failure') { + box.write(BoxName.phoneDriver, phoneController.text); + var nameParts = box.read(BoxName.name).toString().split(' '); + var firstName = nameParts.isNotEmpty ? nameParts[0] : 'unknown'; + var lastName = nameParts.length > 1 ? nameParts[1] : 'unknown'; + + var payload = { + 'id': box.read(BoxName.passengerID), + 'phone': phoneController.text, + 'email': box.read(BoxName.email), + 'password': 'unknown', + 'gender': 'unknown', + 'birthdate': '2002-01-01', + 'site': box.read(BoxName.passengerPhotoUrl) ?? 'unknown', + 'first_name': firstName, + 'last_name': lastName, + }; + + var res1 = await CRUD().post( + link: AppLink.signUp, payload: payload, ); - CRUD().post( - link: '${AppLink.seferGizaServer}/auth/signup.php', - payload: payload, - ); - box.write(BoxName.isVerified, '1'); - box.write(BoxName.phone, '+${phoneController.text}'); - Get.offAll(const MapPagePassenger()); + + if (res1 != 'failure') { + await CRUD().post( + link: '${AppLink.seferAlexandriaServer}/auth/signup.php', + payload: payload, + ); + await CRUD().post( + link: '${AppLink.seferGizaServer}/auth/signup.php', + payload: payload, + ); + + box.write(BoxName.isVerified, '1'); + box.write(BoxName.isFirstTime, '0'); + box.write(BoxName.phone, phoneController.text); + + Get.put(LoginController()).loginUsingCredentials( + box.read(BoxName.passengerID).toString(), + box.read(BoxName.email).toString(), + ); + } else { + Get.snackbar('Error'.tr, + "The email or phone number is already registered.".tr, + backgroundColor: Colors.redAccent); + } + } else { + Get.snackbar('Error'.tr, "phone not verified".tr, + backgroundColor: Colors.redAccent); } } else { - Get.snackbar( - 'Error'.tr, "The email or phone number is already registered.".tr, - backgroundColor: Colors.redAccent); + Get.snackbar('Error'.tr, "you must insert token code".tr, + backgroundColor: AppColor.redColor); } + } catch (e) { + addError(e.toString(), 'passenger sign up '); + Get.snackbar('Error'.tr, "Something went wrong. Please try again.".tr, + backgroundColor: Colors.redAccent); } } diff --git a/lib/controller/firebase/access_token.dart b/lib/controller/firebase/access_token.dart index 1d81d88..93c6898 100644 --- a/lib/controller/firebase/access_token.dart +++ b/lib/controller/firebase/access_token.dart @@ -11,10 +11,9 @@ class AccessTokenManager { factory AccessTokenManager(String jsonKey) { if (_instance._isServiceAccountKeyInitialized()) { - print('Service account key already initialized.'); + // Prevent re-initialization return _instance; } - print('Initializing service account key.'); _instance.serviceAccountJsonKey = jsonKey; return _instance; } @@ -29,45 +28,23 @@ class AccessTokenManager { } Future getAccessToken() async { - print('Attempting to get a new access token...'); - + if (_accessToken != null && DateTime.now().isBefore(_expiryDate!)) { + return _accessToken!.data; + } try { - // Parse service account credentials from JSON - print('Parsing service account credentials...'); final serviceAccountCredentials = ServiceAccountCredentials.fromJson( json.decode(serviceAccountJsonKey)); - - // Log service account email (or other non-sensitive information) - print('Service account email: ${serviceAccountCredentials.email}'); - - // Create an authenticated client via the service account - print('Creating authenticated client via service account...'); final client = await clientViaServiceAccount( serviceAccountCredentials, ['https://www.googleapis.com/auth/firebase.messaging'], ); - // Log successful client creation - print('Authenticated client created successfully.'); - - // Update the access token and expiry date _accessToken = client.credentials.accessToken; _expiryDate = client.credentials.accessToken.expiry; - - // Log the obtained token and expiry time - print('Access token obtained: ${_accessToken!.data}'); - print('Token expiry date: $_expiryDate'); - - // Close the client to prevent resource leaks - print('Closing authenticated client...'); client.close(); - - // Return the newly fetched access token return _accessToken!.data; } catch (e) { - // Log error if token fetch fails - print('Failed to obtain a new access token: $e'); - throw Exception('Failed to obtain a new access token: $e'); + throw Exception('Failed to obtain access token'); } } } diff --git a/lib/controller/firebase/firbase_messge.dart b/lib/controller/firebase/firbase_messge.dart index f473004..18d324c 100644 --- a/lib/controller/firebase/firbase_messge.dart +++ b/lib/controller/firebase/firbase_messge.dart @@ -1,12 +1,9 @@ import 'dart:convert'; import 'dart:io'; -import 'package:SEFER/env/env.dart'; import 'package:SEFER/views/widgets/my_dialog.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:googleapis_auth/auth_io.dart'; -import 'package:googleapis_auth/googleapis_auth.dart'; import 'package:http/http.dart' as http; import 'package:SEFER/controller/functions/toast.dart'; import 'package:SEFER/views/widgets/elevated_btn.dart'; @@ -69,6 +66,9 @@ class FirebaseMessagesController extends GetxController { } } + NotificationController notificationController = + Get.put(NotificationController()); + Future getTokens() async { String? basicAuthCredentials = await storage.read(key: BoxName.basicAuthCredentials); @@ -122,7 +122,15 @@ class FirebaseMessagesController extends GetxController { void fireBaseTitles(RemoteMessage message) { if (message.notification!.title! == 'Order'.tr) { - } else if (message.notification!.title! == 'Apply Ride'.tr) { + if (Platform.isAndroid) { + notificationController.showNotification( + 'Order', message.notification!.body!, 'Order'); + } + } else if (message.notification!.title! == 'Accepted Ride') { + if (Platform.isAndroid) { + notificationController.showNotification( + 'Apply Order'.tr, 'Driver Applied the Ride for You'.tr, 'ding'); + } var passengerList = message.data['passengerList']; var myList = jsonDecode(passengerList) as List; @@ -131,16 +139,20 @@ class FirebaseMessagesController extends GetxController { Get.find().statusRide == 'Apply'; Get.find().isSearchingWindow == false; Get.find().update(); - NotificationController().showNotification( - 'Apply Order'.tr, 'Driver Applied the Ride for You'.tr, 'order1'); + Get.find().rideAppliedFromDriver(true); + // driverAppliedTripSnakBar(); } else if (message.notification!.title! == 'Promo'.tr) { - NotificationController() - .showNotification('Promo', 'Show latest promo'.tr, 'promo'); + if (Platform.isAndroid) { + notificationController.showNotification( + 'Promo', 'Show latest promo'.tr, 'promo'); + } Get.to(const PromosPassengerPage()); } else if (message.notification!.title! == 'Trip Monitoring'.tr) { - NotificationController() - .showNotification('Trip Monitoring'.tr, '', 'iphone_ringtone'); + if (Platform.isAndroid) { + notificationController.showNotification( + 'Trip Monitoring'.tr, '', 'iphone_ringtone'); + } var myListString = message.data['passengerList']; var myList = jsonDecode(myListString) as List; Get.toNamed('/tripmonitor', arguments: { @@ -148,53 +160,72 @@ class FirebaseMessagesController extends GetxController { 'driverId': myList[1].toString(), }); } else if (message.notification!.title! == 'token change'.tr) { - NotificationController() - .showNotification('token change'.tr, 'token change'.tr, 'cancel'); + if (Platform.isAndroid) { + notificationController.showNotification( + 'token change'.tr, 'token change'.tr, 'cancel'); + } GoogleSignInHelper.signOut(); } else if (message.notification!.title! == 'DriverIsGoingToPassenger'.tr) { Get.find().isDriverInPassengerWay = true; Get.find().update(); - NotificationController().showNotification('Driver is Going To You'.tr, - 'Please stay on the picked point.'.tr, 'tone1'); + if (Platform.isAndroid) { + notificationController.showNotification('Driver is Going To You'.tr, + 'Please stay on the picked point.'.tr, 'tone1'); + } // Get.snackbar('Driver is Going To Passenger', '', // backgroundColor: AppColor.greenColor); } else if (message.notification!.title! == 'message From passenger') { - NotificationController() - .showNotification('message From passenger'.tr, ''.tr, 'tone2'); + if (Platform.isAndroid) { + notificationController.showNotification( + 'message From passenger'.tr, ''.tr, 'ding'); + } passengerDialog(message.notification!.body!); update(); } else if (message.notification!.title! == 'message From Driver') { - NotificationController() - .showNotification('message From passenger'.tr, ''.tr, 'tone2'); + if (Platform.isAndroid) { + notificationController.showNotification( + 'message From Driver'.tr, ''.tr, 'ding'); + } passengerDialog(message.notification!.body!); update(); } else if (message.notification!.title! == 'RideIsBegin'.tr) { + if (Platform.isAndroid) { + notificationController.showNotification( + 'Trip is Begin'.tr, ''.tr, 'start'); + } Get.find().getBeginRideFromDriver(); // Get.snackbar('RideIsBegin', '', backgroundColor: AppColor.greenColor); box.write(BoxName.passengerWalletTotal, '0'); - NotificationController() - .showNotification('Trip is Begin'.tr, ''.tr, 'start'); update(); } else if (message.notification!.title! == 'Hi ,I will go now'.tr) { // Get.snackbar('Hi ,I will go now', '', // backgroundColor: AppColor.greenColor); - NotificationController().showNotification( - 'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'tone2'); + if (Platform.isAndroid) { + notificationController.showNotification( + 'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'ding'); + } update(); - } else if (message.notification!.title! == 'Hi ,I Arrive your site'.tr) { - NotificationController() - .showNotification('Hi ,I Arrive your site'.tr, ''.tr, 'tone2'); + } else if (message.notification!.title! == 'Hi ,I Arrive your site') { + if (Platform.isAndroid) { + notificationController.showNotification( + 'Hi ,I Arrive your site'.tr, ''.tr, 'ding'); + } driverArrivePassengerDialoge(); update(); - } else if (message.notification!.title! == "Cancel Trip from driver".tr) { + } else if (message.notification!.title! == "Cancel Trip from driver") { Get.back(); + if (Platform.isAndroid) { + notificationController.showNotification("Cancel Trip from driver".tr, + "We will look for a new driver.\nPlease wait.".tr, 'cancel'); + } Get.defaultDialog( title: "The driver canceled your ride.".tr, - middleText: "We will look for a new driver.\\nPlease wait.".tr, + middleText: "We will look for a new driver.\nPlease wait.".tr, confirm: MyElevatedButton( + kolor: AppColor.greenColor, title: 'Ok'.tr, onPressed: () async { Get.back(); @@ -204,32 +235,38 @@ class FirebaseMessagesController extends GetxController { ), cancel: MyElevatedButton( title: 'Cancel'.tr, + kolor: AppColor.redColor, onPressed: () { - Get.offAll(const MapPagePassenger()); + Get.offAll(() => const MapPagePassenger()); }, ) // Get.find() // .searchNewDriverAfterRejectingFromDriver(); ); } else if (message.notification!.title! == 'Driver Finish Trip'.tr) { - var myListString = message.data['passengerList']; + var myListString = message.data['DriverList']; var driverList = jsonDecode(myListString) as List; - NotificationController().showNotification( - 'Driver Finish Trip'.tr, - 'you will pay to Driver'.tr + ' ${driverList[3].toString()} \$'.tr, - 'tone1'); + if (Platform.isAndroid) { + notificationController.showNotification( + 'Driver Finish Trip'.tr, + 'you will pay to Driver'.tr + ' ${driverList[3].toString()} \$'.tr, + 'tone1'); + } Get.find().stopRecording(); if (double.parse(box.read(BoxName.passengerWalletTotal)) < 0) { box.write(BoxName.passengerWalletTotal, 0); } Get.find().tripFinishedFromDriver(); - + notificationController.showNotification( + 'Don’t forget your personal belongings.'.tr, + 'Please make sure you have all your personal belongings and that any remaining fare, if applicable, has been added to your wallet before leaving. Thank you for choosing the Sefer app' + .tr, + 'ding'); Get.to(() => RateDriverFromPassenger(), arguments: { 'driverId': driverList[0].toString(), 'rideId': driverList[1].toString(), 'price': driverList[3].toString() }); - // } } else if (message.notification!.title! == "Finish Monitor".tr) { Get.defaultDialog( titleStyle: AppStyle.title, @@ -249,11 +286,13 @@ class FirebaseMessagesController extends GetxController { var myListString = message.data['passengerList']; var driverList = jsonDecode(myListString) as List; // if (Platform.isAndroid) { - NotificationController().showNotification( - 'Call Income'.tr, - message.notification!.body!, - 'iphone_ringtone', - ); + if (Platform.isAndroid) { + notificationController.showNotification( + 'Call Income'.tr, + message.notification!.body!, + 'iphone_ringtone', + ); + } // } // Assuming GetMaterialApp is initialized and context is valid for navigation // Get.to(() => PassengerCallPage( @@ -267,12 +306,13 @@ class FirebaseMessagesController extends GetxController { var myListString = message.data['passengerList']; var driverList = jsonDecode(myListString) as List; // if (Platform.isAndroid) { - NotificationController().showNotification( - 'Call Income'.tr, - message.notification!.body!, - 'iphone_ringtone', - ); - // } + if (Platform.isAndroid) { + notificationController.showNotification( + 'Call Income'.tr, + message.notification!.body!, + 'iphone_ringtone', + ); + } // Assuming GetMaterialApp is initialized and context is valid for navigation // Get.to(() => PassengerCallPage( // channelName: driverList[1].toString(), @@ -285,24 +325,24 @@ class FirebaseMessagesController extends GetxController { var myListString = message.data['passengerList']; var driverList = jsonDecode(myListString) as List; if (Platform.isAndroid) { - NotificationController().showNotification( + notificationController.showNotification( 'Call End'.tr, message.notification!.body!, - 'tone2', + 'ding', ); } // Assuming GetMaterialApp is initialized and context is valid for navigation // Get.off(const CallPage()); } catch (e) {} - } else if (message.notification!.title! == 'Driver Cancel Your Trip'.tr) { + } else if (message.notification!.title! == 'Driver Cancelled Your Trip') { // Get.snackbar( // 'You will be pay the cost to driver or we will get it from you on next trip' // .tr, // 'message', // backgroundColor: AppColor.redColor); if (Platform.isAndroid) { - NotificationController().showNotification( - 'Driver Cancel Your Trip'.tr, + notificationController.showNotification( + 'Driver Cancelled Your Trip'.tr, 'you will pay to Driver you will be pay the cost of driver time look to your SEFER Wallet' .tr, 'cancel'); @@ -311,7 +351,7 @@ class FirebaseMessagesController extends GetxController { box.remove(BoxName.tokenParent); Get.find().restCounter(); - Get.offAll(const MapPagePassenger()); + Get.offAll(() => const MapPagePassenger()); } // else if (message.notification!.title! == 'Order Applied') { // Get.snackbar( @@ -325,38 +365,13 @@ class FirebaseMessagesController extends GetxController { // } else if (message.notification!.title! == 'Order Applied'.tr) { - NotificationController().showNotification( - 'The order Accepted by another Driver'.tr, - 'We regret to inform you that another driver has accepted this order.' - .tr, - 'order'); - } else if (message.notification!.title! == 'VIP Order Accepted'.tr) { - var myListString = message.data['passengerList']; - var driverList = jsonDecode(myListString) as List; - - // Assuming driverList[1] contains a valid date string - DateTime scheduledTime; - try { - scheduledTime = DateTime.parse(driverList[1]); - } catch (e) { - // Handle the error if the date format is incorrect - Log.print('Error parsing date: $e'); - scheduledTime = DateTime.now() - .add(const Duration(hours: 1)); // Fallback to 1 hour from now + if (Platform.isAndroid) { + notificationController.showNotification( + 'The order Accepted by another Driver'.tr, + 'We regret to inform you that another driver has accepted this order.' + .tr, + 'order'); } - - NotificationController() - .showNotification('The driver accepted your trip'.tr, '', 'order'); - - MyDialog().getDialog( - 'VIP Order Accepted'.tr, - 'The driver accepted your trip'.tr, - () { - // Schedule a notification for the parsed date or fallback date - NotificationController().scheduleNotification('VIP Order'.tr, - 'This is a scheduled notification.'.tr, scheduledTime); - }, - ); } } @@ -393,7 +408,7 @@ class FirebaseMessagesController extends GetxController { title: 'Ok I will go now.'.tr, onPressed: () { FirebaseMessagesController().sendNotificationToPassengerToken( - 'Hi ,I will go now'.tr, + 'Hi ,I will go now', 'I will go now'.tr, Get.find().driverToken, [], @@ -410,7 +425,7 @@ class FirebaseMessagesController extends GetxController { Future passengerDialog(String message) { return Get.defaultDialog( barrierDismissible: false, - title: 'message From passenger'.tr, + title: 'message From Driver'.tr, titleStyle: AppStyle.title, middleTextStyle: AppStyle.title, middleText: message.tr, @@ -456,7 +471,7 @@ class FirebaseMessagesController extends GetxController { )); } - void sendNotificationAll(String title, body) async { + void sendNotificationAll(String title, body, tone) async { // Get the token you want to subtract. String token = box.read(BoxName.tokenFCM); tokens = box.read(BoxName.tokens); @@ -468,25 +483,41 @@ class FirebaseMessagesController extends GetxController { tokens = box.read(BoxName.tokens); for (var i = 0; i < tokens.length; i++) { http - .post(Uri.parse('https://fcm.googleapis.com/fcm/send'), - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'key=${AK.serverAPI}' - }, - body: jsonEncode({ - 'notification': { + .post( + Uri.parse('https://fcm.googleapis.com/fcm/send'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'key=${AK.serverAPI}' + }, + body: jsonEncode({ + 'message': { + 'token': token, + 'notification': { 'title': title, 'body': body, - 'sound': 'ding.wav' }, - 'priority': 'high', - 'data': { - 'click_action': 'FLUTTER_NOTIFICATION_CLICK', - 'id': '1', - 'status': 'done' + // 'data': { + // 'DriverList': jsonEncode(data), + // }, + 'android': { + 'priority': 'high', // Set priority to high + 'notification': { + 'sound': tone, + }, }, - 'to': tokens[i], - })) + 'apns': { + 'headers': { + 'apns-priority': '10', // Set APNs priority to 10 + }, + 'payload': { + 'aps': { + 'sound': tone, + }, + }, + }, + }, + }), + ) .whenComplete(() {}) .catchError((e) {}); } @@ -521,40 +552,6 @@ class FirebaseMessagesController extends GetxController { void sendNotificationToPassengerToken( String title, body, token, List map, String tone) async { - try { - final response = await http.post( - Uri.parse('https://fcm.googleapis.com/fcm/send'), - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'key=${AK.serverAPI}' - }, - body: jsonEncode({ - 'notification': { - 'title': title, - 'body': body, - 'sound': tone - }, - 'data': { - 'passengerList': map, - }, - 'priority': 'high', - 'to': token, - }), - ); - - if (response.statusCode == 200) { - // Notification sent successfully - } else { - // Handle error response - 'Failed to send notification. Status code: ${response.statusCode}'; - } - } catch (e) { - // Handle other exceptions - } - } - - void sendNotificationToAnyWithoutData( - String title, String body, String token, String tone) async { try { String serviceAccountKeyJson = '''{ "type": "service_account", @@ -594,11 +591,15 @@ class FirebaseMessagesController extends GetxController { 'body': body, }, 'android': { + 'priority': 'high', // Set priority to high 'notification': { 'sound': tone, }, }, 'apns': { + 'headers': { + 'apns-priority': '10', // Set APNs priority to 10 + }, 'payload': { 'aps': { 'sound': tone, @@ -610,75 +611,22 @@ class FirebaseMessagesController extends GetxController { ); if (response.statusCode == 200) { - print('Notification sent successfully.'); + print( + 'Notification sent successfully. Status code: ${response.statusCode}'); print('Response body: ${response.body}'); } else { print( 'Failed to send notification. Status code: ${response.statusCode}'); print('Response body: ${response.body}'); - - // Parse the response body to handle specific errors like 'UNREGISTERED' - final responseBody = jsonDecode(response.body); - if (responseBody['error'] != null && - responseBody['error']['status'] == 'NOT_FOUND' && - responseBody['error']['details'] != null) { - for (var detail in responseBody['error']['details']) { - if (detail['errorCode'] == 'UNREGISTERED') { - print( - 'FCM token is unregistered or invalid. Consider removing this token.'); - // Remove the unregistered token from your database here. - // Example: removeTokenFromDatabase(token); - } - } - } } } catch (e) { print('Error sending notification: $e'); } } - // void sendNotificationToDriverMAP(String title, String body, String token, - // List data, String tone) async { - // try { - // final response = await http.post( - // // Uri.parse( - // // 'https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send'), - // Uri.parse('https://fcm.googleapis.com/fcm/send'), - // headers: { - // 'Content-Type': 'application/json', - // // 'Authorization': 'Bearer 104815009508844392546' - // 'Authorization': 'key=${AK.serverAPI}' - // }, - // body: jsonEncode({ - // 'notification': { - // 'title': title, - // 'body': body, - // 'sound': tone - // }, - // 'data': { - // 'DriverList': data, - // }, - // 'priority': 'high', - // 'to': token, - // }), - // ); - - // if (response.statusCode == 200) { - // Log.print( - // 'Notification sent successfully. Status code: ${response.statusCode}'); - // Log.print('Response body: ${response.body}'); - // } else { - // Log.print( - // 'Failed to send notification. Status code: ${response.statusCode}'); - // Log.print('Response body: ${response.body}'); - // } - // } catch (e) { - // Log.print('Error sending notification: $e'); - // } - // } - - Future sendNotificationToDriverMAP(String title, String body, - String token, List data, String tone) async { + Future sendNotificationToDriverMAP( + String title, String body, String token, List data, String tone, + {int retryCount = 2}) async { try { String serviceAccountKeyJson = '''{ "type": "service_account", @@ -700,7 +648,7 @@ class FirebaseMessagesController extends GetxController { // Obtain an OAuth 2.0 access token final accessToken = await accessTokenManager.getAccessToken(); - Log.print('accessToken: ${accessToken}'); + // Log.print('accessToken: ${accessToken}'); // Send the notification final response = await http.post( @@ -721,11 +669,15 @@ class FirebaseMessagesController extends GetxController { 'DriverList': jsonEncode(data), }, 'android': { + 'priority': 'high', // Set priority to high 'notification': { 'sound': tone, }, }, 'apns': { + 'headers': { + 'apns-priority': '10', // Set APNs priority to 10 + }, 'payload': { 'aps': { 'sound': tone, @@ -735,105 +687,32 @@ class FirebaseMessagesController extends GetxController { }, }), ); + if (response.statusCode == 200) { - print('Notification sent successfully.'); - print('Response body: ${response.body}'); + print( + 'Notification sent successfully. Status code: ${response.statusCode}'); + print('Response token: ${token}'); } else { print( 'Failed to send notification. Status code: ${response.statusCode}'); print('Response body: ${response.body}'); - - // Parse the response body to handle specific errors like 'UNREGISTERED' - final responseBody = jsonDecode(response.body); - if (responseBody['error'] != null && - responseBody['error']['status'] == 'NOT_FOUND' && - responseBody['error']['details'] != null) { - for (var detail in responseBody['error']['details']) { - if (detail['errorCode'] == 'UNREGISTERED') { - print( - 'FCM token is unregistered or invalid. Consider removing this token.'); - // Remove the unregistered token from your database if needed - } - } + if (retryCount > 0) { + print('Retrying... Attempts remaining: $retryCount'); + await Future.delayed( + Duration(seconds: 2)); // Optional delay before retrying + return sendNotificationToDriverMAP(title, body, token, data, tone, + retryCount: retryCount - 1); } } } catch (e) { print('Error sending notification: $e'); - } - } - - void sendNotificationToDriverMapPolyline(String title, String body, - String token, List data, String polylineJson) async { - try { - String serviceAccountKeyJson = '''{ - "type": "service_account", - "project_id": "ride-b1bd8", - "private_key_id": "75e817c0b902db2ef35edf2c2bd159dec1f13249", - "private_key": "-----BEGIN PRIVATE KEY-----\\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD0zH9TQGDQHUv3\\na3/JAD1UKPwAp3wNKT0a6fxiIzjI3JxQWI30QvZCcfl6CdMhIcydX1ncSaYTcEeC\\n/AdPVCPkqyJx1YIGGg6P/mRzCWeaN8fsp6z250m5vcObDCZc3dbJEkepbep+6FPY\\n21m3KO+AHh1glgsTGZOTm5xiU8NGXpdk2QEh8wpiIIlR/HuKwVw9g8urNe3Sno+U\\nDm3z37iFqvZdmpqO8aWTJu6beb3hsREK9XK2I9JqC2JUwiGQRo3idOvPP6hkqrWx\\nKSX96vglQFYfakvJdDp2ZATOlpBYPMtS/IWhJ985u58TSS+Kl8qpnpaZBSxgJirf\\nhWzhnKLfAgMBAAECggEAJP785SePGhS7ZN6ltspm+l+hSjYFrPWFCxq+rlQ1YkHZ\\nC9l+RqKSFhOkiPmQI2s4wbXl3kFxLHHlFNoi/q2wKQBmGb8TQfnRJpjjNHGA61Ev\\n0Ue7/6qPvVb9B2MsLw/FxKiTFPuMG3bgKR9pbSFuJLYoaW7zqITOhVnYphGTqwAY\\nBVVcvISSLvELDmH9VZcv/9DVqVlqbbESHWh1Z4W6XGPoEqeDH/upNTyQQ/46Msgm\\nTGE6VqLHpWuSf6SqHp+r0Y0lI3vIPM1vz5FAJDJbOE/enHa0fSup0OHSMxl0HVMn\\nnO1yrGF3vsIPOej5HKr5d71bEIckzk73/yjNC1/mDQKBgQD7RtUvc9omsSsFMJ6e\\nBASAn6Dktx/QY/XNJjFzHQj69cywLDe5t5AL2gUi3phQ2oqB5XJdwnd5bTIEPEPZ\\nDOuOai2802p6FJk6kjmZAMVGx5JtXBH+vs6jrmQQSMiKbjwN1TT6xIWakvLOonUi\\nX6ZvjYYjU/E0YJU3jSiXWEr76wKBgQD5Zn4SouJ6BCDZMbausJVMBkk3qxsYooip\\np89WakC6e7AZinpkRcqjGGV9GOvc8crJs6fyXAA9ORepGP47Mc0ZrDssOkstznsM\\npr8R0S6MKwEZaT9ixOHdOcLZ47ps+JzA2Wr4KN2OvFHksUkB/46ATD1j9WZVgB8M\\namsYp/Y73QKBgHOo+PvsoZ9psVmkNX6abtAdqdtdB0HOoRea2uwXk0ig12TIFaZg\\nfedWpUKVnxqoXVTJHklV99RmlL0qWDiSH+LfsMnXro0e6iDxqZ1po2Se/CFmXcoa\\nXdctsFVmixhdATuExewfhTfPKABA+xWlXWC/jdy5CK+JPWXijaqMM4edAoGAE5Bj\\nsWiPpYyvWvpYX0nA3G7dzX0hqgQN/mkIjbnWDArp3IcNZNJIvBSM2Yxb7EAXbU0n\\njo6DAkp5Pa2VO+WDNlFZbvW/sf8xjeOCt44WPa6d7nVgIIpbQXRngZoopKW3/jTP\\n/FmQT8McFXmGxZ5belsAsdetSGW9icbLUerTGQ0CgYEAmf/G8Ag3XxmqTXvvHuv2\\n14OP7WnrVqkEMnydrftEwn4peXd/Lz+/GYX5Zc4ZoNgbN8IvZ5z0+OmRsallsbiW\\nBw0/tc68CjzxXOvReWxDluUopqWVGj5tlGqE5xUDku9SWJSxbkiQ3rqutzBdPXpr\\noqHwPyDrmK/Zgqn+uiIm4Ck=\\n-----END PRIVATE KEY-----\\n", - "client_email": "firebase-adminsdk-o2wqi@ride-b1bd8.iam.gserviceaccount.com", - "client_id": "111210077025005706623", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-o2wqi%40ride-b1bd8.iam.gserviceaccount.com", - "universe_domain": "googleapis.com" -} -'''; // As defined above - - // Initialize AccessTokenManager - final accessTokenManager = AccessTokenManager(serviceAccountKeyJson); - - // Obtain an OAuth 2.0 access token - final accessToken = await accessTokenManager.getAccessToken(); - - // Send the notification - final response = await http.post( - Uri.parse( - 'https://fcm.googleapis.com/v1/projects/ride-b1bd8/messages:send'), - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $accessToken', - }, - body: jsonEncode({ - 'notification': { - 'title': title, - 'body': body, - // 'sound': 'tone2.wav', - 'sound': 'order.wav' - }, - 'data': { - 'DriverList': data, - 'PolylineJson': polylineJson, - }, - 'priority': 'high', - 'to': token, - }), - ); - - if (response.statusCode == 200) { - print('Notification sent successfully.'); - print('Response body: ${response.body}'); - } else { - print( - 'Failed to send notification. Status code: ${response.statusCode}'); - print('Response body: ${response.body}'); - - // Parse the response body to handle specific errors like 'UNREGISTERED' - final responseBody = jsonDecode(response.body); - if (responseBody['error'] != null && - responseBody['error']['status'] == 'NOT_FOUND' && - responseBody['error']['details'] != null) { - for (var detail in responseBody['error']['details']) { - if (detail['errorCode'] == 'UNREGISTERED') { - print( - 'FCM token is unregistered or invalid. Consider removing this token.'); - // Remove the unregistered token from your database if needed - } - } - } + if (retryCount > 0) { + print('Retrying... Attempts remaining: $retryCount'); + await Future.delayed( + Duration(seconds: 2)); // Optional delay before retrying + return sendNotificationToDriverMAP(title, body, token, data, tone, + retryCount: retryCount - 1); } - } catch (e) { - // Handle other exceptions } } } diff --git a/lib/controller/firebase/local_notification.dart b/lib/controller/firebase/local_notification.dart index fc28a8e..fb2fc26 100644 --- a/lib/controller/firebase/local_notification.dart +++ b/lib/controller/firebase/local_notification.dart @@ -1,8 +1,14 @@ +import 'dart:async'; +import 'dart:io'; + import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:get/get.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/timezone.dart' as tz; +import '../../main.dart'; + class NotificationController extends GetxController { final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); @@ -11,53 +17,315 @@ class NotificationController extends GetxController { void onInit() { super.onInit(); initNotifications(); - tz.initializeTimeZones(); } // Initializes the local notifications plugin Future initNotifications() async { const AndroidInitializationSettings android = AndroidInitializationSettings('@mipmap/launcher_icon'); - const InitializationSettings initializationSettings = - InitializationSettings(android: android); + DarwinInitializationSettings ios = DarwinInitializationSettings( + requestAlertPermission: true, + requestBadgePermission: true, + requestSoundPermission: true, + onDidReceiveLocalNotification: + (int id, String? title, String? body, String? payload) async {}, + ); + InitializationSettings initializationSettings = + InitializationSettings(android: android, iOS: ios); await _flutterLocalNotificationsPlugin.initialize(initializationSettings); + + tz.initializeTimeZones(); + print('Notifications initialized'); } // Displays a notification with the given title and message void showNotification(String title, String message, String tone) async { - AndroidNotificationDetails android = AndroidNotificationDetails( - 'your channel id', 'your channel name', - importance: Importance.max, - priority: Priority.high, - showWhen: false, - sound: RawResourceAndroidNotificationSound(tone)); + final AndroidNotificationDetails android = AndroidNotificationDetails( + 'high_importance_channel', + 'High Importance Notifications', + importance: Importance.max, + priority: Priority.high, + showWhen: false, + sound: RawResourceAndroidNotificationSound(tone), + ); - NotificationDetails details = NotificationDetails(android: android); + const DarwinNotificationDetails ios = DarwinNotificationDetails( + sound: 'default', + presentAlert: true, + presentBadge: true, + presentSound: true, + ); + + final NotificationDetails details = + NotificationDetails(android: android, iOS: ios); await _flutterLocalNotificationsPlugin.show(0, title, message, details); + print('Notification shown: $title - $message'); + } +// /Users/hamzaaleghwairyeen/development/App/ride 2/lib/controller/firebase/local_notification.dart + +// Assume _flutterLocalNotificationsPlugin is initialized somewhere in your code + + // void scheduleNotificationsForSevenDays( + // String title, String message, String tone) async { + // final AndroidNotificationDetails android = AndroidNotificationDetails( + // 'high_importance_channel', + // 'High Importance Notifications', + // importance: Importance.max, + // priority: Priority.high, + // sound: RawResourceAndroidNotificationSound(tone), + // ); + + // const DarwinNotificationDetails ios = DarwinNotificationDetails( + // sound: 'default', + // presentAlert: true, + // presentBadge: true, + // presentSound: true, + // ); + + // final NotificationDetails details = + // NotificationDetails(android: android, iOS: ios); + + // // Check for the exact alarm permission on Android 12 and above + // if (Platform.isAndroid) { + // if (await Permission.scheduleExactAlarm.isDenied) { + // if (await Permission.scheduleExactAlarm.request().isGranted) { + // print('SCHEDULE_EXACT_ALARM permission granted'); + // } else { + // print('SCHEDULE_EXACT_ALARM permission denied'); + // return; + // } + // } + // } + + // // Schedule notifications for the next 7 days + // for (int day = 0; day < 7; day++) { + // // Schedule for 8:00 AM + // await _scheduleNotificationForTime( + // day, 8, 0, title, message, details, day * 1000 + 1); + + // // Schedule for 3:00 PM + // await _scheduleNotificationForTime( + // day, 15, 0, title, message, details, day * 1000 + 2); // Unique ID + + // // Schedule for 8:00 PM + // await _scheduleNotificationForTime( + // day, 20, 0, title, message, details, day * 1000 + 3); // Unique ID + // } + + // print('Notifications scheduled successfully for the next 7 days'); + // } + void scheduleNotificationsForSevenDays( + String title, String message, String tone) async { + final AndroidNotificationDetails android = AndroidNotificationDetails( + 'high_importance_channel', + 'High Importance Notifications', + importance: Importance.max, + priority: Priority.high, + sound: RawResourceAndroidNotificationSound(tone), + ); + + const DarwinNotificationDetails ios = DarwinNotificationDetails( + sound: 'default', + presentAlert: true, + presentBadge: true, + presentSound: true, + ); + + final NotificationDetails details = + NotificationDetails(android: android, iOS: ios); + + // Check for the exact alarm permission on Android 12 and above + if (Platform.isAndroid) { + if (await Permission.scheduleExactAlarm.isDenied) { + if (await Permission.scheduleExactAlarm.request().isGranted) { + print('SCHEDULE_EXACT_ALARM permission granted'); + } else { + print('SCHEDULE_EXACT_ALARM permission denied'); + return; + } + } + } + + // Schedule notifications for the next 7 days + for (int day = 0; day < 7; day++) { + // List of notification times + final notificationTimes = [ + {'hour': 8, 'minute': 0, 'id': day * 1000 + 1}, // 8:00 AM + {'hour': 15, 'minute': 0, 'id': day * 1000 + 2}, // 3:00 PM + {'hour': 20, 'minute': 0, 'id': day * 1000 + 3}, // 8:00 PM + ]; + + for (var time in notificationTimes) { + final notificationId = time['id'] as int; + + // Check if this notification ID is already stored + bool isScheduled = box.read('notification_$notificationId') ?? false; + + if (!isScheduled) { + // Schedule the notification if not already scheduled + await _scheduleNotificationForTime( + day, + time['hour'] as int, + time['minute'] as int, + title, + message, + details, + notificationId, + ); + + // Mark this notification ID as scheduled in GetStorage + box.write('notification_$notificationId', true); + } else { + print('Notification with ID $notificationId is already scheduled.'); + } + } + } + + print('Notifications scheduled successfully for the next 7 days'); } - // Schedules a notification for a specific time - Future scheduleNotification( - String title, String body, DateTime scheduledTime) async { - await _flutterLocalNotificationsPlugin.zonedSchedule( - 0, + void scheduleNotificationsForTimeSelected( + String title, String message, String tone, DateTime timeSelected) async { + final AndroidNotificationDetails android = AndroidNotificationDetails( + 'high_importance_channel', + 'High Importance Notifications', + importance: Importance.max, + priority: Priority.high, + sound: RawResourceAndroidNotificationSound(tone), + ); + + const DarwinNotificationDetails ios = DarwinNotificationDetails( + sound: 'default', + presentAlert: true, + presentBadge: true, + presentSound: true, + ); + + final NotificationDetails details = + NotificationDetails(android: android, iOS: ios); + + // Check for the exact alarm permission on Android 12 and above + if (Platform.isAndroid) { + if (await Permission.scheduleExactAlarm.isDenied) { + if (await Permission.scheduleExactAlarm.request().isGranted) { + print('SCHEDULE_EXACT_ALARM permission granted'); + } else { + print('SCHEDULE_EXACT_ALARM permission denied'); + return; + } + } + } + + // Schedule notifications for 10 and 30 minutes before the timeSelected + await _scheduleNotificationForTimeVIP( + timeSelected.subtract(const Duration(minutes: 10)), // 10 minutes before title, - body, - tz.TZDateTime.from(scheduledTime, tz.local), - const NotificationDetails( - android: AndroidNotificationDetails( - 'your_channel_id', - 'your_channel_name', - channelDescription: 'your_channel_description', - importance: Importance.max, - priority: Priority.high, - showWhen: false, - ), - ), - androidAllowWhileIdle: true, + message, + details, + 1, // Unique ID for 10-minute before notification + ); + + await _scheduleNotificationForTimeVIP( + timeSelected.subtract(const Duration(minutes: 30)), // 30 minutes before + title, + message, + details, + 2, // Unique ID for 30-minute before notification + ); + + print('Notifications scheduled successfully for the time selected'); + } + + Future _scheduleNotificationForTimeVIP( + DateTime scheduledDate, + String title, + String message, + NotificationDetails details, + int notificationId, + ) async { + // Initialize and set Cairo timezone + tz.initializeTimeZones(); + var cairoLocation = tz.getLocation('Africa/Cairo'); + + final now = tz.TZDateTime.now(cairoLocation); + + // Convert to Cairo time + tz.TZDateTime scheduledTZDateTime = + tz.TZDateTime.from(scheduledDate, cairoLocation); + + // Check if 10 minutes before the scheduled time is in the past + if (scheduledTZDateTime + .subtract(const Duration(minutes: 10)) + .isBefore(now)) { + // If the 10 minutes before the scheduled time is in the past, don't schedule + print( + 'Scheduled time minus 10 minutes is in the past. Skipping notification.'); + return; // Skip this notification + } + + print('Current time (Cairo): $now'); + print('Scheduling notification for: $scheduledTZDateTime'); + + await _flutterLocalNotificationsPlugin.zonedSchedule( + notificationId, // Unique ID for each notification + title, + message, + scheduledTZDateTime, + details, + androidScheduleMode: AndroidScheduleMode.exact, uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, - matchDateTimeComponents: DateTimeComponents.time, + matchDateTimeComponents: + null, // Don't repeat automatically; we handle manually ); + + print('Notification scheduled successfully for: $scheduledTZDateTime'); + } + + Future _scheduleNotificationForTime( + int dayOffset, + int hour, + int minute, + String title, + String message, + NotificationDetails details, + int notificationId, + ) async { + // Initialize and set Cairo timezone + tz.initializeTimeZones(); + var cairoLocation = tz.getLocation('Africa/Cairo'); + + final now = tz.TZDateTime.now(cairoLocation); + tz.TZDateTime scheduledDate = tz.TZDateTime( + cairoLocation, + now.year, + now.month, + now.day + dayOffset, // Add offset to schedule for the next days + hour, + minute, + ); + + // If the scheduled time is in the past, move it to the next day + if (scheduledDate.isBefore(now)) { + scheduledDate = scheduledDate.add(const Duration(days: 1)); + } + + print('Current time (Cairo): $now'); + print('Scheduling notification for: $scheduledDate'); + + await _flutterLocalNotificationsPlugin.zonedSchedule( + notificationId, // Unique ID for each notification + title, + message, + scheduledDate, + details, + androidScheduleMode: AndroidScheduleMode.exact, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, + matchDateTimeComponents: + null, // Don't repeat automatically; we handle 7 days manually + ); + + print('Notification scheduled successfully for: $scheduledDate'); } } diff --git a/lib/controller/functions/add_error.dart b/lib/controller/functions/add_error.dart new file mode 100644 index 0000000..320a996 --- /dev/null +++ b/lib/controller/functions/add_error.dart @@ -0,0 +1,19 @@ +import '../../constant/box_name.dart'; +import '../../constant/links.dart'; +import '../../main.dart'; +import 'crud.dart'; + +addError(String error, where) async { + CRUD().post(link: AppLink.addError, payload: { + 'error': error.toString(), // Example error description + 'userId': box.read(BoxName.driverID) ?? + box.read(BoxName.passengerID), // Example user ID + 'userType': box.read(BoxName.driverID) != null + ? 'Driver' + : 'passenger', // Example user type + 'phone': box.read(BoxName.phone) ?? + box.read(BoxName.phoneDriver), // Example phone number + + 'device': where + }); +} diff --git a/lib/controller/functions/crud.dart b/lib/controller/functions/crud.dart index fb79448..c309e9e 100644 --- a/lib/controller/functions/crud.dart +++ b/lib/controller/functions/crud.dart @@ -9,6 +9,8 @@ import 'package:SEFER/env/env.dart'; import '../../constant/api_key.dart'; import '../../print.dart'; +import '../../views/widgets/elevated_btn.dart'; +import 'add_error.dart'; import 'upload_image.dart'; class CRUD { @@ -28,8 +30,9 @@ class CRUD { 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials.toString()))}', }, ); - // Log.print('payload: ${payload}'); + Log.print('response.request: ${response.request}'); + Log.print('payload: ${payload}'); // Log.print('response.reasonPhrase: ${response.reasonPhrase}'); Log.print('response.body: ${response.body}'); @@ -43,7 +46,6 @@ class CRUD { return jsonData['status']; } - // } Future getTokenParent({ required String link, Map? payload, @@ -71,6 +73,73 @@ class CRUD { } } + Future sendWhatsAppAuth(String to, String token) async { + var res = await CRUD() + .get(link: AppLink.getApiKey, payload: {'keyName': 'whatsapp_key'}); + var accesstoken = jsonDecode(res)['message']['whatsapp_key']; + var headers = { + 'Authorization': 'Bearer $accesstoken', + 'Content-Type': 'application/json' + }; + + var url = 'https://graph.facebook.com/v20.0/${Env.whatappID}/messages'; + var request = http.Request('POST', Uri.parse(url)); + + var body = json.encode({ + "messaging_product": "whatsapp", + "to": to, + "type": "template", + "template": { + "name": "sefer1", + "language": {"code": "en"}, + "components": [ + { + "type": "body", + "parameters": [ + { + "type": "text", + "text": token, + } + ] + } + ] + } + }); + + request.body = body; + request.headers.addAll(headers); + + try { + print('Sending request to $url'); + print('Request headers: $headers'); + print('Request body: $body'); + + http.StreamedResponse response = await request.send(); + + if (response.statusCode == 200) { + String responseBody = await response.stream.bytesToString(); + print('Response: $responseBody'); + + Get.defaultDialog( + title: 'You will receive a code in WhatsApp Messenger'.tr, + middleText: 'wait 1 minute to recive message'.tr, + confirm: MyElevatedButton( + title: 'OK'.tr, + onPressed: () { + Get.back(); + }, + ), + ); + } else { + String errorBody = await response.stream.bytesToString(); + print('Error ${response.statusCode}: ${response.reasonPhrase}'); + print('Error body: $errorBody'); + } + } catch (e) { + print('Exception occurred: $e'); + } + } + Future getAgoraToken({ required String channelName, required String uid, @@ -215,36 +284,83 @@ class CRUD { } else {} } + // Future post({ + // required String link, + // Map? payload, + // }) async { + // // String? basicAuthCredentials = + // // await storage.read(key: BoxName.basicAuthCredentials); + // var url = Uri.parse( + // link, + // ); + // var response = await http.post( + // url, + // body: payload, + // headers: { + // "Content-Type": "application/x-www-form-urlencoded", + // 'Authorization': + // 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}', + // }, + // ); + // Log.print('payload: ${payload}'); + // Log.print('response.request: ${response.request}'); + // Log.print('response.body: ${response.body}'); + // var jsonData = jsonDecode(response.body); + // if (response.statusCode == 200) { + // if (jsonData['status'] == 'success') { + // return response.body; + // } else { + // return (jsonData['status']); + // } + // } else { + // return response.statusCode; + // } + // } Future post({ required String link, Map? payload, }) async { - // String? basicAuthCredentials = - // await storage.read(key: BoxName.basicAuthCredentials); - var url = Uri.parse( - link, - ); - var response = await http.post( - url, - body: payload, - headers: { - "Content-Type": "application/x-www-form-urlencoded", - 'Authorization': - 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}', - }, - ); - // Log.print('payload: ${payload}'); - // Log.print('response.request: ${response.request}'); - Log.print('response.body: ${response.body}'); - var jsonData = jsonDecode(response.body); - if (response.statusCode == 200) { - if (jsonData['status'] == 'success') { - return response.body; + var url = Uri.parse(link); + try { + var response = await http.post( + url, + body: payload, + headers: { + "Content-Type": "application/x-www-form-urlencoded", + 'Authorization': + 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}', + }, + ); + + Log.print('Response.request: ${response.request}'); + Log.print('Payload: $payload'); + // Log.print('Response.statusCode: ${response.statusCode}'); + Log.print('Response.body: ${response.body}'); + + if (response.statusCode == 200) { + try { + var jsonData = jsonDecode(response.body); + + if (jsonData['status'] == 'success') { + return jsonData; + } else { + return jsonData['status']; + } + } catch (e) { + Log.print('JSON parsing error: $e'); + addError(e.toString(), 'crud().post'); + return 'failure'; // Return a recognizable failure string for JSON errors + } } else { - return (jsonData['status']); + Log.print('Non-200 response code: ${response.statusCode}'); + addError( + 'Non-200 response code: ${response.statusCode}', 'crud().post'); + return 'failure'; // Handle unexpected status codes as failures } - } else { - return response.statusCode; + } catch (e) { + Log.print('HTTP request error: $e'); + addError('HTTP request error: $e', 'crud().post'); + return 'failure'; // Handle HTTP request errors as failures } } @@ -393,6 +509,26 @@ class CRUD { return (jsonData['status']); } + Future getHereMap({ + required String link, + }) async { + var url = Uri.parse(link); + try { + var response = await http.get(url); + + if (response.statusCode == 200) { + // Ensure the response body is decoded as UTF-8 + var decodedBody = utf8.decode(response.bodyBytes); + var data = jsonDecode(decodedBody); + return data; + } else { + return null; + } + } catch (e) { + return null; + } + } + Future update({ required String endpoint, required Map data, diff --git a/lib/controller/functions/package_info.dart b/lib/controller/functions/package_info.dart index dc8edc2..bfcef9c 100644 --- a/lib/controller/functions/package_info.dart +++ b/lib/controller/functions/package_info.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:SEFER/constant/links.dart'; import 'package:SEFER/controller/functions/crud.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -17,7 +16,7 @@ Future checkForUpdate(BuildContext context) async { final version = packageInfo.version; print('currentVersion is : $currentVersion'); // Fetch the latest version from your server - String latestVersion = await getPackageInfo(); + String latestVersion = box.read(BoxName.package); box.write(BoxName.packagInfo, version); if (latestVersion.isNotEmpty && latestVersion != currentVersion) { @@ -25,18 +24,22 @@ Future checkForUpdate(BuildContext context) async { } } -Future getPackageInfo() async { - final response = await CRUD().get(link: AppLink.packageInfo, payload: { - "platform": Platform.isAndroid ? 'android' : 'ios', - "appName": AppInformation.appName, - }); - - if (response != 'failure') { - return jsonDecode(response)['message'][0]['version']; - } - return ''; +checkForBounusInvitation() { + if (box.read(BoxName.inviteCode) != null) {} } +// Future getPackageInfo() async { +// final response = await CRUD().get(link: AppLink.packageInfo, payload: { +// "platform": Platform.isAndroid ? 'android' : 'ios', +// "appName": AppInformation.appName, +// }); + +// if (response != 'failure') { +// return jsonDecode(response)['message'][0]['version']; +// } +// return ''; +// } + void showUpdateDialog(BuildContext context) { final String storeUrl = Platform.isAndroid ? 'https://play.google.com/store/apps/details?id=com.mobileapp.store.ride' @@ -65,10 +68,11 @@ void showUpdateDialog(BuildContext context) { }, ), CupertinoDialogAction( - child: Text('Cancel'.tr), - onPressed: () { - Navigator.of(context).pop(); - }) + child: Text('Cancel'.tr), + onPressed: () async { + Navigator.of(context).pop(); + }, + ), ], ); }, diff --git a/lib/controller/functions/sms_controller.dart b/lib/controller/functions/sms_controller.dart index d63defe..09b0b63 100644 --- a/lib/controller/functions/sms_controller.dart +++ b/lib/controller/functions/sms_controller.dart @@ -11,6 +11,7 @@ import 'package:SEFER/views/widgets/elevated_btn.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; +import '../../print.dart'; import '../auth/register_controller.dart'; import 'crud.dart'; @@ -29,12 +30,12 @@ class SmsEgyptController extends GetxController { Future sendSmsEgypt(String phone, otp) async { String sender = await getSender(); var body = jsonEncode({ - "username": AppInformation.appName, + "username": 'Sefer', "password": AK.smsPasswordEgypt, "message": "${AppInformation.appName} app code is $otp\ncopy it to app", "language": box.read(BoxName.lang) == 'en' ? "e" : 'r', "sender": sender, //"Sefer Egy", - "receiver": "2$phone" + "receiver": phone }); var res = await http.post( diff --git a/lib/controller/functions/tts.dart b/lib/controller/functions/tts.dart index c541c4f..5bd1b6a 100644 --- a/lib/controller/functions/tts.dart +++ b/lib/controller/functions/tts.dart @@ -1,49 +1,50 @@ import 'package:SEFER/constant/box_name.dart'; -import 'package:SEFER/main.dart'; import 'package:flutter/material.dart'; import 'package:flutter_tts/flutter_tts.dart'; import 'package:get/get.dart'; +import '../../main.dart'; + class TextToSpeechController extends GetxController { final flutterTts = FlutterTts(); - // Initialize TTS in initState @override void onInit() { super.onInit(); initTts(); } - // Dispose of TTS when controller is closed @override void onClose() { + flutterTts.stop(); // Stop any ongoing TTS super.onClose(); - flutterTts.completionHandler; } - // Function to initialize TTS engine + // Initialize TTS engine with language check Future initTts() async { - String? lang = - WidgetsBinding.instance.platformDispatcher.locale.countryCode; - await flutterTts - .setLanguage(box.read(BoxName.lang).toString()); //'en-US' Set language - // await flutterTts.setLanguage('ar-SA'); //'en-US' Set language - // await flutterTts.setLanguage(lang!); //'en-US' Set language - await flutterTts.setSpeechRate(0.5); // Adjust speech rate - await flutterTts.setVolume(1.0); // Set volume + try { + String langCode = box.read(BoxName.lang) ?? 'en-US'; + bool isAvailable = await flutterTts.isLanguageAvailable(langCode); + + // If language is unavailable, default to 'en-US' + if (!isAvailable) { + langCode = 'en-US'; + } + + await flutterTts.setLanguage(langCode); + await flutterTts.setSpeechRate(0.5); // Adjust speech rate + await flutterTts.setVolume(1.0); // Set volume + } catch (error) { + Get.snackbar('Error', 'Failed to initialize TTS: $error'); + } } // Function to speak the given text Future speakText(String text) async { try { await flutterTts.awaitSpeakCompletion(true); - var result = await flutterTts.speak(text); - if (result == 1) { - // TTS operation has started - // You can perform additional operations here, if needed - } + await flutterTts.speak(text); } catch (error) { - // Handle error gracefully, e.g., show a message Get.snackbar('Error', 'Failed to speak text: $error'); } } diff --git a/lib/controller/home/blinking_promo_controller.dart.dart b/lib/controller/home/blinking_promo_controller.dart.dart new file mode 100644 index 0000000..df0bc82 --- /dev/null +++ b/lib/controller/home/blinking_promo_controller.dart.dart @@ -0,0 +1,91 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../constant/links.dart'; +import '../../views/widgets/elevated_btn.dart'; +import '../functions/crud.dart'; + +class BlinkingController extends GetxController { + final promoFormKey = GlobalKey(); + + final promo = TextEditingController(); + bool promoTaken = false; + void applyPromoCodeToPassenger() async { + //TAWJIHI + if (promoFormKey.currentState!.validate()) { + CRUD().get(link: AppLink.getPassengersPromo, payload: { + 'promo_code': promo.text, + }).then((value) { + if (value == 'failure') { + Get.defaultDialog( + title: 'Promo End !'.tr, + confirm: MyElevatedButton( + title: 'Back', + onPressed: () { + Get.back(); + Get.back(); + }, + )); + } + var decode = jsonDecode(value); + + // if (decode["status"] == "success") { + // var firstElement = decode["message"][0]; + // if (double.parse(box.read(BoxName.passengerWalletTotal)) < 0) { + // totalPassenger = totalCostPassenger - + // (totalCostPassenger * int.parse(firstElement['amount']) / 100); + // update(); + // } else { + // totalPassenger = totalCostPassenger - + // (totalCostPassenger * int.parse(firstElement['amount']) / 100); + // update(); + // } + + // totalDriver = totalDriver - + // (totalDriver * int.parse(firstElement['amount']) / 100); + // promoTaken = true; + // update(); + // Get.back(); + // } + }); + } + } + + // Reactive variable for blinking (on/off) + var isLightOn = false.obs; + + // To animate the border color + var borderColor = Colors.black.obs; + + Timer? _blinkingTimer; + + // Method to start blinking for 5 seconds + void startBlinking() { + int count = 0; + + _blinkingTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + // Toggle light on/off + isLightOn.value = !isLightOn.value; + borderColor.value = isLightOn.value + ? Colors.yellow + : Colors.black; // Animate border color + + count++; + + // Stop blinking after 5 seconds + if (count >= 35) { + timer.cancel(); + isLightOn.value = false; // Ensure light turns off + borderColor.value = Colors.black; // Reset the border color + } + }); + } + + @override + void onClose() { + _blinkingTimer?.cancel(); + super.onClose(); + } +} diff --git a/lib/controller/home/contact_us_controller.dart b/lib/controller/home/contact_us_controller.dart new file mode 100644 index 0000000..afc879f --- /dev/null +++ b/lib/controller/home/contact_us_controller.dart @@ -0,0 +1,78 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_font_icons/flutter_font_icons.dart'; +import 'package:get/get.dart'; + +import '../../../constant/colors.dart'; +import '../functions/launch.dart'; + +class ContactUsController extends GetxController { + final String phone1 = '+201018805430'; + final String phone2 = '+201080182934'; + final TimeOfDay workStartTime = const TimeOfDay(hour: 12, minute: 0); + final TimeOfDay workEndTime = const TimeOfDay(hour: 19, minute: 0); + + bool _isWithinWorkTime(TimeOfDay now) { + return (now.hour > workStartTime.hour || + (now.hour == workStartTime.hour && + now.minute >= workStartTime.minute)) && + (now.hour < workEndTime.hour || + (now.hour == workEndTime.hour && now.minute <= workEndTime.minute)); + } + + void showContactDialog(BuildContext context) { + TimeOfDay now = TimeOfDay.now(); + + showCupertinoModalPopup( + context: context, + builder: (context) => CupertinoActionSheet( + title: Text('Contact Us'.tr), + message: Text('Choose a contact option'.tr), + actions: [ + if (_isWithinWorkTime(now)) + CupertinoActionSheetAction( + child: Text(phone1), + onPressed: () => makePhoneCall( + phone1, + ), + ), + if (_isWithinWorkTime(now)) + CupertinoActionSheetAction( + child: Text(phone2), + onPressed: () => makePhoneCall(phone2), + ), + if (!_isWithinWorkTime(now)) + CupertinoActionSheetAction( + child: Text( + 'Work time is from 12:00 - 19:00.\nYou can send a WhatsApp message or email.' + .tr), + onPressed: () => Navigator.pop(context), + ), + CupertinoActionSheetAction( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const Icon( + FontAwesome.whatsapp, + color: AppColor.greenColor, + ), + Text('Send WhatsApp Message'.tr), + ], + ), + onPressed: () => + launchCommunication('whatsapp', phone1, 'Hello'.tr), + ), + CupertinoActionSheetAction( + child: Text('Send Email'.tr), + onPressed: () => + launchCommunication('email', 'support@sefer.live', 'Hello'.tr), + ), + ], + cancelButton: CupertinoActionSheetAction( + child: Text('Cancel'.tr), + onPressed: () => Navigator.pop(context), + ), + ), + ); + } +} diff --git a/lib/controller/home/map_passenger_controller.dart b/lib/controller/home/map_passenger_controller.dart index 47af48b..bf3e671 100644 --- a/lib/controller/home/map_passenger_controller.dart +++ b/lib/controller/home/map_passenger_controller.dart @@ -1,7 +1,13 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:math' show Random, cos, pi, pow, sin, sqrt; +import 'dart:math' show Random, cos, max, min, pi, pow, sin, sqrt; import 'dart:math' as math; +import 'dart:ui'; +import 'package:SEFER/constant/univeries_polygon.dart'; +import 'package:SEFER/controller/firebase/local_notification.dart'; +import 'package:SEFER/views/widgets/mysnakbar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_confetti/flutter_confetti.dart'; import 'package:vector_math/vector_math.dart' show radians, degrees; import 'package:SEFER/controller/functions/tts.dart'; @@ -26,6 +32,7 @@ import '../../constant/links.dart'; import '../../constant/table_names.dart'; import '../../main.dart'; import '../../models/model/locations.dart'; +import '../../models/model/painter_copoun.dart'; import '../../print.dart'; import '../../views/home/map_widget.dart/car_details_widget_to_go.dart'; import '../../views/home/map_widget.dart/select_driver_mishwari.dart'; @@ -36,6 +43,7 @@ import '../functions/crud.dart'; import '../functions/launch.dart'; import '../functions/secure_storage.dart'; import '../payment/payment_controller.dart'; +import 'vip_waitting_page.dart'; class MapPassengerController extends GetxController { bool isLoading = true; @@ -51,6 +59,7 @@ class MapPassengerController extends GetxController { TextEditingController whatsAppLocationText = TextEditingController(); TextEditingController messageToDriver = TextEditingController(); final sosFormKey = GlobalKey(); + final promoFormKey = GlobalKey(); final messagesFormKey = GlobalKey(); final increaseFeeFormKey = GlobalKey(); List data = []; @@ -137,6 +146,7 @@ class MapPassengerController extends GetxController { double naturePrice = 0; bool heightMenuBool = false; String statusRide = 'wait'; + String statusRideVip = 'wait'; bool statusRideFromStart = false; bool isPickerShown = false; bool isPointsPageForRider = false; @@ -258,7 +268,7 @@ class MapPassengerController extends GetxController { void getCurrentLocationFormString() async { currentLocationToFormPlaces = true; currentLocationString = 'Waiting for your location'.tr; - getLocation(); + await getLocation(); currentLocationString = passengerLocation.toString(); newStartPointLocation = passengerLocation; update(); @@ -358,13 +368,46 @@ class MapPassengerController extends GetxController { "order_id": rideId.toString(), // Convert to String "status": 'waiting' }); + if (AppLink.endPoint != AppLink.seferCairoServer) { + CRUD().post( + link: "${AppLink.endPoint}/ride/driver_order/update.php", + payload: { + "order_id": rideId.toString(), // Convert to String + "status": 'waiting' + }); + } await CRUD().post(link: AppLink.updateRides, payload: { "id": rideId.toString(), // Convert to String "status": 'waiting' }); + if (AppLink.endPoint != AppLink.seferCairoServer) { + CRUD().post( + link: "${AppLink.endPoint}/ride/rides/update.php", + payload: { + "id": rideId.toString(), // Convert to String + "status": 'waiting' + }); + } + CRUD().post(link: AppLink.updateWaitingTrip, payload: { + "id": rideId.toString(), // Convert to String + "status": 'wait' + }); + if (AppLink.endPoint != AppLink.seferCairoServer) { + CRUD().post( + link: + "${AppLink.endPoint}/ride/notificationCaptain/updateWaitingTrip.php", + payload: { + "id": rideId.toString(), // Convert to String + "status": 'wait' + }); + } tick = 0; } - confirmRideForAllDriverAvailable(); + await getCarsLocationByPassengerAndReloadMarker( + box.read(BoxName.carType), 4000); + // confirmRideForAllDriverAvailable(); + + increaseForSameRideAndDelay(); } } } @@ -430,9 +473,12 @@ class MapPassengerController extends GetxController { hintTextDestinationPoint = 'Search for your destination'.tr; update(); } else { - hintTextDestinationPoint = placesDestination[index]['name']; - double lat = placesDestination[index]['geometry']['location']['lat']; - double lng = placesDestination[index]['geometry']['location']['lng']; + hintTextDestinationPoint = placesDestination[index]['title']; + // hintTextDestinationPoint = placesDestination[index]['name']; + // double lat = placesDestination[index]['geometry']['location']['lat']; + double lat = placesDestination[index]['position']['lat']; + double lng = placesDestination[index]['position']['lng']; + // double lng = placesDestination[index]['geometry']['location']['lng']; newMyLocation = LatLng(lat, lng); update(); @@ -500,7 +546,7 @@ class MapPassengerController extends GetxController { // Format the message. String message = - 'Hi! This is ${box.read(BoxName.name)}.\n I am using ${box.read(AppInformation.appName)} to ride with $firstName as the driver. $firstName \nis driving a $model\n with license plate $licensePlate.\n I am currently located at $passengerLocation.\n If you need to reach me, please contact the driver directly at\n\n $driverPhone.'; + 'Hi! This is ${box.read(BoxName.name)}.\n I am using ${box.read(AppInformation.appName)} to ride with $passengerName as the driver. $passengerName \nis driving a $model\n with license plate $licensePlate.\n I am currently located at $passengerLocation.\n If you need to reach me, please contact the driver directly at\n\n $driverPhone.'; // Launch the URL to send the SMS. launchCommunication('sms', to, message); @@ -512,7 +558,7 @@ class MapPassengerController extends GetxController { // Format the message. String message = - '${'${'Hi! This is'.tr} ${box.read(BoxName.name)}.\n${' I am using'.tr}'} ${AppInformation.appName}${' to ride with'.tr} $firstName${' as the driver.'.tr} $firstName \n${'is driving a '.tr}$model\n${' with license plate '.tr}$licensePlate.\n${' I am currently located at '.tr} https://www.google.com/maps/place/${passengerLocation.latitude},${passengerLocation.longitude}.\n${' If you need to reach me, please contact the driver directly at'.tr}\n\n $driverPhone.'; + '${'${'Hi! This is'.tr} ${box.read(BoxName.name)}.\n${' I am using'.tr}'} ${AppInformation.appName}${' to ride with'.tr} $passengerName${' as the driver.'.tr} $passengerName \n${'is driving a '.tr}$model\n${' with license plate '.tr}$licensePlate.\n${' I am currently located at '.tr} https://www.google.com/maps/place/${passengerLocation.latitude},${passengerLocation.longitude}.\n${' If you need to reach me, please contact the driver directly at'.tr}\n\n $driverPhone.'; // Launch the URL to send the WhatsApp message. launchCommunication('whatsapp', to, message); @@ -544,44 +590,134 @@ class MapPassengerController extends GetxController { return distance2; } + // bool isTimerFromDriverToPassengerAfterAppliedRunning = true; + // int beginRideInterval = 4; // Interval in seconds for getBeginRideFromDriver + + // void startTimerFromDriverToPassengerAfterApplied() async { + // int secondsElapsed = 0; + + // while (secondsElapsed <= timeToPassengerFromDriverAfterApplied && + // isTimerFromDriverToPassengerAfterAppliedRunning) { + // await Future.delayed(const Duration(seconds: 1)); + // secondsElapsed++; + + // progressTimerToPassengerFromDriverAfterApplied = + // secondsElapsed / timeToPassengerFromDriverAfterApplied; + // remainingTimeToPassengerFromDriverAfterApplied = + // timeToPassengerFromDriverAfterApplied - secondsElapsed; + + // if (remainingTimeToPassengerFromDriverAfterApplied < 59) { + // if (rideTimerBegin == false) { + // rideTimerBegin = true; + // } + // } + + // // Call getBeginRideFromDriver every 4 seconds + // if (secondsElapsed % beginRideInterval == 0) { + // getBeginRideFromDriver(); + // uploadPassengerLocation(); + // } + + // int minutes = + // (remainingTimeToPassengerFromDriverAfterApplied / 60).floor(); + // int seconds = remainingTimeToPassengerFromDriverAfterApplied % 60; + // stringRemainingTimeToPassenger = + // '$minutes:${seconds.toString().padLeft(2, '0')}'; + + // update(); + // } + // } + + StreamController _timerStreamController = StreamController(); + Stream get timerStream => _timerStreamController.stream; bool isTimerFromDriverToPassengerAfterAppliedRunning = true; + bool isTimerRunning = false; // Flag to track if the timer is running int beginRideInterval = 4; // Interval in seconds for getBeginRideFromDriver - void startTimerFromDriverToPassengerAfterApplied() async { + void startTimerFromDriverToPassengerAfterApplied() { + if (isTimerRunning) return; // Prevent duplicate streams + isTimerRunning = true; + int secondsElapsed = 0; - while (secondsElapsed <= timeToPassengerFromDriverAfterApplied && - isTimerFromDriverToPassengerAfterAppliedRunning) { - await Future.delayed(const Duration(seconds: 1)); - secondsElapsed++; + // Start the stream + Timer.periodic(const Duration(seconds: 1), (timer) { + if (secondsElapsed > timeToPassengerFromDriverAfterApplied || + !isTimerFromDriverToPassengerAfterAppliedRunning) { + timer.cancel(); + isTimerRunning = false; + _timerStreamController.close(); // Close the stream when done + return; + } + secondsElapsed++; + _timerStreamController.add(secondsElapsed); // Emit elapsed time + + // Calculate progress and remaining time progressTimerToPassengerFromDriverAfterApplied = secondsElapsed / timeToPassengerFromDriverAfterApplied; remainingTimeToPassengerFromDriverAfterApplied = timeToPassengerFromDriverAfterApplied - secondsElapsed; - if (remainingTimeToPassengerFromDriverAfterApplied < 59) { - if (rideTimerBegin == false) { - rideTimerBegin = true; - } - } - - // Call getBeginRideFromDriver every 4 seconds - if (secondsElapsed % beginRideInterval == 0) { - getBeginRideFromDriver(); - uploadPassengerLocation(); - } - + // Update remaining time as string int minutes = (remainingTimeToPassengerFromDriverAfterApplied / 60).floor(); int seconds = remainingTimeToPassengerFromDriverAfterApplied % 60; stringRemainingTimeToPassenger = '$minutes:${seconds.toString().padLeft(2, '0')}'; - update(); - } + if (remainingTimeToPassengerFromDriverAfterApplied < 59 && + !rideTimerBegin) { + rideTimerBegin = true; + } + + // Call periodic functions + if (secondsElapsed % beginRideInterval == 0) { + getBeginRideFromDriver(); + uploadPassengerLocation(); + } + + update(); // Notify listeners + }); } + // void startTimerFromDriverToPassengerAfterApplied() async { + // if (isTimerRunning) return; // Exit if timer is already running + // isTimerRunning = true; // Set the flag to true + + // int secondsElapsed = 0; + // while (secondsElapsed <= timeToPassengerFromDriverAfterApplied && + // isTimerFromDriverToPassengerAfterAppliedRunning) { + // await Future.delayed(const Duration(seconds: 1)); + // secondsElapsed++; + + // progressTimerToPassengerFromDriverAfterApplied = + // secondsElapsed / timeToPassengerFromDriverAfterApplied; + // remainingTimeToPassengerFromDriverAfterApplied = + // timeToPassengerFromDriverAfterApplied - secondsElapsed; + + // if (remainingTimeToPassengerFromDriverAfterApplied < 59) { + // if (rideTimerBegin == false) { + // rideTimerBegin = true; + // } + // } + + // // Call getBeginRideFromDriver every 4 seconds + // if (secondsElapsed % beginRideInterval == 0) { + // getBeginRideFromDriver(); + // uploadPassengerLocation(); + // } + + // int minutes = + // (remainingTimeToPassengerFromDriverAfterApplied / 60).floor(); + // int seconds = remainingTimeToPassengerFromDriverAfterApplied % 60; + // stringRemainingTimeToPassenger = + // '$minutes:${seconds.toString().padLeft(2, '0')}'; + + // update(); + // } + // isTimerRunning = false; // Reset the flag when timer completes + // } // Remove the getBeginRideFromDriverForDuration function as it's no longer needed // Function to stop the timer @@ -640,8 +776,16 @@ class MapPassengerController extends GetxController { await Future.delayed(const Duration(seconds: 1)); progressTimerRideBegin = i / durationToRide; remainingTimeTimerRideBegin = durationToRide - i; + if (i == (durationToRide / 4).round() && (statusRide == 'Begin')) { + NotificationController().showNotification("Record Your Trip".tr, + "You can call or record audio during this trip.".tr, 'tone1'); + } bool sendSOS = false; if (speed > 100 && sendSOS == false) { + NotificationController().showNotification( + "Warning: Speeding detected!".tr, + 'You can call or record audio of this trip'.tr, + 'tone1'); Get.defaultDialog( barrierDismissible: false, title: "Warning: Speeding detected!".tr, @@ -661,7 +805,7 @@ class MapPassengerController extends GetxController { // Get trip details from GetX or relevant provider String origin = passengerLocation.toString(); String destination = myDestination.toString(); - String driverName = firstName; + String driverName = passengerName; String driverCarPlate = licensePlate; // Add trip details to the message @@ -707,12 +851,95 @@ class MapPassengerController extends GetxController { // update(); } - void tripFinishedFromDriver() async { + int progressTimerRideBeginVip = 0; + int elapsedTimeInSeconds = 0; // Timer starts from 0 + String stringElapsedTimeRideBegin = '0:00'; + String stringElapsedTimeRideBeginVip = '0:00'; + bool rideInProgress = true; // To control when to stop the timer + + void rideIsBeginPassengerTimerVIP() async { + rideInProgress = true; // Start the ride timer + bool sendSOS = false; + while (rideInProgress) { + await Future.delayed(const Duration(seconds: 1)); + + // Increment elapsed time + elapsedTimeInSeconds++; + + // Update the time display + int minutes = (elapsedTimeInSeconds / 60).floor(); + int seconds = elapsedTimeInSeconds % 60; + stringElapsedTimeRideBeginVip = + '$minutes:${seconds.toString().padLeft(2, '0')}'; + + // Check for speed and SOS conditions + if (speed > 100 && !sendSOS) { + Get.defaultDialog( + barrierDismissible: false, + title: "Warning: Speeding detected!".tr, + titleStyle: AppStyle.title, + content: Text( + "We noticed the speed is exceeding 100 km/h. Please slow down for your safety. If you feel unsafe, you can share your trip details with a contact or call the police using the red SOS button." + .tr, + style: AppStyle.title, + ), + confirm: MyElevatedButton( + title: "Share Trip Details".tr, + onPressed: () { + Get.back(); + // Implement sharing trip details logic here + String message = "**Emergency SOS from Passenger:**\n"; + + // Get trip details from GetX or relevant provider + String origin = passengerLocation.toString(); + String destination = myDestination.toString(); + String driverName = passengerName; + String driverCarPlate = licensePlate; + + // Add trip details to the message + message += "* ${'Origin'.tr}: $origin\n"; + message += "* ${'Destination'.tr}: $destination\n"; + message += "* ${'Driver Name'.tr}: $driverName\n"; + message += "* ${'Driver Car Plate'.tr}: $driverCarPlate\n\n"; + message += "* ${'Driver Phone'.tr}: $driverPhone\n\n"; + + // Add current location + message += + "${'Current Location'.tr}:https://www.google.com/maps/place/${passengerLocation.latitude},${passengerLocation.longitude} \n"; + + // Append a call to action + message += "Please help! Contact me as soon as possible.".tr; + + // Launch WhatsApp communication + launchCommunication( + 'whatsapp', box.read(BoxName.sosPhonePassenger), message); + sendSOS = true; + }, + kolor: AppColor.redColor, + ), + cancel: MyElevatedButton( + title: "Cancel".tr, + onPressed: () { + Get.back(); + }, + kolor: AppColor.greenColor, + ), + ); + } + + // Update the UI + update(); + } + } + + void tripFinishedFromDriver() { isRideFinished = true; rideTimerBegin = false; + statusRideVip = 'Finished'; box.write(BoxName.arrivalTime, ''); remainingTimeTimerRideBegin = 0; box.write(BoxName.passengerWalletTotal, '0'); + update(); if (box.read(BoxName.parentTripSelected) == true) { FirebaseMessagesController().sendNotificationToPassengerToken( "Finish Monitor".tr, @@ -724,34 +951,116 @@ class MapPassengerController extends GetxController { box.write(BoxName.parentTripSelected, false); box.remove(BoxName.tokenParent); } - update(); } - void getBeginRideFromDriver() async { - try { - var res = await CRUD() - .get(link: AppLink.getRideStatusBegin, payload: {'ride_id': rideId}); - if (res != 'failure') { - var decode = jsonDecode(res); + // bool isBeginRideFromDriver = false; + // void getBeginRideFromDriver() async { + // try { + // if (isBeginRideFromDriver) return; // Prevent duplicate streams + // isBeginRideFromDriver = true; + // var res = await CRUD() + // .get(link: AppLink.getRideStatusBegin, payload: {'ride_id': rideId}); + // if (res != 'failure') { + // var decode = jsonDecode(res); - if (decode['data']['status'] == 'Begin') { - timeToPassengerFromDriverAfterApplied = 0; - remainingTime = 0; - remainingTimeToPassengerFromDriverAfterApplied = 0; - remainingTimeDriverWaitPassenger5Minute = 0; - rideTimerBegin = true; - statusRide = 'Begin'; - isDriverInPassengerWay = false; - isDriverArrivePassenger = false; - update(); - // isCancelRidePageShown = true; - rideIsBeginPassengerTimer(); - runWhenRideIsBegin(); + // // if (decode['data']['status'] != 'Apply') { + // if (decode['data']['status'] == 'Begin') { + // timeToPassengerFromDriverAfterApplied = 0; + // remainingTime = 0; + // remainingTimeToPassengerFromDriverAfterApplied = 0; + // remainingTimeDriverWaitPassenger5Minute = 0; + // rideTimerBegin = true; + // statusRide = 'Begin'; + // isDriverInPassengerWay = false; + // isDriverArrivePassenger = false; + // update(); + // // isCancelRidePageShown = true; + // rideIsBeginPassengerTimer(); + // runWhenRideIsBegin(); + // } else {} + // } + // } catch (e) { + // // Handle the error or perform any necessary actions + // } + // } + + StreamController _beginRideStreamController = + StreamController.broadcast(); + Stream get beginRideStream => _beginRideStreamController.stream; + + bool isBeginRideFromDriverRunning = false; + + void getBeginRideFromDriver() { + if (isBeginRideFromDriverRunning) return; // Prevent duplicate streams + isBeginRideFromDriverRunning = true; + + Timer.periodic(const Duration(seconds: 1), (timer) async { + try { + var res = await CRUD().get( + link: AppLink.getRideStatusBegin, payload: {'ride_id': rideId}); + print(res); + print('1002'); + if (res != 'failure') { + var decode = jsonDecode(res); + _beginRideStreamController + .add(decode['data']['status']); // Emit the status + + if (decode['data']['status'] == 'Begin') { + // Stop the periodic check + timer.cancel(); + isBeginRideFromDriverRunning = false; + + timeToPassengerFromDriverAfterApplied = 0; + remainingTime = 0; + remainingTimeToPassengerFromDriverAfterApplied = 0; + remainingTimeDriverWaitPassenger5Minute = 0; + rideTimerBegin = true; + statusRide = 'Begin'; + isDriverInPassengerWay = false; + isDriverArrivePassenger = false; + update(); + + // Trigger additional actions + rideIsBeginPassengerTimer(); + runWhenRideIsBegin(); + NotificationController().showNotification( + 'Trip is begin'.tr, + 'The trip has started! Feel free to contact emergency numbers, share your trip, or activate voice recording for the journey' + .tr, + 'ding'); + } } + } catch (e) { + // Handle errors + _beginRideStreamController.addError(e); } - } catch (e) { - // Handle the error or perform any necessary actions - } + }); + } + +// Call this method to listen to the stream + void listenToBeginRideStream() { + beginRideStream.listen((status) { + print("Ride status: $status"); + // Perform additional actions based on the status + }, onError: (error) { + print("Error in Begin Ride Stream: $error"); + }); + } + + begiVIPTripFromPassenger() async { + timeToPassengerFromDriverAfterApplied = 0; + remainingTime = 0; + isBottomSheetShown = false; + remainingTimeToPassengerFromDriverAfterApplied = 0; + remainingTimeDriverWaitPassenger5Minute = 0; + rideTimerBegin = true; + statusRideVip = 'Begin'; + isDriverInPassengerWay = false; + isDriverArrivePassenger = false; + update(); + // isCancelRidePageShown = true; + rideIsBeginPassengerTimerVIP(); + runWhenRideIsBegin(); } Map rideStatusFromStartApp = {}; @@ -760,6 +1069,8 @@ class MapPassengerController extends GetxController { var res = await CRUD().get( link: AppLink.getRideStatusFromStartApp, payload: {'passenger_id': box.read(BoxName.passengerID)}); + print(res); + print('1070'); if (res == 'failure') { print( "No rides found for the given passenger ID within the last hour."); @@ -768,7 +1079,7 @@ class MapPassengerController extends GetxController { if (rideStatusFromStartApp['data']['status'] == 'Begin') { statusRide = 'Begin'; driverId = rideStatusFromStartApp['data']['driver_id']; - firstName = rideStatusFromStartApp['data']['driverName']; + passengerName = rideStatusFromStartApp['data']['driverName']; driverRate = rideStatusFromStartApp['data']['rateDriver'].toString(); statusRideFromStart = true; // DateTime endTime = @@ -921,7 +1232,7 @@ class MapPassengerController extends GetxController { // Extract the URL part from the link by finding the first occurrence of "http" int urlStartIndex = link.indexOf(RegExp(r'https?://')); if (urlStartIndex == -1) { - throw FormatException('No URL found in the provided link.'); + throw const FormatException('No URL found in the provided link.'); } // Extract the URL and clean it @@ -1010,320 +1321,438 @@ class MapPassengerController extends GetxController { int currentTimeSearchingCaptainWindow = 0; late String driverPhone = ''; late String driverRate = ''; - late String firstName = ''; + late String passengerName = ''; late String carColor = ''; + late String colorHex = ''; late String carYear = ''; late String model = ''; late String make = ''; late String licensePlate = ''; - confirmRideForFirstDriver() async { - startCarLocationSearch(box.read(BoxName.carType)); - // await getCarsLocationByPassengerAndReloadMarker( - // box.read(BoxName.carType), 7000); - await getNearestDriverByPassengerLocationAPIGOOGLE(); - Log.print('dataCarsLocationByPassenger: ${dataCarsLocationByPassenger}'); - if (dataCarsLocationByPassenger != 'failure' || - dataCarsLocationByPassenger != null) { - driverToken = - dataCarsLocationByPassenger['message'][carsOrder]['token'].toString(); - driverPhone = - dataCarsLocationByPassenger['message'][carsOrder]['phone'].toString(); - firstName = dataCarsLocationByPassenger['message'][carsOrder] - ['first_name'] // driverName - .toString(); - carColor = - dataCarsLocationByPassenger['message'][carsOrder]['color'].toString(); - driverRate = dataCarsLocationByPassenger['message'][carsOrder] - ['ratingDriver'] - .toString(); - carYear = - dataCarsLocationByPassenger['message'][carsOrder]['year'].toString(); - model = - '${dataCarsLocationByPassenger['message'][carsOrder]['model']} - ${dataCarsLocationByPassenger['message'][carsOrder]['make']}'; - licensePlate = dataCarsLocationByPassenger['message'][carsOrder] - ['car_plate'] - .toString(); - PaymentController paymentController = Get.find(); - rideConfirm = true; - shouldFetch = true; - isBottomSheetShown = false; - timeToPassengerFromDriverAfterApplied = - durationToPassenger; //60 todo durationToPassenger;/ - isDriversTokensSend = false; + String driverOrderStatus = 'yet'; + bool isDriversTokensSend = false; - update(); - await CRUD().post(link: AppLink.addRides, payload: { - "start_location": //'${data[0]['start_address']}', - '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', - "end_location": //'${data[0]['end_address']}', - '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}', - "date": DateTime.now().toString(), - "time": DateTime.now().toString(), - "endtime": durationToAdd.toString(), - "price": totalPassenger.toStringAsFixed(2), - "passenger_id": box.read(BoxName.passengerID).toString(), - "driver_id": dataCarsLocationByPassenger['message'][carsOrder] - ['driver_id'] - .toString(), - "status": "waiting", - 'carType': box.read(BoxName.carType), - "price_for_driver": totalPassenger.toString(), - "price_for_passenger": totalME.toString(), - "distance": distance.toString(), - "paymentMethod": paymentController.isWalletChecked.toString(), - }).then((value) { - // List body = [ - rideId = jsonDecode(value)['message']; - List body = [ + Set notifiedDrivers = {}; + + addRideToNotificationDriverAvailable() async { + await CRUD().post(link: AppLink.addWaitingRide, payload: { + 'id': rideId.toString(), + 'start_location': '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', + 'end_location': '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}', - totalPassenger.toStringAsFixed(2), - totalDriver.toStringAsFixed(2), - durationToRide.toString(), - distance.toStringAsFixed(2), - dataCarsLocationByPassenger['message'][carsOrder]['driver_id'] - .toString(), - box.read(BoxName.passengerID).toString(), - box.read(BoxName.name).toString(), - box.read(BoxName.tokenFCM).toString(), - box.read(BoxName.phone).toString(), - durationByPassenger.toString(), - distanceByPassenger.toString(), - paymentController.isWalletChecked.toString(), - dataCarsLocationByPassenger['message'][carsOrder]['token'].toString(), - durationToPassenger.toString(), - rideId, - rideTimerBegin.toString(), - dataCarsLocationByPassenger['message'][carsOrder]['driver_id'] - .toString(), - durationToRide.toString(), - Get.find().wayPoints.length > 1 - ? 'haveSteps' - : 'startEnd', - placesCoordinate[0], - placesCoordinate[1], - placesCoordinate[2], - placesCoordinate[3], - placesCoordinate[4], - costForDriver.toStringAsFixed(2), - double.parse(box.read(BoxName.passengerWalletTotal)) < 0 - ? double.parse(box.read(BoxName.passengerWalletTotal)) - .toStringAsFixed(2) - : '0', - box.read(BoxName.email).toString(), - startNameAddress, - endNameAddress, - box.read(BoxName.carType), - kazan.toStringAsFixed(0), - passengerRate.toStringAsFixed(2), - ]; - Log.print('body: ${body}'); - FirebaseMessagesController().sendNotificationToDriverMAP( - 'Order'.tr, - 'from: $startNameAddress\nto: $startNameAddress\ndistanceFromMe: $distanceByPassenger\nDistance :$distance\nPrice ; $totalPassenger', - // jsonDecode(value)['message'].toString(), - dataCarsLocationByPassenger['message'][carsOrder]['token'] - .toString(), - body, - 'order.wav' + "date": DateTime.now().toString(), + "time": DateTime.now().toString(), + "price": totalPassenger.toStringAsFixed(2), + 'passenger_id': box.read(BoxName.passengerID).toString(), + 'status': 'waiting', + 'carType': box.read(BoxName.carType), + 'passengerRate': passengerRate.toStringAsFixed(2), + 'price_for_passenger': totalME.toStringAsFixed(2), + 'distance': distance.toStringAsFixed(1), + 'duration': duration.toStringAsFixed(1), + }); - // polylineCoordinates.toString() - ); - Log.print( - 'body: ${dataCarsLocationByPassenger['message'][carsOrder]['token']}'); - }); + if (AppLink.endPoint != AppLink.seferCairoServer) { CRUD().post( - link: '${AppLink.seferAlexandriaServer}/ride/rides/add.php', + link: '${AppLink.endPoint}/notificationCaptain/addWaitingRide.php', payload: { - "start_location": //'${data[0]['start_address']}', + 'id': rideId.toString(), + 'start_location': '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', - "end_location": //'${data[0]['end_address']}', + 'end_location': '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}', "date": DateTime.now().toString(), "time": DateTime.now().toString(), - "endtime": durationToAdd.toString(), "price": totalPassenger.toStringAsFixed(2), - "passenger_id": box.read(BoxName.passengerID).toString(), - "driver_id": dataCarsLocationByPassenger['message'][carsOrder] - ['driver_id'] - .toString(), - "status": "waiting", + 'passenger_id': box.read(BoxName.passengerID).toString(), + 'status': 'waiting', 'carType': box.read(BoxName.carType), - "price_for_driver": totalPassenger.toString(), - "price_for_passenger": totalME.toString(), - "distance": distance.toString(), - "paymentMethod": paymentController.isWalletChecked.toString(), + 'passengerRate': passengerRate.toStringAsFixed(2), + 'price_for_passenger': totalME.toStringAsFixed(2), + 'distance': distance.toStringAsFixed(1), + 'duration': duration.toStringAsFixed(0), }); - CRUD().post( - link: '${AppLink.seferGizaServer}/ride/rides/add.php', - payload: { - "start_location": //'${data[0]['start_address']}', - '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', - "end_location": //'${data[0]['end_address']}', - '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}', - "date": DateTime.now().toString(), - "time": DateTime.now().toString(), - "endtime": durationToAdd.toString(), - "price": totalPassenger.toStringAsFixed(2), - "passenger_id": box.read(BoxName.passengerID).toString(), - "driver_id": dataCarsLocationByPassenger['message'][carsOrder] - ['driver_id'] - .toString(), - "status": "waiting", - 'carType': box.read(BoxName.carType), - "price_for_driver": totalPassenger.toString(), - "price_for_passenger": totalME.toString(), - "distance": distance.toString(), - "paymentMethod": paymentController.isWalletChecked.toString(), - }); - - delayAndFetchRideStatus(rideId); - if (shouldFetch == false) { - startTimer(); - update(); - } - update(); - } else { - Get.defaultDialog( - title: 'No Car or Driver Found in your area.'.tr, - titleStyle: AppStyle.title, - middleText: 'Please Try anther time '.tr, - middleTextStyle: AppStyle.title.copyWith(color: AppColor.yellowColor), - confirm: MyElevatedButton( - title: 'Ok'.tr, - onPressed: () { - Get.back(); - isSearchingWindow = false; - cancelRide(); - update(); - })); } } - bool isDriversTokensSend = false; - confirmRideForAllDriverAvailable() async { - await getCarsLocationByPassengerAndReloadMarker( - box.read(BoxName.carType), 7000); - if (dataCarsLocationByPassenger != 'failure') { - driversToken.remove(driverToken); - PaymentController paymentController = Get.find(); - rideConfirm = true; - shouldFetch = true; - isBottomSheetShown = false; - timeToPassengerFromDriverAfterApplied = 60; + // Future confirmRideForAllDriverAvailable1() async { + // // Try to fetch car locations up to 4 times with a 2-second delay + // bool driversFound = false; + // for (int attempt = 0; attempt < 8; attempt++) { + // await getCarsLocationByPassengerAndReloadMarker( + // box.read(BoxName.carType), attempt > 5 ? 4500 : 3000); - List body = [ - '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', - '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}', - totalPassenger.toStringAsFixed(2), - totalDriver.toStringAsFixed(2), - durationToRide.toString(), - distance.toStringAsFixed(2), - dataCarsLocationByPassenger['message'][carsOrder]['driver_id'] - .toString(), - box.read(BoxName.passengerID).toString(), - box.read(BoxName.name).toString(), - box.read(BoxName.tokenFCM).toString(), - box.read(BoxName.phone).toString(), - durationByPassenger.toString(), - distanceByPassenger.toString(), - paymentController.isWalletChecked.toString(), - dataCarsLocationByPassenger['message'][carsOrder]['token'].toString(), - durationToPassenger.toString(), - rideId, - rideTimerBegin.toString(), - dataCarsLocationByPassenger['message'][carsOrder]['driver_id'] - .toString(), - durationToRide.toString(), - Get.find().wayPoints.length > 1 - ? 'haveSteps' - : 'startEnd', - placesCoordinate[0], - placesCoordinate[1], - placesCoordinate[2], - placesCoordinate[3], - placesCoordinate[4], - costForDriver.toStringAsFixed(2), - double.parse(box.read(BoxName.passengerWalletTotal)) < 0 - ? double.parse(box.read(BoxName.passengerWalletTotal)) - .toStringAsFixed(2) - : '0', - box.read(BoxName.email).toString(), - startNameAddress, - endNameAddress, - box.read(BoxName.carType), - kazan.toStringAsFixed(0), - passengerRate.toStringAsFixed(2), - ]; - Log.print('body: ${body}'); - for (var i = 1; i < driversToken.length; i++) { - FirebaseMessagesController().sendNotificationToDriverMapPolyline( - 'OrderSpeed', - rideId.toString(), - driversToken[i], - body, - polylineCoordinates.toString()); - } + // // Check if dataCarsLocationByPassenger is valid and contains drivers + // if (dataCarsLocationByPassenger != 'failure' && + // dataCarsLocationByPassenger != null && + // dataCarsLocationByPassenger.containsKey('data') && + // dataCarsLocationByPassenger['message'] != null) { + // driversFound = true; + // break; // Exit loop if drivers are found + // } - (rideId); + // // Wait 2 seconds before next attempt + // await Future.delayed(const Duration(seconds: 2)); + // } - update(); - } else { - MyDialog().getDialog("No Car or Driver Found in your area.".tr, - "No Car or Driver Found in your area.".tr, () { - Get.back(); - Get.offAll(const MapPagePassenger()); - }); - } + // // If no drivers were found after 4 attempts, show a dialog + // if (!driversFound) { + // Get.dialog( + // BackdropFilter( + // filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + // child: CupertinoAlertDialog( + // title: Text( + // "No Car or Driver Found in your area.".tr, + // style: AppStyle.title.copyWith( + // fontSize: 20, + // fontWeight: FontWeight.bold, + // ), + // ), + // content: Text( + // "No Car or Driver Found in your area.".tr, + // style: AppStyle.title.copyWith(fontSize: 16), + // ), + // actions: [ + // CupertinoDialogAction( + // onPressed: () { + // Get.back(); + // Get.offAll(() => const MapPagePassenger()); + // }, + // child: Text('OK'.tr, + // style: const TextStyle(color: AppColor.greenColor)), + // ), + // ], + // ), + // ), + // barrierDismissible: false, + // ); + + // return; + // } + + // // Proceed with the rest of the function if drivers are found + // PaymentController paymentController = Get.find(); + // rideConfirm = true; + // shouldFetch = true; + // isBottomSheetShown = false; + // timeToPassengerFromDriverAfterApplied = 60; + + // // Add ride to database + // await CRUD() + // .post(link: "${AppLink.seferCairoServer}/ride/rides/add.php", payload: { + // "start_location": + // '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', + // "end_location": + // '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}', + // "date": DateTime.now().toString(), + // "time": DateTime.now().toString(), + // "endtime": durationToAdd.toString(), + // "price": totalPassenger.toStringAsFixed(2), + // "passenger_id": box.read(BoxName.passengerID).toString(), + // "driver_id": dataCarsLocationByPassenger['message'][carsOrder]['driver_id'] + // .toString(), + // "status": "waiting", + // 'carType': box.read(BoxName.carType), + // "price_for_driver": totalPassenger.toString(), + // "price_for_passenger": totalME.toString(), + // "distance": distance.toString(), + // "paymentMethod": paymentController.isWalletChecked.toString(), + // }).then((value) { + // if (value is String) { + // final parsedValue = jsonDecode(value); + // rideId = parsedValue['message']; + // } else if (value is Map) { + // rideId = value['message']; + // } else { + // Log.print('Unexpected response type: ${value.runtimeType}'); + // } + + // // Timer to notify drivers every 2 seconds for 5 iterations + // int iteration = 0; + // Timer.periodic(const Duration(seconds: 2), (timer) async { + // if (iteration >= 5) { + // timer.cancel(); + // return; + // } + // iteration++; + + // // Reload driver locations and notify available drivers + // await getCarsLocationByPassengerAndReloadMarker( + // box.read(BoxName.carType), 3000); + // if (dataCarsLocationByPassenger != null && + // dataCarsLocationByPassenger.containsKey('data') && + // dataCarsLocationByPassenger['message'] != null) { + // for (var driverData in dataCarsLocationByPassenger['message']) { + // String driverId = driverData['driver_id'].toString(); + // if (!notifiedDrivers.contains(driverId)) { + // notifiedDrivers.add(driverId); + // List body = [ + // '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', + // '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}', + // totalPassenger.toStringAsFixed(2), + // totalDriver.toStringAsFixed(2), + // durationToRide.toString(), + // distance.toStringAsFixed(2), + // driverId.toString(), + // box.read(BoxName.passengerID).toString(), + // box.read(BoxName.name).toString(), + // box.read(BoxName.tokenFCM).toString(), + // box.read(BoxName.phone).toString(), + // durationByPassenger.toString(), + // distanceByPassenger.toString(), + // paymentController.isWalletChecked.toString(), + // driverData['token'].toString(), + // durationToPassenger.toString(), + // rideId.toString(), + // rideTimerBegin.toString(), + // driverId.toString(), + // durationToRide.toString(), + // Get.find().wayPoints.length > 1 + // ? 'haveSteps' + // : 'startEnd', + // placesCoordinate[0], + // placesCoordinate[1], + // placesCoordinate[2], + // placesCoordinate[3], + // placesCoordinate[4], + // costForDriver.toStringAsFixed(2), + // (double.parse(box.read(BoxName.passengerWalletTotal)) < 0 + // ? double.parse(box.read(BoxName.passengerWalletTotal)) + // .toStringAsFixed(2) + // : '0'), + // box.read(BoxName.email).toString(), + // data[0]['start_address'], + // data[0]['end_address'], + // box.read(BoxName.carType), + // kazan.toStringAsFixed(0), + // passengerRate.toStringAsFixed(2), + // ]; + // Log.print('body: ${body}'); + // FirebaseMessagesController().sendNotificationToDriverMAP( + // 'OrderSpeed', + // rideId, + // driverData['token'].toString(), + // body, + // 'order.wav', + // ); + // } + // } + // } + // }); + // }); + + // // If an additional endpoint is available, post data there as well + // if (AppLink.endPoint != AppLink.seferCairoServer) { + // CRUD().post(link: '${AppLink.endPoint}/ride/rides/add.php', payload: { + // "start_location": + // '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', + // "end_location": + // '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}', + // "date": DateTime.now().toString(), + // "time": DateTime.now().toString(), + // "endtime": durationToAdd.toString(), + // "price": totalPassenger.toStringAsFixed(2), + // "passenger_id": box.read(BoxName.passengerID).toString(), + // "driver_id": dataCarsLocationByPassenger['message'][carsOrder]['driver_id'] + // .toString(), + // "status": "waiting", + // 'carType': box.read(BoxName.carType), + // "price_for_driver": totalPassenger.toString(), + // "price_for_passenger": totalME.toString(), + // "distance": distance.toString(), + // "paymentMethod": paymentController.isWalletChecked.toString(), + // }); + // } + // delayAndFetchRideStatusForAllDriverAvailable(rideId); + // update(); + // } + + increaseForSameRideAndDelay() async { + reSearchAfterCanceledFromDriver(); + // bool driversFound = false; + // for (int attempt = 0; attempt < 8; attempt++) { + // await getCarsLocationByPassengerAndReloadMarker( + // box.read(BoxName.carType), 4500); + + // // Check if dataCarsLocationByPassenger is valid and contains drivers + // if (dataCarsLocationByPassenger != 'failure' && + // dataCarsLocationByPassenger != null && + // dataCarsLocationByPassenger.containsKey('message') && + // dataCarsLocationByPassenger['message'] != null) { + // driversFound = true; + // break; // Exit loop if drivers are found + // } + + // // Wait 2 seconds before next attempt + // await Future.delayed(const Duration(seconds: 2)); + // } + + // // If no drivers were found after 4 attempts, show a dialog + // if (!driversFound) { + // Get.dialog( + // BackdropFilter( + // filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + // child: CupertinoAlertDialog( + // title: Text( + // "No Car or Driver Found in your area.".tr, + // style: AppStyle.title.copyWith( + // fontSize: 20, + // fontWeight: FontWeight.bold, + // ), + // ), + // content: Text( + // "No Car or Driver Found in your area.".tr, + // style: AppStyle.title.copyWith(fontSize: 16), + // ), + // actions: [ + // CupertinoDialogAction( + // onPressed: () { + // Get.back(); + // Get.offAll(() => const MapPagePassenger()); + // }, + // child: Text('OK'.tr, + // style: const TextStyle(color: AppColor.greenColor)), + // ), + // ], + // ), + // ), + // barrierDismissible: false, + // ); + + // return; + // } + // PaymentController paymentController = Get.find(); + // rideConfirm = true; + // shouldFetch = true; + // isBottomSheetShown = false; + // timeToPassengerFromDriverAfterApplied = 60; + // // confirmRideForAllDriverAvailable(); + // for (var i = 0; i < dataCarsLocationByPassenger['message'].length; i++) { + // List body = [ + // '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', + // '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}', + // totalPassenger.toStringAsFixed(2), + // totalDriver.toStringAsFixed(2), + // durationToRide.toString(), + // distance.toStringAsFixed(2), + // dataCarsLocationByPassenger['message'][i]['driver_id'].toString(), + // box.read(BoxName.passengerID).toString(), + // box.read(BoxName.name).toString(), + // box.read(BoxName.tokenFCM).toString(), + // box.read(BoxName.phone).toString(), + // durationByPassenger.toString(), + // distanceByPassenger.toString(), + // paymentController.isWalletChecked.toString(), + // dataCarsLocationByPassenger['message'][i]['token'].toString(), + // durationToPassenger.toString(), + // rideId.toString(), + // rideTimerBegin.toString(), + // dataCarsLocationByPassenger['message'][i]['driver_id'].toString(), + // durationToRide.toString(), + // Get.find().wayPoints.length > 1 + // ? 'haveSteps' + // : 'startEnd', + // placesCoordinate[0], + // placesCoordinate[1], + // placesCoordinate[2], + // placesCoordinate[3], + // placesCoordinate[4], + // costForDriver.toStringAsFixed(2), + // double.parse(box.read(BoxName.passengerWalletTotal)) < 0 + // ? double.parse(box.read(BoxName.passengerWalletTotal)) + // .toStringAsFixed(2) + // : '0', + // box.read(BoxName.email).toString(), + // data[0]['start_address'], + // data[0]['end_address'], + // box.read(BoxName.carType), + // kazan.toStringAsFixed(0), + // passengerRate.toStringAsFixed(2), + // ]; + // // Log.print('body: ${body}'); + + // FirebaseMessagesController().sendNotificationToDriverMAP( + // 'OrderSpeed', + // rideId.toString(), + // dataCarsLocationByPassenger['message'][i]['token'].toString(), + // body, + // 'order.wav'); + // } } int tick = 0; // Move tick outside the function to maintain its state - void delayAndFetchRideStatus(String rideId) { - Timer.periodic(const Duration(seconds: 1), (timer) async { - if (shouldFetch) { - if (remainingTimeToPassengerFromDriverAfterApplied > 0) { - String res = await getRideStatus(rideId); + // void delayAndFetchRideStatus(String rideId, carType) { + // Timer.periodic(const Duration(seconds: 1), (timer) async { + // if (shouldFetch) { + // if (remainingTimeToPassengerFromDriverAfterApplied > 0) { + // String res = await getRideStatus(rideId); - Log.print('tick: $tick'); - - if (res.toString() == 'waiting' && tick >= 15) { - timer.cancel(); // Stop the current timer - showAndResearchForCaptain(); - // delayAndFetchRideStatusForAllDriverAvailable(rideId); - } else if (res.toString() == 'Apply') { - // todo play sound - Get.find() - .playSoundFromAssets('assets/start.wav'); - timer.cancel(); // Stop the current timer - shouldFetch = false; // Stop further fetches - statusRide = 'Apply'; - rideConfirm = false; - isSearchingWindow = false; - update(); - startTimerFromDriverToPassengerAfterApplied(); - } else if (res.toString() == 'Refused') { - statusRide = 'Refused'; - if (isDriversTokensSend == false) { - confirmRideForAllDriverAvailable(); - isDriversTokensSend = true; - } // Start 15-second timer - } - //else if (isDriversTokensSend == false) { - // No need to recall delayAndFetchRideStatus as Timer.periodic is already running - update(); - // } - tick++; - } else { - timer.cancel(); - // Stop the timer if remainingTimeToPassengerFromDriverAfterApplied <= 0 - } - } else { - timer.cancel(); // Stop the timer if shouldFetch is false - } - }); - } + // Log.print('tick: $tick'); + // String rideStatusDelayed = res.toString(); + // if ((rideStatusDelayed == 'waiting' || + // rideStatusDelayed == 'Refused') && + // tick >= 15) { + // timer.cancel(); // Stop the current timer + // showAndResearchForCaptain(); + // //TODO add to wait + // await getCarsLocationByPassengerAndReloadMarker(carType, 3000); + // // await getNearestDriverByPassengerLocationAPIGOOGLE(); + // // getCarForFirstConfirm(carType); + // confirmRideForAllDriverAvailable(); + // // delayAndFetchRideStatusForAllDriverAvailable(rideId); + // } else if (rideStatusDelayed == 'Apply' || statusRide == 'Apply') { + // Log.print('rideStatusDelayed == Apply: $rideStatusDelayed'); + // // todo play sound + // Get.find() + // .playSoundFromAssets('assets/start.wav'); + // timer.cancel(); // Stop the current timer + // await getUpdatedRideForDriverApply(rideId); + // shouldFetch = false; // Stop further fetches + // statusRide = 'Apply'; + // rideConfirm = false; + // isSearchingWindow = false; + // update(); + // startTimerFromDriverToPassengerAfterApplied(); + // if (box.read(BoxName.carType) == 'Speed' || + // box.read(BoxName.carType) == 'Awfar Car') { + // NotificationController().showNotification( + // 'The captain is responsible for the route.'.tr, + // 'This price is fixed even if the route changes for the driver.' + // .tr, + // 'ding'); + // } else if (box.read(BoxName.carType) == 'Comfort' || + // box.read(BoxName.carType) == 'Lady') { + // NotificationController().showNotification('Attention'.tr, + // 'The price may increase if the route changes.'.tr, 'ding'); + // } + // } else if (rideStatusDelayed == 'Refused') { + // statusRide = 'Refused'; + // if (isDriversTokensSend == false) { + // confirmRideForAllDriverAvailable(); + // isDriversTokensSend = true; + // } // Start 15-second timer + // } + // //else if (isDriversTokensSend == false) { + // // No need to recall delayAndFetchRideStatus as Timer.periodic is already running + // update(); + // // } + // if (tick < 19) { + // tick++; + // } else { + // timer.cancel(); + // // Stop the timer if remainingTimeToPassengerFromDriverAfterApplied <= 0 + // } + // } else { + // timer.cancel(); + // // Stop the timer if remainingTimeToPassengerFromDriverAfterApplied <= 0 + // } + // } else { + // timer.cancel(); // Stop the timer if shouldFetch is false + // } + // }); + // } showAndResearchForCaptain() { Get.snackbar( @@ -1333,128 +1762,368 @@ class MapPassengerController extends GetxController { duration: const Duration(seconds: 5), backgroundColor: AppColor.yellowColor, ); + isSearchingWindow == true; + update(); } - void delayAndFetchRideStatusForAllDriverAvailable(String rideId) async { + String driversStatusForSearchWindow = ''; + Future confirmRideForAllDriverAvailable() async { + bool driversFound = false; + const maxAttempts = 8; + const attemptDelay = Duration(seconds: 3); + + for (int attempt = 0; attempt < maxAttempts; attempt++) { + final reloadDuration = attempt > 5 ? 4500 : 3000; + await getCarsLocationByPassengerAndReloadMarker( + box.read(BoxName.carType), reloadDuration); + // await getNearestDriverByPassengerLocation(); + driversStatusForSearchWindow = 'We are search for nearst driver'.tr; + if (isDriversDataValid()) { + driversFound = true; + break; + } + + await Future.delayed(attemptDelay); + } + + if (!driversFound) { + showNoDriversDialog(); + return; + } + driversStatusForSearchWindow = 'Your order is being prepared'.tr; + Log.print('driversStatusForSearchWindow: ${driversStatusForSearchWindow}'); + update(); + await postRideDetailsToServer(); + driversStatusForSearchWindow = 'Your order sent to drivers'.tr; + await notifyAvailableDrivers(); + + driversStatusForSearchWindow = 'The drivers are reviewing your request'.tr; + Log.print('driversStatusForSearchWindow: ${driversStatusForSearchWindow}'); + update(); + delayAndFetchRideStatusForAllDriverAvailable(rideId); + update(); + } + + Future updateConfirmRideForAllDriverAvailable() async { + bool driversFound = false; + const maxAttempts = 8; + const attemptDelay = Duration(seconds: 3); + + for (int attempt = 0; attempt < maxAttempts; attempt++) { + final reloadDuration = attempt > 5 ? 4500 : 3000; + await getCarsLocationByPassengerAndReloadMarker( + box.read(BoxName.carType), reloadDuration); + // await getNearestDriverByPassengerLocation(); + + if (isDriversDataValid()) { + driversFound = true; + break; + } + + await Future.delayed(attemptDelay); + } + + if (!driversFound) { + showNoDriversDialog(); + return; + } + + // await postRideDetailsToServer(); + await notifyAvailableDrivers(); + delayAndFetchRideStatusForAllDriverAvailable(rideId); + update(); + } + + bool isDriversDataValid() { + return dataCarsLocationByPassenger != 'failure' && + dataCarsLocationByPassenger != null && + dataCarsLocationByPassenger.containsKey('message') && + dataCarsLocationByPassenger['message'] != null; + } + + void showNoDriversDialog() { + Get.dialog( + BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: CupertinoAlertDialog( + title: Text("No Car or Driver Found in your area.".tr, + style: AppStyle.title + .copyWith(fontSize: 20, fontWeight: FontWeight.bold)), + content: Text("No Car or Driver Found in your area.".tr, + style: AppStyle.title.copyWith(fontSize: 16)), + actions: [ + CupertinoDialogAction( + onPressed: () { + Get.back(); + Get.offAll(() => const MapPagePassenger()); + }, + child: Text('OK'.tr, + style: const TextStyle(color: AppColor.greenColor)), + ), + ], + ), + ), + barrierDismissible: false, + ); + } + + Future postRideDetailsToServer() async { + final paymentController = Get.find(); + final payload = constructRidePayload(paymentController); + + try { + final response = await CRUD().post( + link: "${AppLink.seferCairoServer}/ride/rides/add.php", + payload: payload); + if (response is String) { + final parsedValue = jsonDecode(response); + rideId = parsedValue['message']; + } else if (response is Map) { + rideId = response['message']; + } else { + Log.print('Unexpected response type: ${response.runtimeType}'); + } + } catch (e) { + Log.print('Error posting ride details: $e'); + } + } + + Map constructRidePayload( + PaymentController paymentController) { + final startLocation = + '${data[0]['start_location']['lat']},${data[0]['start_location']['lng']}'; + final endLocation = + '${data[0]['end_location']['lat']},${data[0]['end_location']['lng']}'; + + return { + "start_location": startLocation, + "end_location": endLocation, + "date": DateTime.now().toString(), + "time": DateTime.now().toString(), + "endtime": durationToAdd.toString(), + "price": totalPassenger.toStringAsFixed(2), + "passenger_id": box.read(BoxName.passengerID).toString(), + "driver_id": dataCarsLocationByPassenger['message'][carsOrder] + ['driver_id'] + .toString(), + "status": "waiting", + 'carType': box.read(BoxName.carType), + "price_for_driver": totalPassenger.toString(), + "price_for_passenger": totalME.toString(), + "distance": distance.toString(), + "paymentMethod": paymentController.isWalletChecked.toString(), + }; + } + + Future notifyAvailableDrivers() async { + int iteration = 0; + const maxIterations = 5; + const iterationDelay = Duration(seconds: 2); + + while (iteration < maxIterations) { + await Future.delayed(iterationDelay); + await getCarsLocationByPassengerAndReloadMarker( + box.read(BoxName.carType), 3000); + + if (dataCarsLocationByPassenger != null && + dataCarsLocationByPassenger.containsKey('message') && + dataCarsLocationByPassenger['message'] != null) { + for (var driverData in dataCarsLocationByPassenger['message']) { + String driverId = driverData['driver_id'].toString(); + if (!notifiedDrivers.contains(driverId)) { + notifiedDrivers.add(driverId); + double driverLat = double.parse(driverData['latitude']); + double driverLng = double.parse(driverData['longitude']); + double distanceToDriverInMeters = Geolocator.distanceBetween( + passengerLocation.latitude, + passengerLocation.longitude, + driverLat, + driverLng, + ); + + double distanceToDriverInKm = distanceToDriverInMeters * + 1.25 / //to approximate to stright distance + 1000; + double durationToDriverInHours = + distanceToDriverInKm / 25; // 25 km/h as default speed + double durationToDriverInSeconds = durationToDriverInHours * 3600; + durationToPassenger = durationToDriverInSeconds.toInt(); + distanceByPassenger = + (distanceToDriverInMeters * 1.25).toStringAsFixed(0); + Future.delayed(const Duration(microseconds: 10)); + final body = constructNotificationBody(driverData); + // Log.print('body:ww ${body}'); + FirebaseMessagesController().sendNotificationToDriverMAP( + 'OrderSpeed', + rideId, + driverData['token'].toString(), + body, + 'order.wav'); + } + } + } + iteration++; + } + } + + List constructNotificationBody(var driverData) { + final paymentController = Get.find(); + return [ + '${data[0]['start_location']['lat']},${data[0]['start_location']['lng']}', + '${data[0]['end_location']['lat']},${data[0]['end_location']['lng']}', + totalPassenger.toStringAsFixed(2), + totalDriver.toStringAsFixed(2), + durationToRide.toString(), + distance.toStringAsFixed(2), + driverData['driver_id'].toString(), + box.read(BoxName.passengerID).toString(), + box.read(BoxName.name).toString(), + box.read(BoxName.tokenFCM).toString(), + box.read(BoxName.phone).toString(), + durationToPassenger.toStringAsFixed(0) ?? '120', + distanceByPassenger.toString() ?? '2000', + paymentController.isWalletChecked.toString(), + driverData['token'].toString(), + durationToPassenger.toString(), + rideId.toString(), + rideTimerBegin.toString(), + driverData['driver_id'].toString(), + durationToRide.toString(), + Get.find().wayPoints.length > 1 + ? 'haveSteps' + : 'startEnd', + placesCoordinate[0], + placesCoordinate[1], + placesCoordinate[2], + placesCoordinate[3], + placesCoordinate[4], + costForDriver.toStringAsFixed(2), + (double.parse(box.read(BoxName.passengerWalletTotal)) < 0 + ? double.parse(box.read(BoxName.passengerWalletTotal)) + .toStringAsFixed(2) + : '0'), + box.read(BoxName.email).toString(), + data[0]['start_address'], + data[0]['end_address'], + box.read(BoxName.carType), + kazan.toStringAsFixed(0), + passengerRate.toStringAsFixed(2), + ]; + } + + StreamController _rideStatusStreamController = + StreamController.broadcast(); + Stream get rideStatusStream => _rideStatusStreamController.stream; + + Future delayAndFetchRideStatusForAllDriverAvailable( + String rideId) async { const int maxAttempts = 15; int attemptCounter = 0; bool isApplied = false; tick = 0; - Log.print('tick delayAndFetchRideStatusForAllDriverAvailable: ${tick}'); + await addRideToNotificationDriverAvailable(); + Timer.periodic(const Duration(seconds: 1), (timer) async { + if (attemptCounter >= maxAttempts || isApplied) { + timer.cancel(); + _rideStatusStreamController.close(); // Close the stream when done + return; + } - void fetchRideStatus() async { - if (attemptCounter < maxAttempts && !isApplied && tick < 20) { - attemptCounter++; - tick++; + attemptCounter++; + tick++; + + try { var res = await getRideStatus(rideId); + Log.print('res:2022 ${res}'); + String rideStatusDelayed = res.toString(); + Log.print('rideStatusDelayed: ${rideStatusDelayed}'); - if (res.toString() == 'Apply') { - getUpdatedRideForDriverApply(rideId); - isApplied = true; - shouldFetch = false; - statusRide = 'Apply'; - rideConfirm = false; - isSearchingWindow = false; - update(); - startTimerFromDriverToPassengerAfterApplied(); - } else if (attemptCounter >= maxAttempts) { - shouldFetch = false; - // If the status is still not "Apply" after 15 attempts - MyDialog().getDialog('upgrade price'.tr, - 'You can upgrade price to may driver accept your order'.tr, () { + _rideStatusStreamController + .add(rideStatusDelayed); // Emit the ride status + // addRideToNotificationDriverString(); + if (rideStatusDelayed == 'Cancel') { + timer.cancel(); + NotificationController().showNotification( + "Order Cancelled".tr, "you canceled order".tr, 'ding'); + _rideStatusStreamController + .close(); // Close stream after cancellation + // + // + } else if (rideStatusDelayed == 'Apply' || + rideStatusDelayed == 'Applied') { + rideAppliedFromDriver(isApplied); + timer.cancel(); + // Close stream after applying + } else if (attemptCounter >= maxAttempts || + rideStatusDelayed != 'Cancel') { + timer.cancel(); //todo + // addRideToNotificationDriverString(); + // Show dialog to increase fee... + MyDialog().getDialog( + 'Are you want to wait drivers to accept your order'.tr, '', () { Get.back(); - Get.defaultDialog( - barrierDismissible: false, - title: "Increase Your Trip Fee (Optional)".tr, - titleStyle: AppStyle.title, - content: Column( - children: [ - Text( - "We haven't found any drivers yet. Consider increasing your trip fee to make your offer more attractive to drivers." - .tr, - style: AppStyle.title, - textAlign: TextAlign.center, - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton( - onPressed: () { - increasFeeFromPassenger.text = - (totalPassenger + 6).toStringAsFixed(1); - update(); - }, - icon: Column( - children: [ - Text( - '6', - style: AppStyle.number, - ), - Container( - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: AppColor.greenColor), - child: const Icon( - Icons.arrow_circle_up, - size: 30, - color: AppColor.secondaryColor, - ), - ), - ], - ), - ), - SizedBox( - width: 100, - child: Form( - key: increaseFeeFormKey, - child: MyTextForm( - controller: increasFeeFromPassenger, - label: totalPassenger.toStringAsFixed(2), - hint: totalPassenger.toStringAsFixed(2), - type: TextInputType.number), - ), - ), - ], - ) - ], - ), - actions: [ - MyElevatedButton( - title: "No, thanks", - onPressed: () { - Get.back(); - cancelRide(); - }), - MyElevatedButton( - title: "Increase Fee".tr, - kolor: AppColor.greenColor, - onPressed: () { - increaseFeeByPassengerAndReOrder(); - }) - ], - ); + addRideToNotificationDriverAvailable(); }); update(); - print('Stopped fetching ride status after 15 attempts.'); - } else { - Timer(const Duration(seconds: 2), fetchRideStatus); + _rideStatusStreamController + .close(); // Close stream after max attempts } + } catch (e) { + _rideStatusStreamController.addError(e); // Handle errors in the stream } - } + }); + } - fetchRideStatus(); // Initial call to start the process + rideAppliedFromDriver(bool isApplied) async { + await getUpdatedRideForDriverApply(rideId); + NotificationController().showNotification( + 'Order Accepted'.tr, + '$driverName ${'accepted your order at price'.tr} ${totalPassenger.toStringAsFixed(1)} ${'with type'.tr} ${box.read(BoxName.carType)}', + 'ding'); + if (box.read(BoxName.carType) == 'Speed' || + box.read(BoxName.carType) == 'Awfar Car') { + NotificationController().showNotification( + 'The captain is responsible for the route.'.tr, + 'This price is fixed even if the route changes for the driver.'.tr, + 'ding'); + } else if (box.read(BoxName.carType) == 'Comfort' || + box.read(BoxName.carType) == 'Lady') { + NotificationController().showNotification('Attention'.tr, + 'The price may increase if the route changes.'.tr, 'ding'); + } + isApplied = true; + statusRide = 'Apply'; + rideConfirm = false; + isSearchingWindow = false; + update(); + startTimer(); +// todo stop this because this method in startTimer() + // startTimerFromDriverToPassengerAfterApplied(); + + // timer.cancel(); + _rideStatusStreamController.close(); + } + +// Listening to the Stream + void listenToRideStatusStream() { + rideStatusStream.listen((rideStatus) { + print("Ride Status: $rideStatus"); + // Handle updates based on the ride status + }, onError: (error) { + print("Error in Ride Status Stream: $error"); + // Handle stream errors + }, onDone: () { + print("Ride status stream closed."); + }); } reSearchAfterCanceledFromDriver() async { - await getCarsLocationByPassengerAndReloadMarker( - box.read(BoxName.carType), 7000); - - confirmRideForAllDriverAvailable(); shouldFetch = true; // Stop further fetches statusRide = 'wait'; rideConfirm = true; isSearchingWindow = true; update(); + updateConfirmRideForAllDriverAvailable(); } void start15SecondTimer(String rideId) { @@ -1485,44 +2154,56 @@ class MapPassengerController extends GetxController { void timerEnded() async { runEvery30SecondsUntilConditionMet(); isCancelRidePageShown = false; - print('isCancelRidePageShown: ${isCancelRidePageShown}'); + print('isCancelRidePageShown: $isCancelRidePageShown'); update(); } Future getRideStatus(String rideId) async { - final response = - await CRUD().get(link: AppLink.getRideStatus, payload: {'id': rideId}); - + final response = await CRUD().get( + link: "${AppLink.endPoint}/ride/rides/getRideStatus.php", + payload: {'id': rideId}); + print(response); + print('2140'); return jsonDecode(response)['data']; } - late String driverCarModel, driverCarMake, driverLicensePlate, driverName; + late String driverCarModel, + driverCarMake, + driverLicensePlate, + driverName = ''; getUpdatedRideForDriverApply(String rideId) async { // if (isDriversTokensSend) { - final res = - await CRUD().get(link: AppLink.getRideOrderID, payload: {'id': rideId}); + final res = await CRUD().get( + link: "${AppLink.endPoint}/ride/rides/getRideOrderID.php", + payload: {'id': rideId}); if (res != 'failure') { var response = jsonDecode(res); + Log.print('getUpdatedRideForDriverApply: $response'); driverId = response['data']['driver_id']; driverPhone = response['data']['phone']; driverCarMake = response['data']['make']; model = response['data']['model']; + colorHex = response['data']['color_hex']; + carColor = response['data']['color']; make = response['data']['make']; licensePlate = response['data']['car_plate']; - firstName = response['data']['first_name']; + passengerName = response['data']['passengerName']; + driverName = response['data']['driverName'].toString(); driverToken = response['data']['token']; + // Log.print('driverToken updated: $driverToken'); carYear = response['data']['year']; - driverRate = response['data']['ratingDriver']; - } - driversToken.remove(driverToken); - for (var i = 1; i < driversToken.length; i++) { - FirebaseMessagesController().sendNotificationToAnyWithoutData( - 'Order Applied'.tr, - '$driverName Apply order\nTake attention in other order'.tr, - driversToken[i], - 'start.wav', - ); + driverRate = response['data']['ratingDriver'].toString(); } + // driversToken.remove(driverToken); + // for (var i = 1; i < driversToken.length; i++) { + FirebaseMessagesController().sendNotificationToDriverMAP( + 'Order Accepted'.tr, + '$driverName${'Accepted your order'.tr}', + driverToken.toString(), + [], + 'start.wav', + ); + // } // } } @@ -1645,34 +2326,71 @@ class MapPassengerController extends GetxController { final int updateIntervalMs = 100; // Update every 100ms final double minMovementThreshold = 10; // Minimum movement in meters to trigger update + Future getCarForFirstConfirm(String carType) async { + bool foundCars = false; + int attempt = 0; + + // Set up the periodic timer + Timer? timer = Timer.periodic(const Duration(seconds: 4), (Timer t) async { + // Attempt to get car location + foundCars = await getCarsLocationByPassengerAndReloadMarker( + carType, attempt * 2000); + Log.print('foundCars: $foundCars'); + + if (foundCars) { + // If cars are found, cancel the timer and exit the search + t.cancel(); + } else if (attempt >= 4) { + // After 4 attempts, stop the search + t.cancel(); + + // No cars found after 4 attempts + // MyDialog().getDialog( + // "No Car or Driver Found in your area.".tr, + // "No Car or Driver Found in your area.".tr, + // () { + // Get.back(); + // }, + // ); + if (!foundCars) { + noCarString = true; + dataCarsLocationByPassenger = 'failure'; + } + + update(); + } + + attempt++; // Increment attempt + }); + } void startCarLocationSearch(String carType) { int searchInterval = 5; // Interval in seconds - Log.print('searchInterval: ${searchInterval}'); + Log.print('searchInterval: $searchInterval'); int boundIncreaseStep = 2500; // Initial bounds in meters - Log.print('boundIncreaseStep: ${boundIncreaseStep}'); + Log.print('boundIncreaseStep: $boundIncreaseStep'); int maxAttempts = 3; // Maximum attempts to increase bounds int maxBoundIncreaseStep = 6000; // Maximum bounds increase step int attempt = 0; // Current attempt - Log.print('initial attempt: ${attempt}'); + Log.print('initial attempt: $attempt'); Timer.periodic(Duration(seconds: searchInterval), (Timer timer) async { - Log.print('Current attempt: ${attempt}'); // Log current attempt + Log.print('Current attempt: $attempt'); // Log current attempt bool foundCars = false; if (attempt >= maxAttempts) { timer.cancel(); if (foundCars == false) { noCarString = true; - dataCarsLocationByPassenger = 'failure'; + // dataCarsLocationByPassenger = 'failure'; update(); } - return; + // return; } else if (reloadStartApp == true) { - Log.print('reloadStartApp: ${reloadStartApp}'); + Log.print('reloadStartApp: $reloadStartApp'); foundCars = await getCarsLocationByPassengerAndReloadMarker( carType, boundIncreaseStep); - Log.print('foundCars: ${foundCars}'); + Log.print('foundCars: $foundCars'); if (foundCars) { timer.cancel(); @@ -1682,7 +2400,7 @@ class MapPassengerController extends GetxController { timer.cancel(); } Log.print( - 'Incrementing attempt to: ${attempt}'); // Log incremented attempt + 'Incrementing attempt to: $attempt'); // Log incremented attempt if (boundIncreaseStep < maxBoundIncreaseStep) { boundIncreaseStep += 1500; // Increase bounds @@ -1691,7 +2409,7 @@ class MapPassengerController extends GetxController { maxBoundIncreaseStep; // Ensure it does not exceed the maximum } Log.print( - 'New boundIncreaseStep: ${boundIncreaseStep}'); // Log new bounds + 'New boundIncreaseStep: $boundIncreaseStep'); // Log new bounds } } } @@ -1704,11 +2422,12 @@ class MapPassengerController extends GetxController { longitude >= 31.215009 && longitude <= 31.532186) { box.write(BoxName.serverChosen, AppLink.seferCairoServer); + return 'Cairo'; } else if (latitude >= 29.904975 && latitude <= 30.143372 && longitude >= 30.787030 && - longitude <= 31.238843) { + longitude <= 31.215009) { box.write(BoxName.serverChosen, AppLink.seferGizaServer); return 'Giza'; } else if (latitude >= 30.396286 && @@ -1719,114 +2438,141 @@ class MapPassengerController extends GetxController { return 'Alexandria'; } else { box.write(BoxName.serverChosen, AppLink.seferCairoServer); - return 'Outside'; + return 'Cairo'; } } Future getCarsLocationByPassengerAndReloadMarker( String carType, int boundIncreaseStep) async { - if (statusRide == 'wait') { - carsLocationByPassenger = []; - LatLngBounds bounds = calculateBounds(passengerLocation.latitude, - passengerLocation.longitude, boundIncreaseStep.toDouble()); - var res; - // await getLocation(); + // if (statusRide == 'wait') { + carsLocationByPassenger = []; + LatLngBounds bounds = calculateBounds(passengerLocation.latitude, + passengerLocation.longitude, boundIncreaseStep.toDouble()); + var res; + // await getLocation(); - switch (carType) { - case 'Lady': - res = await CRUD() - .get(link: AppLink.getFemalDriverLocationByPassenger, payload: { - 'southwestLat': bounds.southwest.latitude.toString(), - 'southwestLon': bounds.southwest.longitude.toString(), - 'northeastLat': bounds.northeast.latitude.toString(), - 'northeastLon': bounds.northeast.longitude.toString(), - }); - break; - case 'Comfort': - res = await CRUD() - .get(link: AppLink.getCarsLocationByPassengerComfort, payload: { - 'southwestLat': bounds.southwest.latitude.toString(), - 'southwestLon': bounds.southwest.longitude.toString(), - 'northeastLat': bounds.northeast.latitude.toString(), - 'northeastLon': bounds.northeast.longitude.toString(), - }); - break; - case 'Speed': - res = await CRUD() - .get(link: AppLink.getCarsLocationByPassengerSpeed, payload: { - 'southwestLat': bounds.southwest.latitude.toString(), - 'southwestLon': bounds.southwest.longitude.toString(), - 'northeastLat': bounds.northeast.latitude.toString(), - 'northeastLon': bounds.northeast.longitude.toString(), - }); - break; - case 'Scooter': - res = await CRUD() - .get(link: AppLink.getCarsLocationByPassengerDelivery, payload: { - 'southwestLat': bounds.southwest.latitude.toString(), - 'southwestLon': bounds.southwest.longitude.toString(), - 'northeastLat': bounds.northeast.latitude.toString(), - 'northeastLon': bounds.northeast.longitude.toString(), - }); - break; - case 'Balash': - res = await CRUD() - .get(link: AppLink.getCarsLocationByPassengerBalash, payload: { - 'southwestLat': bounds.southwest.latitude.toString(), - 'southwestLon': bounds.southwest.longitude.toString(), - 'northeastLat': bounds.northeast.latitude.toString(), - 'northeastLon': bounds.northeast.longitude.toString(), - }); - break; - default: - res = await CRUD() - .get(link: AppLink.getCarsLocationByPassenger, payload: { - 'southwestLat': bounds.southwest.latitude.toString(), - 'southwestLon': bounds.southwest.longitude.toString(), - 'northeastLat': bounds.northeast.latitude.toString(), - 'northeastLon': bounds.northeast.longitude.toString(), - }); - } - - if (res == 'failure') { - noCarString = true; - dataCarsLocationByPassenger = 'failure'; - update(); - return false; - } else { - noCarString = false; - dataCarsLocationByPassenger = jsonDecode(res); - driverId = dataCarsLocationByPassenger['message'][carsOrder] - ['driver_id'] - .toString(); - gender = dataCarsLocationByPassenger['message'][carsOrder]['gender'] - .toString(); - - carsLocationByPassenger.clear(); // Clear existing markers - - for (var i = 0; - i < dataCarsLocationByPassenger['message'].length; - i++) { - var json = dataCarsLocationByPassenger['message'][i]; - _updateOrCreateMarker( - MarkerId(json['latitude']).toString(), - LatLng(double.parse(json['latitude']), - double.parse(json['longitude'])), - double.parse(json['heading']), - _getIconForCar(json), - ); - - driversToken.add(json['token']); - } - - // Add fake car markers - _addFakeCarMarkers(passengerLocation, 2); - - update(); - return true; - } + switch (carType) { + case 'Lady': + res = await CRUD() + .get(link: AppLink.getFemalDriverLocationByPassenger, payload: { + 'southwestLat': bounds.southwest.latitude.toString(), + 'southwestLon': bounds.southwest.longitude.toString(), + 'northeastLat': bounds.northeast.latitude.toString(), + 'northeastLon': bounds.northeast.longitude.toString(), + }); + break; + case 'Comfort': + res = await CRUD() + .get(link: AppLink.getCarsLocationByPassengerComfort, payload: { + 'southwestLat': bounds.southwest.latitude.toString(), + 'southwestLon': bounds.southwest.longitude.toString(), + 'northeastLat': bounds.northeast.latitude.toString(), + 'northeastLon': bounds.northeast.longitude.toString(), + }); + break; + case 'Speed': + res = await CRUD() + .get(link: AppLink.getCarsLocationByPassengerSpeed, payload: { + 'southwestLat': bounds.southwest.latitude.toString(), + 'southwestLon': bounds.southwest.longitude.toString(), + 'northeastLat': bounds.northeast.latitude.toString(), + 'northeastLon': bounds.northeast.longitude.toString(), + }); + break; + case 'Scooter': + res = await CRUD() + .get(link: AppLink.getCarsLocationByPassengerDelivery, payload: { + 'southwestLat': bounds.southwest.latitude.toString(), + 'southwestLon': bounds.southwest.longitude.toString(), + 'northeastLat': bounds.northeast.latitude.toString(), + 'northeastLon': bounds.northeast.longitude.toString(), + }); + break; + case 'Awfar Car': + res = await CRUD() + .get(link: AppLink.getCarsLocationByPassengerBalash, payload: { + 'southwestLat': bounds.southwest.latitude.toString(), + 'southwestLon': bounds.southwest.longitude.toString(), + 'northeastLat': bounds.northeast.latitude.toString(), + 'northeastLon': bounds.northeast.longitude.toString(), + }); + break; + case 'Pink Bike': + res = await CRUD() + .get(link: AppLink.getCarsLocationByPassengerPinkBike, payload: { + 'southwestLat': bounds.southwest.latitude.toString(), + 'southwestLon': bounds.southwest.longitude.toString(), + 'northeastLat': bounds.northeast.latitude.toString(), + 'northeastLon': bounds.northeast.longitude.toString(), + }); + break; + default: + res = await CRUD() + .get(link: AppLink.getCarsLocationByPassenger, payload: { + 'southwestLat': bounds.southwest.latitude.toString(), + 'southwestLon': bounds.southwest.longitude.toString(), + 'northeastLat': bounds.northeast.latitude.toString(), + 'northeastLon': bounds.northeast.longitude.toString(), + }); } - return false; + + if (res == 'failure') { + noCarString = true; + // dataCarsLocationByPassenger = 'failure'; + update(); + return false; + } else { + noCarString = false; + dataCarsLocationByPassenger = jsonDecode(res); + Log.print( + 'dataCarsLocationByPassenger:getCarsLocationByPassengerAndReloadMarker ${dataCarsLocationByPassenger}'); + + // Check if 'message' is present and not null + if (dataCarsLocationByPassenger != null && + dataCarsLocationByPassenger.isNotEmpty) { + // Check if carsOrder is within bounds + // if (carsOrder < dataCarsLocationByPassenger['message'].length) { + // driverId = dataCarsLocationByPassenger['message'][carsOrder] + // ['driver_id'] + // .toString(); + // gender = dataCarsLocationByPassenger['message'][carsOrder]['gender'] + // .toString(); + // driverToken = dataCarsLocationByPassenger['message'][carsOrder] + // ['token'] + // .toString(); + // } else { + print('carsOrder is in of bounds for message array'); + // return false; + // } + } else { + // Get.defaultDialog(title: 'No cars available '); + print('No cars available or message is null'); + return false; + } + + carsLocationByPassenger.clear(); // Clear existing markers + + for (var i = 0; i < dataCarsLocationByPassenger['message'].length; i++) { + var json = dataCarsLocationByPassenger['message'][i]; + _updateOrCreateMarker( + MarkerId(json['latitude']).toString(), + LatLng( + double.parse(json['latitude']), double.parse(json['longitude'])), + double.parse(json['heading']), + _getIconForCar(json), + ); + + driversToken.add(json['token']); + } + + // Add fake car markers + _addFakeCarMarkers(passengerLocation, 1); + + update(); + return true; + } + // } + // return false; } final List> fakeCarData = []; @@ -1834,7 +2580,7 @@ class MapPassengerController extends GetxController { void _addFakeCarMarkers(LatLng center, int count) { if (fakeCarData.isEmpty) { Random random = Random(); - double radiusInKm = 1.5; // 3 km diameter, so 1.5 km radius + double radiusInKm = 2.5; // 3 km diameter, so 1.5 km radius for (int i = 0; i < count; i++) { // Generate a random angle and distance within the circle @@ -1997,6 +2743,93 @@ class MapPassengerController extends GetxController { } } + // Function to check if the point is inside the polygon + bool isPointInPolygon(LatLng point, List polygon) { + int intersections = 0; + for (int i = 0; i < polygon.length; i++) { + LatLng vertex1 = polygon[i]; + LatLng vertex2 = + polygon[(i + 1) % polygon.length]; // Loop back to the start + + if (_rayIntersectsSegment(point, vertex1, vertex2)) { + intersections++; + } + } + + // If the number of intersections is odd, the point is inside + return intersections % 2 != 0; + } + +// Helper function to check if a ray from the point intersects with a polygon segment + bool _rayIntersectsSegment(LatLng point, LatLng vertex1, LatLng vertex2) { + double px = point.longitude; + double py = point.latitude; + + double v1x = vertex1.longitude; + double v1y = vertex1.latitude; + double v2x = vertex2.longitude; + double v2y = vertex2.latitude; + + // Check if the point is outside the vertical bounds of the segment + if ((py < v1y && py < v2y) || (py > v1y && py > v2y)) { + return false; + } + + // Calculate the intersection of the ray and the segment + double intersectX = v1x + (py - v1y) * (v2x - v1x) / (v2y - v1y); + + // Check if the intersection is to the right of the point + return intersectX > px; + } + + bool isInUniversity = false; +// Function to check if the passenger is in any university polygon + // Function to check if the passenger is in any university polygon and return the university name + String checkPassengerLocation(LatLng passengerLocation, + List> universityPolygons, List universityNames) { + for (int i = 0; i < universityPolygons.length; i++) { + if (isPointInPolygon(passengerLocation, universityPolygons[i])) { + isInUniversity = true; + return "Passenger is in ${universityNames[i]}"; + } + } + return "Passenger is not in any university"; + } + + String passengerLocationStringUnvirsity = 'unKnown'; + void getPassengerLocationUniversity() { + // Check if the passenger is inside any of the university polygons and get the university name + passengerLocationStringUnvirsity = checkPassengerLocation( + passengerLocation, + UniversitiesPolygons.universityPolygons, + UniversitiesPolygons.universityNames, + ); + if (passengerLocationStringUnvirsity != 'unKnown') { + // Get.snackbar('you are in $passengerLocationStringUnvirsity', ""); + } + print(passengerLocationStringUnvirsity); + } + + var polygons = {}.obs; + + // Initialize polygons from UniversitiesPolygons + void _initializePolygons() { + List> universityPolygons = + UniversitiesPolygons.universityPolygons; + List universityNames = UniversitiesPolygons.universityNames; + + for (int i = 0; i < universityPolygons.length; i++) { + Polygon polygon = Polygon( + polygonId: PolygonId(universityNames[i]), + points: universityPolygons[i], + strokeColor: Colors.blueAccent, + fillColor: Colors.blueAccent.withOpacity(0.2), + strokeWidth: 2, + ); + polygons.add(polygon); // Add polygon to observable set + } + } + void handleResponse(Map res1) { if (res1['message'] == "No passenger found for the given phone number") { Get.defaultDialog( @@ -2101,12 +2934,13 @@ class MapPassengerController extends GetxController { double tripDurationInMinutes = durationToRide / 6; int loopCount = tripDurationInMinutes.ceil(); // If the trip duration is less than or equal to 50 minutes, then break the loop. + clearMarkersExceptStartEnd(); for (var i = 0; i < loopCount; i++) { // Wait for 50 seconds. await Future.delayed(const Duration(seconds: 4)); - if (rideTimerBegin == true) { - await getDriverCarsLocationToPassengerAfterApplied(); - } + // if (rideTimerBegin == true && statusRide == 'Apply') { + await getDriverCarsLocationToPassengerAfterApplied(); + // } reloadMarkerDriverCarsLocationToPassengerAfterApplied(); } } @@ -2208,18 +3042,6 @@ class MapPassengerController extends GetxController { update(); } - searchNewDriverAfterRejectingFromDriver() { -// - - shouldFetch = true; // Stop further fetches - statusRide = 'wait'; - rideConfirm = true; - isSearchingWindow = true; - confirmRideForFirstDriver(); - - update(); - } - Future cancelRideAfterRejectFromAll() async { clearPlacesDestination(); clearPolyline(); @@ -2228,6 +3050,12 @@ class MapPassengerController extends GetxController { "id": rideId.toString(), // Convert to String "status": 'notApplyFromAnyDriver' }); + if (AppLink.endPoint != AppLink.seferCairoServer) { + CRUD().post(link: "${AppLink.endPoint}/ride/rides/update.php", payload: { + "id": rideId.toString(), // Convert to String + "status": 'notApplyFromAnyDriver' + }); + } rideConfirm = false; statusRide == 'Cancel'; isSearchingWindow = false; @@ -2244,46 +3072,66 @@ class MapPassengerController extends GetxController { } Future cancelRide() async { - if (rideConfirm == false && statusRide == 'Apply') { - clearPlacesDestination(); - clearPolyline(); - // clearPolylineAll(); - data = []; - changeCancelRidePageShow(); - if (rideId != 'yet') { - await CRUD().post(link: AppLink.updateDriverOrder, payload: { + // if (rideConfirm == true || + // statusRide == 'Apply' || + // statusRide == 'Applied' || + // statusRide == 'wait' || + // statusRide == 'waiting') { + clearPlacesDestination(); + clearPolyline(); + // clearPolylineAll(); + data = []; + changeCancelRidePageShow(); + if (rideId != 'yet') { + Log.print('cancelRide: 1'); + FirebaseMessagesController().sendNotificationToDriverMAP( + 'Cancel Trip'.tr, + 'Trip Cancelled'.tr, + driverToken.toString(), + [], + 'cancel.wav', + ); + + await Future.wait([ + CRUD().post(link: AppLink.updateRides, payload: { + "id": rideId.toString(), // Convert to String + "status": 'Cancel' + }), + CRUD().post(link: AppLink.updateDriverOrder, payload: { "order_id": rideId.toString(), // Convert to String "status": 'Cancel' - }); - await CRUD().post(link: AppLink.updateRides, payload: { + }), + CRUD().post(link: AppLink.updateWaitingTrip, payload: { + "id": rideId.toString(), // Convert to String + "status": 'Cancel' + }), + ]); + + if (AppLink.endPoint != AppLink.seferCairoServer) { + CRUD().post( + link: "${AppLink.endPoint}/ride/driver_order/update.php", + payload: { + "order_id": rideId.toString(), // Convert to String + "status": 'Cancel' + }); + CRUD() + .post(link: "${AppLink.endPoint}/ride/rides/update.php", payload: { "id": rideId.toString(), // Convert to String "status": 'Cancel' }); - print('Cancel'); - FirebaseMessagesController().sendNotificationToDriverMAP( - 'Cancel Trip', - 'Trip Cancelled'.tr, - driverToken, - [], - 'cancel.wav', - ); + CRUD().post( + link: + "${AppLink.endPoint}/ride/notificationCaptain/updateWaitingTrip.php", + payload: { + "id": rideId.toString(), // Convert to String + "status": 'Cancel' + }); } - - Get.offAll(const MapPagePassenger()); - } else { - clearPlacesDestination(); - clearPolyline(); - data = []; - // await CRUD().post(link: AppLink.updateDriverOrder, payload: { - // "order_id": rideId.toString(), // Convert to String - // "status": 'Cancel' - // }); - // await CRUD().post(link: AppLink.updateRides, payload: { - // "id": rideId.toString(), // Convert to String - // "status": 'Cancel' - // }); - Get.offAll(const MapPagePassenger()); + print('Cancel'); + // } } + Future.delayed(const Duration(seconds: 1)); + Get.offAll(() => const MapPagePassenger()); } void changePickerShown() { @@ -2425,21 +3273,120 @@ class MapPassengerController extends GetxController { update(); } - Future getPlaces() async { - var url = - // '${AppLink.googleMapsLink}place/nearbysearch/json?location=${mylocation.longitude}&radius=25000&language=ar&keyword=&key=${placeController.text}${AK.mapAPIKEY}'; - '${AppLink.googleMapsLink}place/nearbysearch/json?keyword=${placeDestinationController.text}&location=${passengerLocation.latitude},${passengerLocation.longitude}&radius=50000&language=ar&key=${AK.mapAPIKEY.toString()}'; +// Future getPlaces() async { +// var languageCode; - var response = await CRUD().getGoogleApi(link: url, payload: {}); +// // Check if `placeDestinationController.text` contains English characters +// if (RegExp(r'[a-zA-Z]').hasMatch(placeDestinationController.text)) { +// languageCode = 'en'; +// } else { +// languageCode = 'ar'; +// } +// var url = +// '${AppLink.searcMaps}?q=${placeDestinationController.text}&in=circle:${passengerLocation.latitude},${passengerLocation.longitude};r=250000&countryCode=${box.read(BoxName.countryCode) == 'EGYPT' ? 'EGY' : 'JOR'}&apiKey=${AK.apiKeyHere}'; +// // '${AppLink.googleMapsLink}place/nearbysearch/json?location=${mylocation.longitude}&radius=25000&language=ar&keyword=&key=${placeController.text}${AK.mapAPIKEY}'; +// // '${AppLink.googleMapsLink}place/nearbysearch/json?keyword=${placeDestinationController.text}&location=${passengerLocation.latitude},${passengerLocation.longitude}&radius=250000&language=$languageCode&key=${AK.mapAPIKEY.toString()}'; +// print(url); +// var response = await CRUD().getGoogleApi(link: url, payload: {}); +// Log.print('response: ${response}'); - placesDestination = response['results']; +// placesDestination = response['results']; +// update(); +// } + getAIKey(String key) async { + var res = + await CRUD().get(link: AppLink.getapiKey, payload: {"keyName": key}); + if (res != 'failure') { + var d = jsonDecode(res)['message']; + return d[key].toString(); + } else {} + } + + Future getPlaces() async { + var languageCode; + + // Check if `placeDestinationController.text` contains English characters + if (RegExp(r'[a-zA-Z]').hasMatch(placeDestinationController.text)) { + languageCode = 'en'; + } else { + languageCode = 'ar'; + } + + // Construct the URL + var url = Uri.parse( + '${AppLink.searcMaps}?q=${Uri.encodeQueryComponent(placeDestinationController.text)}&limit=4&in=circle:${passengerLocation.latitude},${passengerLocation.longitude};r=250000&countryCode=${box.read(BoxName.countryCode) == 'EGYPT' ? 'EGY' : 'JOR'}&lang=$languageCode&apiKey=$k', + ); + + // Log the URL for debugging + print(url); + // box.remove(BoxName.placesDestination); + try { + // Make the API request + var response = await CRUD().getHereMap( + link: url.toString(), + ); + + // Log the response for debugging + // Log.print('response: ${response}'); + + // Check if the response is valid + if (response != null && response['items'] != null) { + placesDestination = response['items']; + // Log.print('placesDestination: ${placesDestination}'); + + placesDestination = response['items']; + // box.write(BoxName.placesDestination, placesDestination); + for (var i = 0; i < placesDestination.length; i++) { + var res = placesDestination[i]; + + // Extract fields with null safety + var title = res['title']?.toString() ?? 'Unknown Place'; + var position = res['position']; + var address = res['address']?['label'] ?? 'Unknown Address'; + if (position == null) { + Log.print('Position is null for place: $title'); + continue; // Skip this place and continue with the next one + } + + String latitude = position['lat']?.toString() ?? '0.0'; + String longitude = position['lng']?.toString() ?? '0.0'; + + try { + await savePlaceToServer(latitude, longitude, title, address); + // Log.print('Place saved successfully: $title'); + } catch (e) { + // Log.print('Failed to save place: $e'); + } + } // todo save key in env then get key and use it + } else { + placesDestination = []; + } + } catch (e) { + // Handle any errors that occur during the API request + Log.print('Error fetching places: $e'); + placesDestination = []; + } + + // Notify listeners that the state has changed update(); } Future getPlacesStart() async { + var languageCode = wayPoint0Controller.text; + + // Regular expression to check for English alphabet characters + final englishRegex = RegExp(r'[a-zA-Z]'); + + // Check if text contains English characters + if (englishRegex.hasMatch(languageCode)) { + languageCode = 'en'; + } else { + languageCode = 'ar'; + } + var url = // '${AppLink.googleMapsLink}place/nearbysearch/json?location=${mylocation.longitude}&radius=25000&language=ar&keyword=&key=${placeController.text}${AK.mapAPIKEY}'; - '${AppLink.googleMapsLink}place/nearbysearch/json?keyword=${placeStartController.text}&location=${passengerLocation.latitude},${passengerLocation.longitude}&radius=50000&language=ar&key=${AK.mapAPIKEY.toString()}'; + '${AppLink.googleMapsLink}place/nearbysearch/json?keyword=${placeStartController.text}&location=${passengerLocation.latitude},${passengerLocation.longitude}&radius=250000&language=$languageCode&key=${AK.mapAPIKEY.toString()}'; var response = await CRUD().getGoogleApi(link: url, payload: {}); @@ -2448,16 +3395,65 @@ class MapPassengerController extends GetxController { } Future getPlacesListsWayPoint(int index) async { + var languageCode = wayPoint0Controller.text; + + // Regular expression to check for English alphabet characters + final englishRegex = RegExp(r'[a-zA-Z]'); + + // Check if text contains English characters + if (englishRegex.hasMatch(languageCode)) { + languageCode = 'en'; + } else { + languageCode = 'ar'; + } + var url = - '${AppLink.googleMapsLink}place/nearbysearch/json?keyword=${wayPoint0Controller.text}&location=${passengerLocation.latitude},${passengerLocation.longitude}&radius=50000&language=ar&key=${AK.mapAPIKEY.toString()}'; + '${AppLink.googleMapsLink}place/nearbysearch/json?keyword=${wayPoint0Controller.text}&location=${passengerLocation.latitude},${passengerLocation.longitude}&radius=250000&language=$languageCode&key=${AK.mapAPIKEY.toString()}'; - var response = await CRUD().getGoogleApi(link: url, payload: {}); + try { + var response = await CRUD().getGoogleApi(link: url, payload: {}); - wayPoint0 = response['results']; - placeListResponseAll[index] = response['results']; - update(); + if (response != null && response['results'] != null) { + wayPoint0 = response['results']; + placeListResponseAll[index] = response['results']; + update(); + } else { + print('Error: Invalid response from Google Places API'); + } + } catch (e) { + print('Error fetching places: $e'); + } } + Future savePlaceToServer( + String latitude, String longitude, String name, String rate) async { + var data = { + 'latitude': latitude, + 'longitude': longitude, + 'name': name, + 'rate': rate, + }; + + try { + CRUD().post( + link: AppLink.savePlacesServer, + payload: data, + ); + } catch (e) { + print('Error: $e'); + } + } + // Future getPlacesListsWayPoint(int index) async { + // var url = + // '${AppLink.googleMapsLink}place/nearbysearch/json?keyword=${wayPoint0Controller.text}&location=${passengerLocation.latitude},${passengerLocation.longitude}&radius=80000&language=${}&key=${AK.mapAPIKEY.toString()}'; + + // var response = await CRUD().getGoogleApi(link: url, payload: {}); + + // wayPoint0 = response['results']; + // placeListResponseAll[index] = response['results']; + // update(); + // } + LatLng fromString(String location) { List parts = location.split(','); double lat = double.parse(parts[0]); @@ -2636,28 +3632,61 @@ class MapPassengerController extends GetxController { ? LatLng(_locationData.latitude!, _locationData.longitude!) : null)!; getLocationArea(passengerLocation.latitude, passengerLocation.longitude); + Log.print('AppLink.endPoint: ${AppLink.endPoint}'); + // Log.print('BoxName.serverChosen: ${box.read(BoxName.serverChosen)}'); + newStartPointLocation = passengerLocation; + Log.print('passengerLocation: $passengerLocation'); speed = _locationData.speed!; // //print location details isLoading = false; update(); } - LatLngBounds calculateBounds( - double centerLat, double centerLng, double radius) { - // double radius = 4000; // 10 km in meters + // LatLngBounds calculateBounds(double lat, double lng, double radiusInMeters) { + // const double earthRadius = 6378137.0; // Earth's radius in meters - southwest = LatLng( - centerLat - (radius / 111000), - centerLng - (radius / (111000 * cos(centerLat))), + // double latDelta = radiusInMeters / earthRadius * (180 / pi); + // double lngDelta = + // radiusInMeters / (earthRadius * cos(pi * lat / 180)) * (180 / pi); + + // return LatLngBounds( + // southwest: LatLng(lat - latDelta, lng - lngDelta), + // northeast: LatLng(lat + latDelta, lng + lngDelta), + // ); + // } + LatLngBounds calculateBounds(double lat, double lng, double radiusInMeters) { + const double earthRadius = 6378137.0; // Earth's radius in meters + + double latDelta = (radiusInMeters / earthRadius) * (180 / pi); + double lngDelta = + (radiusInMeters / (earthRadius * cos(pi * lat / 180))) * (180 / pi); + + double minLat = lat - latDelta; + double maxLat = lat + latDelta; + + double minLng = lng - lngDelta; + double maxLng = lng + lngDelta; + + // Ensure the latitude is between -90 and 90 + minLat = max(-90.0, minLat); + maxLat = min(90.0, maxLat); + + // Ensure the longitude is between -180 and 180 + minLng = (minLng + 180) % 360 - 180; + maxLng = (maxLng + 180) % 360 - 180; + + // Ensure the bounds are in the correct order + if (minLng > maxLng) { + double temp = minLng; + minLng = maxLng; + maxLng = temp; + } + + return LatLngBounds( + southwest: LatLng(minLat, minLng), + northeast: LatLng(maxLat, maxLng), ); - - northeast = LatLng( - centerLat + (radius / 111000), - centerLng + (radius / (111000 * cos(centerLat))), - ); - - return LatLngBounds(southwest: southwest, northeast: northeast); } GoogleMapController? mapController; @@ -2686,19 +3715,17 @@ class MapPassengerController extends GetxController { bool reloadStartApp = false; int reloadCount = 0; startMarkerReloading() async { - Log.print('AppLink.endPoint: ${AppLink.endPoint}'); - if (reloadStartApp == false) { - Timer.periodic(const Duration(seconds: 4), (timer) async { + Timer.periodic(const Duration(seconds: 3), (timer) async { reloadCount++; - Log.print('reloadCount: ${reloadCount}'); + Log.print('reloadCount: $reloadCount'); - if (!rideConfirm) { + if (rideConfirm == false) { clearMarkersExceptStartEnd(); // _smoothlyUpdateMarker(); // startCarLocationSearch(box.read(BoxName.carType)); await getCarsLocationByPassengerAndReloadMarker( - box.read(BoxName.carType), 6000); + box.read(BoxName.carType), 5000); await getNearestDriverByPassengerLocation(); Log.print('reloadMarkers: from startMarkerReloading'); } else { @@ -2720,55 +3747,128 @@ class MapPassengerController extends GetxController { late Duration durationFromDriverToPassenger; double nearestDistance = double.infinity; + // Future getNearestDriverByPassengerLocation() async { + // if (polyLines.isEmpty || data.isEmpty) { + // return null; // Early return if data is empty + // } + + // if (!rideConfirm) { + // if (dataCarsLocationByPassenger != 'failure') { + // if (dataCarsLocationByPassenger != null) { + // if (dataCarsLocationByPassenger['message'].length > 0) { + // double nearestDistance = double + // .infinity; // Initialize nearest distance to a large number + // CarLocation? nearestCar; + + // for (var i = 0; + // i < dataCarsLocationByPassenger['message'].length; + // i++) { + // var carLocation = dataCarsLocationByPassenger['message'][i]; + + // // Calculate the distance between passenger's location and current driver's location + // final distance = Geolocator.distanceBetween( + // passengerLocation.latitude, + // passengerLocation.longitude, + // double.parse(carLocation['latitude']), + // double.parse(carLocation['longitude']), + // ); + + // // Calculate duration assuming an average speed of 25 km/h (adjust as needed) + // int durationToPassenger = + // (distance * 25 * (1000 / 3600)).round(); // 25 km/h in m/s + + // // Update the UI with the distance and duration for each car + // update(); + + // // If this distance is smaller than the nearest distance found so far, update nearestCar + // if (distance < nearestDistance) { + // nearestDistance = distance; + + // nearestCar = CarLocation( + // distance: distance, + // duration: durationToPassenger.toDouble(), + // id: carLocation['driver_id'], + // latitude: double.parse(carLocation['latitude']), + // longitude: double.parse(carLocation['longitude']), + // ); + + // // Update the UI with the nearest driver + // update(); + // } + // } + + // // Return the nearest car found + // return nearestCar; + // } + // } + // } + // } + + // // Return null if no drivers are found or if ride is confirmed + // return null; + // } Future getNearestDriverByPassengerLocation() async { - if (polyLines.isEmpty || data.isEmpty) { - return null; // Early return if data is empty - } if (!rideConfirm) { - if (dataCarsLocationByPassenger != 'failure') { - if (dataCarsLocationByPassenger != null) { - if (dataCarsLocationByPassenger['message'].length > 0) { - for (var i = 0; - i < dataCarsLocationByPassenger['message'].length; - i++) { - var carLocation = dataCarsLocationByPassenger['message'][i]; + if (dataCarsLocationByPassenger != 'failure' && + dataCarsLocationByPassenger != null && + dataCarsLocationByPassenger['message'] != null && + dataCarsLocationByPassenger['message'].length > 0) { + double nearestDistance = double.infinity; // Initialize nearest distance + CarLocation? nearestCar; - // Calculate the distance between the passenger's location and the current driver's location - final distance = Geolocator.distanceBetween( - passengerLocation.latitude, - passengerLocation.longitude, - double.parse(carLocation['latitude']), - double.parse(carLocation['longitude']), + for (var i = 0; + i < dataCarsLocationByPassenger['message'].length; + i++) { + var carLocation = dataCarsLocationByPassenger['message'][i]; + Log.print('carLocation: $carLocation'); + + try { + // Calculate distance between passenger's location and current driver's location + final distance = Geolocator.distanceBetween( + passengerLocation.latitude, + passengerLocation.longitude, + double.parse(carLocation['latitude']), + double.parse(carLocation['longitude']), + ); + + // Calculate duration assuming an average speed of 25 km/h (adjust as needed) + int durationToPassenger = (distance / 1000 / 25 * 3600).round(); + Log.print('distance: $distance'); + Log.print('durationToPassenger: $durationToPassenger'); + Log.print('passengerLocation: $passengerLocation'); + Log.print('carLocation: $carLocation'); + Log.print('distance: $distance meters'); + Log.print('durationToPassenger: $durationToPassenger seconds'); + // Update the UI with the distance and duration for each car + update(); + + // If this distance is smaller than the nearest distance found so far, update nearestCar + if (distance < nearestDistance) { + nearestDistance = distance; + + nearestCar = CarLocation( + distance: distance, + duration: durationToPassenger.toDouble(), + id: carLocation['driver_id'], + latitude: double.parse(carLocation['latitude']), + longitude: double.parse(carLocation['longitude']), ); - durationToPassenger = (distance * 25 * (1000 / 3600)) - .round(); //////35 is avg of speed in city - // Update the UI with the distance and duration + Log.print('nearestCar: $nearestCar'); + // Update the UI with the nearest driver update(); - - // If the distance is less than the nearest distance, update the nearest driver - if (distance < nearestDistance) { - nearestDistance = distance; - - nearestCar = CarLocation( - distance: distance, - duration: - 0, // We don't have duration information from Geolocator - id: carLocation['driver_id'], - latitude: double.parse(carLocation['latitude']), - longitude: double.parse(carLocation['longitude']), - ); - - // Update the UI with the nearest driver - update(); - } } - } else {} + } catch (e) { + Log.print('Error calculating distance/duration: $e'); + } } + + // Return the nearest car found + return nearestCar; } } - // Return the nearest driver - return nearestCar; + // Return null if no drivers are found or if ride is confirmed + return null; } getNearestDriverByPassengerLocationAPIGOOGLE() async { @@ -2916,7 +4016,8 @@ class MapPassengerController extends GetxController { update(); remainingTime = 25; //to make cancel every call // startCarLocationSearch(box.read(BoxName.carType)); - getCarsLocationByPassengerAndReloadMarker(box.read(BoxName.carType), 7000); + await getCarsLocationByPassengerAndReloadMarker( + box.read(BoxName.carType), 5000); // await getCarsLocationByPassengerAndReloadMarker(); var coordDestination = destination.split(','); double latPassengerDestination = double.parse(coordDestination[0]); @@ -2929,7 +4030,7 @@ class MapPassengerController extends GetxController { isLoading = false; update(); var url = - ('${AppLink.googleMapsLink}directions/json?&language=ar&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}'); + ('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang) ?? 'ar'}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}'); var response = await CRUD().getGoogleApi(link: url, payload: {}); data = response['routes'][0]['legs']; @@ -3195,7 +4296,7 @@ class MapPassengerController extends GetxController { update(); } - int selectedIndex = 1; // Initialize with no selection + int selectedIndex = -1; // Initialize with no selection void selectCarFromList(int index) { selectedIndex = index; // Update selected index carTypes.forEach( @@ -3214,44 +4315,150 @@ class MapPassengerController extends GetxController { final promo = TextEditingController(); bool promoTaken = false; - void applyPromoCodeToPassenger() async { - //TAWJIHI - - CRUD().get(link: AppLink.getPassengersPromo, payload: { - 'promo_code': promo.text, - }).then((value) { - if (value == 'failure') { - Get.defaultDialog( - title: 'Promo End !'.tr, - confirm: MyElevatedButton( - title: 'Back', - onPressed: () { - Get.back(); + applyPromoCodeToPassenger(BuildContext context) async { + if (promoTaken == false) { + if (promoFormKey.currentState!.validate()) { + CRUD().get(link: AppLink.getPassengersPromo, payload: { + 'promo_code': promo.text, + }).then((value) { + if (value == 'failure') { + MyDialog().getDialog( + 'Promo Ended'.tr, + 'The promotion period has ended.'.tr, + () { Get.back(); }, - )); + ); + } else if (totalPassengerComfort > 30 || + totalPassengerLady > 30 || + totalPassengerSpeed > 20 || + totalPassengerBalash > 20) { + var decode = jsonDecode(value); + + if (decode["status"] == "success") { + Get.snackbar('Promo Code Accepted'.tr, '', + backgroundColor: AppColor.greenColor); + var firstElement = decode["message"][0]; + double burc = + double.parse(box.read(BoxName.passengerWalletTotal)); + if (burc < 0) { + int discountPercentage = int.parse(firstElement['amount']); + +// 1. Calculate and apply discount for totalPassengerComfort + totalPassengerComfortDiscount = + totalPassengerComfort * discountPercentage / 100; + Log.print( + 'totalPassengerComfortDiscount: $totalPassengerComfortDiscount'); + +// Apply the formula: totalPassengerComfort + (-1 * burc) - discount + totalPassengerComfort = totalPassengerComfort + + (-1 * burc) - + totalPassengerComfortDiscount; + +// 2. Calculate and apply discount for totalPassengerLady + totalPassengerLadyDiscount = + totalPassengerLady * discountPercentage / 100; + +// Apply the formula: totalPassengerLady + (-1 * burc) - discount + totalPassengerLady = totalPassengerLady + + (-1 * burc) - + totalPassengerLadyDiscount; + +// 3. Calculate and apply discount for totalPassengerSpeed + totalPassengerSpeedDiscount = + totalPassengerSpeed * discountPercentage / 100; + +// Apply the formula: totalPassengerSpeed + (-1 * burc) - discount + totalPassengerSpeed = totalPassengerSpeed + + (-1 * burc) - + totalPassengerSpeedDiscount; + +// 4. Calculate and apply discount for totalPassengerBalash + totalPassengerBalashDiscount = + totalPassengerBalash * discountPercentage / 100; + +// Apply the formula: totalPassengerBalash + (-1 * burc) - discount + totalPassengerBalash = totalPassengerBalash + + (-1 * burc) - + totalPassengerBalashDiscount; + +// Mark promo as taken + promoTaken = true; + +// Update UI + update(); + } else { + int discountPercentage = int.parse(firstElement['amount']); + +// Calculate discounts for each category, ensuring they don't exceed 25 + totalPassengerComfortDiscount = + totalPassengerComfort * discountPercentage / 100; + if (totalPassengerComfortDiscount > 25) { + totalPassengerComfortDiscount = + 25; // Limit the discount to 25 + } + Log.print( + 'totalPassengerComfortDiscount: $totalPassengerComfortDiscount'); + totalPassengerComfort = + totalPassengerComfort - totalPassengerComfortDiscount; + + totalPassengerLadyDiscount = + totalPassengerLady * discountPercentage / 100; + if (totalPassengerLadyDiscount > 25) { + totalPassengerLadyDiscount = 25; // Limit the discount to 25 + } + totalPassengerLady = + totalPassengerLady - totalPassengerLadyDiscount; + + totalPassengerSpeedDiscount = + totalPassengerSpeed * discountPercentage / 100; + if (totalPassengerSpeedDiscount > 25) { + totalPassengerSpeedDiscount = 25; // Limit the discount to 25 + } + totalPassengerSpeed = + totalPassengerSpeed - totalPassengerSpeedDiscount; + + totalPassengerBalashDiscount = + totalPassengerBalash * discountPercentage / 100; + if (totalPassengerBalashDiscount > 25) { + totalPassengerBalashDiscount = 25; // Limit the discount to 25 + } + totalPassengerBalash = + totalPassengerBalash - totalPassengerBalashDiscount; + +// Trigger UI update + update(); + } + + totalDriver = totalDriver - + (totalDriver * int.parse(firstElement['amount']) / 100); + promoTaken = true; + + // Launch confetti for success feedback + Confetti.launch( + context, + options: const ConfettiOptions( + particleCount: 100, spread: 70, y: 0.6), + ); + + update(); + Get.back(); + Future.delayed(const Duration(microseconds: 111)); + } + } else { + Get.snackbar('Lowest Price Achieved'.tr, + 'Cannot apply further discounts.'.tr, + backgroundColor: AppColor.yellowColor); + } + }); } - var decode = jsonDecode(value); - - if (decode["status"] == "success") { - var firstElement = decode["message"][0]; - if (double.parse(box.read(BoxName.passengerWalletTotal)) < 0) { - totalPassenger = totalCostPassenger - - (totalCostPassenger * int.parse(firstElement['amount']) / 100); - update(); - } else { - totalPassenger = totalCostPassenger - - (totalCostPassenger * int.parse(firstElement['amount']) / 100); - update(); - } - - totalDriver = totalDriver - - (totalDriver * int.parse(firstElement['amount']) / 100); - promoTaken = true; - update(); + } else { + MyDialog().getDialog( + 'Promo Already Used'.tr, 'You have already used this promo code.'.tr, + () { Get.back(); - } - }); + }); + } } double getDistanceFromText(String distanceText) { @@ -3291,6 +4498,7 @@ class MapPassengerController extends GetxController { costRayehGaiBalash, costRayehGaiComfort = 0; update(); + if (startNameAddress.toLowerCase().contains('airport') || endNameAddress.toLowerCase().contains('airport') || startNameAddress.contains('مطار') || @@ -3430,45 +4638,92 @@ class MapPassengerController extends GetxController { var totalDriver1 = costDistance + costDuration; totalCostPassenger = totalDriver1 + (totalDriver1 * kazan / 100); totalPassenger = costSpeed + (costSpeed * kazan / 100); - totalPassengerComfort = - (costComfort + (costComfort * kazan / 100)).ceilToDouble(); - totalPassengerLady = (costLady + (costLady * kazan / 100)).ceilToDouble(); - totalPassengerSpeed = - (costSpeed + (costSpeed * kazan / 100)).ceilToDouble(); - totalPassengerBalash = - (costBalash + (costBalash * kazan / 100)).ceilToDouble(); - totalPassengerRayehGai = - (costRayehGai + (costRayehGai * kazan / 100)).ceilToDouble(); - totalPassengerRayehGaiComfort = - (costRayehGaiComfort + (costRayehGaiComfort * kazan / 100)) - .ceilToDouble(); - totalPassengerRayehGaiBalash = - (costRayehGaiBalash + (costRayehGaiBalash * kazan / 100)) - .ceilToDouble(); - totalPassengerComfortDiscount = - totalPassengerComfort + totalPassengerComfort * (kazan - 0) / 100; - totalPassengerLadyDiscount = - totalPassengerLady + totalPassengerLady * (kazan - 0) / 100; - totalPassengerSpeedDiscount = - totalPassengerSpeed + totalPassengerSpeed * (kazan) / 100; - totalPassengerBalashDiscount = - totalPassengerBalash + totalPassengerBalash * (kazan) / 100; - totalPassengerRaihGaiDiscount = - totalPassengerRayehGai + totalPassengerRayehGai * (kazan) / 100; - totalPassengerScooter = - (costDelivery + (costDelivery * kazan / 100)).ceilToDouble(); - totalPassengerComfort = totalPassengerComfortDiscount - - (totalPassengerComfortDiscount * kazan / 100); - totalPassengerSpeed = totalPassengerSpeedDiscount - - (totalPassengerSpeedDiscount * kazan / 100); - totalPassengerBalash = totalPassengerBalashDiscount - - (totalPassengerBalashDiscount * kazan / 100); - totalPassengerLady = totalPassengerLadyDiscount - - (totalPassengerLadyDiscount * kazan / 100); - totalDriver = totalDriver1 + (totalDriver1 * kazan / 100); - tax = totalCostPassenger * kazan / 100; - totalME = totalCostPassenger - tax; - costForDriver = fuelPrice * (20 / 210) * distance; + if (isInUniversity) { + Log.print('isInUniversity: $isInUniversity'); + totalPassengerComfort = + 20 + (costComfort + (costComfort * kazan / 100)).ceilToDouble(); + totalPassengerLady = + 20 + (costLady + (costLady * kazan / 100)).ceilToDouble(); + totalPassengerSpeed = + 20 + (costSpeed + (costSpeed * kazan / 100)).ceilToDouble(); + totalPassengerBalash = + 20 + (costBalash + (costBalash * kazan / 100)).ceilToDouble(); + totalPassengerRayehGai = + (costRayehGai + (costRayehGai * kazan / 100)).ceilToDouble(); + totalPassengerRayehGaiComfort = + (costRayehGaiComfort + (costRayehGaiComfort * kazan / 100)) + .ceilToDouble(); + totalPassengerRayehGaiBalash = + (costRayehGaiBalash + (costRayehGaiBalash * kazan / 100)) + .ceilToDouble(); + totalPassengerComfortDiscount = + totalPassengerComfort + totalPassengerComfort * (kazan - 0) / 100; + totalPassengerLadyDiscount = + totalPassengerLady + totalPassengerLady * (kazan - 0) / 100; + totalPassengerSpeedDiscount = + totalPassengerSpeed + totalPassengerSpeed * (kazan) / 100; + totalPassengerBalashDiscount = + totalPassengerBalash + totalPassengerBalash * (kazan) / 100; + totalPassengerRaihGaiDiscount = + totalPassengerRayehGai + totalPassengerRayehGai * (kazan) / 100; + totalPassengerScooter = + (costDelivery + (costDelivery * kazan / 100)).ceilToDouble(); + totalPassengerComfort = totalPassengerComfortDiscount - + (totalPassengerComfortDiscount * kazan / 100); + totalPassengerSpeed = totalPassengerSpeedDiscount - + (totalPassengerSpeedDiscount * kazan / 100); + totalPassengerBalash = totalPassengerBalashDiscount - + (totalPassengerBalashDiscount * kazan / 100); + totalPassengerLady = totalPassengerLadyDiscount - + (totalPassengerLadyDiscount * kazan / 100); + totalDriver = totalDriver1 + (totalDriver1 * kazan / 100); + tax = totalCostPassenger * kazan / 100; + totalME = totalCostPassenger - tax; + costForDriver = fuelPrice * (20 / 210) * distance; + } else { + Log.print('isInUniversity: $isInUniversity'); + totalPassengerComfort = + (costComfort + (costComfort * kazan / 100)).ceilToDouble(); + totalPassengerLady = + (costLady + (costLady * kazan / 100)).ceilToDouble(); + totalPassengerSpeed = + (costSpeed + (costSpeed * kazan / 100)).ceilToDouble(); + totalPassengerBalash = + (costBalash + (costBalash * kazan / 100)).ceilToDouble(); + totalPassengerRayehGai = + (costRayehGai + (costRayehGai * kazan / 100)).ceilToDouble(); + totalPassengerRayehGaiComfort = + (costRayehGaiComfort + (costRayehGaiComfort * kazan / 100)) + .ceilToDouble(); + totalPassengerRayehGaiBalash = + (costRayehGaiBalash + (costRayehGaiBalash * kazan / 100)) + .ceilToDouble(); + totalPassengerComfortDiscount = + totalPassengerComfort + totalPassengerComfort * (kazan - 0) / 100; + totalPassengerLadyDiscount = + totalPassengerLady + totalPassengerLady * (kazan - 0) / 100; + totalPassengerSpeedDiscount = + totalPassengerSpeed + totalPassengerSpeed * (kazan) / 100; + totalPassengerBalashDiscount = + totalPassengerBalash + totalPassengerBalash * (kazan) / 100; + totalPassengerRaihGaiDiscount = + totalPassengerRayehGai + totalPassengerRayehGai * (kazan) / 100; + totalPassengerScooter = + (costDelivery + (costDelivery * kazan / 100)).ceilToDouble(); + totalPassengerComfort = totalPassengerComfortDiscount - + (totalPassengerComfortDiscount * kazan / 100); + totalPassengerSpeed = totalPassengerSpeedDiscount - + (totalPassengerSpeedDiscount * kazan / 100); + totalPassengerBalash = totalPassengerBalashDiscount - + (totalPassengerBalashDiscount * kazan / 100); + totalPassengerLady = totalPassengerLadyDiscount - + (totalPassengerLadyDiscount * kazan / 100); + totalDriver = totalDriver1 + (totalDriver1 * kazan / 100); + tax = totalCostPassenger * kazan / 100; + totalME = totalCostPassenger - tax; + costForDriver = fuelPrice * (20 / 210) * distance; + } + if (totalPassengerSpeed < 20) { // for eygpt 20 le open ride totalCostPassenger = 20; @@ -3504,10 +4759,23 @@ class MapPassengerController extends GetxController { } addToken() async { - await CRUD().post(link: AppLink.addTokens, payload: { + await CRUD() + .post(link: "${AppLink.server}/ride/firebase/add.php", payload: { 'token': box.read(BoxName.tokenFCM), 'passengerID': box.read(BoxName.passengerID).toString() }); + CRUD().post( + link: "${AppLink.seferAlexandriaServer}/ride/firebase/add.php", + payload: { + 'token': box.read(BoxName.tokenFCM), + 'passengerID': box.read(BoxName.passengerID).toString() + }); + CRUD().post( + link: "${AppLink.seferGizaServer}/ride/firebase/add.php", + payload: { + 'token': box.read(BoxName.tokenFCM), + 'passengerID': box.read(BoxName.passengerID).toString() + }); } List polylineCoordinate = []; @@ -3573,15 +4841,51 @@ class MapPassengerController extends GetxController { } List driversForMishwari = []; + Future selectDriverAndCarForMishwariTrip() async { - var res = await CRUD() - .get(link: AppLink.selectDriverAndCarForMishwariTrip, payload: {}); - if (res != 'failure') { - var d = jsonDecode(res); - driversForMishwari = d['message']; - update(); - } else { - return 'No driver available now try later time\nthanks for using our app' + // Calculate the bounds for 20km + double latitudeOffset = 0.1795; // 20km range in latitude + double longitudeOffset = 0.2074; // 20km range in longitude + + // Calculate bounding box based on passenger's location + double southwestLat = passengerLocation.latitude - latitudeOffset; + double northeastLat = passengerLocation.latitude + latitudeOffset; + double southwestLon = passengerLocation.longitude - longitudeOffset; + double northeastLon = passengerLocation.longitude + longitudeOffset; + + // Create the payload with calculated bounds + var payload = { + 'southwestLat': southwestLat.toString(), + 'northeastLat': northeastLat.toString(), + 'southwestLon': southwestLon.toString(), + 'northeastLon': northeastLon.toString(), + }; + + try { + // Fetch data from the API + var res = await CRUD().get( + link: AppLink.selectDriverAndCarForMishwariTrip, payload: payload); + + if (res != 'failure') { + // Check if response is valid JSON + try { + var d = jsonDecode(res); + driversForMishwari = d['message']; + Log.print('driversForMishwari: ${driversForMishwari}'); + update(); + } catch (e) { + // Handle invalid JSON format + print("Error decoding JSON: $e"); + return 'Server returned invalid data. Please try again later.'; + } + } else { + return 'No driver available now, try again later. Thanks for using our app.' + .tr; + } + } catch (e) { + // Handle network or other exceptions + print("Error fetching data: $e"); + return 'There was an issue connecting to the server. Please try again later.' .tr; } } @@ -3593,99 +4897,203 @@ class MapPassengerController extends GetxController { } Future mishwariOption() async { - // isBottomSheetShown = false; + isLoading = true; update(); // add dialoug for select driver and car await selectDriverAndCarForMishwariTrip(); Future.delayed(Duration.zero); - + isLoading = false; + update(); Get.to(() => CupertinoDriverListWidget()); // changeCashConfirmPageShown(); } + var driverIdVip = ''; Future saveTripData( Map driver, DateTime tripDateTime) async { try { // Prepare trip data Map tripData = { - 'driverId': driver['id'].toString(), - 'phone': driver['phone'].toString(), - 'gender': driver['gender'].toString(), - 'name': driver['NAME'] - .toString(), // Confirm this key exists in the driver map - 'name_english': driver['name_english'].toString(), - 'address': driver['address'].toString(), - 'religion': driver['religion'], - 'age': driver['age'].toString(), - 'education': driver['education'].toString(), - 'license_type': driver['license_type'].toString(), - 'national_number': driver['national_number'].toString(), - 'car_plate': driver['car_plate'].toString(), - 'make': driver['make'].toString(), - 'model': driver['model'].toString(), - 'color': driver['color'].toString(), - 'color_hex': driver['color_hex'].toString(), - 'token': driver['token'].toString(), - 'rating': driver['rating'].toString(), - 'countRide': driver['countRide'].toString(), - 'passengerId': box.read(BoxName.passengerID).toString(), - 'timeSelected': tripDateTime.toString(), + 'id': driver['driver_id'].toString(), // Ensure the id is a string + 'phone': driver['phone'], + 'gender': driver['gender'], + 'name': driver['NAME'], + 'name_english': driver['name_english'], + 'address': driver['address'], + 'religion': driver['religion'] ?? 'UnKnown', + 'age': driver['age'].toString(), // Convert age to String + 'education': driver['education'] ?? 'UnKnown', //startlocationname + 'license_type': driver['license_type'] ?? 'UnKnown', + 'national_number': driver['national_number'] ?? 'UnKnown', + 'car_plate': driver['car_plate'], + 'make': driver['make'], + 'model': driver['model'], + 'year': driver['year'].toString(), // Convert year to String + 'color': driver['color'], + 'color_hex': driver['color_hex'], + 'displacement': driver['displacement'], + 'fuel': driver['fuel'], + 'token': driver['token'], + 'rating': driver['rating'].toString(), // Convert rating to String + 'countRide': + driver['ride_count'].toString(), // Convert countRide to String + 'passengerId': box.read(BoxName.passengerID), + 'timeSelected': tripDateTime.toIso8601String(), 'status': 'pending', + 'startNameAddress': startNameAddress.toString(), + 'locationCoordinate': + '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', }; - - Log.print('tripData prepared: $tripData'); + Log.print('tripData: $tripData'); // Send data to server - var response = await CRUD().post( - link: AppLink.addMishwari, - payload: tripData, - ); + var response = + await CRUD().post(link: AppLink.addMishwari, payload: tripData); + // Log.print('response: $response'); - Log.print('Server response received: $response'); + if (response != 'failure') { + // Trip saved successfully + // Get.snackbar('Success'.tr, 'Trip booked successfully'.tr); + var id = response['message']['id'].toString(); + await CRUD().post( + link: '${AppLink.seferCairoServer}/ride/rides/add.php', + payload: { + "start_location": + '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', + "end_location": + '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', + "date": DateTime.now().toString(), + "time": DateTime.now().toString(), + "endtime": + DateTime.now().add(const Duration(hours: 2)).toString(), + "price": '50', + "passenger_id": box.read(BoxName.passengerID).toString(), + "driver_id": driver['driver_id'].toString(), + "status": "waiting", + 'carType': 'vip', + "price_for_driver": '50', + "price_for_passenger": '50', + "distance": '20', + "paymentMethod": 'cash', + }).then((value) { + if (value is String) { + final parsedValue = jsonDecode(value); + rideId = parsedValue['message']; + } else if (value is Map) { + rideId = value['message']; + } else { + Log.print('Unexpected response type: ${value.runtimeType}'); + } + }); + if (AppLink.endPoint != AppLink.seferCairoServer) { + await CRUD().post( + link: "${AppLink.endPoint}/ride/mishwari/add.php", + payload: tripData); + CRUD().post(link: '${AppLink.endPoint}/ride/rides/add.php', payload: { + "start_location": + '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', + "end_location": + '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}', + "date": DateTime.now().toString(), + "time": DateTime.now().toString(), + "endtime": DateTime.now().add(const Duration(hours: 2)).toString(), + "price": '50', + "passenger_id": box.read(BoxName.passengerID).toString(), + "driver_id": driver['driver_id'].toString(), + "status": "waiting", + 'carType': 'vip', + "price_for_driver": '50', + "price_for_passenger": '50', + "distance": '20', + "paymentMethod": 'cash', + }); + } + driverIdVip = driver['driver_id'].toString(); + driverId = driver['driver_id'].toString(); - if (response != 'failure' && response != null) { - Get.back(); - Future.delayed(Duration.zero); - Get.defaultDialog( - title: 'Success'.tr, - middleText: 'Trip booked successfully'.tr, - confirm: MyElevatedButton( - title: 'Accept'.tr, - onPressed: () async { - FirebaseMessagesController().sendNotificationToDriverMAP( - 'VIP Order'.tr, - 'from: ${box.read(BoxName.name)}', - driver['token'].toString(), - [ - box.read(BoxName.tokenFCM).toString(), - tripDateTime.toString() - ], - 'order.wav'); - Get.back(); - }, - kolor: AppColor.greenColor, - )); - Get.snackbar('Success'.tr, 'Trip booked successfully'.tr, - backgroundColor: AppColor.greenColor); + DateTime timeSelected = DateTime.parse(tripDateTime.toIso8601String()); + Get.find().scheduleNotificationsForTimeSelected( + "Your trip is scheduled".tr, + "Don't forget your ride!".tr, + "tone1", + timeSelected); + // Optionally, set up local notification or send a push notification + + await FirebaseMessagesController().sendNotificationToDriverMAP( + 'OrderVIP', + rideId.toString(), + driver['token'].toString(), + [ + id, + rideId, + driver['id'], + passengerLocation.latitude.toString(), + startNameAddress.toString(), + passengerLocation.longitude.toString(), + box.read(BoxName.name).toString(), + box.read(BoxName.passengerID).toString(), + box.read(BoxName.phone).toString(), + box.read(BoxName.email).toString(), + box.read(BoxName.passengerPhotoUrl).toString(), + box.read(BoxName.tokenFCM).toString(), + driver['token'].toString(), + ], + 'order.wav'); + if (response['message'] == "Trip updated successfully") { + mySnackbarSuccess("Trip updated successfully".tr); + Log.print( + 'previous_driver_token: ${response['previous_driver_token']}'); + + FirebaseMessagesController().sendNotificationToDriverMAP( + 'Order VIP Canceld'.tr, + 'Passenger cancel order'.tr, + response['previous_driver_token'].toString(), + [], + 'cancel.wav', + ); + } + // data = []; + isBottomSheetShown = false; + update(); + Get.to(() => VipWaittingPage()); } else { - Get.back(); - MyDialog().getDialog( - 'Error'.tr, - "Driver already has 2 trips within the specified period.".tr, - () {}); - - Get.snackbar('Error'.tr, 'Trip booked successfully'.tr, - backgroundColor: AppColor.redColor); - Log.print('Error response from server: $response'); - throw Exception('Failed to save trip with response: $response'); + throw Exception('Failed to save trip'); } } catch (e) { - Log.print('Exception caught: $e'); - Get.snackbar('Error'.tr, 'Failed to book trip: $e'.tr); + // Show error message with more details for debugging + Get.snackbar('Error'.tr, 'Failed to book trip: $e'.tr, + backgroundColor: AppColor.redColor); + Log.print('Error: $e'); } } + cancelVip(String token, tripId) async { + // FirebaseMessagesController().sendNotificationToDriverMAP( + // 'Order VIP Canceld'.tr, + // 'Passenger cancel order'.tr, + // token, + // [], + // 'cancel.wav', + // ); + var res = await CRUD() + .post(link: AppLink.cancelMishwari, payload: {'id': tripId}); + if (res != 'failur') { + Get.back(); + mySnackbarSuccess('You canceled VIP trip'.tr); + } + } + + sendToDriverAgain(String token) { + FirebaseMessagesController().sendNotificationToDriverMAP( + 'Order VIP Canceld'.tr, + 'Passenger cancel order'.tr, + token, + [], + 'cancel.wav', + ); + } + initilizeGetStorage() async { if (box.read(BoxName.addWork) == null) { box.write(BoxName.addWork, 'addWork'); @@ -3696,11 +5104,17 @@ class MapPassengerController extends GetxController { } late List recentPlaces = []; - getFavioratePlaces() async { + getFavioratePlaces0() async { recentPlaces = await sql.getCustomQuery( 'SELECT DISTINCT latitude, longitude, name, rate FROM ${TableName.recentLocations}'); } + getFavioratePlaces() async { + recentPlaces = await sql.getCustomQuery( + 'SELECT * FROM ${TableName.recentLocations} ORDER BY createdAt DESC'); + // Log.print('recentPlaces: ${recentPlaces}'); + } + double passengerRate = 5; double comfortPrice = 8; double speedPrice = 4; @@ -3726,7 +5140,7 @@ class MapPassengerController extends GetxController { } void startFetchingData() { - Timer.periodic(Duration(milliseconds: 50), (Timer timer) async { + Timer.periodic(const Duration(milliseconds: 50), (Timer timer) async { await getKazanPercent(); }); } @@ -3746,9 +5160,47 @@ class MapPassengerController extends GetxController { } } + firstTimeRunToGetCoupon() async { + // Check if it's the first time and the app is installed and gift token is available + if (box.read(BoxName.isFirstTime).toString() == '0' && + box.read(BoxName.isInstall).toString() == '1' && + box.read(BoxName.isGiftToken).toString() == '0') { + var promo, discount, validity; + var resPromo = await CRUD().get(link: AppLink.getPromoFirst, payload: { + "passengerID": box.read(BoxName.passengerID).toString(), + }); + if (resPromo != 'failure') { + var d1 = jsonDecode(resPromo); + promo = d1['message']['promo_code']; + discount = d1['message']['amount']; + validity = d1['message']['validity_end_date']; + } + box.write(BoxName.isFirstTime, '1'); + + // Show a full-screen modal styled as an ad + Get.dialog( + AlertDialog( + contentPadding: + EdgeInsets.zero, // Removes the padding around the content + content: SizedBox( + width: 300, // Match the width of PromoBanner + // height: 250, // Match the height of PromoBanner + child: PromoBanner( + promoCode: promo, + discountPercentage: discount, + validity: validity, + ), + ), + ), + ); + } + } + + var k; @override void onInit() async { mapAPIKEY = await storage.read(key: BoxName.mapAPIKEY); + k = await getAIKey('HERE_API'); getFavioratePlaces(); readyWayPoints(); addCustomPicker(); @@ -3758,8 +5210,10 @@ class MapPassengerController extends GetxController { addCustomStepIcon(); addCustomStartIcon(); addCustomEndIcon(); + // addToken(); await getLocation(); - + getPassengerLocationUniversity(); + _initializePolygons(); // await addToken(); getKazanPercent(); getPassengerRate(); @@ -3771,7 +5225,7 @@ class MapPassengerController extends GetxController { box.write(BoxName.tipPercentage, '0'); Get.put(AudioRecorderController()); // await getNearestDriverByPassengerLocation(); - + firstTimeRunToGetCoupon(); initilizeGetStorage(); cardNumber = await SecureStorage().readData(BoxName.cardNumber); @@ -3781,9 +5235,9 @@ class MapPassengerController extends GetxController { uploadPassengerLocation() async { await CRUD().post(link: AppLink.addpassengerLocation, payload: { "passengerId": box.read(BoxName.passengerID), - "lat": passengerLocation.latitude, - "lng": passengerLocation.longitude, - "rideId": rideId + "lat": passengerLocation.latitude.toString(), + "lng": passengerLocation.longitude.toString(), + "rideId": rideId.toString() }); } } diff --git a/lib/controller/home/profile/complaint_controller.dart b/lib/controller/home/profile/complaint_controller.dart index b3f774f..3e3aa42 100644 --- a/lib/controller/home/profile/complaint_controller.dart +++ b/lib/controller/home/profile/complaint_controller.dart @@ -1,7 +1,10 @@ import 'dart:convert'; +import 'dart:io'; +import 'package:SEFER/views/widgets/my_dialog.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:http/http.dart' as http; import 'package:SEFER/constant/box_name.dart'; import 'package:SEFER/constant/colors.dart'; import 'package:SEFER/constant/links.dart'; @@ -9,11 +12,38 @@ import 'package:SEFER/constant/style.dart'; import 'package:SEFER/controller/functions/crud.dart'; import 'package:SEFER/main.dart'; import 'package:SEFER/views/widgets/elevated_btn.dart'; +import 'package:http_parser/http_parser.dart'; +import 'package:mime/mime.dart'; + +import '../../../constant/api_key.dart'; +import '../../../print.dart'; class ComplaintController extends GetxController { bool isLoading = false; final formKey = GlobalKey(); final complaintController = TextEditingController(); + final suggestionController = TextEditingController(); + List feedBack = []; + @override + void onInit() { + super.onInit(); + getLatestRidesForPassengers(); + } + + getLatestRidesForPassengers() async { + isLoading = true; + update(); + var res = await CRUD().get(link: AppLink.getFeedBack, payload: { + 'passengerId': box.read(BoxName.passengerID).toString(), + }); + if (res != 'failure') { + var d = jsonDecode(res)['message']; + feedBack = d; + } + + isLoading = false; + update(); + } void addComplaint() async { isLoading = true; @@ -34,11 +64,175 @@ class ComplaintController extends GetxController { title: 'Ok'.tr, onPressed: () { Get.back(); - Get.back(); + // Get.back(); })); } isLoading = false; update(); } + + var isUploading = false.obs; + var uploadSuccess = false.obs; + late String audioLink = ''; + Future uploadAudioFile(File audioFile) async { + try { + isUploading.value = true; + + // Prepare the file upload + var uri = Uri.parse('${AppLink.seferCairoServer}/upload_audio.php'); + var request = http.MultipartRequest('POST', uri); + + // Add the file to the request with MIME type + var mimeType = lookupMimeType(audioFile.path); + request.headers.addAll({ + 'Authorization': + 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials.toString()))}', + }); + request.files.add( + await http.MultipartFile.fromPath( + 'audio', + audioFile.path, + contentType: mimeType != null ? MediaType.parse(mimeType) : null, + ), + ); + + // Send the request + var response = await request.send(); + + // Convert response to string for parsing + var responseBody = await http.Response.fromStream(response); + + // After the upload request + if (response.statusCode == 200) { + var jsonResponse = jsonDecode(responseBody.body); + + if (jsonResponse['status'] == 'Audio file uploaded successfully.') { + uploadSuccess.value = true; + audioLink = jsonResponse['link']; // Get the audio link + Get.back(); + Get.snackbar('Success'.tr, 'Audio uploaded successfully.'.tr, + backgroundColor: const Color.fromARGB(255, 89, 185, 115)); + } else { + uploadSuccess.value = false; + } + } else { + uploadSuccess.value = false; + } + } catch (e) { + uploadSuccess.value = false; + } finally { + isUploading.value = false; + } + } + + var customerServiceSolutions; + var passengerReport; + var driverReport; + var isloading = false; + Future geminiAudio(payload, String audioLink, String complain) async { + String prompt = ''' + Analyze the following complaint between a passenger and driver in a ride-hailing service. The complaint includes an audio link for reference. Provide two possible solutions for customer service to resolve the issue, and generate a detailed report for both the passenger and the driver. + + Complaint details: + - Passenger: $complain + - Driver: [Driver's complaint] + - Ride Information: {ride details such as start_location, end_location, date, price, status, and rating details} + - Audio Link: [$audioLink] + + Output the result in JSON format with the following structure: + { + "customerServiceSolutions": [ + "solution1", + "solution2" + ], + "passengerReport": { + "solution": "Passenger's solution" if passenger right, + "complaint": "Passenger's complaint", + "rideDetails": {detailed ride info} + }, + "driverReport": { + "complaint": "Driver's complaint", + "rideDetails": {detailed ride info} + } + } the response in arabic language with egypt + '''; + + var requestBody = jsonEncode({ + "contents": [ + { + "parts": [ + {"text": "$payload $prompt"} + ] + } + ], + "generationConfig": { + "temperature": 1, + "topK": 64, + "topP": 0.95, + "maxOutputTokens": 8192, + "stopSequences": [] + }, + "safetySettings": [ + { + "category": "HARM_CATEGORY_HARASSMENT", + "threshold": "BLOCK_MEDIUM_AND_ABOVE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "threshold": "BLOCK_MEDIUM_AND_ABOVE" + }, + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "threshold": "BLOCK_MEDIUM_AND_ABOVE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "threshold": "BLOCK_MEDIUM_AND_ABOVE" + } + ] + }); + + final response = await http.post( + Uri.parse( + 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.0-pro:generateContent?key=${AK.geminiApi}'), + headers: {'Content-Type': 'application/json'}, + body: requestBody, + ); + + if (response.statusCode == 200) { + var responseData = jsonDecode(response.body); + var result = responseData['candidates'][0]['content']['parts'][0]['text']; + Log.print('result: ${result}'); + + // Clean up the result by removing surrounding backticks if they exist + result = result.replaceAll(RegExp(r'^```json\s*|\s*```$'), ''); + + // Attempt to decode the cleaned result as JSON + try { + var jsonResult = jsonDecode(result); + + // Access customer service solutions and reports for both passenger and driver + customerServiceSolutions = jsonResult['customerServiceSolutions']; + passengerReport = jsonResult['passengerReport']; + driverReport = jsonResult['driverReport']; + update(); + // Use the data accordingly + // For example, log the reports or display them in a UI dialog + + update(); + } catch (e) { + MyDialog().getDialog( + 'Error'.tr, + 'Unable to parse the response as JSON. Please check the format and try again.' + .tr, () { + Get.back(); + }); + } + } else { + Get.snackbar( + 'Error', "Request failed with status: ${response.statusCode}", + backgroundColor: AppColor.redColor); + } + } } diff --git a/lib/controller/home/profile/invit_controller.dart b/lib/controller/home/profile/invit_controller.dart new file mode 100644 index 0000000..e7307f2 --- /dev/null +++ b/lib/controller/home/profile/invit_controller.dart @@ -0,0 +1,309 @@ +import 'dart:convert'; + +import 'package:SEFER/constant/box_name.dart'; +import 'package:SEFER/constant/colors.dart'; +import 'package:SEFER/constant/links.dart'; +import 'package:SEFER/controller/functions/crud.dart'; +import 'package:SEFER/controller/payment/payment_controller.dart'; +import 'package:SEFER/views/widgets/mysnakbar.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_contacts/contact.dart'; +import 'package:flutter_contacts/flutter_contacts.dart'; +import 'package:get/get.dart'; +import 'package:share/share.dart'; + +import '../../../main.dart'; +import '../../../print.dart'; +import '../../../views/widgets/my_dialog.dart'; +import '../../functions/launch.dart'; +import '../../notification/notification_captain_controller.dart'; + +class InviteController extends GetxController { + final TextEditingController invitePhoneController = TextEditingController(); + List driverInvitationData = []; + List driverInvitationDataToPassengers = []; + String? couponCode; + String? driverCouponCode; + + int selectedTab = 0; + PassengerStats passengerStats = PassengerStats(); + void updateSelectedTab(int index) { + selectedTab = index; + update(); + } + + Future shareCouponCode() async { + // TODO: Implement sharing functionality + // You can use share_plus package to share the coupon code + } + Future shareDriverCode() async { + if (driverCouponCode != null) { + final String shareText = ''' +Join SEFER as a driver using my referral code! +Use code: $driverCouponCode +Download the SEFER Driver app now and earn rewards! +'''; + await Share.share(shareText); + } + } + + Future sharePassengerCode() async { + if (couponCode != null) { + final String shareText = ''' +Get a discount on your first SEFER ride! +Use my referral code: $couponCode +Download the SEFER app now and enjoy your ride! +'''; + await Share.share(shareText); + } + } + + @override + void onInit() { + super.onInit(); + // fetchDriverStats(); + } + + void fetchDriverStats() async { + try { + var response = await CRUD().get(link: AppLink.getInviteDriver, payload: { + "driverId": box.read(BoxName.driverID), + }); + if (response != 'failure') { + var data = jsonDecode(response); + driverInvitationData = data['message']; + update(); + } + } catch (e) {} + } + + void fetchDriverStatsPassengers() async { + try { + var response = await CRUD() + .get(link: AppLink.getDriverInvitationToPassengers, payload: { + "driverId": box.read(BoxName.passengerID), + }); + if (response != 'failure') { + var data = jsonDecode(response); + driverInvitationDataToPassengers = data['message']; + update(); + } + } catch (e) {} + } + + void selectPhone(String phone) { + if (box.read(BoxName.countryCode) == 'Egypt') { + invitePhoneController.text = phone; + update(); + Get.back(); + } + } + + Future saveContactsToServer() async { + try { + // TODO: Implement the actual server upload logic here + // Simulating a server request + await Future.delayed(Duration(seconds: 2)); + Get.snackbar('Success'.tr, + '${selectedContacts.length} contacts saved to server'.tr); + } catch (e) { + Get.snackbar('Error'.tr, + 'An error occurred while saving contacts to server: $e'.tr); + } + } + + List contacts = []; + List selectedContacts = []; + RxList> contactMaps = >[].obs; + + Future pickContacts() async { + try { + // Request contacts permission + if (await FlutterContacts.requestPermission(readonly: true)) { + // Fetch all contacts with full properties + final List allContacts = await FlutterContacts.getContacts( + withProperties: true, + withThumbnail: false, + withPhoto: true, + ); + + // Check if contacts are available + if (allContacts.isNotEmpty) { + // Store the contacts + contacts = allContacts; + Log.print('contacts: $contacts'); + + // Convert contacts to a list of maps + contactMaps.value = await Future.wait(contacts.map((contact) async { + Log.print('Contact name: ${contact.displayName}'); + + // Fetch phone numbers separately + final phones = await contact.phones; + Log.print('Contact phones: $phones'); + + // Fetch email addresses separately + final emails = await contact.emails; + Log.print('Contact emails: $emails'); + + // Handle empty or null values + return { + 'name': contact.displayName ?? '', + 'phones': phones + .where((phone) => phone.normalizedNumber != null) + .map((phone) => phone.normalizedNumber ?? 'No number') + .toList(), + 'emails': emails + .where((email) => email.address != null) + .map((email) => email.address ?? 'No email') + .toList(), + }; + }).toList()); + + update(); + } else { + Get.snackbar('No contacts available'.tr, + 'Please add contacts to your phone.'.tr); + } + } else { + Get.snackbar('Permission denied'.tr, + 'Contact permission is required to pick contacts'.tr); + } + } catch (e) { + Get.snackbar( + 'Error'.tr, 'An error occurred while picking contacts: $e'.tr); + } + } + + void onSelectPassengerInvitation(int index) async { + MyDialog().getDialog( + driverInvitationDataToPassengers[index]['countOfInvitDriver'] < 2 + ? '${'When'.tr} ${driverInvitationDataToPassengers[index]['passengerName']} ${"complete, you can claim your gift".tr} ' + : 'You deserve the gift'.tr, + '${driverInvitationDataToPassengers[index]['passengerName']} ${driverInvitationDataToPassengers[index]['countOfInvitDriver']} / 2 ${'Trip'.tr}', + () async { + if (driverInvitationDataToPassengers[index]['countOfInvitDriver'] < 2) { + Get.back(); + } else { + // Claim the gift if 100 trips are completed + if (driverInvitationDataToPassengers[index]['isGiftToken'] + .toString() == + '0') { + Get.back(); + // Add wallet to the inviter + await Get.find().addPassengersWallet('20'); + // add for invitor too + // await Get.find().addDriverWalletToInvitor( + // 'paymentMethod', + // driverInvitationData[index]['driverInviterId'], + // '50'); + // Update invitation as claimed + await CRUD().post( + link: AppLink.updatePassengerGift, + payload: {'id': driverInvitationDataToPassengers[index]['id']}, + ); + // Notify the inviter + NotificationCaptainController().addNotificationCaptain( + driverInvitationDataToPassengers[index]['passengerInviterId'] + .toString(), + "You have got a gift for invitation".tr, + '${"You have 20".tr} ${'LE'}', + false, + ); + } else { + Get.back(); + MyDialog().getDialog( + "You have got a gift".tr, + "Share the app with another new passenger".tr, + () { + Get.back(); + }, + ); + } + } + }, + ); + } + + savePhoneToServer() async { + for (var i = 0; i < contactMaps.length; i++) { + var phones = contactMaps[i]['phones']; + if (phones != null && phones.isNotEmpty && phones[0].isNotEmpty) { + var res = await CRUD().post(link: AppLink.savePhones, payload: { + "name": contactMaps[i]['name'] ?? 'none', + "phones": phones[0] ?? 'none', + "phones2": phones.join(', ') ?? + 'none', // Convert List to a comma-separated string + }); + if (res != 'failure') {} + } else {} + } + } + + String formatPhoneNumber(String input) { + // Remove any non-digit characters + String digitsOnly = input.replaceAll(RegExp(r'\D'), ''); + + // Ensure the number starts with the country code + if (digitsOnly.startsWith('20')) { + digitsOnly = digitsOnly.substring(1); + } + + return digitsOnly; + } + + void sendInviteToPassenger() async { + if (invitePhoneController.text.isEmpty || + invitePhoneController.text.length < 11) { + mySnackeBarError('Please enter a correct phone'.tr); + + return; + } + + // try { + String phoneNumber = formatPhoneNumber(invitePhoneController.text); + + var response = + await CRUD().post(link: AppLink.addInvitationPassenger, payload: { + "driverId": box.read(BoxName.passengerID), + "inviterPassengerPhone": '+2$phoneNumber' + }); + + if (response != 'failure') { + var d = response; + Get.snackbar('Success', 'Invite sent successfully'.tr); + + String message = '${'*SEFER APP CODE*'.tr}\n\n' + '${"Use this code in registration".tr}\n' + '${"To get a gift for both".tr}\n\n' + '${"The period of this code is 1 hour".tr}\n\n' + '${'before'.tr} *${d['message']['expirationTime'].toString()}*\n\n' + '_*${d['message']['inviteCode'].toString()}*_\n\n' + '${"Install our app:".tr}\n' + '*Android:* https://play.google.com/store/apps/details?id=com.mobileapp.store.ride\n\n\n' + '*iOS:* https://apps.apple.com/us/app/sefer/id6458734951'; + + launchCommunication('whatsapp', '+2$phoneNumber', message); + + invitePhoneController.clear(); + } else { + Get.snackbar('Error'.tr, "Invite code already used".tr, + backgroundColor: AppColor.redColor, + duration: const Duration(seconds: 4)); + } + // } catch (e) { + // Get.snackbar('Error', 'An error occurred'.tr); + // } + } +} + +class PassengerStats { + final int totalInvites; + final int activeUsers; + final double totalEarnings; + + PassengerStats({ + this.totalInvites = 0, + this.activeUsers = 0, + this.totalEarnings = 0.0, + }); +} diff --git a/lib/controller/home/profile/promos_controller.dart b/lib/controller/home/profile/promos_controller.dart index e09fefc..8fe0e36 100644 --- a/lib/controller/home/profile/promos_controller.dart +++ b/lib/controller/home/profile/promos_controller.dart @@ -1,10 +1,11 @@ import 'dart:convert'; +import 'package:SEFER/constant/box_name.dart'; import 'package:get/get.dart'; import 'package:SEFER/constant/links.dart'; -import 'package:SEFER/constant/style.dart'; import 'package:SEFER/controller/functions/crud.dart'; -import 'package:SEFER/views/widgets/elevated_btn.dart'; + +import '../../../main.dart'; class PromosController extends GetxController { List promoList = []; @@ -17,7 +18,9 @@ class PromosController extends GetxController { } Future getPromoByToday() async { - var res = await CRUD().get(link: AppLink.getPromoBytody, payload: {}); + var res = await CRUD().get(link: AppLink.getPromoBytody, payload: { + 'passengerID': box.read(BoxName.passengerID).toString(), + }); if (res.toString() == 'failure') { // Get.defaultDialog( // title: 'No Promo for today .'.tr, diff --git a/lib/controller/home/vip_waitting_page.dart b/lib/controller/home/vip_waitting_page.dart new file mode 100644 index 0000000..7990458 --- /dev/null +++ b/lib/controller/home/vip_waitting_page.dart @@ -0,0 +1,323 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:SEFER/constant/box_name.dart'; +import 'package:SEFER/constant/colors.dart'; +import 'package:SEFER/constant/links.dart'; +import 'package:SEFER/constant/style.dart'; +import 'package:SEFER/controller/home/map_passenger_controller.dart'; +import 'package:SEFER/main.dart'; +import 'package:SEFER/views/widgets/elevated_btn.dart'; +import 'package:SEFER/views/widgets/my_scafold.dart'; +import 'package:SEFER/views/widgets/mycircular.dart'; +import 'package:SEFER/views/widgets/mysnakbar.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_font_icons/flutter_font_icons.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; + +import '../functions/crud.dart'; + +class VipOrderController extends GetxController { + RxBool isLoading = false.obs; + final arguments = Get.arguments; + RxList tripData = [].obs; + RxBool isButtonVisible = false.obs; + RxInt countdown = 60.obs; + Timer? _countdownTimer; + + @override + void onInit() { + super.onInit(); + fetchOrder(); + startCountdown(); + } + + @override + void onClose() { + _countdownTimer?.cancel(); + super.onClose(); + } + + Future fetchOrder() async { + try { + isLoading.value = true; + var mapPassengerController = Get.find(); + + var res = await CRUD().get( + link: AppLink.getMishwari, + payload: { + // 'driverId': mapPassengerController.driverIdVip.toString(), + 'driverId': box.read(BoxName.passengerID).toString(), + }, + ); + + if (res != 'failure') { + var decodedResponse = jsonDecode(res); + if (decodedResponse['message'] is List) { + tripData.value = decodedResponse['message']; + } else { + tripData.clear(); // Ensure empty list if no data + // mySnackeBarError('No trip data found'); + } + } else { + tripData.clear(); + // mySnackeBarError('Failed to fetch trip data'); + } + } catch (e) { + tripData.clear(); + // mySnackeBarError('An error occurred: $e'); + } finally { + isLoading.value = false; + } + } + + void startCountdown() { + _countdownTimer?.cancel(); // Cancel any existing timer + countdown.value = 60; + + _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (countdown.value > 0) { + countdown.value--; + } else { + timer.cancel(); + isButtonVisible.value = true; + } + }); + } + + void sendToDriverAgain() { + // Reset states + isButtonVisible.value = false; + fetchOrder(); // Refresh order + startCountdown(); // Restart countdown + } +} + +class VipWaittingPage extends StatelessWidget { + VipWaittingPage({super.key}); + final VipOrderController vipOrderController = Get.put(VipOrderController()); + + @override + Widget build(BuildContext context) { + return MyScafolld( + title: "Waiting VIP".tr, + body: [ + Obx(() { + // Loading state + if (vipOrderController.isLoading.value) { + return const Center(child: MyCircularProgressIndicator()); + } + + // No data state + if (vipOrderController.tripData.isEmpty) { + return Center( + child: Text( + 'No trip data available'.tr, + style: AppStyle.title, + ), + ); + } + + // Data available + var data = vipOrderController.tripData[0]; + + // Function to get the localized status string + String getLocalizedStatus(String status) { + switch (status) { + case 'pending': + return 'pending'.tr; + case 'accepted': + return 'accepted'.tr; + case 'begin': + return 'begin'.tr; + case 'rejected': + return 'rejected'.tr; + case 'cancelled': + return 'cancelled'.tr; + default: + return 'unknown'.tr; + } + } + + // Function to get the appropriate status color + Color getStatusColor(String status) { + switch (status) { + case 'pending': + return Colors.yellow; + case 'accepted': + return Colors.green; + case 'begin': + return Colors.green; + case 'rejected': + return Colors.red; + case 'cancelled': + return Colors.red; + default: + return Colors.grey; + } + } + + return Card( + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + margin: const EdgeInsets.all(16), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${'Driver Name:'.tr} ${data['name']}", + style: AppStyle.title, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${'Car Plate:'.tr} ${data['car_plate']}", + style: AppStyle.title, + ), + Text( + "${'Car Make:'.tr} ${data['make']}", + style: AppStyle.title, + ), + Text( + "${'Car Model:'.tr} ${data['model']}", + style: AppStyle.title, + ), + Text( + "${"Car Color:".tr} ${data['color']}", + style: AppStyle.title, + ), + ], + ), + SizedBox( + width: 100, + height: 100, + child: Icon( + Fontisto.car, + size: 80, + color: Color( + int.parse( + data['color_hex'].replaceFirst('#', '0xff'), + ), + ), + ), + ), + ], + ), + const SizedBox(height: 12), + const Divider(), + const SizedBox(height: 12), + Container( + color: getStatusColor(data['status']), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "${'Trip Status:'.tr} ${getLocalizedStatus(data['status'])}", + style: const TextStyle(fontSize: 16), + ), + ), + ), + Text( + "${'Scheduled Time:'.tr} ${DateFormat('yyyy-MM-dd hh:mm a').format(DateTime.parse(data['timeSelected']))}", + style: const TextStyle(fontSize: 16), + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + data['status'].toString() != 'begin' + ? MyElevatedButton( + title: "Cancel Trip".tr, + kolor: AppColor.redColor, + onPressed: () { + Get.find().cancelVip( + data['token'].toString(), + data['id'].toString(), + ); + }, + ) + : const SizedBox(), + Obx(() { + // If countdown is still running, show countdown + if (!vipOrderController.isButtonVisible.value) { + return Column( + children: [ + CircularProgressIndicator( + value: 1 - + (vipOrderController.countdown.value / 60), + strokeWidth: 6.0, + color: AppColor.greenColor, + backgroundColor: AppColor.accentColor, + ), + const SizedBox(height: 10), + Text( + "${vipOrderController.countdown.value}s ${'remaining'.tr}", + style: const TextStyle(fontSize: 16), + ), + ], + ); + } + + // Once countdown is complete, show "Send to Driver Again" button + return MyElevatedButton( + title: "Send to Driver Again".tr, + kolor: AppColor.greenColor, + onPressed: () { + Get.find() + .sendToDriverAgain(data['token']); + vipOrderController.fetchOrder(); + }, + ); + }), + ], + ), + const SizedBox( + height: 30, + ), + data['status'].toString() == 'begin' + ? MyElevatedButton( + title: "Click here to begin your trip\n\nGood luck, " + .tr + + box.read(BoxName.name).toString(), + kolor: AppColor.greenColor, + onPressed: () { + final mapPassengerController = + Get.find(); + mapPassengerController.make = data['make']; + mapPassengerController.licensePlate = + data['car_plate']; + mapPassengerController.model = data['model']; + mapPassengerController.driverId = data['driverId']; + mapPassengerController.carColor = data['color']; + mapPassengerController.driverRate = data['rating']; + mapPassengerController.colorHex = data['color_hex']; + mapPassengerController.driverPhone = data['phone']; + mapPassengerController.driverToken = data['token']; + mapPassengerController.driverName = + data['name'].toString().split(' ')[0]; + + Get.back(); + + mapPassengerController.begiVIPTripFromPassenger(); + }, + ) + : const SizedBox() + ], + ), + ), + ); + }), + ], + isleading: true, + ); + } +} diff --git a/lib/controller/local/local_controller.dart b/lib/controller/local/local_controller.dart index 8eb2a76..ebb51cb 100644 --- a/lib/controller/local/local_controller.dart +++ b/lib/controller/local/local_controller.dart @@ -20,72 +20,58 @@ class LocaleController extends GetxController { case "ar": locale = const Locale("ar"); appTheme = themeArabic; - box.write(BoxName.lang, 'ar'); break; case "en": locale = const Locale("en"); appTheme = themeEnglish; - box.write(BoxName.lang, 'en'); break; case "tr": locale = const Locale("tr"); appTheme = themeEnglish; - box.write(BoxName.lang, 'tr'); break; case "fr": locale = const Locale("fr"); appTheme = themeEnglish; - box.write(BoxName.lang, 'fr'); break; case "it": locale = const Locale("it"); appTheme = themeEnglish; - box.write(BoxName.lang, 'it'); break; case "de": locale = const Locale("de"); appTheme = themeEnglish; - box.write(BoxName.lang, 'de'); break; case "el": locale = const Locale("el"); appTheme = themeEnglish; - box.write(BoxName.lang, 'el'); break; case "es": locale = const Locale("es"); appTheme = themeEnglish; - box.write(BoxName.lang, 'es'); break; case "fa": locale = const Locale("fa"); - appTheme = themeEnglish; - box.write(BoxName.lang, 'fa'); + appTheme = themeArabic; break; case "zh": locale = const Locale("zh"); appTheme = themeEnglish; - box.write(BoxName.lang, 'zh'); break; case "ru": locale = const Locale("ru"); appTheme = themeEnglish; - box.write(BoxName.lang, 'ru'); break; case "hi": locale = const Locale("hi"); appTheme = themeEnglish; - box.write(BoxName.lang, 'hi'); break; default: locale = Locale(Get.deviceLocale!.languageCode); - box.write(BoxName.lang, Get.deviceLocale!.languageCode); appTheme = themeEnglish; break; } box.write(BoxName.lang, langcode); - // box.write(BoxName.lang, langcode); Get.changeTheme(appTheme); Get.updateLocale(locale); restartApp(); @@ -94,62 +80,15 @@ class LocaleController extends GetxController { @override void onInit() { - String storedLang = box.read(BoxName.lang) ?? ""; - switch (storedLang) { - case "ar": - language = const Locale("ar"); - appTheme = themeArabic; - break; - case "en": - language = const Locale("en"); - appTheme = themeEnglish; - break; - case "tr": - language = const Locale("tr"); - appTheme = themeEnglish; - break; - case "fr": - language = const Locale("fr"); - appTheme = themeEnglish; - break; - case "it": - language = const Locale("it"); - appTheme = themeEnglish; - break; - case "de": - language = const Locale("de"); - appTheme = themeEnglish; - break; - case "el": - language = const Locale("el"); - appTheme = themeEnglish; - break; - case "es": - language = const Locale("es"); - appTheme = themeEnglish; - break; - case "fa": - language = const Locale("fa"); - appTheme = themeArabic; - break; - case "zh": - language = const Locale("zh"); - appTheme = themeEnglish; - break; - case "ru": - language = const Locale("ru"); - appTheme = themeEnglish; - break; - case "hi": - language = const Locale("hi"); - appTheme = themeEnglish; - break; - default: - language = Locale(Get.deviceLocale!.languageCode); - appTheme = themeEnglish; - break; + String? storedLang = box.read(BoxName.lang); + if (storedLang == null) { + // Use device language if no language is stored + storedLang = Get.deviceLocale!.languageCode; + box.write(BoxName.lang, storedLang); } + changeLang(storedLang); + super.onInit(); } } diff --git a/lib/controller/local/phone_intel/countries.dart b/lib/controller/local/phone_intel/countries.dart new file mode 100644 index 0000000..13e4bab --- /dev/null +++ b/lib/controller/local/phone_intel/countries.dart @@ -0,0 +1,7574 @@ +// see: https://en.wikipedia.org/wiki/List_of_country_calling_codes +// for list of country/calling codes + +const List countries = [ + Country( + name: "Afghanistan", + nameTranslations: { + "sk": "Afganistan", + "se": "Afghanistan", + "pl": "Afganistan", + "no": "Afghanistan", + "ja": "アフガニスタン", + "it": "Afghanistan", + "zh": "阿富汗", + "nl": "Afghanistan", + "de": "Afghanistan", + "fr": "Afghanistan", + "es": "Afganistán", + "en": "Afghanistan", + "pt_BR": "Afeganistão", + "sr-Cyrl": "Авганистан", + "sr-Latn": "Avganistan", + "zh_TW": "阿富汗", + "tr": "Afganistan", + "ro": "Afganistan", + "ar": "أفغانستان", + "fa": "افغانستان", + "yue": "阿富汗" + }, + flag: "🇦🇫", + code: "AF", + dialCode: "93", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Åland Islands", + nameTranslations: { + "sk": "Alandy", + "se": "Ålánda", + "pl": "Wyspy Alandzkie", + "no": "Åland", + "ja": "オーランド諸島", + "it": "Isole Åland", + "zh": "奥兰群岛", + "nl": "Åland", + "de": "Ålandinseln", + "fr": "Îles Åland", + "es": "Islas Åland", + "en": "Åland Islands", + "pt_BR": "Ilhas Aland", + "sr-Cyrl": "Аландска Острва", + "sr-Latn": "Alandska Ostrva", + "zh_TW": "奧蘭群島", + "tr": "Åland", + "ro": "Insulele Åland", + "ar": "جزر أولاند", + "fa": "جزیره اولاند", + "yue": "奧蘭群島" + }, + flag: "🇦🇽", + code: "AX", + dialCode: "358", + minLength: 15, + maxLength: 15, + ), + Country( + name: "Albania", + nameTranslations: { + "sk": "Albánsko", + "se": "Albánia", + "pl": "Albania", + "no": "Albania", + "ja": "アルバニア", + "it": "Albania", + "zh": "阿尔巴尼亚", + "nl": "Albanië", + "de": "Albanien", + "fr": "Albanie", + "es": "Albania", + "en": "Albania", + "pt_BR": "Albânia", + "sr-Cyrl": "Албанија", + "sr-Latn": "Albanija", + "zh_TW": "阿爾巴尼亞", + "tr": "Arnavutluk", + "ro": "Albania", + "ar": "ألبانيا", + "fa": "آلبانی", + "yue": "阿爾巴尼亞" + }, + flag: "🇦🇱", + code: "AL", + dialCode: "355", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Algeria", + nameTranslations: { + "sk": "Alžírsko", + "se": "Algeria", + "pl": "Algieria", + "no": "Algerie", + "ja": "アルジェリア", + "it": "Algeria", + "zh": "阿尔及利亚", + "nl": "Algerije", + "de": "Algerien", + "fr": "Algérie", + "es": "Argelia", + "en": "Algeria", + "pt_BR": "Argélia", + "sr-Cyrl": "Аргентина", + "sr-Latn": "Argentina", + "zh_TW": "阿爾及利亞", + "tr": "Cezayir", + "ro": "Algeria", + "ar": "الجزائر", + "fa": "الجزیره", + "yue": "阿爾及利亞" + }, + flag: "🇩🇿", + code: "DZ", + dialCode: "213", + minLength: 9, + maxLength: 9, + ), + Country( + name: "American Samoa", + nameTranslations: { + "sk": "Americká Samoa", + "se": "Amerihká Samoa", + "pl": "Samoa Amerykańskie", + "no": "Amerikansk Samoa", + "ja": "米領サモア", + "it": "Samoa americane", + "zh": "美属萨摩亚", + "nl": "Amerikaans-Samoa", + "de": "Amerikanisch-Samoa", + "fr": "Samoa américaines", + "es": "Samoa Americana", + "en": "American Samoa", + "pt_BR": "Samoa Americana", + "sr-Cyrl": "Америчка Самоа", + "sr-Latn": "Američka Samoa", + "zh_TW": "美屬薩摩亞", + "tr": "Amerikan Samoası", + "ro": "Samoa Americană", + "ar": "ساموا الأمريكية", + "fa": "ساموا آمریکا", + "yue": "美屬薩摩亞" + }, + flag: "🇦🇸", + code: "AS", + dialCode: "1684", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Andorra", + nameTranslations: { + "sk": "Andorra", + "se": "Andorra", + "pl": "Andora", + "no": "Andorra", + "ja": "アンドラ", + "it": "Andorra", + "zh": "安道尔", + "nl": "Andorra", + "de": "Andorra", + "fr": "Andorre", + "es": "Andorra", + "en": "Andorra", + "pt_BR": "Andorra", + "sr-Cyrl": "Андора", + "sr-Latn": "Andora", + "zh_TW": "安道爾", + "tr": "Andora", + "ro": "Andorra", + "ar": "أندورا", + "fa": "آندورا", + "yue": "安道爾" + }, + flag: "🇦🇩", + code: "AD", + dialCode: "376", + minLength: 6, + maxLength: 6, + ), + Country( + name: "Angola", + nameTranslations: { + "sk": "Angola", + "se": "Angola", + "pl": "Angola", + "no": "Angola", + "ja": "アンゴラ", + "it": "Angola", + "zh": "安哥拉", + "nl": "Angola", + "de": "Angola", + "fr": "Angola", + "es": "Angola", + "en": "Angola", + "pt_BR": "Angola", + "sr-Cyrl": "Ангола", + "sr-Latn": "Angola", + "zh_TW": "安哥拉", + "tr": "Angola", + "ro": "Angola", + "ar": "أنغولا", + "fa": "آنگولا", + "yue": "安哥拉" + }, + flag: "🇦🇴", + code: "AO", + dialCode: "244", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Anguilla", + nameTranslations: { + "sk": "Anguilla", + "se": "Anguilla", + "pl": "Anguilla", + "no": "Anguilla", + "ja": "アンギラ", + "it": "Anguilla", + "zh": "安圭拉", + "nl": "Anguilla", + "de": "Anguilla", + "fr": "Anguilla", + "es": "Anguila", + "en": "Anguilla", + "pt_BR": "Anguilla", + "sr-Cyrl": "Ангвила", + "sr-Latn": "Angvila", + "zh_TW": "安圭拉", + "tr": "Anguilla", + "ro": "Anguilla", + "ar": "أنغويلا", + "fa": "آنگولیا", + "yue": "安圭拉" + }, + flag: "🇦🇮", + code: "AI", + dialCode: "1264", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Antarctica", + nameTranslations: { + "sk": "Antarktída", + "se": "Antárktis", + "pl": "Antarktyda", + "no": "Antarktis", + "ja": "南極", + "it": "Antartide", + "zh": "南极洲", + "nl": "Antarctica", + "de": "Antarktis", + "fr": "Antarctique", + "es": "Antártida", + "en": "Antarctica", + "pt_BR": "Antártica", + "sr-Cyrl": "Антарктик", + "sr-Latn": "Antarktik", + "zh_TW": "南極", + "tr": "Antarktika", + "ro": "Antarctica", + "ar": "القارة القطبية الجنوبية", + "fa": "قطب جنوب", + "yue": "南极洲" + }, + flag: "🇦🇶", + code: "AQ", + dialCode: "672", + minLength: 6, + maxLength: 6, + ), + Country( + name: "Antigua and Barbuda", + nameTranslations: { + "sk": "Antigua a Barbuda", + "se": "Antigua ja Barbuda", + "pl": "Antigua i Barbuda", + "no": "Antigua og Barbuda", + "ja": "アンティグア・バーブーダ", + "it": "Antigua e Barbuda", + "zh": "安提瓜和巴布达", + "nl": "Antigua en Barbuda", + "de": "Antigua und Barbuda", + "fr": "Antigua-et-Barbuda", + "es": "Antigua y Barbuda", + "en": "Antigua & Barbuda", + "pt_BR": "Antigua e Barbuda", + "sr-Cyrl": "Антигва и Барбуда", + "sr-Latn": "Antigva i Barbuda", + "zh_TW": "安提瓜和巴布達", + "tr": "Antigua ve Barbuda", + "ro": "Antigua şi Barbuda", + "ar": "أنتيغوا وباربودا", + "fa": "آنتیگوآ و باربودا", + "yue": "安提瓜同巴布达" + }, + flag: "🇦🇬", + code: "AG", + dialCode: "1268", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Argentina", + nameTranslations: { + "sk": "Argentína", + "se": "Argentina", + "pl": "Argentyna", + "no": "Argentina", + "ja": "アルゼンチン", + "it": "Argentina", + "zh": "阿根廷", + "nl": "Argentinië", + "de": "Argentinien", + "fr": "Argentine", + "es": "Argentina", + "en": "Argentina", + "pt_BR": "Argentina", + "sr-Cyrl": "Аргентина", + "sr-Latn": "Argentina", + "zh_TW": "阿根廷", + "tr": "Arjantin", + "ro": "Argentina", + "ar": "الأرجنتين", + "fa": "آرژانتین", + "yue": "阿根廷" + }, + flag: "🇦🇷", + code: "AR", + dialCode: "54", + minLength: 12, + maxLength: 12, + ), + Country( + name: "Armenia", + nameTranslations: { + "sk": "Arménsko", + "se": "Armenia", + "pl": "Armenia", + "no": "Armenia", + "ja": "アルメニア", + "it": "Armenia", + "zh": "亚美尼亚", + "nl": "Armenië", + "de": "Armenien", + "fr": "Arménie", + "es": "Armenia", + "en": "Armenia", + "pt_BR": "Armênia", + "sr-Cyrl": "Јерменија", + "sr-Latn": "Jermenija", + "zh_TW": "亞美尼亞", + "tr": "Ermenistan", + "ro": "Armenia", + "ar": "أرمينيا", + "fa": "ارمنستان", + "yue": "亞美尼亞" + }, + flag: "🇦🇲", + code: "AM", + dialCode: "374", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Aruba", + nameTranslations: { + "sk": "Aruba", + "se": "Aruba", + "pl": "Aruba", + "no": "Aruba", + "ja": "アルバ", + "it": "Aruba", + "zh": "阿鲁巴", + "nl": "Aruba", + "de": "Aruba", + "fr": "Aruba", + "es": "Aruba", + "en": "Aruba", + "pt_BR": "Aruba", + "sr-Cyrl": "Аруба", + "sr-Latn": "Aruba", + "zh_TW": "阿魯巴", + "tr": "Aruba", + "ro": "Aruba", + "ar": "أروبا", + "fa": "آروبا", + "yue": "阿魯巴島" + }, + flag: "🇦🇼", + code: "AW", + dialCode: "297", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Australia", + nameTranslations: { + "sk": "Austrália", + "se": "Austrália", + "pl": "Australia", + "no": "Australia", + "ja": "オーストラリア", + "it": "Australia", + "zh": "澳大利亚", + "nl": "Australië", + "de": "Australien", + "fr": "Australie", + "es": "Australia", + "en": "Australia", + "pt_BR": "Austrália", + "sr-Cyrl": "Аустралија", + "sr-Latn": "Australija", + "zh_TW": "澳州", + "tr": "Avustralya", + "ro": "Australia", + "ar": "أستراليا", + "fa": "استرالیا", + "yue": "澳洲" + }, + flag: "🇦🇺", + code: "AU", + dialCode: "61", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Austria", + nameTranslations: { + "sk": "Rakúsko", + "se": "Nuortariika", + "pl": "Austria", + "no": "Østerrike", + "ja": "オーストリア", + "it": "Austria", + "zh": "奥地利", + "nl": "Oostenrijk", + "de": "Österreich", + "fr": "Autriche", + "es": "Austria", + "en": "Austria", + "pt_BR": "Áustria", + "sr-Cyrl": "Аустрија", + "sr-Latn": "Austrija", + "zh_TW": "奥地利", + "tr": "Avusturya", + "ro": "Austria", + "ar": "النمسا", + "fa": "اتریش", + "yue": "奧地利" + }, + flag: "🇦🇹", + code: "AT", + dialCode: "43", + minLength: 13, + maxLength: 13, + ), + Country( + name: "Azerbaijan", + nameTranslations: { + "sk": "Azerbajdžan", + "se": "Aserbaižan", + "pl": "Azerbejdżan", + "no": "Aserbajdsjan", + "ja": "アゼルバイジャン", + "it": "Azerbaigian", + "zh": "阿塞拜疆", + "nl": "Azerbeidzjan", + "de": "Aserbaidschan", + "fr": "Azerbaïdjan", + "es": "Azerbaiyán", + "en": "Azerbaijan", + "pt_BR": "Azerbaijão", + "sr-Cyrl": "Азербејџан", + "sr-Latn": "Azerbejdžan", + "zh_TW": "亞塞拜然", + "tr": "Azerbaycan", + "ro": "Azerbaidjan", + "ar": "أذربيجان", + "fa": "آذربایجان", + "yue": "阿塞拜疆" + }, + flag: "🇦🇿", + code: "AZ", + dialCode: "994", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Bahamas", + nameTranslations: { + "sk": "Bahamy", + "se": "Bahamas", + "pl": "Bahamy", + "no": "Bahamas", + "ja": "バハマ", + "it": "Bahamas", + "zh": "巴哈马", + "nl": "Bahama's", + "de": "Bahamas", + "fr": "Bahamas", + "es": "Bahamas", + "en": "Bahamas", + "pt_BR": "Bahamas", + "sr-Cyrl": "Бахаме", + "sr-Latn": "Bahame", + "zh_TW": "巴哈馬", + "tr": "Bahama", + "ro": "Bahamas", + "ar": "باهاماس", + "fa": "باهاماس", + "yue": "巴哈馬" + }, + flag: "🇧🇸", + code: "BS", + dialCode: "1242", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Bahrain", + nameTranslations: { + "sk": "Bahrajn", + "se": "Bahrain", + "pl": "Bahrajn", + "no": "Bahrain", + "ja": "バーレーン", + "it": "Bahrein", + "zh": "巴林", + "nl": "Bahrein", + "de": "Bahrain", + "fr": "Bahreïn", + "es": "Baréin", + "en": "Bahrain", + "pt_BR": "Bahrain", + "sr-Cyrl": "Бахреин", + "sr-Latn": "Bahrein", + "zh_TW": "巴林", + "tr": "Bahreyn", + "ro": "Bahrein", + "ar": "البحرين", + "fa": "بحرین", + "yue": "巴林" + }, + flag: "🇧🇭", + code: "BH", + dialCode: "973", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Bangladesh", + nameTranslations: { + "sk": "Bangladéš", + "se": "Bangladesh", + "pl": "Bangladesz", + "no": "Bangladesh", + "ja": "バングラデシュ", + "it": "Bangladesh", + "zh": "孟加拉国", + "nl": "Bangladesh", + "de": "Bangladesch", + "fr": "Bangladesh", + "es": "Bangladés", + "en": "Bangladesh", + "pt_BR": "Bangladesh", + "sr-Cyrl": "Бангладеш", + "sr-Latn": "Bangladeš", + "zh_TW": "孟加拉", + "tr": "Bangladeş", + "ro": "Bangladesh", + "ar": "بنغلاديش", + "fa": "بنگلادش", + "yue": "孟加拉囯" + }, + flag: "🇧🇩", + code: "BD", + dialCode: "880", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Barbados", + nameTranslations: { + "sk": "Barbados", + "se": "Barbados", + "pl": "Barbados", + "no": "Barbados", + "ja": "バルバドス", + "it": "Barbados", + "zh": "巴巴多斯", + "nl": "Barbados", + "de": "Barbados", + "fr": "Barbade", + "es": "Barbados", + "en": "Barbados", + "pt_BR": "Barbados", + "sr-Cyrl": "Барбадос", + "sr-Latn": "Barbados", + "zh_TW": "巴巴多斯", + "tr": "Barbados", + "ro": "Barbados", + "ar": "باربادوس", + "fa": "باربادوس", + "yue": "巴巴多斯" + }, + flag: "🇧🇧", + code: "BB", + dialCode: "1246", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Belarus", + nameTranslations: { + "sk": "Bielorusko", + "se": "Vilges-Ruošša", + "pl": "Białoruś", + "no": "Hviterussland", + "ja": "ベラルーシ", + "it": "Bielorussia", + "zh": "白俄罗斯", + "nl": "Belarus", + "de": "Belarus", + "fr": "Biélorussie", + "es": "Bielorrusia", + "en": "Belarus", + "pt_BR": "Bielo-Rússia", + "sr-Cyrl": "Белорусија", + "sr-Latn": "Belorusija", + "zh_TW": "白俄羅斯", + "tr": "Belarus", + "ro": "Belarus", + "ar": "بيلاروس", + "fa": "بلاروس", + "yue": "白俄羅斯" + }, + flag: "🇧🇾", + code: "BY", + dialCode: "375", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Belgium", + nameTranslations: { + "sk": "Belgicko", + "se": "Belgia", + "pl": "Belgia", + "no": "Belgia", + "ja": "ベルギー", + "it": "Belgio", + "zh": "比利时", + "nl": "België", + "de": "Belgien", + "fr": "Belgique", + "es": "Bélgica", + "en": "Belgium", + "pt_BR": "Bélgica", + "sr-Cyrl": "Белгија", + "sr-Latn": "Belgija", + "zh_TW": "比利時", + "tr": "Belçika", + "ro": "Belgia", + "ar": "بلجيكا", + "fa": "بلژیک", + "yue": "比利時" + }, + flag: "🇧🇪", + code: "BE", + dialCode: "32", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Belize", + nameTranslations: { + "sk": "Belize", + "se": "Belize", + "pl": "Belize", + "no": "Belize", + "ja": "ベリーズ", + "it": "Belize", + "zh": "伯利兹", + "nl": "Belize", + "de": "Belize", + "fr": "Belize", + "es": "Belice", + "en": "Belize", + "pt_BR": "Belize", + "sr-Cyrl": "Белизе", + "sr-Latn": "Belize", + "zh_TW": "伯利茲", + "tr": "Belize", + "ro": "Belize", + "ar": "بليز", + "fa": "بليز", + "yue": "伯利茲" + }, + flag: "🇧🇿", + code: "BZ", + dialCode: "501", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Benin", + nameTranslations: { + "sk": "Benin", + "se": "Benin", + "pl": "Benin", + "no": "Benin", + "ja": "ベナン", + "it": "Benin", + "zh": "贝宁", + "nl": "Benin", + "de": "Benin", + "fr": "Bénin", + "es": "Benín", + "en": "Benin", + "pt_BR": "Benin", + "sr-Cyrl": "Бенин", + "sr-Latn": "Benin", + "zh_TW": "貝南", + "tr": "Benin", + "ro": "Benin", + "ar": "بنين", + "fa": "بنين", + "yue": "貝寧" + }, + flag: "🇧🇯", + code: "BJ", + dialCode: "229", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Bermuda", + nameTranslations: { + "sk": "Bermudy", + "se": "Bermuda", + "pl": "Bermudy", + "no": "Bermuda", + "ja": "バミューダ", + "it": "Bermuda", + "zh": "百慕大", + "nl": "Bermuda", + "de": "Bermuda", + "fr": "Bermudes", + "es": "Bermudas", + "en": "Bermuda", + "pt_BR": "Bermudas", + "sr-Cyrl": "Бермуда", + "sr-Latn": "Bermuda", + "zh_TW": "百慕達", + "tr": "Bermuda", + "ro": "Insulele Bermude", + "ar": "برمودا", + "fa": "برمودا", + "yue": "百慕大" + }, + flag: "🇧🇲", + code: "BM", + dialCode: "1441", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Bhutan", + nameTranslations: { + "sk": "Bhután", + "se": "Bhutan", + "pl": "Bhutan", + "no": "Bhutan", + "ja": "ブータン", + "it": "Bhutan", + "zh": "不丹", + "nl": "Bhutan", + "de": "Bhutan", + "fr": "Bhoutan", + "es": "Bután", + "en": "Bhutan", + "pt_BR": "Butão", + "sr-Cyrl": "Бутан", + "sr-Latn": "Butan", + "zh_TW": "不丹", + "tr": "Bhutan", + "ro": "Bhutan", + "ar": "بوتان", + "fa": "بوتان", + "yue": "不丹" + }, + flag: "🇧🇹", + code: "BT", + dialCode: "975", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Bolivia, Plurinational State of bolivia", + nameTranslations: { + "sk": "Bolívia", + "se": "Bolivia", + "pl": "Boliwia", + "no": "Bolivia", + "ja": "ボリビア", + "it": "Bolivia", + "zh": "玻利维亚", + "nl": "Bolivia", + "de": "Bolivien", + "fr": "Bolivie", + "es": "Bolivia", + "en": "Bolivia", + "pt_BR": "Bolívia", + "sr-Cyrl": "Боливија", + "sr-Latn": "Bolivija", + "zh_TW": "玻利維亞", + "tr": "Bolivya", + "ro": "Bolivia", + "ar": "بوليفيا", + "fa": "بولیوی", + "yue": "玻利維亞(多民族國家)" + }, + flag: "🇧🇴", + code: "BO", + dialCode: "591", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Bosnia and Herzegovina", + nameTranslations: { + "sk": "Bosna a Hercegovina", + "se": "Bosnia-Hercegovina", + "pl": "Bośnia i Hercegowina", + "no": "Bosnia-Hercegovina", + "ja": "ボスニア・ヘルツェゴビナ", + "it": "Bosnia ed Erzegovina", + "zh": "波斯尼亚和黑塞哥维那", + "nl": "Bosnië en Herzegovina", + "de": "Bosnien und Herzegowina", + "fr": "Bosnie-Herzégovine", + "es": "Bosnia y Herzegovina", + "en": "Bosnia & Herzegovina", + "pt_BR": "Bósnia e Herzegovina", + "sr-Cyrl": "Босна и Херцеговина", + "sr-Latn": "Bosna i Hercegovina", + "zh_TW": "波士尼亞和黑塞哥維那", + "tr": "Bosna Hersek", + "ro": "Bosnia și Herțegovina", + "ar": "البوسنة والهرسك", + "fa": "بوسنی و هرزگوین", + "yue": "波斯尼亞黑塞哥維那" + }, + flag: "🇧🇦", + code: "BA", + dialCode: "387", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Botswana", + nameTranslations: { + "sk": "Botswana", + "se": "Botswana", + "pl": "Botswana", + "no": "Botswana", + "ja": "ボツワナ", + "it": "Botswana", + "zh": "博茨瓦纳", + "nl": "Botswana", + "de": "Botsuana", + "fr": "Botswana", + "es": "Botsuana", + "en": "Botswana", + "pt_BR": "Botswana", + "sr-Cyrl": "Боцвана", + "sr-Latn": "Bocvana", + "zh_TW": "博茨瓦納", + "tr": "Botsvana", + "ro": "Botswana", + "ar": "بوتسوانا", + "fa": "بوتسوانا", + "yue": "博茨瓦納" + }, + flag: "🇧🇼", + code: "BW", + dialCode: "267", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Bouvet Island", + nameTranslations: { + "sk": "Bouvetov ostrov", + "se": "Bouvet-sullot", + "pl": "Wyspa Bouveta", + "no": "Bouvetøya", + "ja": "ブーベ島", + "it": "Isola Bouvet", + "zh": "布韦岛", + "nl": "Bouveteiland", + "de": "Bouvetinsel", + "fr": "Île Bouvet", + "es": "Isla Bouvet", + "en": "Bouvet Island", + "pt_BR": "Ilha Bouvet", + "sr-Cyrl": "Острво Буве", + "sr-Latn": "Ostrvo Buve", + "zh_TW": "布維特島", + "tr": "Bouvet Adası", + "ro": "Insula Bouvet", + "ar": "جزيرة بوفيه", + "fa": "جزیره بووه", + "yue": "布维特岛" + }, + flag: "🇧🇻", + code: "BV", + dialCode: "47", + minLength: 15, + maxLength: 15, + ), + Country( + name: "Brazil", + nameTranslations: { + "sk": "Brazília", + "se": "Brasil", + "pl": "Brazylia", + "no": "Brasil", + "ja": "ブラジル", + "it": "Brasile", + "zh": "巴西", + "nl": "Brazilië", + "de": "Brasilien", + "fr": "Brésil", + "es": "Brasil", + "en": "Brazil", + "pt_BR": "Brasil", + "sr-Cyrl": "Бразил", + "sr-Latn": "Brazil", + "zh_TW": "巴西", + "tr": "Brezilya", + "ro": "Brazilia", + "ar": "البرازيل", + "fa": "برزیل", + "yue": "巴西" + }, + flag: "🇧🇷", + code: "BR", + dialCode: "55", + minLength: 11, + maxLength: 11, + ), + Country( + name: "British Indian Ocean Territory", + nameTranslations: { + "sk": "Britské indickooceánske územie", + "se": "British Indian Ocean Territory", + "pl": "Brytyjskie Terytorium Oceanu Indyjskiego", + "no": "Det britiske territoriet i Indiahavet", + "ja": "英領インド洋地域", + "it": "Territorio britannico dell'Oceano Indiano", + "zh": "英属印度洋领地", + "nl": "Brits Indische Oceaanterritorium", + "de": "Britisches Territorium im Indischen Ozean", + "fr": "Territoire britannique de l'océan Indien", + "es": "Territorio Británico del Océano Índico", + "en": "British Indian Ocean Territory", + "pt_BR": "Território Britânico do Oceano Índico", + "sr-Cyrl": "Британска територија Индијског океана", + "sr-Latn": "Britanska teritorija Indijskog okeana", + "zh_TW": "英屬印度洋領地", + "tr": "Britanya Hint Okyanusu Toprakları", + "ro": "Teritoriul Britanic din Oceanul Indian", + "ar": "إقليم المحيط الهندي البريطاني", + "fa": "سرزمین دریایی هند - بریتانیا", + "yue": "英屬印度洋領土" + }, + flag: "🇮🇴", + code: "IO", + dialCode: "246", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Brunei Darussalam", + nameTranslations: { + "sk": "Brunej", + "se": "Brunei", + "pl": "Brunei", + "no": "Brunei", + "ja": "ブルネイ", + "it": "Brunei", + "zh": "文莱", + "nl": "Brunei", + "de": "Brunei Darussalam", + "fr": "Brunéi Darussalam", + "es": "Brunéi", + "en": "Brunei", + "pt_BR": "Brunei", + "sr-Cyrl": "Брунеј", + "sr-Latn": "Brunej", + "zh_TW": "汶萊", + "tr": "Bruney", + "ro": "Brunei", + "ar": "بروناي", + "fa": "برونئی", + "yue": "文萊達魯薩蘭國" + }, + flag: "🇧🇳", + code: "BN", + dialCode: "673", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Bulgaria", + nameTranslations: { + "sk": "Bulharsko", + "se": "Bulgária", + "pl": "Bułgaria", + "no": "Bulgaria", + "ja": "ブルガリア", + "it": "Bulgaria", + "zh": "保加利亚", + "nl": "Bulgarije", + "de": "Bulgarien", + "fr": "Bulgarie", + "es": "Bulgaria", + "en": "Bulgaria", + "pt_BR": "Bulgária", + "sr-Cyrl": "Бугарска", + "sr-Latn": "Bugarska", + "zh_TW": "保加利亞", + "tr": "Bulgaristan", + "ro": "Bulgaria", + "ar": "بلغاريا", + "fa": "بلغارستان", + "yue": "保加利亞" + }, + flag: "🇧🇬", + code: "BG", + dialCode: "359", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Burkina Faso", + nameTranslations: { + "sk": "Burkina Faso", + "se": "Burkina Faso", + "pl": "Burkina Faso", + "no": "Burkina Faso", + "ja": "ブルキナファソ", + "it": "Burkina Faso", + "zh": "布基纳法索", + "nl": "Burkina Faso", + "de": "Burkina Faso", + "fr": "Burkina Faso", + "es": "Burkina Faso", + "en": "Burkina Faso", + "pt_BR": "Burkina Faso", + "sr-Cyrl": "Буркина Фасо", + "sr-Latn": "Burkina Faso", + "zh_TW": "布吉納法索", + "tr": "Burkina Faso", + "ro": "Burkina Faso", + "ar": "بوركينا فاسو", + "fa": "بورکینافاسو", + "yue": "布基納法索" + }, + flag: "🇧🇫", + code: "BF", + dialCode: "226", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Burundi", + nameTranslations: { + "sk": "Burundi", + "se": "Burundi", + "pl": "Burundi", + "no": "Burundi", + "ja": "ブルンジ", + "it": "Burundi", + "zh": "布隆迪", + "nl": "Burundi", + "de": "Burundi", + "fr": "Burundi", + "es": "Burundi", + "en": "Burundi", + "pt_BR": "Burundi", + "sr-Cyrl": "Бурунди", + "sr-Latn": "Burundi", + "zh_TW": "蒲隆地", + "tr": "Burundi", + "ro": "Burundi", + "ar": "بوروندي", + "fa": "بوروندی", + "yue": "蒲隆地" + }, + flag: "🇧🇮", + code: "BI", + dialCode: "257", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Cambodia", + nameTranslations: { + "sk": "Kambodža", + "se": "Kambodža", + "pl": "Kambodża", + "no": "Kambodsja", + "ja": "カンボジア", + "it": "Cambogia", + "zh": "柬埔寨", + "nl": "Cambodja", + "de": "Kambodscha", + "fr": "Cambodge", + "es": "Camboya", + "en": "Cambodia", + "pt_BR": "Camboja", + "sr-Cyrl": "Камбоџа", + "sr-Latn": "Kambodža", + "zh_TW": "柬埔寨", + "tr": "Kamboçya", + "ro": "Cambogia", + "ar": "كمبوديا", + "fa": "کامبوج", + "yue": "柬埔寨" + }, + flag: "🇰🇭", + code: "KH", + dialCode: "855", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Cameroon", + nameTranslations: { + "sk": "Kamerun", + "se": "Kamerun", + "pl": "Kamerun", + "no": "Kamerun", + "ja": "カメルーン", + "it": "Camerun", + "zh": "喀麦隆", + "nl": "Kameroen", + "de": "Kamerun", + "fr": "Cameroun", + "es": "Camerún", + "en": "Cameroon", + "pt_BR": "Camarões", + "sr-Cyrl": "Камерун", + "sr-Latn": "Kamerun", + "zh_TW": "喀麥隆", + "tr": "Kamerun", + "ro": "Camerun", + "ar": "الكاميرون", + "fa": "کامرون", + "yue": "喀 麥 隆" + }, + flag: "🇨🇲", + code: "CM", + dialCode: "237", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Canada", + nameTranslations: { + "sk": "Kanada", + "se": "Kanáda", + "pl": "Kanada", + "no": "Canada", + "ja": "カナダ", + "it": "Canada", + "zh": "加拿大", + "nl": "Canada", + "de": "Kanada", + "fr": "Canada", + "es": "Canadá", + "en": "Canada", + "pt_BR": "Canadá", + "sr-Cyrl": "Канада", + "sr-Latn": "Kanada", + "zh_TW": "加拿大", + "tr": "Kanada", + "ro": "Canada", + "ar": "كندا", + "fa": "کانادا", + "yue": "加拿大" + }, + flag: "🇨🇦", + code: "CA", + dialCode: "1", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Cayman Islands", + nameTranslations: { + "sk": "Kajmanie ostrovy", + "se": "Cayman-sullot", + "pl": "Kajmany", + "no": "Caymanøyene", + "ja": "ケイマン諸島", + "it": "Isole Cayman", + "zh": "开曼群岛", + "nl": "Kaaimaneilanden", + "de": "Kaimaninseln", + "fr": "Îles Caïmans", + "es": "Islas Caimán", + "en": "Cayman Islands", + "pt_BR": "Ilhas Cayman", + "sr-Cyrl": "Кајманска Острва", + "sr-Latn": "Kajmanska Ostrva", + "zh_TW": "開曼群島", + "tr": "Cayman Adaları", + "ro": "Insulele Cayman", + "ar": "جزر كايمان", + "fa": "جزایر کیمن", + "yue": "開曼群島" + }, + flag: "🇰🇾", + code: "KY", + dialCode: "345", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Central African Republic", + nameTranslations: { + "sk": "Stredoafrická republika", + "se": "Gaska-Afrihká dásseváldi", + "pl": "Republika Środkowoafrykańska", + "no": "Den sentralafrikanske republikk", + "ja": "中央アフリカ共和国", + "it": "Repubblica Centrafricana", + "zh": "中非共和国", + "nl": "Centraal-Afrikaanse Republiek", + "de": "Zentralafrikanische Republik", + "fr": "République centrafricaine", + "es": "República Centroafricana", + "en": "Central African Republic", + "pt_BR": "República Centro-Africana", + "sr-Cyrl": "Централноафричка Република", + "sr-Latn": "Centralnoafrička Republika", + "zh_TW": "中非共和國", + "tr": "Orta Afrika Cumhuriyeti", + "ro": "Republica Centrafricană", + "ar": "جمهورية أفريقيا الوسطى", + "fa": "جمهوری افریقای مرکزی", + "yue": "中非共和國" + }, + flag: "🇨🇫", + code: "CF", + dialCode: "236", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Chad", + nameTranslations: { + "sk": "Čad", + "se": "Tčad", + "pl": "Czad", + "no": "Tsjad", + "ja": "チャド", + "it": "Ciad", + "zh": "乍得", + "nl": "Tsjaad", + "de": "Tschad", + "fr": "Tchad", + "es": "Chad", + "en": "Chad", + "pt_BR": "Chade", + "sr-Cyrl": "Чад", + "sr-Latn": "Čad", + "zh_TW": "查德", + "tr": "Çad", + "ro": "Ciad", + "ar": "تشاد", + "fa": "چاد", + "yue": "乍得" + }, + flag: "🇹🇩", + code: "TD", + dialCode: "235", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Chile", + nameTranslations: { + "sk": "Čile", + "se": "Čiile", + "pl": "Chile", + "no": "Chile", + "ja": "チリ", + "it": "Cile", + "zh": "智利", + "nl": "Chili", + "de": "Chile", + "fr": "Chili", + "es": "Chile", + "en": "Chile", + "pt_BR": "Chile", + "sr-Cyrl": "Чиле", + "sr-Latn": "Čile", + "zh_TW": "智利", + "tr": "Şili", + "ro": "Chile", + "ar": "تشيلي", + "fa": "شیلی", + "yue": "智利" + }, + flag: "🇨🇱", + code: "CL", + dialCode: "56", + minLength: 9, + maxLength: 9, + ), + Country( + name: "China", + nameTranslations: { + "sk": "Čína", + "se": "Kiinná", + "pl": "Chiny", + "no": "Kina", + "ja": "中国", + "it": "Cina", + "zh": "中国", + "nl": "China", + "de": "China", + "fr": "Chine", + "es": "China", + "en": "China", + "pt_BR": "China", + "sr-Cyrl": "Кина", + "sr-Latn": "Kina", + "zh_TW": "中國", + "tr": "Çin", + "ro": "China", + "ar": "الصين", + "fa": "چین", + "yue": "中國" + }, + flag: "🇨🇳", + code: "CN", + dialCode: "86", + minLength: 11, + maxLength: 12, + ), + Country( + name: "Christmas Island", + nameTranslations: { + "sk": "Vianočný ostrov", + "se": "Juovllat-sullot", + "pl": "Wyspa Bożego Narodzenia", + "no": "Christmasøya", + "ja": "クリスマス島", + "it": "Isola Christmas", + "zh": "圣诞岛", + "nl": "Christmaseiland", + "de": "Weihnachtsinsel", + "fr": "Île Christmas", + "es": "Isla de Navidad", + "en": "Christmas Island", + "pt_BR": "Ilha do Natal", + "sr-Cyrl": "Ускршња Острва", + "sr-Latn": "Uskršnja Ostrva", + "zh_TW": "聖誕島", + "tr": "Christmas Adası", + "ro": "Insula Crăciunului", + "ar": "جزيرة عيد الميلاد", + "fa": "جزیره کریسمس", + "yue": "聖誕島" + }, + flag: "🇨🇽", + code: "CX", + dialCode: "61", + minLength: 15, + maxLength: 15, + ), + Country( + name: "Cocos (Keeling) Islands", + nameTranslations: { + "sk": "Kokosové ostrovy", + "se": "Cocos-sullot", + "pl": "Wyspy Kokosowe", + "no": "Kokosøyene", + "ja": "ココス(キーリング)諸島", + "it": "Isole Cocos (Keeling)", + "zh": "科科斯(基林)群岛", + "nl": "Cocoseilanden", + "de": "Kokosinseln", + "fr": "Îles Cocos", + "es": "Islas Cocos", + "en": "Cocos (Keeling) Islands", + "pt_BR": "Ilhas Cocos (Keeling)", + "sr-Cyrl": "Кокосова Острва", + "sr-Latn": "Kokosova Ostrva", + "zh_TW": "科科斯(基林)群島", + "tr": "Cocos (Keyling) Adaları", + "ro": "Insulele Cocos", + "ar": "جزر كوكوس", + "fa": "جزایر کوکوس", + "yue": "可可島(基林)群島" + }, + flag: "🇨🇨", + code: "CC", + dialCode: "61", + minLength: 15, + maxLength: 15, + ), + Country( + name: "Colombia", + nameTranslations: { + "sk": "Kolumbia", + "se": "Kolombia", + "pl": "Kolumbia", + "no": "Colombia", + "ja": "コロンビア", + "it": "Colombia", + "zh": "哥伦比亚", + "nl": "Colombia", + "de": "Kolumbien", + "fr": "Colombie", + "es": "Colombia", + "en": "Colombia", + "pt_BR": "Colômbia", + "sr-Cyrl": "Колумбија", + "sr-Latn": "Kolumbija", + "zh_TW": "哥倫比亞", + "tr": "Kolombiya", + "ro": "Columbia", + "ar": "كولومبيا", + "fa": "کلمبیا", + "yue": "哥倫比亞" + }, + flag: "🇨🇴", + code: "CO", + dialCode: "57", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Comoros", + nameTranslations: { + "sk": "Komory", + "se": "Komoros", + "pl": "Komory", + "no": "Komorene", + "ja": "コモロ", + "it": "Comore", + "zh": "科摩罗", + "nl": "Comoren", + "de": "Komoren", + "fr": "Comores", + "es": "Comoras", + "en": "Comoros", + "pt_BR": "Comores", + "sr-Cyrl": "Комори", + "sr-Latn": "Komori", + "zh_TW": "科摩羅", + "tr": "Komor Adaları", + "ro": "Comore", + "ar": "جزر القمر", + "fa": "جزیره کومور", + "yue": "科摩羅" + }, + flag: "🇰🇲", + code: "KM", + dialCode: "269", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Congo", + nameTranslations: { + "sk": "Konžská republika", + "se": "Kongo-Brazzaville", + "pl": "Kongo", + "no": "Kongo-Brazzaville", + "ja": "コンゴ共和国(ブラザビル)", + "it": "Congo-Brazzaville", + "zh": "刚果(布)", + "nl": "Congo-Brazzaville", + "de": "Kongo-Brazzaville", + "fr": "Congo-Brazzaville", + "es": "Congo", + "en": "Congo - Brazzaville", + "pt_BR": "República do Congo", + "sr-Cyrl": "Република Конго", + "sr-Latn": "Republika Kongo", + "zh_TW": "剛果共和國(布拉柴維爾)", + "tr": "Kongo Cumhuriyeti", + "ro": "Republica Congo", + "ar": "جمهورية الكونغو", + "fa": "جمهوری کنگو", + "yue": "剛果(共和國)" + }, + flag: "🇨🇬", + code: "CG", + dialCode: "242", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Congo, The Democratic Republic of the Congo", + nameTranslations: { + "sk": "Konžská demokratická republika", + "se": "Kongo-Kinshasa", + "pl": "Demokratyczna Republika Konga", + "no": "Kongo-Kinshasa", + "ja": "コンゴ民主共和国(キンシャサ)", + "it": "Congo - Kinshasa", + "zh": "刚果(金)", + "nl": "Congo-Kinshasa", + "de": "Kongo-Kinshasa", + "fr": "Congo-Kinshasa", + "es": "República Democrática del Congo", + "en": "Congo - Kinshasa", + "pt_BR": "República Democrática do Congo", + "sr-Cyrl": "Демократска Република Конго", + "sr-Latn": "Demokratska Republika Kongo", + "zh_TW": "剛果民主共和國(金沙薩)", + "tr": "Kongo Demokratik Cumhuriyeti", + "ro": "Republica Democrată Congo", + "ar": "جمهورية الكونغو الديمقراطية", + "fa": "جمهوری دموکراتیک کنگو", + "yue": "剛果(金)" + }, + flag: "🇨🇩", + code: "CD", + dialCode: "243", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Cook Islands", + nameTranslations: { + "sk": "Cookove ostrovy", + "se": "Cook-sullot", + "pl": "Wyspy Cooka", + "no": "Cookøyene", + "ja": "クック諸島", + "it": "Isole Cook", + "zh": "库克群岛", + "nl": "Cookeilanden", + "de": "Cookinseln", + "fr": "Îles Cook", + "es": "Islas Cook", + "en": "Cook Islands", + "pt_BR": "Ilhas Cook", + "sr-Cyrl": "Кукова Острва", + "sr-Latn": "Kukova Ostrva", + "zh_TW": "庫克群島", + "tr": "Cook Adaları", + "ro": "Insulele Cook", + "ar": "جزر كوك", + "fa": "جزایر کوک", + "yue": "庫克群島" + }, + flag: "🇨🇰", + code: "CK", + dialCode: "682", + minLength: 5, + maxLength: 5, + ), + Country( + name: "Costa Rica", + nameTranslations: { + "sk": "Kostarika", + "se": "Costa Rica", + "pl": "Kostaryka", + "no": "Costa Rica", + "ja": "コスタリカ", + "it": "Costa Rica", + "zh": "哥斯达黎加", + "nl": "Costa Rica", + "de": "Costa Rica", + "fr": "Costa Rica", + "es": "Costa Rica", + "en": "Costa Rica", + "pt_BR": "Costa Rica", + "sr-Cyrl": "Коста Рика", + "sr-Latn": "Kosta Rika", + "zh_TW": "哥斯大黎加", + "tr": "Kosta Rika", + "ro": "Costa Rica", + "ar": "كوستاريكا", + "fa": "کاستاریکا", + "yue": "哥斯達黎加" + }, + flag: "🇨🇷", + code: "CR", + dialCode: "506", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Côte d'Ivoire", + nameTranslations: { + "sk": "Pobrežie Slonoviny", + "se": "Elfenbenariddu", + "pl": "Côte d'Ivoire", + "no": "Elfenbenskysten", + "ja": "コートジボワール", + "it": "Costa d'Avorio", + "zh": "科特迪瓦", + "nl": "Ivoorkust", + "de": "Côte d'Ivoire", + "fr": "Côte d'Ivoire", + "es": "Côte d'Ivoire", + "en": "Côte d'Ivoire", + "pt_BR": "Côte d'Ivoire", + "sr-Cyrl": "Обала Слоноваче", + "sr-Latn": "Obala Slonovače", + "zh_TW": "象牙海岸", + "tr": "Fildişi Kıyısı", + "ro": "Coasta de fildeș", + "ar": "ساحل العاج", + "fa": "ساحل عاج", + "yue": "科特迪瓦" + }, + flag: "🇨🇮", + code: "CI", + dialCode: "225", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Croatia", + nameTranslations: { + "sk": "Chorvátsko", + "se": "Kroátia", + "pl": "Chorwacja", + "no": "Kroatia", + "ja": "クロアチア", + "it": "Croazia", + "zh": "克罗地亚", + "nl": "Kroatië", + "de": "Kroatien", + "fr": "Croatie", + "es": "Croacia", + "en": "Croatia", + "pt_BR": "Croácia", + "sr-Cyrl": "Хрватска", + "sr-Latn": "Hrvatska", + "zh_TW": "克羅埃西亞", + "tr": "Hırvatistan", + "ro": "Croația", + "ar": "كرواتيا", + "fa": "کرواسی", + "yue": "克羅地亞" + }, + flag: "🇭🇷", + code: "HR", + dialCode: "385", + minLength: 12, + maxLength: 12, + ), + Country( + name: "Cuba", + nameTranslations: { + "sk": "Kuba", + "se": "Kuba", + "pl": "Kuba", + "no": "Cuba", + "ja": "キューバ", + "it": "Cuba", + "zh": "古巴", + "nl": "Cuba", + "de": "Kuba", + "fr": "Cuba", + "es": "Cuba", + "en": "Cuba", + "pt_BR": "Cuba", + "sr-Cyrl": "Куба", + "sr-Latn": "Kuba", + "zh_TW": "古巴", + "tr": "Küba", + "ro": "Cuba", + "ar": "كوبا", + "fa": "كوبا", + "yue": "古巴" + }, + flag: "🇨🇺", + code: "CU", + dialCode: "53", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Cyprus", + nameTranslations: { + "sk": "Cyprus", + "se": "Kypros", + "pl": "Cypr", + "no": "Kypros", + "ja": "キプロス", + "it": "Cipro", + "zh": "塞浦路斯", + "nl": "Cyprus", + "de": "Zypern", + "fr": "Chypre", + "es": "Chipre", + "en": "Cyprus", + "pt_BR": "Chipre", + "sr-Cyrl": "Кипар", + "sr-Latn": "Kipar", + "zh_TW": "塞普勒斯", + "tr": "Kıbrıs", + "ro": "Cipru", + "ar": "قبرص", + "fa": "قبرس", + "yue": "塞浦路斯" + }, + flag: "🇨🇾", + code: "CY", + dialCode: "357", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Czech Republic", + nameTranslations: { + "sk": "Česko", + "se": "Čeahkka", + "pl": "Czechy", + "no": "Tsjekkia", + "ja": "チェコ", + "it": "Cechia", + "zh": "捷克", + "nl": "Tsjechië", + "de": "Tschechien", + "fr": "Tchéquie", + "es": "Chequia", + "en": "Czechia", + "pt_BR": "Czechia", + "sr-Cyrl": "Чешка", + "sr-Latn": "Češka", + "zh_TW": "捷克", + "tr": "Çek Cumhuriyeti", + "ro": "Cehia", + "ar": "جمهورية التشيك", + "fa": "جمهوری چک", + "yue": "捷克共和國" + }, + flag: "🇨🇿", + code: "CZ", + dialCode: "420", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Denmark", + nameTranslations: { + "sk": "Dánsko", + "se": "Dánmárku", + "pl": "Dania", + "no": "Danmark", + "ja": "デンマーク", + "it": "Danimarca", + "zh": "丹麦", + "nl": "Denemarken", + "de": "Dänemark", + "fr": "Danemark", + "es": "Dinamarca", + "en": "Denmark", + "pt_BR": "Dinamarca", + "sr-Cyrl": "Данска", + "sr-Latn": "Danska", + "zh_TW": "丹麥", + "tr": "Danimarka", + "ro": "Danemarca", + "ar": "الدنمارك", + "fa": "دانمارک", + "yue": "丹麥" + }, + flag: "🇩🇰", + code: "DK", + dialCode: "45", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Djibouti", + nameTranslations: { + "sk": "Džibutsko", + "se": "Djibouti", + "pl": "Dżibuti", + "no": "Djibouti", + "ja": "ジブチ", + "it": "Gibuti", + "zh": "吉布提", + "nl": "Djibouti", + "de": "Dschibuti", + "fr": "Djibouti", + "es": "Yibuti", + "en": "Djibouti", + "pt_BR": "Djibouti", + "sr-Cyrl": "Џибути", + "sr-Latn": "Džibuti", + "zh_TW": "吉布地", + "tr": "Cibuti", + "ro": "Djibouti", + "ar": "جيبوتي", + "fa": "جیبوتی", + "yue": "吉布提" + }, + flag: "🇩🇯", + code: "DJ", + dialCode: "253", + minLength: 6, + maxLength: 6, + ), + Country( + name: "Dominica", + nameTranslations: { + "sk": "Dominika", + "se": "Dominica", + "pl": "Dominika", + "no": "Dominica", + "ja": "ドミニカ国", + "it": "Dominica", + "zh": "多米尼克", + "nl": "Dominica", + "de": "Dominica", + "fr": "Dominique", + "es": "Dominica", + "en": "Dominica", + "pt_BR": "Dominica", + "sr-Cyrl": "Доминика", + "sr-Latn": "Dominika", + "zh_TW": "多明尼加", + "tr": "Dominika", + "ro": "Dominica", + "ar": "دومينيكا", + "fa": "دومينيكا", + "yue": "多米尼加" + }, + flag: "🇩🇲", + code: "DM", + dialCode: "1767", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Dominican Republic", + nameTranslations: { + "sk": "Dominikánska republika", + "se": "Dominikána dásseváldi", + "pl": "Dominikana", + "no": "Den dominikanske republikk", + "ja": "ドミニカ共和国", + "it": "Repubblica Dominicana", + "zh": "多米尼加共和国", + "nl": "Dominicaanse Republiek", + "de": "Dominikanische Republik", + "fr": "République dominicaine", + "es": "República Dominicana", + "en": "Dominican Republic", + "pt_BR": "República Dominicana", + "sr-Cyrl": "Доминиканска Република", + "sr-Latn": "Dominikanska Republika", + "zh_TW": "多明尼加共和國", + "tr": "Dominik Cumhuriyeti", + "ro": "Republica Dominicană", + "ar": "جمهورية الدومينيكان", + "fa": "جمهوری دومنیکن", + "yue": "多明尼加共和國" + }, + flag: "🇩🇴", + code: "DO", + dialCode: "1", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Ecuador", + nameTranslations: { + "sk": "Ekvádor", + "se": "Ecuador", + "pl": "Ekwador", + "no": "Ecuador", + "ja": "エクアドル", + "it": "Ecuador", + "zh": "厄瓜多尔", + "nl": "Ecuador", + "de": "Ecuador", + "fr": "Équateur", + "es": "Ecuador", + "en": "Ecuador", + "pt_BR": "Equador", + "sr-Cyrl": "Еквадор", + "sr-Latn": "Ekvador", + "zh_TW": "厄瓜多", + "tr": "Ekvador", + "ro": "Ecuador", + "ar": "الإكوادور", + "fa": "اكوادور", + "yue": "厄瓜多爾" + }, + flag: "🇪🇨", + code: "EC", + dialCode: "593", + minLength: 8, + maxLength: 9, + ), + Country( + name: "Egypt", + nameTranslations: { + "sk": "Egypt", + "se": "Egypt", + "pl": "Egipt", + "no": "Egypt", + "ja": "エジプト", + "it": "Egitto", + "zh": "埃及", + "nl": "Egypt", + "de": "Ägypt", + "fr": "Égypte", + "es": "Egipt", + "en": "Egypt", + "pt_BR": "Egito", + "sr-Cyrl": "Египат", + "sr-Latn": "Egipat", + "zh_TW": "埃及", + "tr": "Mısır", + "ro": "Egipt", + "ar": "مصر", + "fa": "مصر", + "yue": "埃及" + }, + flag: "🇪🇬", + code: "EG", + dialCode: "2", + minLength: 11, + maxLength: 11, + ), + Country( + name: "El Salvador", + nameTranslations: { + "sk": "Salvádor", + "se": "El Salvador", + "pl": "Salwador", + "no": "El Salvador", + "ja": "エルサルバドル", + "it": "El Salvador", + "zh": "萨尔瓦多", + "nl": "El Salvador", + "de": "El Salvador", + "fr": "Salvador", + "es": "El Salvador", + "en": "El Salvador", + "pt_BR": "El Salvador", + "sr-Cyrl": "Салвадор", + "sr-Latn": "Salvador", + "zh_TW": "薩爾瓦多", + "tr": "El Salvador", + "ro": "Salvador", + "ar": "السلفادور", + "fa": "ال سالوادور", + "yue": "薩爾瓦多" + }, + flag: "🇸🇻", + code: "SV", + dialCode: "503", + minLength: 11, + maxLength: 11, + ), + Country( + name: "Equatorial Guinea", + nameTranslations: { + "sk": "Rovníková Guinea", + "se": "Ekvatoriála Guinea", + "pl": "Gwinea Równikowa", + "no": "Ekvatorial-Guinea", + "ja": "赤道ギニア", + "it": "Guinea Equatoriale", + "zh": "赤道几内亚", + "nl": "Equatoriaal-Guinea", + "de": "Äquatorialguinea", + "fr": "Guinée équatoriale", + "es": "Guinea Ecuatorial", + "en": "Equatorial Guinea", + "pt_BR": "Guiné Equatorial", + "sr-Cyrl": "Екваторијална Гвинеја", + "sr-Latn": "Ekvatorijalna Gvineja", + "zh_TW": "赤道幾內亞", + "tr": "Ekvator Ginesi", + "ro": "Guineea Ecuatorială", + "ar": "غينيا الاستوائية", + "fa": "گینه استوایی", + "yue": "赤道幾內亞" + }, + flag: "🇬🇶", + code: "GQ", + dialCode: "240", + minLength: 6, + maxLength: 6, + ), + Country( + name: "Eritrea", + nameTranslations: { + "sk": "Eritrea", + "se": "Eritrea", + "pl": "Erytrea", + "no": "Eritrea", + "ja": "エリトリア", + "it": "Eritrea", + "zh": "厄立特里亚", + "nl": "Eritrea", + "de": "Eritrea", + "fr": "Érythrée", + "es": "Eritrea", + "en": "Eritrea", + "pt_BR": "Eritreia", + "sr-Cyrl": "Еритреја", + "sr-Latn": "Eritreja", + "zh_TW": "厄立特裡亞", + "tr": "Eritre", + "ro": "Eritreea", + "ar": "إريتريا", + "fa": "اریتره", + "yue": "厄立特里亞" + }, + flag: "🇪🇷", + code: "ER", + dialCode: "291", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Estonia", + nameTranslations: { + "sk": "Estónsko", + "se": "Estlánda", + "pl": "Estonia", + "no": "Estland", + "ja": "エストニア", + "it": "Estonia", + "zh": "爱沙尼亚", + "nl": "Estland", + "de": "Estland", + "fr": "Estonie", + "es": "Estonia", + "en": "Estonia", + "pt_BR": "Estônia", + "sr-Cyrl": "Естонија", + "sr-Latn": "Estonija", + "zh_TW": "愛沙尼亞", + "tr": "Estonya", + "ro": "Estonia", + "ar": "إستونيا", + "fa": "استونی", + "yue": "愛沙尼亞" + }, + flag: "🇪🇪", + code: "EE", + dialCode: "372", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Ethiopia", + nameTranslations: { + "sk": "Etiópia", + "se": "Etiopia", + "pl": "Etiopia", + "no": "Etiopia", + "ja": "エチオピア", + "it": "Etiopia", + "zh": "埃塞俄比亚", + "nl": "Ethiopië", + "de": "Äthiopien", + "fr": "Éthiopie", + "es": "Etiopía", + "en": "Ethiopia", + "pt_BR": "Etiópia", + "sr-Cyrl": "Етиопија", + "sr-Latn": "Etiopija", + "zh_TW": "伊索比亞", + "tr": "Etiyopya", + "ro": "Etiopia", + "ar": "إثيوبيا", + "fa": "اتیوپی", + "yue": "埃塞俄比亞" + }, + flag: "🇪🇹", + code: "ET", + dialCode: "251", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Falkland Islands (Malvinas)", + nameTranslations: { + "sk": "Falklandy", + "se": "Falklandsullot", + "pl": "Falklandy", + "no": "Falklandsøyene", + "ja": "フォークランド諸島", + "it": "Isole Falkland", + "zh": "福克兰群岛", + "nl": "Falklandeilanden", + "de": "Falklandinseln", + "fr": "Îles Malouines", + "es": "Islas Malvinas", + "en": "Falkland Islands", + "pt_BR": "Ilhas Falkland", + "sr-Cyrl": "Фокландска Острва", + "sr-Latn": "Foklandska Ostrva", + "zh_TW": "福克蘭群島", + "tr": "Falkland Adaları", + "ro": "Insulele Falklands", + "ar": "جزر فوكلاند", + "fa": "جزایر فالکلند", + "yue": "福克蘭群島(馬爾維納斯群島)" + }, + flag: "🇫🇰", + code: "FK", + dialCode: "500", + minLength: 5, + maxLength: 5, + ), + Country( + name: "Faroe Islands", + nameTranslations: { + "sk": "Faerské ostrovy", + "se": "Fearsullot", + "pl": "Wyspy Owcze", + "no": "Færøyene", + "ja": "フェロー諸島", + "it": "Isole Fær Øer", + "zh": "法罗群岛", + "nl": "Faeröer", + "de": "Färöer", + "fr": "Îles Féroé", + "es": "Islas Feroe", + "en": "Faroe Islands", + "pt_BR": "ilhas Faroe", + "sr-Cyrl": "Фарска Острва", + "sr-Latn": "Farska Ostrva", + "zh_TW": "法羅群島", + "tr": "Faroe Adaları", + "ro": "Insulele Feroe", + "ar": "جزر فارو", + "fa": "جزایر فارو", + "yue": "法羅群島" + }, + flag: "🇫🇴", + code: "FO", + dialCode: "298", + minLength: 6, + maxLength: 6, + ), + Country( + name: "Fiji", + nameTranslations: { + "sk": "Fidži", + "se": "Fijisullot", + "pl": "Fidżi", + "no": "Fiji", + "ja": "フィジー", + "it": "Figi", + "zh": "斐济", + "nl": "Fiji", + "de": "Fidschi", + "fr": "Fidji", + "es": "Fiyi", + "en": "Fiji", + "pt_BR": "Fiji", + "sr-Cyrl": "Фиџи", + "sr-Latn": "Fidži", + "zh_TW": "斐濟", + "tr": "Fiji", + "ro": "Fiji", + "ar": "فيجي", + "fa": "فيجي", + "yue": "斐濟" + }, + flag: "🇫🇯", + code: "FJ", + dialCode: "679", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Finland", + nameTranslations: { + "sk": "Fínsko", + "se": "Suopma", + "pl": "Finlandia", + "no": "Finland", + "ja": "フィンランド", + "it": "Finlandia", + "zh": "芬兰", + "nl": "Finland", + "de": "Finnland", + "fr": "Finlande", + "es": "Finlandia", + "en": "Finland", + "pt_BR": "Finlândia", + "sr-Cyrl": "Финска", + "sr-Latn": "Finska", + "zh_TW": "芬蘭", + "tr": "Finlandiya", + "ro": "Finlanda", + "ar": "فنلندا", + "fa": "فنلاند", + "yue": "芬蘭" + }, + flag: "🇫🇮", + code: "FI", + dialCode: "358", + minLength: 12, + maxLength: 12, + ), + Country( + name: "France", + nameTranslations: { + "sk": "Francúzsko", + "se": "Frankriika", + "pl": "Francja", + "no": "Frankrike", + "ja": "フランス", + "it": "Francia", + "zh": "法国", + "nl": "Frankrijk", + "de": "Frankreich", + "fr": "France", + "es": "Francia", + "en": "France", + "pt_BR": "França", + "sr-Cyrl": "Француска", + "sr-Latn": "Francuska", + "zh_TW": "法國", + "tr": "Fransa", + "ro": "Franța", + "ar": "فرنسا", + "fa": "فرانسه", + "yue": "法國" + }, + flag: "🇫🇷", + code: "FR", + dialCode: "33", + minLength: 9, + maxLength: 9, + ), + Country( + name: "French Guiana", + nameTranslations: { + "sk": "Francúzska Guyana", + "se": "Frankriikka Guayana", + "pl": "Gujana Francuska", + "no": "Fransk Guyana", + "ja": "仏領ギアナ", + "it": "Guyana francese", + "zh": "法属圭亚那", + "nl": "Frans-Guyana", + "de": "Französisch-Guayana", + "fr": "Guyane française", + "es": "Guayana Francesa", + "en": "French Guiana", + "pt_BR": "Guiana Francesa", + "sr-Cyrl": "Француска Гвајана", + "sr-Latn": "Francuska Gvajana", + "zh_TW": "法屬蓋亞那", + "tr": "Fransız Guyanası", + "ro": "Guiana Franceză", + "ar": "غويانا الفرنسية", + "fa": "گویان فرانسه", + "yue": "法屬圭亞那" + }, + flag: "🇬🇫", + code: "GF", + dialCode: "594", + minLength: 15, + maxLength: 15, + ), + Country( + name: "French Polynesia", + nameTranslations: { + "sk": "Francúzska Polynézia", + "se": "Frankriikka Polynesia", + "pl": "Polinezja Francuska", + "no": "Fransk Polynesia", + "ja": "仏領ポリネシア", + "it": "Polinesia francese", + "zh": "法属波利尼西亚", + "nl": "Frans-Polynesië", + "de": "Französisch-Polynesien", + "fr": "Polynésie française", + "es": "Polinesia Francesa", + "en": "French Polynesia", + "pt_BR": "Polinésia Francesa", + "sr-Cyrl": "Француска Полинезија", + "sr-Latn": "Francuska Polinezija", + "zh_TW": "法屬玻里尼西亞", + "tr": "Fransız Polinezyası", + "ro": "Polinezia Franceză", + "ar": "بولينزيا الفرنسية", + "fa": "پلی‌نزی فرانسه", + "yue": "法屬波利尼西亞" + }, + flag: "🇵🇫", + code: "PF", + dialCode: "689", + minLength: 6, + maxLength: 6, + ), + Country( + name: "French Southern Territories", + nameTranslations: { + "sk": "Francúzske južné a antarktické územia", + "se": "French Southern Territories", + "pl": "Francuskie Terytoria Południowe i Antarktyczne", + "no": "De franske sørterritorier", + "ja": "仏領極南諸島", + "it": "Terre australi francesi", + "zh": "法属南部领地", + "nl": "Franse Gebieden in de zuidelijke Indische Oceaan", + "de": "Französische Süd- und Antarktisgebiete", + "fr": "Terres australes françaises", + "es": "Territorios Australes Franceses", + "en": "French Southern Territories", + "pt_BR": "Territórios Franceses do Sul", + "sr-Cyrl": "Француске јужне и антарктичке земље", + "sr-Latn": "Francuske južne i antarktičke zemlje", + "zh_TW": "法屬南部屬地", + "tr": "Fransız Güney ve Antarktika Toprakları", + "ro": "Teritoriile australe și antarctice franceze", + "ar": "أراض فرنسية جنوبية وأنتارتيكية", + "fa": "سرزمین‌های جنوبی فرانسه", + "yue": "法國南部領土" + }, + flag: "🇹🇫", + code: "TF", + dialCode: "262", + minLength: 15, + maxLength: 15, + ), + Country( + name: "Gabon", + nameTranslations: { + "sk": "Gabon", + "se": "Gabon", + "pl": "Gabon", + "no": "Gabon", + "ja": "ガボン", + "it": "Gabon", + "zh": "加蓬", + "nl": "Gabon", + "de": "Gabun", + "fr": "Gabon", + "es": "Gabón", + "en": "Gabon", + "pt_BR": "Gabão", + "sr-Cyrl": "Габон", + "sr-Latn": "Gabon", + "zh_TW": "加彭", + "tr": "Gabon", + "ro": "Gabon", + "ar": "الغابون", + "fa": "گابن", + "yue": "加蓬" + }, + flag: "🇬🇦", + code: "GA", + dialCode: "241", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Gambia", + nameTranslations: { + "sk": "Gambia", + "se": "Gámbia", + "pl": "Gambia", + "no": "Gambia", + "ja": "ガンビア", + "it": "Gambia", + "zh": "冈比亚", + "nl": "Gambia", + "de": "Gambia", + "fr": "Gambie", + "es": "Gambia", + "en": "Gambia", + "pt_BR": "Gâmbia", + "sr-Cyrl": "Гамбија", + "sr-Latn": "Gambija", + "zh_TW": "岡比亞", + "tr": "Gambiya", + "ro": "Gambia", + "ar": "غامبيا", + "fa": "گامبیا", + "yue": "岡比亞" + }, + flag: "🇬🇲", + code: "GM", + dialCode: "220", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Georgia", + nameTranslations: { + "sk": "Gruzínsko", + "se": "Georgia", + "pl": "Gruzja", + "no": "Georgia", + "ja": "ジョージア", + "it": "Georgia", + "zh": "格鲁吉亚", + "nl": "Georgië", + "de": "Georgien", + "fr": "Géorgie", + "es": "Georgia", + "en": "Georgia", + "pt_BR": "Georgia", + "sr-Cyrl": "Грузија", + "sr-Latn": "Gruzija", + "zh_TW": "喬治亞", + "tr": "Gürcistan", + "ro": "Georgia", + "ar": "جورجيا", + "fa": "گرجستان", + "yue": "格魯吉亞" + }, + flag: "🇬🇪", + code: "GE", + dialCode: "995", + minLength: 8, + maxLength: 9, + ), + Country( + name: "Germany", + nameTranslations: { + "sk": "Nemecko", + "se": "Duiska", + "pl": "Niemcy", + "no": "Tyskland", + "ja": "ドイツ", + "it": "Germania", + "zh": "德国", + "nl": "Duitsland", + "de": "Deutschland", + "fr": "Allemagne", + "es": "Alemania", + "en": "Germany", + "pt_BR": "Alemanha", + "sr-Cyrl": "Немачка", + "sr-Latn": "Nemačka", + "zh_TW": "德國", + "tr": "Almanya", + "ro": "Germania", + "ar": "ألمانيا", + "fa": "آلمان", + "yue": "德國" + }, + flag: "🇩🇪", + code: "DE", + dialCode: "49", + minLength: 9, + maxLength: 13, + ), + Country( + name: "Ghana", + nameTranslations: { + "sk": "Ghana", + "se": "Ghana", + "pl": "Ghana", + "no": "Ghana", + "ja": "ガーナ", + "it": "Ghana", + "zh": "加纳", + "nl": "Ghana", + "de": "Ghana", + "fr": "Ghana", + "es": "Ghana", + "en": "Ghana", + "pt_BR": "Gana", + "sr-Cyrl": "Гана", + "sr-Latn": "Gana", + "zh_TW": "迦納", + "tr": "Gana", + "ro": "Ghana", + "ar": "غانا", + "fa": "غنا", + "yue": "加納" + }, + flag: "🇬🇭", + code: "GH", + dialCode: "233", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Gibraltar", + nameTranslations: { + "sk": "Gibraltár", + "se": "Gibraltar", + "pl": "Gibraltar", + "no": "Gibraltar", + "ja": "ジブラルタル", + "it": "Gibilterra", + "zh": "直布罗陀", + "nl": "Gibraltar", + "de": "Gibraltar", + "fr": "Gibraltar", + "es": "Gibraltar", + "en": "Gibraltar", + "pt_BR": "Gibraltar", + "sr-Cyrl": "Гибралтар", + "sr-Latn": "Gibraltar", + "zh_TW": "直布羅陀", + "tr": "Cebelitarık", + "ro": "Gibraltar", + "ar": "جبل طارق", + "fa": "جبل الطارق", + "yue": "直布羅陀" + }, + flag: "🇬🇮", + code: "GI", + dialCode: "350", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Greece", + nameTranslations: { + "sk": "Grécko", + "se": "Greika", + "pl": "Grecja", + "no": "Hellas", + "ja": "ギリシャ", + "it": "Grecia", + "zh": "希腊", + "nl": "Griekenland", + "de": "Griechenland", + "fr": "Grèce", + "es": "Grecia", + "en": "Greece", + "pt_BR": "Grécia", + "sr-Cyrl": "Грчка", + "sr-Latn": "Grčka", + "zh_TW": "希臘", + "tr": "Yunanistan", + "ro": "Grecia", + "ar": "اليونان", + "fa": "یونان", + "yue": "希臘" + }, + flag: "🇬🇷", + code: "GR", + dialCode: "30", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Greenland", + nameTranslations: { + "sk": "Grónsko", + "se": "Kalaallit Nunaat", + "pl": "Grenlandia", + "no": "Grønland", + "ja": "グリーンランド", + "it": "Groenlandia", + "zh": "格陵兰", + "nl": "Groenland", + "de": "Grönland", + "fr": "Groenland", + "es": "Groenlandia", + "en": "Greenland", + "pt_BR": "Groenlândia", + "sr-Cyrl": "Гренланд", + "sr-Latn": "Grenland", + "zh_TW": "格陵蘭", + "tr": "Grönland", + "ro": "Groenlanda", + "ar": "جرينلاند", + "fa": "گرینلند", + "yue": "格陵蘭" + }, + flag: "🇬🇱", + code: "GL", + dialCode: "299", + minLength: 6, + maxLength: 6, + ), + Country( + name: "Grenada", + nameTranslations: { + "sk": "Grenada", + "se": "Grenada", + "pl": "Grenada", + "no": "Grenada", + "ja": "グレナダ", + "it": "Grenada", + "zh": "格林纳达", + "nl": "Grenada", + "de": "Grenada", + "fr": "Grenade", + "es": "Granada", + "en": "Grenada", + "pt_BR": "Grenada", + "sr-Cyrl": "Гренада", + "sr-Latn": "Grenada", + "zh_TW": "格林納達", + "tr": "Grenada", + "ro": "Grenada", + "ar": "غرينادا", + "fa": "گرنادا", + "yue": "格林納達" + }, + flag: "🇬🇩", + code: "GD", + dialCode: "1473", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Guadeloupe", + nameTranslations: { + "sk": "Guadeloupe", + "se": "Guadeloupe", + "pl": "Gwadelupa", + "no": "Guadeloupe", + "ja": "グアドループ", + "it": "Guadalupa", + "zh": "瓜德罗普", + "nl": "Guadeloupe", + "de": "Guadeloupe", + "fr": "Guadeloupe", + "es": "Guadalupe", + "en": "Guadeloupe", + "pt_BR": "Guadalupe", + "sr-Cyrl": "Гваделуп", + "sr-Latn": "Gvadelup", + "zh_TW": "瓜地洛普", + "tr": "Guadeloupe", + "ro": "Guadelupa", + "ar": "غوادلوب", + "fa": "گوادلوپ", + "yue": "瓜德罗普" + }, + flag: "🇬🇵", + code: "GP", + dialCode: "590", + minLength: 15, + maxLength: 15, + ), + Country( + name: "Guam", + nameTranslations: { + "sk": "Guam", + "se": "Guam", + "pl": "Guam", + "no": "Guam", + "ja": "グアム", + "it": "Guam", + "zh": "关岛", + "nl": "Guam", + "de": "Guam", + "fr": "Guam", + "es": "Guam", + "en": "Guam", + "pt_BR": "Guam", + "sr-Cyrl": "Гвам", + "sr-Latn": "Gvam", + "zh_TW": "關島", + "tr": "Guam", + "ro": "Guam", + "ar": "غوام", + "fa": "گوام", + "yue": "關島" + }, + flag: "🇬🇺", + code: "GU", + dialCode: "1671", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Guatemala", + nameTranslations: { + "sk": "Guatemala", + "se": "Guatemala", + "pl": "Gwatemala", + "no": "Guatemala", + "ja": "グアテマラ", + "it": "Guatemala", + "zh": "危地马拉", + "nl": "Guatemala", + "de": "Guatemala", + "fr": "Guatemala", + "es": "Guatemala", + "en": "Guatemala", + "pt_BR": "Guatemala", + "sr-Cyrl": "Гватемала", + "sr-Latn": "Gvatemala", + "zh_TW": "瓜地馬拉", + "tr": "Guatemala", + "ro": "Guatemala", + "ar": "غواتيمالا", + "fa": "گواتمالا", + "yue": "危地馬拉" + }, + flag: "🇬🇹", + code: "GT", + dialCode: "502", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Guernsey", + nameTranslations: { + "sk": "Guernsey", + "se": "Guernsey", + "pl": "Guernsey", + "no": "Guernsey", + "ja": "ガーンジー", + "it": "Guernsey", + "zh": "根西岛", + "nl": "Guernsey", + "de": "Guernsey", + "fr": "Guernesey", + "es": "Guernsey", + "en": "Guernsey", + "pt_BR": "Guernsey", + "sr-Cyrl": "Гернзи", + "sr-Latn": "Gernzi", + "zh_TW": "根息島", + "tr": "Guernsey", + "ro": "Guernsey", + "ar": "غيرنزي", + "fa": "گرنزی", + "yue": "格恩西島" + }, + flag: "🇬🇬", + code: "GG", + dialCode: "44", + minLength: 6, + maxLength: 6, + ), + Country( + name: "Guinea", + nameTranslations: { + "sk": "Guinea", + "se": "Guinea", + "pl": "Gwinea", + "no": "Guinea", + "ja": "ギニア", + "it": "Guinea", + "zh": "几内亚", + "nl": "Guinee", + "de": "Guinea", + "fr": "Guinée", + "es": "Guinea", + "en": "Guinea", + "pt_BR": "Guiné", + "sr-Cyrl": "Гвинеја", + "sr-Latn": "Gvineja", + "zh_TW": "幾內亞", + "tr": "Gine", + "ro": "Guinea", + "ar": "غينيا", + "fa": "گینه", + "yue": "幾內亞" + }, + flag: "🇬🇳", + code: "GN", + dialCode: "224", + minLength: 8, + maxLength: 9, + ), + Country( + name: "Guinea-Bissau", + nameTranslations: { + "sk": "Guinea-Bissau", + "se": "Guinea-Bissau", + "pl": "Gwinea Bissau", + "no": "Guinea-Bissau", + "ja": "ギニアビサウ", + "it": "Guinea-Bissau", + "zh": "几内亚比绍", + "nl": "Guinee-Bissau", + "de": "Guinea-Bissau", + "fr": "Guinée-Bissau", + "es": "Guinea-Bisáu", + "en": "Guinea-Bissau", + "pt_BR": "Guiné-bissau", + "sr-Cyrl": "Гвинеја Бисао", + "sr-Latn": "Gvineja Bisao", + "zh_TW": "幾內亞比索", + "tr": "Gine-Bissau", + "ro": "Guineea-Bissau", + "ar": "غينيا بيساو", + "fa": "گینه بیسائو", + "yue": "幾內亞比紹" + }, + flag: "🇬🇼", + code: "GW", + dialCode: "245", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Guyana", + nameTranslations: { + "sk": "Guyana", + "se": "Guyana", + "pl": "Gujana", + "no": "Guyana", + "ja": "ガイアナ", + "it": "Guyana", + "zh": "圭亚那", + "nl": "Guyana", + "de": "Guyana", + "fr": "Guyana", + "es": "Guyana", + "en": "Guyana", + "pt_BR": "Guiana", + "sr-Cyrl": "Гвајана", + "sr-Latn": "Gvajana", + "zh_TW": "蓋亞那", + "tr": "Guyana", + "ro": "Guyana", + "ar": "غيانا", + "fa": "گویان", + "yue": "圭亞那" + }, + flag: "🇬🇾", + code: "GY", + dialCode: "592", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Haiti", + nameTranslations: { + "sk": "Haiti", + "se": "Haiti", + "pl": "Haiti", + "no": "Haiti", + "ja": "ハイチ", + "it": "Haiti", + "zh": "海地", + "nl": "Haïti", + "de": "Haiti", + "fr": "Haïti", + "es": "Haití", + "en": "Haiti", + "pt_BR": "Haiti", + "sr-Cyrl": "Хаити", + "sr-Latn": "Haiti", + "zh_TW": "海地", + "tr": "Haiti", + "ro": "Haiti", + "ar": "هايتي", + "fa": "هائیتی", + "yue": "海地" + }, + flag: "🇭🇹", + code: "HT", + dialCode: "509", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Heard Island and Mcdonald Islands", + nameTranslations: { + "sk": "Heardov ostrov a Macdonaldove ostrovy", + "se": "Heard- ja McDonald-sullot", + "pl": "Wyspy Heard i McDonalda", + "no": "Heard- og McDonaldøyene", + "ja": "ハード島・マクドナルド諸島", + "it": "Isole Heard e McDonald", + "zh": "赫德岛和麦克唐纳群岛", + "nl": "Heard en McDonaldeilanden", + "de": "Heard und McDonaldinseln", + "fr": "Îles Heard et McDonald", + "es": "Islas Heard y McDonald", + "en": "Heard & McDonald Islands", + "pt_BR": "Ilhas Heard e McDonald", + "sr-Cyrl": "Острва Херд и Макдоналд", + "sr-Latn": "Ostrva Herd i Makdonald", + "zh_TW": "赫德暨麥當勞群島", + "tr": "Heard Adası ve McDonald Adaları", + "ro": "Insula Heard și Insulele McDonald", + "ar": "جزيرة هيرد وجزر ماكدونالد", + "fa": "جزیره هرد و جزایر مک‌دونالد", + "yue": "赫德岛同麦克唐纳群岛" + }, + flag: "🇭🇲", + code: "HM", + dialCode: "672", + minLength: 15, + maxLength: 15, + ), + Country( + name: "Holy See (Vatican City State)", + nameTranslations: { + "sk": "Vatikán", + "se": "Vatikána", + "pl": "Watykan", + "no": "Vatikanstaten", + "ja": "バチカン市国", + "it": "Città del Vaticano", + "zh": "梵蒂冈", + "nl": "Vaticaanstad", + "de": "Vatikanstadt", + "fr": "État de la Cité du Vatican", + "es": "Ciudad del Vaticano", + "en": "Vatican City", + "pt_BR": "Cidade do Vaticano", + "sr-Cyrl": "Ватикан", + "sr-Latn": "Vatikan", + "zh_TW": "梵蒂岡", + "tr": "Vatikan", + "ro": "Vatican", + "ar": "الفاتيكان", + "fa": "واتیکان", + "yue": "梵蒂岡城國" + }, + flag: "🇻🇦", + code: "VA", + dialCode: "379", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Honduras", + nameTranslations: { + "sk": "Honduras", + "se": "Honduras", + "pl": "Honduras", + "no": "Honduras", + "ja": "ホンジュラス", + "it": "Honduras", + "zh": "洪都拉斯", + "nl": "Honduras", + "de": "Honduras", + "fr": "Honduras", + "es": "Honduras", + "en": "Honduras", + "pt_BR": "Honduras", + "sr-Cyrl": "Хондурас", + "sr-Latn": "Honduras", + "zh_TW": "宏都拉斯", + "tr": "Honduras", + "ro": "Honduras", + "ar": "هندوراس", + "fa": "هندوراس", + "yue": "洪都拉斯" + }, + flag: "🇭🇳", + code: "HN", + dialCode: "504", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Hong Kong", + nameTranslations: { + "sk": "Hongkong – OAO Číny", + "se": "Hongkong", + "pl": "SRA Hongkong (Chiny)", + "no": "Hongkong S.A.R. Kina", + "ja": "中華人民共和国香港特別行政区", + "it": "RAS di Hong Kong", + "zh": "中国香港特别行政区", + "nl": "Hongkong SAR van China", + "de": "Sonderverwaltungsregion Hongkong", + "fr": "R.A.S. chinoise de Hong Kong", + "es": "RAE de Hong Kong (China)", + "en": "Hong Kong SAR China", + "pt_BR": "RAE de Hong Kong China", + "sr-Cyrl": "Хонг Конг", + "sr-Latn": "Hong Kong", + "zh_TW": "香港", + "tr": "Hong Kong", + "ro": "Hong Kong", + "ar": "هونغ كونغ", + "fa": "هنگ کنگ", + "yue": "香港" + }, + flag: "🇭🇰", + code: "HK", + dialCode: "852", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Hungary", + nameTranslations: { + "sk": "Maďarsko", + "se": "Ungár", + "pl": "Węgry", + "no": "Ungarn", + "ja": "ハンガリー", + "it": "Ungheria", + "zh": "匈牙利", + "nl": "Hongarije", + "de": "Ungarn", + "fr": "Hongrie", + "es": "Hungría", + "en": "Hungary", + "pt_BR": "Hungria", + "sr-Cyrl": "Мађарска", + "sr-Latn": "Mađarska", + "zh_TW": "匈牙利", + "tr": "Macaristan", + "ro": "Ungaria", + "ar": "المجر", + "fa": "مجارستان", + "yue": "匈牙利" + }, + flag: "🇭🇺", + code: "HU", + dialCode: "36", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Iceland", + nameTranslations: { + "sk": "Island", + "se": "Islánda", + "pl": "Islandia", + "no": "Island", + "ja": "アイスランド", + "it": "Islanda", + "zh": "冰岛", + "nl": "IJsland", + "de": "Island", + "fr": "Islande", + "es": "Islandia", + "en": "Iceland", + "pt_BR": "Islândia", + "sr-Cyrl": "Исланд", + "sr-Latn": "Island", + "zh_TW": "冰島", + "tr": "İzlanda", + "ro": "Islanda", + "ar": "آيسلندا", + "fa": "ایسلند", + "yue": "冰島" + }, + flag: "🇮🇸", + code: "IS", + dialCode: "354", + minLength: 7, + maxLength: 9, + ), + Country( + name: "India", + nameTranslations: { + "sk": "India", + "se": "India", + "pl": "Indie", + "no": "India", + "ja": "インド", + "it": "India", + "zh": "印度", + "nl": "India", + "de": "Indien", + "fr": "Inde", + "es": "India", + "en": "India", + "pt_BR": "Índia", + "sr-Cyrl": "Индија", + "sr-Latn": "Indija", + "zh_TW": "印度", + "tr": "Hindistan", + "ro": "India", + "ar": "الهند", + "fa": "هند", + "yue": "印度" + }, + flag: "🇮🇳", + code: "IN", + dialCode: "91", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Indonesia", + nameTranslations: { + "sk": "Indonézia", + "se": "Indonesia", + "pl": "Indonezja", + "no": "Indonesia", + "ja": "インドネシア", + "it": "Indonesia", + "zh": "印度尼西亚", + "nl": "Indonesië", + "de": "Indonesien", + "fr": "Indonésie", + "es": "Indonesia", + "en": "Indonesia", + "pt_BR": "Indonésia", + "sr-Cyrl": "Индонезија", + "sr-Latn": "Indonezija", + "zh_TW": "印尼", + "tr": "Endonezya", + "ro": "Indonezia", + "ar": "إندونيسيا", + "fa": "اندونزی", + "yue": "印尼" + }, + flag: "🇮🇩", + code: "ID", + dialCode: "62", + minLength: 10, + maxLength: 13, + ), + Country( + name: "Iran, Islamic Republic of Persian Gulf", + nameTranslations: { + "sk": "Irán", + "se": "Iran", + "pl": "Iran", + "no": "Iran", + "ja": "イラン", + "it": "Iran", + "zh": "伊朗", + "nl": "Iran", + "de": "Iran", + "fr": "Iran", + "es": "Irán", + "en": "Iran", + "pt_BR": "Irã", + "sr-Cyrl": "Иран", + "sr-Latn": "Iran", + "zh_TW": "伊朗", + "tr": "İran", + "ro": "Iran", + "ar": "إيران", + "fa": "ایران", + "yue": "伊朗" + }, + flag: "🇮🇷", + code: "IR", + dialCode: "98", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Iraq", + nameTranslations: { + "sk": "Irak", + "se": "Irak", + "pl": "Irak", + "no": "Irak", + "ja": "イラク", + "it": "Iraq", + "zh": "伊拉克", + "nl": "Irak", + "de": "Irak", + "fr": "Irak", + "es": "Irak", + "en": "Iraq", + "pt_BR": "Iraque", + "sr-Cyrl": "Ирак", + "sr-Latn": "Irak", + "zh_TW": "伊拉克", + "tr": "Irak", + "ro": "Irak", + "ar": "العراق", + "fa": "عراق", + "yue": "伊拉克" + }, + flag: "🇮🇶", + code: "IQ", + dialCode: "964", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Ireland", + nameTranslations: { + "sk": "Írsko", + "se": "Irlánda", + "pl": "Irlandia", + "no": "Irland", + "ja": "アイルランド", + "it": "Irlanda", + "zh": "爱尔兰", + "nl": "Ierland", + "de": "Irland", + "fr": "Irlande", + "es": "Irlanda", + "en": "Ireland", + "pt_BR": "Irlanda", + "sr-Cyrl": "Ирска", + "sr-Latn": "Irska", + "zh_TW": "愛爾蘭", + "tr": "İrlanda", + "ro": "Irlanda", + "ar": "أيرلندا", + "fa": "ایرلند", + "yue": "愛爾蘭" + }, + flag: "🇮🇪", + code: "IE", + dialCode: "353", + minLength: 7, + maxLength: 9, + ), + Country( + name: "Isle of Man", + nameTranslations: { + "sk": "Ostrov Man", + "se": "Mann-sullot", + "pl": "Wyspa Man", + "no": "Man", + "ja": "マン島", + "it": "Isola di Man", + "zh": "马恩岛", + "nl": "Isle of Man", + "de": "Isle of Man", + "fr": "Île de Man", + "es": "Isla de Man", + "en": "Isle of Man", + "pt_BR": "Ilha de Man", + "sr-Cyrl": "Острво Мен", + "sr-Latn": "Ostrvo Men", + "zh_TW": "曼島", + "tr": "Man Adası", + "ro": "Insula Man", + "ar": "جزيرة مان", + "fa": "جزیره مان", + "yue": "马伊岛" + }, + flag: "🇮🇲", + code: "IM", + dialCode: "44", + minLength: 6, + maxLength: 6, + ), + Country( + name: "Israel", + nameTranslations: { + "sk": "Izrael", + "se": "Israel", + "pl": "Izrael", + "no": "Israel", + "ja": "イスラエル", + "it": "Israele", + "zh": "以色列", + "nl": "Israël", + "de": "Israel", + "fr": "Israël", + "es": "Israel", + "en": "Israel", + "pt_BR": "Israel", + "sr-Cyrl": "Израел", + "sr-Latn": "Izrael", + "zh_TW": "以色列", + "tr": "İsrail", + "ro": "Israel", + "ar": "إسرائيل", + "fa": "إسرائيل", + "yue": "以色列" + }, + flag: "🇮🇱", + code: "IL", + dialCode: "972", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Campione d'Italia", + nameTranslations: { + "sk": "Taliansko", + "se": "Itália", + "pl": "Włochy", + "no": "Italia", + "ja": "イタリア", + "it": "Italia", + "zh": "意大利", + "nl": "Italië", + "de": "Italien", + "fr": "Italie", + "es": "Italia", + "en": "Italy", + "pt_BR": "Itália", + "sr-Cyrl": "Италија", + "sr-Latn": "Italija", + "zh_TW": "義大利", + "tr": "İtalya", + "ro": "Italia", + "ar": "إيطاليا", + "fa": "ایتالیا", + "yue": "意大利" + }, + flag: "🇮🇹", + code: "IT", + dialCode: "39", + minLength: 9, + maxLength: 10, + ), + Country( + name: "Jamaica", + nameTranslations: { + "sk": "Jamajka", + "se": "Jamaica", + "pl": "Jamajka", + "no": "Jamaica", + "ja": "ジャマイカ", + "it": "Giamaica", + "zh": "牙买加", + "nl": "Jamaica", + "de": "Jamaika", + "fr": "Jamaïque", + "es": "Jamaica", + "en": "Jamaica", + "pt_BR": "Jamaica", + "sr-Cyrl": "Јамајка", + "sr-Latn": "Jamajka", + "zh_TW": "牙買加", + "tr": "Jamaika", + "ro": "Jamaica", + "ar": "جامايكا", + "fa": "جامائیکا", + "yue": "牙買加" + }, + flag: "🇯🇲", + code: "JM", + dialCode: "1876", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Japan", + nameTranslations: { + "sk": "Japonsko", + "se": "Japána", + "pl": "Japonia", + "no": "Japan", + "ja": "日本", + "it": "Giappone", + "zh": "日本", + "nl": "Japan", + "de": "Japan", + "fr": "Japon", + "es": "Japón", + "en": "Japan", + "pt_BR": "Japão", + "sr-Cyrl": "Јапан", + "sr-Latn": "Japan", + "zh_TW": "日本", + "tr": "Japonya", + "ro": "Japonia", + "ar": "اليابان", + "fa": "ژاپن", + "yue": "日本" + }, + flag: "🇯🇵", + code: "JP", + dialCode: "81", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Jersey", + nameTranslations: { + "sk": "Jersey", + "se": "Jersey", + "pl": "Jersey", + "no": "Jersey", + "ja": "ジャージー", + "it": "Jersey", + "zh": "泽西岛", + "nl": "Jersey", + "de": "Jersey", + "fr": "Jersey", + "es": "Jersey", + "en": "Jersey", + "pt_BR": "Jersey", + "sr-Cyrl": "Џерзи", + "sr-Latn": "Džerzi", + "zh_TW": "澤西", + "tr": "Jersey", + "ro": "Jersey", + "ar": "جيرزي", + "fa": "جرزی", + "yue": "澤西" + }, + flag: "🇯🇪", + code: "JE", + dialCode: "44", + minLength: 6, + maxLength: 6, + ), + Country( + name: "Jordan", + nameTranslations: { + "sk": "Jordánsko", + "se": "Jordánia", + "pl": "Jordania", + "no": "Jordan", + "ja": "ヨルダン", + "it": "Giordania", + "zh": "约旦", + "nl": "Jordanië", + "de": "Jordanien", + "fr": "Jordanie", + "es": "Jordania", + "en": "Jordan", + "pt_BR": "Jordânia", + "sr-Cyrl": "Јордан", + "sr-Latn": "Jordan", + "zh_TW": "約旦", + "tr": "Mavera-i Ürdün", + "ro": "Iordania", + "ar": "الأردن", + "fa": "اردن", + "yue": "約旦" + }, + flag: "🇯🇴", + code: "JO", + dialCode: "962", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Kazakhstan", + nameTranslations: { + "sk": "Kazachstan", + "se": "Kasakstan", + "pl": "Kazachstan", + "no": "Kasakhstan", + "ja": "カザフスタン", + "it": "Kazakistan", + "zh": "哈萨克斯坦", + "nl": "Kazachstan", + "de": "Kasachstan", + "fr": "Kazakhstan", + "es": "Kazajistán", + "en": "Kazakhstan", + "pt_BR": "Cazaquistão", + "sr-Cyrl": "Казахстан", + "sr-Latn": "Kazahstan", + "zh_TW": "哈薩克", + "tr": "Kazakistan", + "ro": "Kazahstan", + "ar": "كازاخستان", + "fa": "قزاقستان", + "yue": "哈薩克斯坦" + }, + flag: "🇰🇿", + code: "KZ", + dialCode: "7", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Kenya", + nameTranslations: { + "sk": "Keňa", + "se": "Kenia", + "pl": "Kenia", + "no": "Kenya", + "ja": "ケニア", + "it": "Kenya", + "zh": "肯尼亚", + "nl": "Kenia", + "de": "Kenia", + "fr": "Kenya", + "es": "Kenia", + "en": "Kenya", + "pt_BR": "Quênia", + "sr-Cyrl": "Кенија", + "sr-Latn": "Kenija", + "zh_TW": "肯亞", + "tr": "Kenya", + "ro": "Kenya", + "ar": "كينيا", + "fa": "كنيا", + "yue": "肯雅" + }, + flag: "🇰🇪", + code: "KE", + dialCode: "254", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Kiribati", + nameTranslations: { + "sk": "Kiribati", + "se": "Kiribati", + "pl": "Kiribati", + "no": "Kiribati", + "ja": "キリバス", + "it": "Kiribati", + "zh": "基里巴斯", + "nl": "Kiribati", + "de": "Kiribati", + "fr": "Kiribati", + "es": "Kiribati", + "en": "Kiribati", + "pt_BR": "Kiribati", + "sr-Cyrl": "Кирибати", + "sr-Latn": "Kiribati", + "zh_TW": "吉里巴斯", + "tr": "Kiribati", + "ro": "Kiribati", + "ar": "كيريباتي", + "fa": "کیریباتی", + "yue": "基里巴斯" + }, + flag: "🇰🇮", + code: "KI", + dialCode: "686", + minLength: 5, + maxLength: 5, + ), + Country( + name: "Korea, Democratic People's Republic of Korea", + nameTranslations: { + "sk": "Severná Kórea", + "se": "Davvi-Korea", + "pl": "Korea Północna", + "no": "Nord-Korea", + "ja": "北朝鮮", + "it": "Corea del Nord", + "zh": "朝鲜", + "nl": "Noord-Korea", + "de": "Nordkorea", + "fr": "Corée du Nord", + "es": "Corea del Norte", + "en": "North Korea", + "pt_BR": "Coreia do Norte", + "sr-Cyrl": "Северна Кореја", + "sr-Latn": "Severna Koreja", + "zh_TW": "北韓", + "tr": "Kuzey Kore", + "ro": "Coreea de Nord", + "ar": "كوريا الشمالية", + "fa": "کره شمالی", + "yue": "朝鮮(朝鮮民主主義人民共咊囯)" + }, + flag: "🇰🇵", + code: "KP", + dialCode: "850", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Korea, Republic of South Korea", + nameTranslations: { + "sk": "Južná Kórea", + "se": "Mátta-Korea", + "pl": "Korea Południowa", + "no": "Sør-Korea", + "ja": "韓国", + "it": "Corea del Sud", + "zh": "韩国", + "nl": "Zuid-Korea", + "de": "Südkorea", + "fr": "Corée du Sud", + "es": "Corea del Sur", + "en": "South Korea", + "pt_BR": "Coreia do Sul", + "sr-Cyrl": "Јужна Кореја", + "sr-Latn": "Južna Koreja", + "zh_TW": "南韓", + "tr": "Güney Kore", + "ro": "Coreea de Sud", + "ar": "كوريا الجنوبية", + "fa": "کره جنوبی", + "yue": "韓國(大韓民國)" + }, + flag: "🇰🇷", + code: "KR", + dialCode: "82", + minLength: 11, + maxLength: 11, + ), + Country( + name: "Kuwait", + nameTranslations: { + "sk": "Kuvajt", + "se": "Kuwait", + "pl": "Kuwejt", + "no": "Kuwait", + "ja": "クウェート", + "it": "Kuwait", + "zh": "科威特", + "nl": "Koeweit", + "de": "Kuwait", + "fr": "Koweït", + "es": "Kuwait", + "en": "Kuwait", + "pt_BR": "Kuwait", + "sr-Cyrl": "Кувајт", + "sr-Latn": "Kuvajt", + "zh_TW": "科威特", + "tr": "Kuveyt", + "ro": "Kuweit", + "ar": "الكويت", + "fa": "کویت", + "yue": "科威特" + }, + flag: "🇰🇼", + code: "KW", + dialCode: "965", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Kyrgyzstan", + nameTranslations: { + "sk": "Kirgizsko", + "se": "Kirgisistan", + "pl": "Kirgistan", + "no": "Kirgisistan", + "ja": "キルギス", + "it": "Kirghizistan", + "zh": "吉尔吉斯斯坦", + "nl": "Kirgizië", + "de": "Kirgisistan", + "fr": "Kirghizistan", + "es": "Kirguistán", + "en": "Kyrgyzstan", + "pt_BR": "Quirguistão", + "sr-Cyrl": "Киргистан", + "sr-Latn": "Kirgistan", + "zh_TW": "吉爾吉斯", + "tr": "Kırgızistan", + "ro": "Kîrgîzstan", + "ar": "قيرغيزستان", + "fa": "قرقیزستان", + "yue": "吉爾吉斯斯坦" + }, + flag: "🇰🇬", + code: "KG", + dialCode: "996", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Laos", + nameTranslations: { + "sk": "Laos", + "se": "Laos", + "pl": "Laos", + "no": "Laos", + "ja": "ラオス", + "it": "Laos", + "zh": "老挝", + "nl": "Laos", + "de": "Laos", + "fr": "Laos", + "es": "Laos", + "en": "Laos", + "pt_BR": "Laos", + "sr-Cyrl": "Лаос", + "sr-Latn": "Laos", + "zh_TW": "寮國", + "tr": "Laos", + "ro": "Laos", + "ar": "لاوس", + "fa": "لائوس", + "yue": "老撾人民民主共和國" + }, + flag: "🇱🇦", + code: "LA", + dialCode: "856", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Latvia", + nameTranslations: { + "sk": "Lotyšsko", + "se": "Látvia", + "pl": "Łotwa", + "no": "Latvia", + "ja": "ラトビア", + "it": "Lettonia", + "zh": "拉脱维亚", + "nl": "Letland", + "de": "Lettland", + "fr": "Lettonie", + "es": "Letonia", + "en": "Latvia", + "pt_BR": "Letônia", + "sr-Cyrl": "Летонија", + "sr-Latn": "Letonija", + "zh_TW": "拉托維亞", + "tr": "Letonya", + "ro": "Letonia", + "ar": "لاتفيا", + "fa": "لتونی", + "yue": "拉脫維亞" + }, + flag: "🇱🇻", + code: "LV", + dialCode: "371", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Lebanon", + nameTranslations: { + "sk": "Libanon", + "se": "Libanon", + "pl": "Liban", + "no": "Libanon", + "ja": "レバノン", + "it": "Libano", + "zh": "黎巴嫩", + "nl": "Libanon", + "de": "Libanon", + "fr": "Liban", + "es": "Líbano", + "en": "Lebanon", + "pt_BR": "Líbano", + "sr-Cyrl": "Либан", + "sr-Latn": "Liban", + "zh_TW": "黎巴嫩", + "tr": "Lübnan", + "ro": "Liban", + "ar": "لبنان", + "fa": "لبنان", + "yue": "黎巴嫩" + }, + flag: "🇱🇧", + code: "LB", + dialCode: "961", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Lesotho", + nameTranslations: { + "sk": "Lesotho", + "se": "Lesotho", + "pl": "Lesotho", + "no": "Lesotho", + "ja": "レソト", + "it": "Lesotho", + "zh": "莱索托", + "nl": "Lesotho", + "de": "Lesotho", + "fr": "Lesotho", + "es": "Lesoto", + "en": "Lesotho", + "pt_BR": "Lesoto", + "sr-Cyrl": "Лесото", + "sr-Latn": "Lesoto", + "zh_TW": "賴索托", + "tr": "Lesotho", + "ro": "Lesotho", + "ar": "ليسوتو", + "fa": "لسوتو", + "yue": "萊索托" + }, + flag: "🇱🇸", + code: "LS", + dialCode: "266", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Liberia", + nameTranslations: { + "sk": "Libéria", + "se": "Liberia", + "pl": "Liberia", + "no": "Liberia", + "ja": "リベリア", + "it": "Liberia", + "zh": "利比里亚", + "nl": "Liberia", + "de": "Liberia", + "fr": "Libéria", + "es": "Liberia", + "en": "Liberia", + "pt_BR": "Libéria", + "sr-Cyrl": "Либерија", + "sr-Latn": "Liberija", + "zh_TW": "賴比瑞亞", + "tr": "Liberya", + "ro": "Liberia", + "ar": "ليبيريا", + "fa": "لیبریا", + "yue": "利比里亞" + }, + flag: "🇱🇷", + code: "LR", + dialCode: "231", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Libyan Arab Jamahiriya", + nameTranslations: { + "sk": "Líbya", + "se": "Libya", + "pl": "Libia", + "no": "Libya", + "ja": "リビア", + "it": "Libia", + "zh": "利比亚", + "nl": "Libië", + "de": "Libyen", + "fr": "Libye", + "es": "Libia", + "en": "Libya", + "pt_BR": "Líbia", + "sr-Cyrl": "Либија", + "sr-Latn": "Libija", + "zh_TW": "利比亞", + "tr": "Libya", + "ro": "Libia", + "ar": "ليبيا", + "fa": "لیبی", + "yue": "利比亞" + }, + flag: "🇱🇾", + code: "LY", + dialCode: "218", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Liechtenstein", + nameTranslations: { + "sk": "Lichtenštajnsko", + "se": "Liechtenstein", + "pl": "Liechtenstein", + "no": "Liechtenstein", + "ja": "リヒテンシュタイン", + "it": "Liechtenstein", + "zh": "列支敦士登", + "nl": "Liechtenstein", + "de": "Liechtenstein", + "fr": "Liechtenstein", + "es": "Liechtenstein", + "en": "Liechtenstein", + "pt_BR": "Liechtenstein", + "sr-Cyrl": "Лихтенштајн", + "sr-Latn": "Lihtenštajn", + "zh_TW": "列支敦斯登", + "tr": "Lihtenştayn", + "ro": "Liechtenstein", + "ar": "ليختنشتاين", + "fa": "لیختن‌اشتاین", + "yue": "列支敦士登" + }, + flag: "🇱🇮", + code: "LI", + dialCode: "423", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Lithuania", + nameTranslations: { + "sk": "Litva", + "se": "Lietuva", + "pl": "Litwa", + "no": "Litauen", + "ja": "リトアニア", + "it": "Lituania", + "zh": "立陶宛", + "nl": "Litouwen", + "de": "Litauen", + "fr": "Lituanie", + "es": "Lituania", + "en": "Lithuania", + "pt_BR": "Lituânia", + "sr-Cyrl": "Литванија", + "sr-Latn": "Litvanija", + "zh_TW": "立陶宛", + "tr": "Litvanya", + "ro": "Lituania", + "ar": "ليتوانيا", + "fa": "لیتوانی", + "yue": "立陶宛" + }, + flag: "🇱🇹", + code: "LT", + dialCode: "370", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Luxembourg", + nameTranslations: { + "sk": "Luxembursko", + "se": "Luxembourg", + "pl": "Luksemburg", + "no": "Luxemburg", + "ja": "ルクセンブルク", + "it": "Lussemburgo", + "zh": "卢森堡", + "nl": "Luxemburg", + "de": "Luxemburg", + "fr": "Luxembourg", + "es": "Luxemburgo", + "en": "Luxembourg", + "pt_BR": "Luxemburgo", + "sr-Cyrl": "Луксенбург", + "sr-Latn": "Luksenburg", + "zh_TW": "盧森堡", + "tr": "Lüksemburg", + "ro": "Luxemburg", + "ar": "لوكسمبورغ", + "fa": "لوکزامبورگ", + "yue": "盧森堡" + }, + flag: "🇱🇺", + code: "LU", + dialCode: "352", + minLength: 11, + maxLength: 11, + ), + Country( + name: "Macao", + nameTranslations: { + "sk": "Macao – OAO Číny", + "se": "Makáo", + "pl": "SRA Makau (Chiny)", + "no": "Macao S.A.R. Kina", + "ja": "中華人民共和国マカオ特別行政区", + "it": "RAS di Macao", + "zh": "中国澳门特别行政区", + "nl": "Macau SAR van China", + "de": "Sonderverwaltungsregion Macau", + "fr": "R.A.S. chinoise de Macao", + "es": "RAE de Macao (China)", + "en": "Macao SAR China", + "pt_BR": "RAE de Macau China", + "sr-Cyrl": "Макао", + "sr-Latn": "Makao", + "zh_TW": "澳門", + "tr": "Makao", + "ro": "Macao", + "ar": "ماكاو", + "fa": "ماكائو", + "yue": "澳門" + }, + flag: "🇲🇴", + code: "MO", + dialCode: "853", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Macedonia", + nameTranslations: { + "sk": "Severné Macedónsko", + "se": "North Macedonia", + "pl": "Macedonia Północna", + "no": "Nord-Makedonia", + "ja": "北マケドニア", + "it": "Macedonia del Nord", + "zh": "北马其顿", + "nl": "Noord-Macedonië", + "de": "Nordmazedonien", + "fr": "Macédoine du Nord", + "es": "Macedonia del Norte", + "en": "North Macedonia", + "pt_BR": "Macedônia do Norte", + "sr-Cyrl": "Северна Македонија", + "sr-Latn": "Severna Makedonija", + "zh_TW": "北馬其頓", + "tr": "Kuzey Makedonya", + "ro": "Macedonia de Nord", + "ar": "مقدونيا", + "fa": "مقدونیه", + "yue": "馬其頓(前南斯拉夫共和國)" + }, + flag: "🇲🇰", + code: "MK", + dialCode: "389", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Madagascar", + nameTranslations: { + "sk": "Madagaskar", + "se": "Madagaskar", + "pl": "Madagaskar", + "no": "Madagaskar", + "ja": "マダガスカル", + "it": "Madagascar", + "zh": "马达加斯加", + "nl": "Madagaskar", + "de": "Madagaskar", + "fr": "Madagascar", + "es": "Madagascar", + "en": "Madagascar", + "pt_BR": "Madagáscar", + "sr-Cyrl": "Мадагаскар", + "sr-Latn": "Madagaskar", + "zh_TW": "馬達加斯加", + "tr": "Madagaskar", + "ro": "Madagascar", + "ar": "مدغشقر", + "fa": "ماداگاسکار", + "yue": "馬達加斯加" + }, + flag: "🇲🇬", + code: "MG", + dialCode: "261", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Malawi", + nameTranslations: { + "sk": "Malawi", + "se": "Malawi", + "pl": "Malawi", + "no": "Malawi", + "ja": "マラウイ", + "it": "Malawi", + "zh": "马拉维", + "nl": "Malawi", + "de": "Malawi", + "fr": "Malawi", + "es": "Malaui", + "en": "Malawi", + "pt_BR": "Malawi", + "sr-Cyrl": "Малави", + "sr-Latn": "Malavi", + "zh_TW": "馬拉威", + "tr": "Malavi", + "ro": "Malawi", + "ar": "مالاوي", + "fa": "مالاوی", + "yue": "馬拉維" + }, + flag: "🇲🇼", + code: "MW", + dialCode: "265", + minLength: 7, + maxLength: 9, + ), + Country( + name: "Malaysia", + nameTranslations: { + "sk": "Malajzia", + "se": "Malesia", + "pl": "Malezja", + "no": "Malaysia", + "ja": "マレーシア", + "it": "Malaysia", + "zh": "马来西亚", + "nl": "Maleisië", + "de": "Malaysia", + "fr": "Malaisie", + "es": "Malasia", + "en": "Malaysia", + "pt_BR": "Malásia", + "sr-Cyrl": "Малезија", + "sr-Latn": "Malezija", + "zh_TW": "馬來西亞", + "tr": "Malezya", + "ro": "Malaezia", + "ar": "ماليزيا", + "fa": "مالزی", + "yue": "馬來西亞" + }, + flag: "🇲🇾", + code: "MY", + dialCode: "60", + minLength: 9, + maxLength: 10, + ), + Country( + name: "Maldives", + nameTranslations: { + "sk": "Maldivy", + "se": "Malediivvat", + "pl": "Malediwy", + "no": "Maldivene", + "ja": "モルディブ", + "it": "Maldive", + "zh": "马尔代夫", + "nl": "Maldiven", + "de": "Malediven", + "fr": "Maldives", + "es": "Maldivas", + "en": "Maldives", + "pt_BR": "Maldivas", + "sr-Cyrl": "Малдиви", + "sr-Latn": "Maldivi", + "zh_TW": "馬爾地夫", + "tr": "Maldivler", + "ro": "Maldive", + "ar": "جزر المالديف", + "fa": "مالدیو", + "yue": "馬爾代夫" + }, + flag: "🇲🇻", + code: "MV", + dialCode: "960", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Mali", + nameTranslations: { + "sk": "Mali", + "se": "Mali", + "pl": "Mali", + "no": "Mali", + "ja": "マリ", + "it": "Mali", + "zh": "马里", + "nl": "Mali", + "de": "Mali", + "fr": "Mali", + "es": "Mali", + "en": "Mali", + "pt_BR": "Mali", + "sr-Cyrl": "Мали", + "sr-Latn": "Mali", + "zh_TW": "馬里", + "tr": "Mali", + "ro": "Mali", + "ar": "مالي", + "fa": "مالی", + "yue": "馬里" + }, + flag: "🇲🇱", + code: "ML", + dialCode: "223", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Malta", + nameTranslations: { + "sk": "Malta", + "se": "Málta", + "pl": "Malta", + "no": "Malta", + "ja": "マルタ", + "it": "Malta", + "zh": "马耳他", + "nl": "Malta", + "de": "Malta", + "fr": "Malte", + "es": "Malta", + "en": "Malta", + "pt_BR": "Malta", + "sr-Cyrl": "Малта", + "sr-Latn": "Malta", + "zh_TW": "馬爾他", + "tr": "Malta", + "ro": "Malta", + "ar": "مالطا", + "fa": "مالت", + "yue": "馬耳他" + }, + flag: "🇲🇹", + code: "MT", + dialCode: "356", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Marshall Islands", + nameTranslations: { + "sk": "Marshallove ostrovy", + "se": "Marshallsullot", + "pl": "Wyspy Marshalla", + "no": "Marshalløyene", + "ja": "マーシャル諸島", + "it": "Isole Marshall", + "zh": "马绍尔群岛", + "nl": "Marshalleilanden", + "de": "Marshallinseln", + "fr": "Îles Marshall", + "es": "Islas Marshall", + "en": "Marshall Islands", + "pt_BR": "Ilhas Marshall", + "sr-Cyrl": "Маршалска Острва", + "sr-Latn": "Maršalska Ostrva", + "zh_TW": "馬紹爾群島", + "tr": "Marshall Adaları", + "ro": "Insulele Marshall", + "ar": "جزر مارشال", + "fa": "جزایر مارشال", + "yue": "馬紹爾群島" + }, + flag: "🇲🇭", + code: "MH", + dialCode: "692", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Martinique", + nameTranslations: { + "sk": "Martinik", + "se": "Martinique", + "pl": "Martynika", + "no": "Martinique", + "ja": "マルティニーク", + "it": "Martinica", + "zh": "马提尼克", + "nl": "Martinique", + "de": "Martinique", + "fr": "Martinique", + "es": "Martinica", + "en": "Martinique", + "pt_BR": "Martinica", + "sr-Cyrl": "Мартиник", + "sr-Latn": "Martinik", + "zh_TW": "馬丁尼克", + "tr": "Martinique", + "ro": "Martinica", + "ar": "مارتينيك", + "fa": "مارتینیک", + "yue": "马提尼克" + }, + flag: "🇲🇶", + code: "MQ", + dialCode: "596", + minLength: 15, + maxLength: 15, + ), + Country( + name: "Mauritania", + nameTranslations: { + "sk": "Mauritánia", + "se": "Mauretánia", + "pl": "Mauretania", + "no": "Mauritania", + "ja": "モーリタニア", + "it": "Mauritania", + "zh": "毛里塔尼亚", + "nl": "Mauritanië", + "de": "Mauretanien", + "fr": "Mauritanie", + "es": "Mauritania", + "en": "Mauritania", + "pt_BR": "Mauritânia", + "sr-Cyrl": "Мауританија", + "sr-Latn": "Mauritanija", + "zh_TW": "茅利塔尼亞", + "tr": "Moritanya", + "ro": "Mauritania", + "ar": "موريتانيا", + "fa": "موریتانی", + "yue": "毛里塔尼亞" + }, + flag: "🇲🇷", + code: "MR", + dialCode: "222", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Mauritius", + nameTranslations: { + "sk": "Maurícius", + "se": "Mauritius", + "pl": "Mauritius", + "no": "Mauritius", + "ja": "モーリシャス", + "it": "Mauritius", + "zh": "毛里求斯", + "nl": "Mauritius", + "de": "Mauritius", + "fr": "Maurice", + "es": "Mauricio", + "en": "Mauritius", + "pt_BR": "Maurício", + "sr-Cyrl": "Маурицијус", + "sr-Latn": "Mauricijus", + "zh_TW": "模里西斯", + "tr": "Mauritius", + "ro": "Mauritius", + "ar": "موريشيوس", + "fa": "موریس", + "yue": "毛里求斯" + }, + flag: "🇲🇺", + code: "MU", + dialCode: "230", + minLength: 7, + maxLength: 8, + ), + Country( + name: "Mayotte", + nameTranslations: { + "sk": "Mayotte", + "se": "Mayotte", + "pl": "Majotta", + "no": "Mayotte", + "ja": "マヨット", + "it": "Mayotte", + "zh": "马约特", + "nl": "Mayotte", + "de": "Mayotte", + "fr": "Mayotte", + "es": "Mayotte", + "en": "Mayotte", + "pt_BR": "Mayotte", + "sr-Cyrl": "Мајота", + "sr-Latn": "Majota", + "zh_TW": "馬約特", + "tr": "Mayotte", + "ro": "Mayotte", + "ar": "مايوت", + "fa": "مایوت", + "yue": "馬約特" + }, + flag: "🇾🇹", + code: "YT", + dialCode: "262", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Mexico", + nameTranslations: { + "sk": "Mexiko", + "se": "Meksiko", + "pl": "Meksyk", + "no": "Mexico", + "ja": "メキシコ", + "it": "Messico", + "zh": "墨西哥", + "nl": "Mexico", + "de": "Mexiko", + "fr": "Mexique", + "es": "México", + "en": "Mexico", + "pt_BR": "México", + "sr-Cyrl": "Мексико", + "sr-Latn": "Meksiko", + "zh_TW": "墨西哥", + "tr": "Meksika", + "ro": "Mexic", + "ar": "المكسيك", + "fa": "مکزیک", + "yue": "墨西哥" + }, + flag: "🇲🇽", + code: "MX", + dialCode: "52", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Micronesia, Federated States of Micronesia", + nameTranslations: { + "sk": "Mikronézia", + "se": "Mikronesia", + "pl": "Mikronezja", + "no": "Mikronesiaføderasjonen", + "ja": "ミクロネシア連邦", + "it": "Micronesia", + "zh": "密克罗尼西亚", + "nl": "Micronesia", + "de": "Mikronesien", + "fr": "États fédérés de Micronésie", + "es": "Micronesia", + "en": "Micronesia", + "pt_BR": "Micronésia", + "sr-Cyrl": "Микронезија", + "sr-Latn": "Mikronezija", + "zh_TW": "密克羅尼西亞", + "tr": "Mikronezya", + "ro": "Micronezia", + "ar": "ولايات ميكرونيسيا المتحدة", + "fa": "ایالات فدرال میکرونزی", + "yue": "密克罗尼西亚(聯邦)" + }, + flag: "🇫🇲", + code: "FM", + dialCode: "691", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Moldova", + nameTranslations: { + "sk": "Moldavsko", + "se": "Moldávia", + "pl": "Mołdawia", + "no": "Moldova", + "ja": "モルドバ", + "it": "Moldavia", + "zh": "摩尔多瓦", + "nl": "Moldavië", + "de": "Republik Moldau", + "fr": "Moldavie", + "es": "Moldavia", + "en": "Moldova", + "pt_BR": "Moldova", + "sr-Cyrl": "Молдавија", + "sr-Latn": "Moldavija", + "zh_TW": "摩爾多瓦", + "tr": "Moldova", + "ro": "Moldova", + "ar": "مولدوفا", + "fa": "مولداوی", + "yue": "摩爾多瓦(共和國)" + }, + flag: "🇲🇩", + code: "MD", + dialCode: "373", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Monaco", + nameTranslations: { + "sk": "Monako", + "se": "Monaco", + "pl": "Monako", + "no": "Monaco", + "ja": "モナコ", + "it": "Monaco", + "zh": "摩纳哥", + "nl": "Monaco", + "de": "Monaco", + "fr": "Monaco", + "es": "Mónaco", + "en": "Monaco", + "pt_BR": "Mônaco", + "sr-Cyrl": "Монако", + "sr-Latn": "Monako", + "zh_TW": "摩納哥", + "tr": "Monako", + "ro": "Monaco", + "ar": "موناكو", + "fa": "موناكو", + "yue": "摩納哥" + }, + flag: "🇲🇨", + code: "MC", + dialCode: "377", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Mongolia", + nameTranslations: { + "sk": "Mongolsko", + "se": "Mongolia", + "pl": "Mongolia", + "no": "Mongolia", + "ja": "モンゴル", + "it": "Mongolia", + "zh": "蒙古", + "nl": "Mongolië", + "de": "Mongolei", + "fr": "Mongolie", + "es": "Mongolia", + "en": "Mongolia", + "pt_BR": "Mongólia", + "sr-Cyrl": "Монголија", + "sr-Latn": "Mongolija", + "zh_TW": "蒙古", + "tr": "Moğolistan", + "ro": "Mongolia", + "ar": "منغوليا", + "fa": "مغولستان", + "yue": "蒙古" + }, + flag: "🇲🇳", + code: "MN", + dialCode: "976", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Montenegro", + nameTranslations: { + "sk": "Čierna Hora", + "se": "Montenegro", + "pl": "Czarnogóra", + "no": "Montenegro", + "ja": "モンテネグロ", + "it": "Montenegro", + "zh": "黑山", + "nl": "Montenegro", + "de": "Montenegro", + "fr": "Monténégro", + "es": "Montenegro", + "en": "Montenegro", + "pt_BR": "Montenegro", + "sr-Cyrl": "Црна Гора", + "sr-Latn": "Crna Gora", + "zh_TW": "蒙特內哥羅", + "tr": "Karadağ", + "ro": "Muntenegru", + "ar": "الجبل الأسود", + "fa": "مونته‌نگرو", + "yue": "黑山" + }, + flag: "🇲🇪", + code: "ME", + dialCode: "382", + minLength: 12, + maxLength: 12, + ), + Country( + name: "Montserrat", + nameTranslations: { + "sk": "Montserrat", + "se": "Montserrat", + "pl": "Montserrat", + "no": "Montserrat", + "ja": "モントセラト", + "it": "Montserrat", + "zh": "蒙特塞拉特", + "nl": "Montserrat", + "de": "Montserrat", + "fr": "Montserrat", + "es": "Montserrat", + "en": "Montserrat", + "pt_BR": "Montserrat", + "sr-Cyrl": "Монтсерат", + "sr-Latn": "Montserat", + "zh_TW": "蒙哲臘", + "tr": "Montserrat", + "ro": "Montserrat", + "ar": "مونتسرات", + "fa": "مونتسرات", + "yue": "蒙特塞拉特" + }, + flag: "🇲🇸", + code: "MS", + dialCode: "1664", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Morocco", + nameTranslations: { + "sk": "Maroko", + "se": "Marokko", + "pl": "Maroko", + "no": "Marokko", + "ja": "モロッコ", + "it": "Marocco", + "zh": "摩洛哥", + "nl": "Marokko", + "de": "Marokko", + "fr": "Maroc", + "es": "Marruecos", + "en": "Morocco", + "pt_BR": "Marrocos", + "sr-Cyrl": "Мароко", + "sr-Latn": "Maroko", + "zh_TW": "摩洛哥", + "tr": "Fas", + "ro": "Maroc", + "ar": "المغرب", + "fa": "مراکش", + "yue": "摩洛哥" + }, + flag: "🇲🇦", + code: "MA", + dialCode: "212", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Mozambique", + nameTranslations: { + "sk": "Mozambik", + "se": "Mosambik", + "pl": "Mozambik", + "no": "Mosambik", + "ja": "モザンビーク", + "it": "Mozambico", + "zh": "莫桑比克", + "nl": "Mozambique", + "de": "Mosambik", + "fr": "Mozambique", + "es": "Mozambique", + "en": "Mozambique", + "pt_BR": "Moçambique", + "sr-Cyrl": "Мозамбик", + "sr-Latn": "Mozambik", + "zh_TW": "莫三比克", + "tr": "Mozambik", + "ro": "Mozambic", + "ar": "موزمبيق", + "fa": "موزامبیک", + "yue": "莫桑比克" + }, + flag: "🇲🇿", + code: "MZ", + dialCode: "258", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Myanmar", + nameTranslations: { + "sk": "Mjanmarsko", + "se": "Burma", + "pl": "Mjanma (Birma)", + "no": "Myanmar (Burma)", + "ja": "ミャンマー (ビルマ)", + "it": "Myanmar (Birmania)", + "zh": "缅甸", + "nl": "Myanmar (Birma)", + "de": "Myanmar", + "fr": "Myanmar (Birmanie)", + "es": "Myanmar (Birmania)", + "en": "Myanmar (Burma)", + "pt_BR": "Mianmar (Birmânia)", + "sr-Cyrl": "Мјанмар (Бурма)", + "sr-Latn": "Mjanmar (Burma)", + "zh_TW": "緬甸", + "tr": "Myanmar", + "ro": "Myanmar", + "ar": "ميانمار", + "fa": "میانمار", + "yue": "緬甸" + }, + flag: "🇲🇲", + code: "MM", + dialCode: "95", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Namibia", + nameTranslations: { + "sk": "Namíbia", + "se": "Namibia", + "pl": "Namibia", + "no": "Namibia", + "ja": "ナミビア", + "it": "Namibia", + "zh": "纳米比亚", + "nl": "Namibië", + "de": "Namibia", + "fr": "Namibie", + "es": "Namibia", + "en": "Namibia", + "pt_BR": "Namibia", + "sr-Cyrl": "Намибија", + "sr-Latn": "Namibija", + "zh_TW": "納米比亞", + "tr": "Namibya", + "ro": "Namibia", + "ar": "ناميبيا", + "fa": "نامیبیا", + "yue": "納米比亞" + }, + flag: "🇳🇦", + code: "NA", + dialCode: "264", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Nauru", + nameTranslations: { + "sk": "Nauru", + "se": "Nauru", + "pl": "Nauru", + "no": "Nauru", + "ja": "ナウル", + "it": "Nauru", + "zh": "瑙鲁", + "nl": "Nauru", + "de": "Nauru", + "fr": "Nauru", + "es": "Nauru", + "en": "Nauru", + "pt_BR": "Nauru", + "sr-Cyrl": "Науру", + "sr-Latn": "Nauru", + "zh_TW": "諾魯", + "tr": "Nauru", + "ro": "Nauru", + "ar": "ناورو", + "fa": "نائورو", + "yue": "瑙魯" + }, + flag: "🇳🇷", + code: "NR", + dialCode: "674", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Nepal", + nameTranslations: { + "sk": "Nepál", + "se": "Nepal", + "pl": "Nepal", + "no": "Nepal", + "ja": "ネパール", + "it": "Nepal", + "zh": "尼泊尔", + "nl": "Nepal", + "de": "Nepal", + "fr": "Népal", + "es": "Nepal", + "en": "Nepal", + "pt_BR": "Nepal", + "sr-Cyrl": "Непал", + "sr-Latn": "Nepal", + "zh_TW": "尼泊爾", + "tr": "Nepal", + "ro": "Nepal", + "ar": "نيبال", + "fa": "نپال", + "yue": "尼泊爾" + }, + flag: "🇳🇵", + code: "NP", + dialCode: "977", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Netherlands", + nameTranslations: { + "sk": "Holandsko", + "se": "Vuolleeatnamat", + "pl": "Holandia", + "no": "Nederland", + "ja": "オランダ", + "it": "Paesi Bassi", + "zh": "荷兰", + "nl": "Nederland", + "de": "Niederlande", + "fr": "Pays-Bas", + "es": "Países Bajos", + "en": "Netherlands", + "pt_BR": "Países Baixos", + "sr-Cyrl": "Холандија", + "sr-Latn": "Holandija", + "zh_TW": "荷蘭", + "tr": "Hollanda", + "ro": "Olanda", + "ar": "هولندا", + "fa": "هلند", + "yue": "荷蘭" + }, + flag: "🇳🇱", + code: "NL", + dialCode: "31", + minLength: 9, + maxLength: 9, + ), + Country( + name: "New Caledonia", + nameTranslations: { + "sk": "Nová Kaledónia", + "se": "Ođđa-Kaledonia", + "pl": "Nowa Kaledonia", + "no": "Ny-Caledonia", + "ja": "ニューカレドニア", + "it": "Nuova Caledonia", + "zh": "新喀里多尼亚", + "nl": "Nieuw-Caledonië", + "de": "Neukaledonien", + "fr": "Nouvelle-Calédonie", + "es": "Nueva Caledonia", + "en": "New Caledonia", + "pt_BR": "Nova Caledônia", + "sr-Cyrl": "Нова Каледонија", + "sr-Latn": "Nova Kaledonija", + "zh_TW": "新喀里多尼亞", + "tr": "Yeni Kaledonya", + "ro": "Noua Caledonie", + "ar": "كاليدونيا الجديدة", + "fa": "کالدونیای جدید", + "yue": "新喀里多尼亚" + }, + flag: "🇳🇨", + code: "NC", + dialCode: "687", + minLength: 6, + maxLength: 6, + ), + Country( + name: "New Zealand", + nameTranslations: { + "sk": "Nový Zéland", + "se": "Ođđa-Selánda", + "pl": "Nowa Zelandia", + "no": "New Zealand", + "ja": "ニュージーランド", + "it": "Nuova Zelanda", + "zh": "新西兰", + "nl": "Nieuw-Zeeland", + "de": "Neuseeland", + "fr": "Nouvelle-Zélande", + "es": "Nueva Zelanda", + "en": "New Zealand", + "pt_BR": "Nova Zelândia", + "sr-Cyrl": "Нови Зеланд", + "sr-Latn": "Novi Zeland", + "zh_TW": "紐西蘭", + "tr": "Yeni Zelanda", + "ro": "Noua Zeelandă", + "ar": "نيوزيلندا", + "fa": "نیوزلند", + "yue": "紐西蘭" + }, + flag: "🇳🇿", + code: "NZ", + dialCode: "64", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Nicaragua", + nameTranslations: { + "sk": "Nikaragua", + "se": "Nicaragua", + "pl": "Nikaragua", + "no": "Nicaragua", + "ja": "ニカラグア", + "it": "Nicaragua", + "zh": "尼加拉瓜", + "nl": "Nicaragua", + "de": "Nicaragua", + "fr": "Nicaragua", + "es": "Nicaragua", + "en": "Nicaragua", + "pt_BR": "Nicarágua", + "sr-Cyrl": "Никарагва", + "sr-Latn": "Nikaragva", + "zh_TW": "尼加拉瓜", + "tr": "Nikaragua", + "ro": "Nicaragua", + "ar": "نيكاراغوا", + "fa": "نیکاراگوئه", + "yue": "尼加拉瓜" + }, + flag: "🇳🇮", + code: "NI", + dialCode: "505", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Niger", + nameTranslations: { + "sk": "Niger", + "se": "Niger", + "pl": "Niger", + "no": "Niger", + "ja": "ニジェール", + "it": "Niger", + "zh": "尼日尔", + "nl": "Niger", + "de": "Niger", + "fr": "Niger", + "es": "Níger", + "en": "Niger", + "pt_BR": "Níger", + "sr-Cyrl": "Нигер", + "sr-Latn": "Niger", + "zh_TW": "尼日爾", + "tr": "Nijer", + "ro": "Niger", + "ar": "النيجر", + "fa": "نیجر", + "yue": "尼日爾" + }, + flag: "🇳🇪", + code: "NE", + dialCode: "227", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Nigeria", + nameTranslations: { + "sk": "Nigéria", + "se": "Nigeria", + "pl": "Nigeria", + "no": "Nigeria", + "ja": "ナイジェリア", + "it": "Nigeria", + "zh": "尼日利亚", + "nl": "Nigeria", + "de": "Nigeria", + "fr": "Nigéria", + "es": "Nigeria", + "en": "Nigeria", + "pt_BR": "Nigéria", + "sr-Cyrl": "Нигерија", + "sr-Latn": "Nigerija", + "zh_TW": "奈及利亞", + "tr": "Nijerya", + "ro": "Nigeria", + "ar": "نيجيريا", + "fa": "نیجریه", + "yue": "尼日利亞" + }, + flag: "🇳🇬", + code: "NG", + dialCode: "234", + minLength: 10, + maxLength: 11, + ), + Country( + name: "Niue", + nameTranslations: { + "sk": "Niue", + "se": "Niue", + "pl": "Niue", + "no": "Niue", + "ja": "ニウエ", + "it": "Niue", + "zh": "纽埃", + "nl": "Niue", + "de": "Niue", + "fr": "Niue", + "es": "Niue", + "en": "Niue", + "pt_BR": "Niue", + "sr-Cyrl": "Нијуе", + "sr-Latn": "Nijue", + "zh_TW": "紐埃", + "tr": "Niue", + "ro": "Niue", + "ar": "نييوي", + "fa": "نیووی", + "yue": "紐埃" + }, + flag: "🇳🇺", + code: "NU", + dialCode: "683", + minLength: 4, + maxLength: 4, + ), + Country( + name: "Norfolk Island", + nameTranslations: { + "sk": "Norfolk", + "se": "Norfolksullot", + "pl": "Norfolk", + "no": "Norfolkøya", + "ja": "ノーフォーク島", + "it": "Isola Norfolk", + "zh": "诺福克岛", + "nl": "Norfolk", + "de": "Norfolkinsel", + "fr": "Île Norfolk", + "es": "Isla Norfolk", + "en": "Norfolk Island", + "pt_BR": "Ilha Norfolk", + "sr-Cyrl": "Острво Норфок", + "sr-Latn": "Ostrvo Norfok", + "zh_TW": "諾福克島", + "tr": "Norfolk Adası", + "ro": "Insulele Norfolk", + "ar": "جزيرة نورفولك", + "fa": "جزیره نورفک", + "yue": "诺福克岛" + }, + flag: "🇳🇫", + code: "NF", + dialCode: "672", + minLength: 15, + maxLength: 15, + ), + Country( + name: "Northern Mariana Islands", + nameTranslations: { + "sk": "Severné Mariány", + "se": "Davvi-Mariánat", + "pl": "Mariany Północne", + "no": "Nord-Marianene", + "ja": "北マリアナ諸島", + "it": "Isole Marianne settentrionali", + "zh": "北马里亚纳群岛", + "nl": "Noordelijke Marianen", + "de": "Nördliche Marianen", + "fr": "Îles Mariannes du Nord", + "es": "Islas Marianas del Norte", + "en": "Northern Mariana Islands", + "pt_BR": "Ilhas Marianas do Norte", + "sr-Cyrl": "Северна Маријанска Острва", + "sr-Latn": "Severna Marijanska Ostrva", + "zh_TW": "北馬利安納群島", + "tr": "Kuzey Mariana Adaları", + "ro": "Insulelor Mariane de Nord", + "ar": "جزر ماريانا الشمالية", + "fa": "جزایر ماریانای شمالی", + "yue": "北馬里亞納群島" + }, + flag: "🇲🇵", + code: "MP", + dialCode: "1670", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Norway", + nameTranslations: { + "sk": "Nórsko", + "se": "Norga", + "pl": "Norwegia", + "no": "Norge", + "ja": "ノルウェー", + "it": "Norvegia", + "zh": "挪威", + "nl": "Noorwegen", + "de": "Norwegen", + "fr": "Norvège", + "es": "Noruega", + "en": "Norway", + "pt_BR": "Noruega", + "sr-Cyrl": "Норвешка", + "sr-Latn": "Norveška", + "zh_TW": "挪威", + "tr": "Norveç", + "ro": "Norvegia", + "ar": "النرويج", + "fa": "نروژ", + "yue": "挪威" + }, + flag: "🇳🇴", + code: "NO", + dialCode: "47", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Oman", + nameTranslations: { + "sk": "Omán", + "se": "Oman", + "pl": "Oman", + "no": "Oman", + "ja": "オマーン", + "it": "Oman", + "zh": "阿曼", + "nl": "Oman", + "de": "Oman", + "fr": "Oman", + "es": "Omán", + "en": "Oman", + "pt_BR": "Omã", + "sr-Cyrl": "Оман", + "sr-Latn": "Oman", + "zh_TW": "阿曼", + "tr": "Umman", + "ro": "Oman", + "ar": "عمان", + "fa": "عمان", + "yue": "阿曼" + }, + flag: "🇴🇲", + code: "OM", + dialCode: "968", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Pakistan", + nameTranslations: { + "sk": "Pakistan", + "se": "Pakistan", + "pl": "Pakistan", + "no": "Pakistan", + "ja": "パキスタン", + "it": "Pakistan", + "zh": "巴基斯坦", + "nl": "Pakistan", + "de": "Pakistan", + "fr": "Pakistan", + "es": "Pakistán", + "en": "Pakistan", + "pt_BR": "Paquistão", + "sr-Cyrl": "Пакистан", + "sr-Latn": "Pakistan", + "zh_TW": "巴基斯坦", + "tr": "Pakistan", + "ro": "Pakistan", + "ar": "باكستان", + "fa": "پاکستان", + "yue": "巴基斯坦" + }, + flag: "🇵🇰", + code: "PK", + dialCode: "92", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Palau", + nameTranslations: { + "sk": "Palau", + "se": "Palau", + "pl": "Palau", + "no": "Palau", + "ja": "パラオ", + "it": "Palau", + "zh": "帕劳", + "nl": "Palau", + "de": "Palau", + "fr": "Palaos", + "es": "Palaos", + "en": "Palau", + "pt_BR": "Palau", + "sr-Cyrl": "Палау", + "sr-Latn": "Palau", + "zh_TW": "帛琉", + "tr": "Palau", + "ro": "Palau", + "ar": "بالاو", + "fa": "پالائو", + "yue": "帕劳" + }, + flag: "🇵🇼", + code: "PW", + dialCode: "680", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Palestinian Territory, Occupied", + nameTranslations: { + "sk": "Palestínske územia", + "se": "Palestina", + "pl": "Terytoria Palestyńskie", + "no": "Det palestinske området", + "ja": "パレスチナ自治区", + "it": "Territori palestinesi", + "zh": "巴勒斯坦领土", + "nl": "Palestijnse gebieden", + "de": "Palästinensische Autonomiegebiete", + "fr": "Territoires palestiniens", + "es": "Territorios Palestinos", + "en": "Palestinian Territories", + "pt_BR": "Territórios Palestinos", + "sr-Cyrl": "Палестина", + "sr-Latn": "Palestina", + "zh_TW": "巴勒斯坦", + "tr": "Filistin", + "ro": "Palestina", + "ar": "فلسطين", + "fa": "فلسطین", + "yue": "巴勒斯坦,国" + }, + flag: "🇵🇸", + code: "PS", + dialCode: "970", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Panama", + nameTranslations: { + "sk": "Panama", + "se": "Panama", + "pl": "Panama", + "no": "Panama", + "ja": "パナマ", + "it": "Panamá", + "zh": "巴拿马", + "nl": "Panama", + "de": "Panama", + "fr": "Panama", + "es": "Panamá", + "en": "Panama", + "pt_BR": "Panamá", + "sr-Cyrl": "Панама", + "sr-Latn": "Panama", + "zh_TW": "巴拿馬", + "tr": "Panama", + "ro": "Panama", + "ar": "بنما", + "fa": "پاناما", + "yue": "巴拿馬" + }, + flag: "🇵🇦", + code: "PA", + dialCode: "507", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Papua New Guinea", + nameTranslations: { + "sk": "Papua-Nová Guinea", + "se": "Papua-Ođđa-Guinea", + "pl": "Papua-Nowa Gwinea", + "no": "Papua Ny-Guinea", + "ja": "パプアニューギニア", + "it": "Papua Nuova Guinea", + "zh": "巴布亚新几内亚", + "nl": "Papoea-Nieuw-Guinea", + "de": "Papua-Neuguinea", + "fr": "Papouasie-Nouvelle-Guinée", + "es": "Papúa Nueva Guinea", + "en": "Papua New Guinea", + "pt_BR": "Papua Nova Guiné", + "sr-Cyrl": "Папуа Нова Гвинеја", + "sr-Latn": "Papua Nova Gvineja", + "zh_TW": "巴布亞新幾內亞", + "tr": "Papua Yeni Gine", + "ro": "Papua Noua Guinee", + "ar": "بابوا غينيا الجديدة", + "fa": "پاپوآ گینه نو", + "yue": "巴布亚新几内亚" + }, + flag: "🇵🇬", + code: "PG", + dialCode: "675", + minLength: 11, + maxLength: 11, + ), + Country( + name: "Paraguay", + nameTranslations: { + "sk": "Paraguaj", + "se": "Paraguay", + "pl": "Paragwaj", + "no": "Paraguay", + "ja": "パラグアイ", + "it": "Paraguay", + "zh": "巴拉圭", + "nl": "Paraguay", + "de": "Paraguay", + "fr": "Paraguay", + "es": "Paraguay", + "en": "Paraguay", + "pt_BR": "Paraguai", + "sr-Cyrl": "Парагвај", + "sr-Latn": "Paragvaj", + "zh_TW": "巴拉圭", + "tr": "Paraguay", + "ro": "Paraguay", + "ar": "باراغواي", + "fa": "پاراگوئه", + "yue": "巴拉圭" + }, + flag: "🇵🇾", + code: "PY", + dialCode: "595", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Peru", + nameTranslations: { + "sk": "Peru", + "se": "Peru", + "pl": "Peru", + "no": "Peru", + "ja": "ペルー", + "it": "Perù", + "zh": "秘鲁", + "nl": "Peru", + "de": "Peru", + "fr": "Pérou", + "es": "Perú", + "en": "Peru", + "pt_BR": "Peru", + "sr-Cyrl": "Перу", + "sr-Latn": "Peru", + "zh_TW": "秘鲁", + "tr": "Peru", + "ro": "Peru", + "ar": "بيرو", + "fa": "پرو", + "yue": "秘魯" + }, + flag: "🇵🇪", + code: "PE", + dialCode: "51", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Philippines", + nameTranslations: { + "sk": "Filipíny", + "se": "Filippiinnat", + "pl": "Filipiny", + "no": "Filippinene", + "ja": "フィリピン", + "it": "Filippine", + "zh": "菲律宾", + "nl": "Filipijnen", + "de": "Philippinen", + "fr": "Philippines", + "es": "Filipinas", + "en": "Philippines", + "pt_BR": "Filipinas", + "sr-Cyrl": "Филипини", + "sr-Latn": "Filipini", + "zh_TW": "菲律賓", + "tr": "Filipinler", + "ro": "Filipine", + "ar": "الفلبين", + "fa": "فیلیپین", + "yue": "菲律賓" + }, + flag: "🇵🇭", + code: "PH", + dialCode: "63", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Pitcairn", + nameTranslations: { + "sk": "Pitcairnove ostrovy", + "se": "Pitcairn", + "pl": "Pitcairn", + "no": "Pitcairnøyene", + "ja": "ピトケアン諸島", + "it": "Isole Pitcairn", + "zh": "皮特凯恩群岛", + "nl": "Pitcairneilanden", + "de": "Pitcairninseln", + "fr": "Îles Pitcairn", + "es": "Islas Pitcairn", + "en": "Pitcairn Islands", + "pt_BR": "Ilhas Pitcairn", + "sr-Cyrl": "Острва Питкерн", + "sr-Latn": "Ostrva Pitkern", + "zh_TW": "皮特肯群島", + "tr": "Pitcairn Adaları", + "ro": "Insulele Pitcairn", + "ar": "جزر بيتكيرن", + "fa": "جزایر پیت‌کرن", + "yue": "皮特凱恩" + }, + flag: "🇵🇳", + code: "PN", + dialCode: "64", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Poland", + nameTranslations: { + "sk": "Poľsko", + "se": "Polen", + "pl": "Polska", + "no": "Polen", + "ja": "ポーランド", + "it": "Polonia", + "zh": "波兰", + "nl": "Polen", + "de": "Polen", + "fr": "Pologne", + "es": "Polonia", + "en": "Poland", + "pt_BR": "Polônia", + "sr-Cyrl": "Пољска", + "sr-Latn": "Poljska", + "zh_TW": "波蘭", + "tr": "Polonya", + "ro": "Polonia", + "ar": "بولندا", + "fa": "لهستان", + "yue": "波蘭" + }, + flag: "🇵🇱", + code: "PL", + dialCode: "48", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Portugal", + nameTranslations: { + "sk": "Portugalsko", + "se": "Portugála", + "pl": "Portugalia", + "no": "Portugal", + "ja": "ポルトガル", + "it": "Portogallo", + "zh": "葡萄牙", + "nl": "Portugal", + "de": "Portugal", + "fr": "Portugal", + "es": "Portugal", + "en": "Portugal", + "pt_BR": "Portugal", + "sr-Cyrl": "Португалија", + "sr-Latn": "Portugalija", + "zh_TW": "葡萄牙", + "tr": "Portekiz", + "ro": "Portugalia", + "ar": "البرتغال", + "fa": "پرتغال", + "yue": "葡萄牙" + }, + flag: "🇵🇹", + code: "PT", + dialCode: "351", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Puerto Rico", + nameTranslations: { + "sk": "Portoriko", + "se": "Puerto Rico", + "pl": "Portoryko", + "no": "Puerto Rico", + "ja": "プエルトリコ", + "it": "Portorico", + "zh": "波多黎各", + "nl": "Puerto Rico", + "de": "Puerto Rico", + "fr": "Porto Rico", + "es": "Puerto Rico", + "en": "Puerto Rico", + "pt_BR": "Porto Rico", + "sr-Cyrl": "Порто Рико", + "sr-Latn": "Porto Riko", + "zh_TW": "波多黎各", + "tr": "Porto Riko", + "ro": "Puerto Rico", + "ar": "بورتوريكو", + "fa": "پورتوریکو", + "yue": "波多黎各" + }, + flag: "🇵🇷", + code: "PR", + dialCode: "1939", + minLength: 15, + maxLength: 15, + ), + Country( + name: "Qatar", + nameTranslations: { + "sk": "Katar", + "se": "Qatar", + "pl": "Katar", + "no": "Qatar", + "ja": "カタール", + "it": "Qatar", + "zh": "卡塔尔", + "nl": "Qatar", + "de": "Katar", + "fr": "Qatar", + "es": "Catar", + "en": "Qatar", + "pt_BR": "Catar", + "sr-Cyrl": "Катар", + "sr-Latn": "Katar", + "zh_TW": "卡達", + "tr": "Katar", + "ro": "Qatar", + "ar": "قطر", + "fa": "قطر", + "yue": "卡塔爾" + }, + flag: "🇶🇦", + code: "QA", + dialCode: "974", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Romania", + nameTranslations: { + "sk": "Rumunsko", + "se": "Románia", + "pl": "Rumunia", + "no": "Romania", + "ja": "ルーマニア", + "it": "Romania", + "zh": "罗马尼亚", + "nl": "Roemenië", + "de": "Rumänien", + "fr": "Roumanie", + "es": "Rumanía", + "en": "Romania", + "pt_BR": "Romênia", + "sr-Cyrl": "Румунија", + "sr-Latn": "Rumunija", + "zh_TW": "羅馬尼亞", + "tr": "Romanya", + "ro": "România", + "ar": "رومانيا", + "fa": "رومانی", + "yue": "羅馬尼亞" + }, + flag: "🇷🇴", + code: "RO", + dialCode: "40", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Russia", + nameTranslations: { + "sk": "Rusko", + "se": "Ruošša", + "pl": "Rosja", + "no": "Russland", + "ja": "ロシア", + "it": "Russia", + "zh": "俄罗斯", + "nl": "Rusland", + "de": "Russland", + "fr": "Russie", + "es": "Rusia", + "en": "Russia", + "pt_BR": "Rússia", + "sr-Cyrl": "Русија", + "sr-Latn": "Rusija", + "zh_TW": "俄羅斯", + "tr": "Rusya", + "ro": "Rusia", + "ar": "روسيا", + "fa": "روسیه", + "yue": "俄儸斯聯邦" + }, + flag: "🇷🇺", + code: "RU", + dialCode: "7", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Rwanda", + nameTranslations: { + "sk": "Rwanda", + "se": "Rwanda", + "pl": "Rwanda", + "no": "Rwanda", + "ja": "ルワンダ", + "it": "Ruanda", + "zh": "卢旺达", + "nl": "Rwanda", + "de": "Ruanda", + "fr": "Rwanda", + "es": "Ruanda", + "en": "Rwanda", + "pt_BR": "Ruanda", + "sr-Cyrl": "Руанда", + "sr-Latn": "Ruanda", + "zh_TW": "盧安達", + "tr": "Ruanda", + "ro": "Rwanda", + "ar": "رواندا", + "fa": "رواندا", + "yue": "盧旺達" + }, + flag: "🇷🇼", + code: "RW", + dialCode: "250", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Reunion", + nameTranslations: { + "sk": "Réunion", + "se": "Réunion", + "pl": "Reunion", + "no": "Réunion", + "ja": "レユニオン", + "it": "Riunione", + "zh": "留尼汪", + "nl": "Réunion", + "de": "Réunion", + "fr": "La Réunion", + "es": "Reunión", + "en": "Réunion", + "pt_BR": "Reunião", + "sr-Cyrl": "Реинион", + "sr-Latn": "Reinion", + "zh_TW": "留尼旺", + "tr": "La Réunion", + "ro": "La Réunion", + "ar": "لا ريونيون", + "fa": "رئونیون", + "yue": "留尼汪" + }, + flag: "🇷🇪", + code: "RE", + dialCode: "262", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Saint Barthelemy", + nameTranslations: { + "sk": "Svätý Bartolomej", + "se": "Saint Barthélemy", + "pl": "Saint-Barthélemy", + "no": "Saint-Barthélemy", + "ja": "サン・バルテルミー", + "it": "Saint-Barthélemy", + "zh": "圣巴泰勒米", + "nl": "Saint-Barthélemy", + "de": "St. Barthélemy", + "fr": "Saint-Barthélemy", + "es": "San Bartolomé", + "en": "St. Barthélemy", + "pt_BR": "São Bartolomeu", + "sr-Cyrl": "Сент Бартелеми", + "sr-Latn": "Sent Bartelemi", + "zh_TW": "聖巴瑟米", + "tr": "Saint Barthélemy", + "ro": "Saint Barthélemy", + "ar": "سان بارتيلمي", + "fa": "سن بارتلمی", + "yue": "聖巴泰勒米" + }, + flag: "🇧🇱", + code: "BL", + dialCode: "590", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Saint Helena, Ascension and Tristan Da Cunha", + nameTranslations: { + "sk": "Svätá Helena", + "se": "Saint Helena", + "pl": "Wyspa Świętej Heleny", + "no": "St. Helena", + "ja": "セントヘレナ", + "it": "Sant'Elena", + "zh": "圣赫勒拿", + "nl": "Sint-Helena", + "de": "St. Helena", + "fr": "Sainte-Hélène", + "es": "Santa Elena", + "en": "St. Helena", + "pt_BR": "Santa Helena", + "sr-Cyrl": "Света Јелена, Асенсион и Тристан да Куња", + "sr-Latn": "Sveta Jelena, Asension i Tristan de Kunja", + "zh_TW": "聖凱倫拿島", + "tr": "Saint Helena", + "ro": "Sfânta Elena", + "ar": "سانت هيلانة وأسينشين وتريستان دا كونا", + "fa": "سنت هلن", + "yue": "圣赫勒拿、阿森松同特里斯坦·达库尼亚" + }, + flag: "🇸🇭", + code: "SH", + dialCode: "290", + minLength: 4, + maxLength: 4, + ), + Country( + name: "Saint Kitts and Nevis", + nameTranslations: { + "sk": "Svätý Krištof a Nevis", + "se": "Saint Kitts ja Nevis", + "pl": "Saint Kitts i Nevis", + "no": "Saint Kitts og Nevis", + "ja": "セントクリストファー・ネーヴィス", + "it": "Saint Kitts e Nevis", + "zh": "圣基茨和尼维斯", + "nl": "Saint Kitts en Nevis", + "de": "St. Kitts und Nevis", + "fr": "Saint-Christophe-et-Niévès", + "es": "San Cristóbal y Nieves", + "en": "St. Kitts & Nevis", + "pt_BR": "São Cristóvão e Nevis", + "sr-Cyrl": "Сент Китс и Невис", + "sr-Latn": "Sent Kits i Nevis", + "zh_TW": "聖克里斯多福及尼維斯", + "tr": "Saint Kitts ve Nevis", + "ro": "Sfântul Kitts și Nevis", + "ar": "سانت كيتس ونيفيس", + "fa": "سنت کیتس و نویس", + "yue": "圣基茨同尼维斯" + }, + flag: "🇰🇳", + code: "KN", + dialCode: "1869", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Saint Lucia", + nameTranslations: { + "sk": "Svätá Lucia", + "se": "Saint Lucia", + "pl": "Saint Lucia", + "no": "St. Lucia", + "ja": "セントルシア", + "it": "Saint Lucia", + "zh": "圣卢西亚", + "nl": "Saint Lucia", + "de": "St. Lucia", + "fr": "Sainte-Lucie", + "es": "Santa Lucía", + "en": "St. Lucia", + "pt_BR": "Santa Lúcia", + "sr-Cyrl": "Света Луција", + "sr-Latn": "Sveta Lucija", + "zh_TW": "聖露西亞", + "tr": "Saint Lucia", + "ro": "Sfânta Elena", + "ar": "سانت لوسيا", + "fa": "سنت لوسیا", + "yue": "聖盧西亞" + }, + flag: "🇱🇨", + code: "LC", + dialCode: "1758", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Saint Martin", + nameTranslations: { + "sk": "Svätý Martin (fr.)", + "se": "Frankriikka Saint Martin", + "pl": "Saint-Martin", + "no": "Saint-Martin", + "ja": "サン・マルタン", + "it": "Saint Martin", + "zh": "法属圣马丁", + "nl": "Saint-Martin", + "de": "St. Martin", + "fr": "Saint-Martin", + "es": "San Martín", + "en": "St. Martin", + "pt_BR": "São Martinho", + "sr-Cyrl": "Свети Мартин", + "sr-Latn": "Sveti Martin", + "zh_TW": "聖馬丁", + "tr": "Saint Martin", + "ro": "Sfântul Martin", + "ar": "تجمع سان مارتين", + "fa": "سن مارتن", + "yue": "聖馬丁(法國部分)" + }, + flag: "🇲🇫", + code: "MF", + dialCode: "590", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Saint Pierre and Miquelon", + nameTranslations: { + "sk": "Saint Pierre a Miquelon", + "se": "Saint Pierre ja Miquelon", + "pl": "Saint-Pierre i Miquelon", + "no": "Saint-Pierre-et-Miquelon", + "ja": "サンピエール島・ミクロン島", + "it": "Saint-Pierre e Miquelon", + "zh": "圣皮埃尔和密克隆群岛", + "nl": "Saint-Pierre en Miquelon", + "de": "St. Pierre und Miquelon", + "fr": "Saint-Pierre-et-Miquelon", + "es": "San Pedro y Miquelón", + "en": "St. Pierre & Miquelon", + "pt_BR": "São Pedro e Miquelon", + "sr-Cyrl": "Сен Пјер и Микелон", + "sr-Latn": "Sen Pjer i Mikelon", + "zh_TW": "聖皮埃與密克隆群島", + "tr": "Saint Pierre ve Miquelon", + "ro": "Saint Pierre și Miquelon", + "ar": "سان بيير وميكلون", + "fa": "سن-پیر و میکلون", + "yue": "聖皮埃尔同米克隆" + }, + flag: "🇵🇲", + code: "PM", + dialCode: "508", + minLength: 6, + maxLength: 6, + ), + Country( + name: "Saint Vincent and the Grenadines", + nameTranslations: { + "sk": "Svätý Vincent a Grenadíny", + "se": "Saint Vincent ja Grenadine", + "pl": "Saint Vincent i Grenadyny", + "no": "St. Vincent og Grenadinene", + "ja": "セントビンセント及びグレナディーン諸島", + "it": "Saint Vincent e Grenadine", + "zh": "圣文森特和格林纳丁斯", + "nl": "Saint Vincent en de Grenadines", + "de": "St. Vincent und die Grenadinen", + "fr": "Saint-Vincent-et-les-Grenadines", + "es": "San Vicente y las Granadinas", + "en": "St. Vincent & Grenadines", + "pt_BR": "São Vicente e Granadinas", + "sr-Cyrl": "Свети Винсент и Гренадини", + "sr-Latn": "Sveti Vinsent i Grenadini", + "zh_TW": "聖文森及格瑞那丁", + "tr": "Saint Vincent ve Grenadinler", + "ro": "Sfântul Vincențiu și Grenadinele", + "ar": "سانت فينسنت والغرينادين", + "fa": "سنت وینسنت و گرنادین‌ها", + "yue": "聖文森特同格林纳丁斯" + }, + flag: "🇻🇨", + code: "VC", + dialCode: "1784", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Samoa", + nameTranslations: { + "sk": "Samoa", + "se": "Samoa", + "pl": "Samoa", + "no": "Samoa", + "ja": "サモア", + "it": "Samoa", + "zh": "萨摩亚", + "nl": "Samoa", + "de": "Samoa", + "fr": "Samoa", + "es": "Samoa", + "en": "Samoa", + "pt_BR": "Samoa", + "sr-Cyrl": "Самоа", + "sr-Latn": "Samoa", + "zh_TW": "薩摩亞", + "tr": "Samoa", + "ro": "Samoa", + "ar": "ساموا", + "fa": "ساموآ", + "yue": "薩摩亞" + }, + flag: "🇼🇸", + code: "WS", + dialCode: "685", + minLength: 7, + maxLength: 7, + ), + Country( + name: "San Marino", + nameTranslations: { + "sk": "San Maríno", + "se": "San Marino", + "pl": "San Marino", + "no": "San Marino", + "ja": "サンマリノ", + "it": "San Marino", + "zh": "圣马力诺", + "nl": "San Marino", + "de": "San Marino", + "fr": "Saint-Marin", + "es": "San Marino", + "en": "San Marino", + "pt_BR": "San Marino", + "sr-Cyrl": "Сан Марино", + "sr-Latn": "San Marino", + "zh_TW": "聖馬利諾", + "tr": "San Marino", + "ro": "San Marino", + "ar": "سان مارينو", + "fa": "سان مارینو", + "yue": "聖馬力諾" + }, + flag: "🇸🇲", + code: "SM", + dialCode: "378", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Sao Tome and Principe", + nameTranslations: { + "sk": "Svätý Tomáš a Princov ostrov", + "se": "São Tomé ja Príncipe", + "pl": "Wyspy Świętego Tomasza i Książęca", + "no": "São Tomé og Príncipe", + "ja": "サントメ・プリンシペ", + "it": "São Tomé e Príncipe", + "zh": "圣多美和普林西比", + "nl": "Sao Tomé en Principe", + "de": "São Tomé und Príncipe", + "fr": "Sao Tomé-et-Principe", + "es": "Santo Tomé y Príncipe", + "en": "São Tomé & Príncipe", + "pt_BR": "São Tomé e Príncipe", + "sr-Cyrl": "Сао Томе и Принсипе", + "sr-Latn": "Sao Tome i Prinsipe", + "zh_TW": "聖多美普林西比", + "tr": "São Tomé ve Príncipe", + "ro": "Sao Tome şi Principe", + "ar": "ساو تومي وبرينسيب", + "fa": "سائوتومه و پرنسیپ", + "yue": "聖多美和普林西比" + }, + flag: "🇸🇹", + code: "ST", + dialCode: "239", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Saudi Arabia", + nameTranslations: { + "sk": "Saudská Arábia", + "se": "Saudi-Arábia", + "pl": "Arabia Saudyjska", + "no": "Saudi-Arabia", + "ja": "サウジアラビア", + "it": "Arabia Saudita", + "zh": "沙特阿拉伯", + "nl": "Saoedi-Arabië", + "de": "Saudi-Arabien", + "fr": "Arabie saoudite", + "es": "Arabia Saudí", + "en": "Saudi Arabia", + "pt_BR": "Arábia Saudita", + "sr-Cyrl": "Саудијска Арабија", + "sr-Latn": "Saudijska Arabija", + "zh_TW": "沙烏地阿拉", + "tr": "Suudi Arabistan", + "ro": "Arabia Saudită", + "ar": "السعودية", + "fa": "عربستان سعودی", + "yue": "沙地阿拉伯" + }, + flag: "🇸🇦", + code: "SA", + dialCode: "966", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Senegal", + nameTranslations: { + "sk": "Senegal", + "se": "Senegal", + "pl": "Senegal", + "no": "Senegal", + "ja": "セネガル", + "it": "Senegal", + "zh": "塞内加尔", + "nl": "Senegal", + "de": "Senegal", + "fr": "Sénégal", + "es": "Senegal", + "en": "Senegal", + "pt_BR": "Senegal", + "sr-Cyrl": "Сенегал", + "sr-Latn": "Senegal", + "zh_TW": "塞內加爾", + "tr": "Senegal", + "ro": "Senegal", + "ar": "السنغال", + "fa": "سنگال", + "yue": "塞內加爾" + }, + flag: "🇸🇳", + code: "SN", + dialCode: "221", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Serbia", + nameTranslations: { + "sk": "Srbsko", + "se": "Serbia", + "pl": "Serbia", + "no": "Serbia", + "ja": "セルビア", + "it": "Serbia", + "zh": "塞尔维亚", + "nl": "Servië", + "de": "Serbien", + "fr": "Serbie", + "es": "Serbia", + "en": "Serbia", + "pt_BR": "Sérvia", + "sr-Cyrl": "Србија", + "sr-Latn": "Srbija", + "zh_TW": "塞爾維亞", + "tr": "Sırbistan", + "ro": "Serbia", + "ar": "صربيا", + "fa": "صربستان", + "yue": "塞爾維亞" + }, + flag: "🇷🇸", + code: "RS", + dialCode: "381", + minLength: 12, + maxLength: 12, + ), + Country( + name: "Seychelles", + nameTranslations: { + "sk": "Seychely", + "se": "Seychellsullot", + "pl": "Seszele", + "no": "Seychellene", + "ja": "セーシェル", + "it": "Seychelles", + "zh": "塞舌尔", + "nl": "Seychellen", + "de": "Seychellen", + "fr": "Seychelles", + "es": "Seychelles", + "en": "Seychelles", + "pt_BR": "Seychelles", + "sr-Cyrl": "Сејшели", + "sr-Latn": "Sejšeli", + "zh_TW": "塞席爾", + "tr": "Seyşeller", + "ro": "Seychelles", + "ar": "سيشل", + "fa": "سیشل", + "yue": "塞舌爾" + }, + flag: "🇸🇨", + code: "SC", + dialCode: "248", + minLength: 6, + maxLength: 6, + ), + Country( + name: "Sierra Leone", + nameTranslations: { + "sk": "Sierra Leone", + "se": "Sierra Leone", + "pl": "Sierra Leone", + "no": "Sierra Leone", + "ja": "シエラレオネ", + "it": "Sierra Leone", + "zh": "塞拉利昂", + "nl": "Sierra Leone", + "de": "Sierra Leone", + "fr": "Sierra Leone", + "es": "Sierra Leona", + "en": "Sierra Leone", + "pt_BR": "Serra Leoa", + "sr-Cyrl": "Сијера Леоне", + "sr-Latn": "Sijera Leone", + "zh_TW": "獅子山", + "tr": "Sierra Leone", + "ro": "Sierra Leone", + "ar": "سيراليون", + "fa": "سیرالئون", + "yue": "塞拉利昂" + }, + flag: "🇸🇱", + code: "SL", + dialCode: "232", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Singapore", + nameTranslations: { + "sk": "Singapur", + "se": "Singapore", + "pl": "Singapur", + "no": "Singapore", + "ja": "シンガポール", + "it": "Singapore", + "zh": "新加坡", + "nl": "Singapore", + "de": "Singapur", + "fr": "Singapour", + "es": "Singapur", + "en": "Singapore", + "pt_BR": "Cingapura", + "sr-Cyrl": "Сингапур", + "sr-Latn": "Singapur", + "zh_TW": "新加坡", + "tr": "Singapur", + "ro": "Singapore", + "ar": "سنغافورة", + "fa": "سنگاپور", + "yue": "星架坡" + }, + flag: "🇸🇬", + code: "SG", + dialCode: "65", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Slovakia", + nameTranslations: { + "sk": "Slovensko", + "se": "Slovákia", + "pl": "Słowacja", + "no": "Slovakia", + "ja": "スロバキア", + "it": "Slovacchia", + "zh": "斯洛伐克", + "nl": "Slowakije", + "de": "Slowakei", + "fr": "Slovaquie", + "es": "Eslovaquia", + "en": "Slovakia", + "pt_BR": "Eslováquia", + "sr-Cyrl": "Словачка", + "sr-Latn": "Slovačka", + "zh_TW": "斯洛伐克", + "tr": "Slovakya", + "ro": "Slovacia", + "ar": "سلوفاكيا", + "fa": "اسلواکی", + "yue": "斯洛伐克" + }, + flag: "🇸🇰", + code: "SK", + dialCode: "421", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Slovenia", + nameTranslations: { + "sk": "Slovinsko", + "se": "Slovenia", + "pl": "Słowenia", + "no": "Slovenia", + "ja": "スロベニア", + "it": "Slovenia", + "zh": "斯洛文尼亚", + "nl": "Slovenië", + "de": "Slowenien", + "fr": "Slovénie", + "es": "Eslovenia", + "en": "Slovenia", + "pt_BR": "Eslovênia", + "sr-Cyrl": "Словеније", + "sr-Latn": "Slovenija", + "zh_TW": "斯洛維尼亞", + "tr": "Slovenya", + "ro": "Slovenia", + "ar": "سلوفينيا", + "fa": "اسلوونی", + "yue": "斯洛文尼亞" + }, + flag: "🇸🇮", + code: "SI", + dialCode: "386", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Solomon Islands", + nameTranslations: { + "sk": "Šalamúnove ostrovy", + "se": "Salomon-sullot", + "pl": "Wyspy Salomona", + "no": "Salomonøyene", + "ja": "ソロモン諸島", + "it": "Isole Salomone", + "zh": "所罗门群岛", + "nl": "Salomonseilanden", + "de": "Salomonen", + "fr": "Îles Salomon", + "es": "Islas Salomón", + "en": "Solomon Islands", + "pt_BR": "Ilhas Salomão", + "sr-Cyrl": "Соломонска Острва", + "sr-Latn": "Solomonska Ostrva", + "zh_TW": "所羅門群島", + "tr": "Solomon Adaları", + "ro": "Insulele Solomon", + "ar": "جزر سليمان", + "fa": "جزایر سلیمان", + "yue": "所羅門群島" + }, + flag: "🇸🇧", + code: "SB", + dialCode: "677", + minLength: 5, + maxLength: 5, + ), + Country( + name: "Somalia", + nameTranslations: { + "sk": "Somálsko", + "se": "Somália", + "pl": "Somalia", + "no": "Somalia", + "ja": "ソマリア", + "it": "Somalia", + "zh": "索马里", + "nl": "Somalië", + "de": "Somalia", + "fr": "Somalie", + "es": "Somalia", + "en": "Somalia", + "pt_BR": "Somália", + "sr-Cyrl": "Сомалија", + "sr-Latn": "Somalija", + "zh_TW": "索馬利亞", + "tr": "Somali", + "ro": "Somalia", + "ar": "الصومال", + "fa": "سومالی", + "yue": "索馬里" + }, + flag: "🇸🇴", + code: "SO", + dialCode: "252", + minLength: 8, + maxLength: 8, + ), + Country( + name: "South Africa", + nameTranslations: { + "sk": "Južná Afrika", + "se": "Mátta-Afrihká", + "pl": "Republika Południowej Afryki", + "no": "Sør-Afrika", + "ja": "南アフリカ", + "it": "Sudafrica", + "zh": "南非", + "nl": "Zuid-Afrika", + "de": "Südafrika", + "fr": "Afrique du Sud", + "es": "Sudáfrica", + "en": "South Africa", + "pt_BR": "África do Sul", + "sr-Cyrl": "Јужноафричка Република", + "sr-Latn": "Južnoafrička Republika", + "zh_TW": "南非", + "tr": "Güney Afrika", + "ro": "Africa de Sud", + "ar": "جنوب أفريقيا", + "fa": "آفریقای جنوبی", + "yue": "南非" + }, + flag: "🇿🇦", + code: "ZA", + dialCode: "27", + minLength: 9, + maxLength: 9, + ), + Country( + name: "South Sudan", + nameTranslations: { + "sk": "Južný Sudán", + "se": "Máttasudan", + "pl": "Sudan Południowy", + "no": "Sør-Sudan", + "ja": "南スーダン", + "it": "Sud Sudan", + "zh": "南苏丹", + "nl": "Zuid-Soedan", + "de": "Südsudan", + "fr": "Soudan du Sud", + "es": "Sudán del Sur", + "en": "South Sudan", + "pt_BR": "Sudão do Sul", + "sr-Cyrl": "Јужни Судан", + "sr-Latn": "Južni Sudan", + "zh_TW": "南蘇丹", + "tr": "Güney Sudan", + "ro": "Sudanul de Sud", + "ar": "جنوب السودان", + "fa": "سودان جنوبی", + "yue": "南蘇丹" + }, + flag: "🇸🇸", + code: "SS", + dialCode: "211", + minLength: 9, + maxLength: 9, + ), + Country( + name: "South Georgia and the South Sandwich Islands", + nameTranslations: { + "sk": "Južná Georgia a Južné Sandwichove ostrovy", + "se": "Lulli Georgia ja Lulli Sandwich-sullot", + "pl": "Georgia Południowa i Sandwich Południowy", + "no": "Sør-Georgia og Sør-Sandwichøyene", + "ja": "サウスジョージア・サウスサンドウィッチ諸島", + "it": "Georgia del Sud e Sandwich australi", + "zh": "南乔治亚和南桑威奇群岛", + "nl": "Zuid-Georgia en Zuidelijke Sandwicheilanden", + "de": "Südgeorgien und die Südlichen Sandwichinseln", + "fr": "Géorgie du Sud et îles Sandwich du Sud", + "es": "Islas Georgia del Sur y Sandwich del Sur", + "en": "South Georgia & South Sandwich Islands", + "pt_BR": "Geórgia do Sul e Ilhas Sandwich do Sul", + "sr-Cyrl": "Јужна Џорџија и Јужна Сендвичка Острва", + "sr-Latn": "Južna Džordžija i Južna Sendvička Ostrva", + "zh_TW": "南喬治亞與南三明治群島 ", + "tr": "Güney Georgia ve Güney Sandwich Adaları", + "ro": "Georgia de Sud și Insulele Sandwich de Sud", + "ar": "جورجيا الجنوبية وجزر ساندويتش الجنوبية", + "fa": "جزایر جورجیای جنوبی و ساندویچ جنوبی", + "yue": "南喬治亞州同南桑威奇群島" + }, + flag: "🇬🇸", + code: "GS", + dialCode: "500", + minLength: 15, + maxLength: 15, + ), + Country( + name: "Spain", + nameTranslations: { + "sk": "Španielsko", + "se": "Spánia", + "pl": "Hiszpania", + "no": "Spania", + "ja": "スペイン", + "it": "Spagna", + "zh": "西班牙", + "nl": "Spanje", + "de": "Spanien", + "fr": "Espagne", + "es": "España", + "en": "Spain", + "pt_BR": "Espanha", + "sr-Cyrl": "Шпанија", + "sr-Latn": "Španija", + "zh_TW": "西班牙", + "tr": "İspanya", + "ro": "Spania", + "ar": "إسبانيا", + "fa": "اسپانیا", + "yue": "西班牙" + }, + flag: "🇪🇸", + code: "ES", + dialCode: "34", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Sri Lanka", + nameTranslations: { + "sk": "Srí Lanka", + "se": "Sri Lanka", + "pl": "Sri Lanka", + "no": "Sri Lanka", + "ja": "スリランカ", + "it": "Sri Lanka", + "zh": "斯里兰卡", + "nl": "Sri Lanka", + "de": "Sri Lanka", + "fr": "Sri Lanka", + "es": "Sri Lanka", + "en": "Sri Lanka", + "pt_BR": "Sri Lanka", + "sr-Cyrl": "Шри Ланка", + "sr-Latn": "Šri Lanka", + "zh_TW": "斯里蘭卡", + "tr": "Sri Lanka", + "ro": "Sri Lanka", + "ar": "سريلانكا", + "fa": "سریلانکا", + "yue": "斯里蘭卡" + }, + flag: "🇱🇰", + code: "LK", + dialCode: "94", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Sudan", + nameTranslations: { + "sk": "Sudán", + "se": "Davvisudan", + "pl": "Sudan", + "no": "Sudan", + "ja": "スーダン", + "it": "Sudan", + "zh": "苏丹", + "nl": "Soedan", + "de": "Sudan", + "fr": "Soudan", + "es": "Sudán", + "en": "Sudan", + "pt_BR": "Sudão", + "sr-Cyrl": "Судан", + "sr-Latn": "Sudan", + "zh_TW": "蘇丹", + "tr": "Sudan", + "ro": "Sudan", + "ar": "السودان", + "fa": "سودان", + "yue": "蘇丹" + }, + flag: "🇸🇩", + code: "SD", + dialCode: "249", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Suriname", + nameTranslations: { + "sk": "Surinam", + "se": "Surinam", + "pl": "Surinam", + "no": "Surinam", + "ja": "スリナム", + "it": "Suriname", + "zh": "苏里南", + "nl": "Suriname", + "de": "Suriname", + "fr": "Suriname", + "es": "Surinam", + "en": "Suriname", + "pt_BR": "Suriname", + "sr-Cyrl": "Суринам", + "sr-Latn": "Surinam", + "zh_TW": "蘇利南", + "tr": "Surinam", + "ro": "Surinam", + "ar": "سورينام", + "fa": "سورینام", + "yue": "蘇里南" + }, + flag: "🇸🇷", + code: "SR", + dialCode: "597", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Svalbard and Jan Mayen", + nameTranslations: { + "sk": "Svalbard a Jan Mayen", + "se": "Svalbárda ja Jan Mayen", + "pl": "Svalbard i Jan Mayen", + "no": "Svalbard og Jan Mayen", + "ja": "スバールバル諸島・ヤンマイエン島", + "it": "Svalbard e Jan Mayen", + "zh": "斯瓦尔巴和扬马延", + "nl": "Spitsbergen en Jan Mayen", + "de": "Spitzbergen und Jan Mayen", + "fr": "Svalbard et Jan Mayen", + "es": "Svalbard y Jan Mayen", + "en": "Svalbard & Jan Mayen", + "pt_BR": "Svalbard e Jan Mayen", + "sr-Cyrl": "Свалбард", + "sr-Latn": "Svalbard", + "zh_TW": "斯瓦巴及尖棉", + "tr": "Svalbard ve Jan Mayen", + "ro": "Svalbard și Jan Mayen", + "ar": "سفالبارد ويان ماين", + "fa": "سوالبارد و یان ماین", + "yue": "斯瓦尔巴德同扬·马延" + }, + flag: "🇸🇯", + code: "SJ", + dialCode: "47", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Eswatini", + nameTranslations: { + "sk": "Eswatini", + "se": "Svazieana", + "pl": "Eswatini", + "no": "Eswatini", + "ja": "エスワティニ", + "it": "Swaziland", + "zh": "斯威士兰", + "nl": "eSwatini", + "de": "Eswatini", + "fr": "Eswatini", + "es": "Esuatini", + "en": "Eswatini", + "pt_BR": "Eswatini", + "sr-Cyrl": "Свазиланд", + "sr-Latn": "Svaziland", + "zh_TW": "史瓦帝尼", + "tr": "Esvatini", + "ro": "Eswatini", + "ar": "إسواتيني", + "fa": "اسواتینی", + "yue": "斯威士蘭" + }, + flag: "🇸🇿", + code: "SZ", + dialCode: "268", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Sweden", + nameTranslations: { + "sk": "Švédsko", + "se": "Ruoŧŧa", + "pl": "Szwecja", + "no": "Sverige", + "ja": "スウェーデン", + "it": "Svezia", + "zh": "瑞典", + "nl": "Zweden", + "de": "Schweden", + "fr": "Suède", + "es": "Suecia", + "en": "Sweden", + "pt_BR": "Suécia", + "sr-Cyrl": "Шведска", + "sr-Latn": "Švedska", + "zh_TW": "瑞典", + "tr": "İsveç", + "ro": "Suedia", + "ar": "السويد", + "fa": "سوئد", + "yue": "瑞典" + }, + flag: "🇸🇪", + code: "SE", + dialCode: "46", + minLength: 7, + maxLength: 13, + ), + Country( + name: "Switzerland", + nameTranslations: { + "sk": "Švajčiarsko", + "se": "Šveica", + "pl": "Szwajcaria", + "no": "Sveits", + "ja": "スイス", + "it": "Svizzera", + "zh": "瑞士", + "nl": "Zwitserland", + "de": "Schweiz", + "fr": "Suisse", + "es": "Suiza", + "en": "Switzerland", + "pt_BR": "Suíça", + "sr-Cyrl": "Швајцарска", + "sr-Latn": "Švajcarska", + "zh_TW": "瑞士", + "tr": "İsviçre", + "ro": "Elveţia", + "ar": "سويسرا", + "fa": "سوئیس", + "yue": "瑞士" + }, + flag: "🇨🇭", + code: "CH", + dialCode: "41", + minLength: 9, + maxLength: 12, + ), + Country( + name: "Syrian Arab Republic", + nameTranslations: { + "sk": "Sýria", + "se": "Syria", + "pl": "Syria", + "no": "Syria", + "ja": "シリア", + "it": "Siria", + "zh": "叙利亚", + "nl": "Syrië", + "de": "Syrien", + "fr": "Syrie", + "es": "Siria", + "en": "Syria", + "pt_BR": "Síria", + "sr-Cyrl": "Сирија", + "sr-Latn": "Sirija", + "zh_TW": "敘利亞", + "tr": "Suriye", + "ro": "Siria", + "ar": "سوريا", + "fa": "سوریه", + "yue": "阿拉伯敘利亞共和國" + }, + flag: "🇸🇾", + code: "SY", + dialCode: "963", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Taiwan", + nameTranslations: { + "sk": "Taiwan", + "se": "Taiwan", + "pl": "Tajwan", + "no": "Taiwan", + "ja": "台湾", + "it": "Taiwan", + "zh": "台湾", + "nl": "Taiwan", + "de": "Taiwan", + "fr": "Taïwan", + "es": "Taiwán", + "en": "Taiwan", + "pt_BR": "Taiwan", + "sr-Cyrl": "Тајван", + "sr-Latn": "Tajvan", + "zh_TW": "台灣", + "tr": "Tayvan", + "ro": "Taiwan", + "ar": "تايوان", + "fa": "تایوان", + "yue": "台灣" + }, + flag: "🇹🇼", + code: "TW", + dialCode: "886", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Tajikistan", + nameTranslations: { + "sk": "Tadžikistan", + "se": "Tažikistan", + "pl": "Tadżykistan", + "no": "Tadsjikistan", + "ja": "タジキスタン", + "it": "Tagikistan", + "zh": "塔吉克斯坦", + "nl": "Tadzjikistan", + "de": "Tadschikistan", + "fr": "Tadjikistan", + "es": "Tayikistán", + "en": "Tajikistan", + "pt_BR": "Tajiquistão", + "sr-Cyrl": "Таџикистан", + "sr-Latn": "Tadžikistan", + "zh_TW": "塔吉克", + "tr": "Tacikistan", + "ro": "Tadiquistão", + "ar": "طاجيكستان", + "fa": "تاجیکستان", + "yue": "塔吉克斯坦" + }, + flag: "🇹🇯", + code: "TJ", + dialCode: "992", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Tanzania, United Republic of Tanzania", + nameTranslations: { + "sk": "Tanzánia", + "se": "Tanzánia", + "pl": "Tanzania", + "no": "Tanzania", + "ja": "タンザニア", + "it": "Tanzania", + "zh": "坦桑尼亚", + "nl": "Tanzania", + "de": "Tansania", + "fr": "Tanzanie", + "es": "Tanzania", + "en": "Tanzania", + "pt_BR": "Tanzânia", + "sr-Cyrl": "Танзанија", + "sr-Latn": "Tanzanija", + "zh_TW": "坦尚尼亞", + "tr": "Tanzanya", + "ro": "Tanzania", + "ar": "تنزانيا", + "fa": "تانزانیا", + "yue": "坦桑尼亞,聯合共和國" + }, + flag: "🇹🇿", + code: "TZ", + dialCode: "255", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Thailand", + nameTranslations: { + "sk": "Thajsko", + "se": "Thaieana", + "pl": "Tajlandia", + "no": "Thailand", + "ja": "タイ", + "it": "Thailandia", + "zh": "泰国", + "nl": "Thailand", + "de": "Thailand", + "fr": "Thaïlande", + "es": "Tailandia", + "en": "Thailand", + "pt_BR": "Tailândia", + "sr-Cyrl": "Тајланд", + "sr-Latn": "Tajland", + "zh_TW": "泰國", + "tr": "Tayland", + "ro": "Tailanda", + "ar": "تايلاند", + "fa": "تایلند", + "yue": "泰國" + }, + flag: "🇹🇭", + code: "TH", + dialCode: "66", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Timor-Leste", + nameTranslations: { + "sk": "Východný Timor", + "se": "Nuorta-Timor", + "pl": "Timor Wschodni", + "no": "Øst-Timor", + "ja": "東ティモール", + "it": "Timor Est", + "zh": "东帝汶", + "nl": "Oost-Timor", + "de": "Timor-Leste", + "fr": "Timor oriental", + "es": "Timor-Leste", + "en": "Timor-Leste", + "pt_BR": "Timor-Leste", + "sr-Cyrl": "Источни Тимор", + "sr-Latn": "Istočni Timor", + "zh_TW": "東帝汶", + "tr": "Doğu Timor", + "ro": "Timorul de Est", + "ar": "تيمور الشرقية", + "fa": "تیمور شرقی", + "yue": "東帝汶" + }, + flag: "🇹🇱", + code: "TL", + dialCode: "670", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Togo", + nameTranslations: { + "sk": "Togo", + "se": "Togo", + "pl": "Togo", + "no": "Togo", + "ja": "トーゴ", + "it": "Togo", + "zh": "多哥", + "nl": "Togo", + "de": "Togo", + "fr": "Togo", + "es": "Togo", + "en": "Togo", + "pt_BR": "Ir", + "sr-Cyrl": "Того", + "sr-Latn": "Togo", + "zh_TW": "多哥", + "tr": "Togo", + "ro": "Togo", + "ar": "توغو", + "fa": "توگو", + "yue": "多哥" + }, + flag: "🇹🇬", + code: "TG", + dialCode: "228", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Tokelau", + nameTranslations: { + "sk": "Tokelau", + "se": "Tokelau", + "pl": "Tokelau", + "no": "Tokelau", + "ja": "トケラウ", + "it": "Tokelau", + "zh": "托克劳", + "nl": "Tokelau", + "de": "Tokelau", + "fr": "Tokelau", + "es": "Tokelau", + "en": "Tokelau", + "pt_BR": "Tokelau", + "sr-Cyrl": "Токелау", + "sr-Latn": "Tokelau", + "zh_TW": "托克勞", + "tr": "Tokelau", + "ro": "Tokelau", + "ar": "توكيلاو", + "fa": "توکلائو", + "yue": "托克劳" + }, + flag: "🇹🇰", + code: "TK", + dialCode: "690", + minLength: 4, + maxLength: 4, + ), + Country( + name: "Tonga", + nameTranslations: { + "sk": "Tonga", + "se": "Tonga", + "pl": "Tonga", + "no": "Tonga", + "ja": "トンガ", + "it": "Tonga", + "zh": "汤加", + "nl": "Tonga", + "de": "Tonga", + "fr": "Tonga", + "es": "Tonga", + "en": "Tonga", + "pt_BR": "Tonga", + "sr-Cyrl": "Тонга", + "sr-Latn": "Tonga", + "zh_TW": "東加", + "tr": "Tonga", + "ro": "Tonga", + "ar": "تونغا", + "fa": "تونگا", + "yue": "湯加" + }, + flag: "🇹🇴", + code: "TO", + dialCode: "676", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Trinidad and Tobago", + nameTranslations: { + "sk": "Trinidad a Tobago", + "se": "Trinidad ja Tobago", + "pl": "Trynidad i Tobago", + "no": "Trinidad og Tobago", + "ja": "トリニダード・トバゴ", + "it": "Trinidad e Tobago", + "zh": "特立尼达和多巴哥", + "nl": "Trinidad en Tobago", + "de": "Trinidad und Tobago", + "fr": "Trinité-et-Tobago", + "es": "Trinidad y Tobago", + "en": "Trinidad & Tobago", + "pt_BR": "Trinidad e Tobago", + "sr-Cyrl": "Тринидад и Тобаго", + "sr-Latn": "Trinidad i Tobago", + "zh_TW": "千里達及托巴哥", + "tr": "Trinidad ve Tobago", + "ro": "Trinidad şi Tobago", + "ar": "ترينيداد وتوباغو", + "fa": "ترینیداد و توباگو", + "yue": "特立尼達和多巴哥" + }, + flag: "🇹🇹", + code: "TT", + dialCode: "1868", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Tunisia", + nameTranslations: { + "sk": "Tunisko", + "se": "Tunisia", + "pl": "Tunezja", + "no": "Tunisia", + "ja": "チュニジア", + "it": "Tunisia", + "zh": "突尼斯", + "nl": "Tunesië", + "de": "Tunesien", + "fr": "Tunisie", + "es": "Túnez", + "en": "Tunisia", + "pt_BR": "Tunísia", + "sr-Cyrl": "Тунис", + "sr-Latn": "Tunis", + "zh_TW": "突尼西亞", + "tr": "Tunus", + "ro": "Tunisia", + "ar": "تونس", + "fa": "تونس", + "yue": "突尼斯" + }, + flag: "🇹🇳", + code: "TN", + dialCode: "216", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Turkey", + nameTranslations: { + "sk": "Turecko", + "se": "Durka", + "pl": "Turcja", + "no": "Tyrkia", + "ja": "トルコ", + "it": "Turchia", + "zh": "土耳其", + "nl": "Turkije", + "de": "Türkei", + "fr": "Turquie", + "es": "Turquía", + "en": "Turkey", + "pt_BR": "Peru", + "sr-Cyrl": "Турска", + "sr-Latn": "Turska", + "zh_TW": "土耳其", + "tr": "Türkiye", + "ro": "Turcia", + "ar": "تركيا", + "fa": "ترکیه", + "yue": "土耳其" + }, + flag: "🇹🇷", + code: "TR", + dialCode: "90", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Turkmenistan", + nameTranslations: { + "sk": "Turkménsko", + "se": "Turkmenistan", + "pl": "Turkmenistan", + "no": "Turkmenistan", + "ja": "トルクメニスタン", + "it": "Turkmenistan", + "zh": "土库曼斯坦", + "nl": "Turkmenistan", + "de": "Turkmenistan", + "fr": "Turkménistan", + "es": "Turkmenistán", + "en": "Turkmenistan", + "pt_BR": "Turcomenistão", + "sr-Cyrl": "Туркменистан", + "sr-Latn": "Turkmenistan", + "zh_TW": "土庫曼", + "tr": "Türkmenistan", + "ro": "Turkmenistan", + "ar": "تركمانستان", + "fa": "ترکمنستان", + "yue": "土庫曼斯坦" + }, + flag: "🇹🇲", + code: "TM", + dialCode: "993", + minLength: 8, + maxLength: 8, + ), + Country( + name: "Turks and Caicos Islands", + nameTranslations: { + "sk": "Turks a Caicos", + "se": "Turks ja Caicos-sullot", + "pl": "Turks i Caicos", + "no": "Turks- og Caicosøyene", + "ja": "タークス・カイコス諸島", + "it": "Isole Turks e Caicos", + "zh": "特克斯和凯科斯群岛", + "nl": "Turks- en Caicoseilanden", + "de": "Turks- und Caicosinseln", + "fr": "Îles Turques-et-Caïques", + "es": "Islas Turcas y Caicos", + "en": "Turks & Caicos Islands", + "pt_BR": "Ilhas Turks e Caicos", + "sr-Cyrl": "Туркс и Кајкос", + "sr-Latn": "Turks i Kajkos", + "zh_TW": "土克斯及開科斯群島", + "tr": "Turks ve Caicos Adaları", + "ro": "Insulele Turks și Caicos", + "ar": "جزر توركس وكايكوس", + "fa": "جزایر تورکس و کایکوس", + "yue": "特克斯同凯科斯群岛" + }, + flag: "🇹🇨", + code: "TC", + dialCode: "1649", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Tuvalu", + nameTranslations: { + "sk": "Tuvalu", + "se": "Tuvalu", + "pl": "Tuvalu", + "no": "Tuvalu", + "ja": "ツバル", + "it": "Tuvalu", + "zh": "图瓦卢", + "nl": "Tuvalu", + "de": "Tuvalu", + "fr": "Tuvalu", + "es": "Tuvalu", + "en": "Tuvalu", + "pt_BR": "Tuvalu", + "sr-Cyrl": "Тувалу", + "sr-Latn": "Tuvalu", + "zh_TW": "圖瓦盧", + "tr": "Tuvalu", + "ro": "Tuvalu", + "ar": "توفالو", + "fa": "تووالو", + "yue": "圖瓦盧" + }, + flag: "🇹🇻", + code: "TV", + dialCode: "688", + minLength: 6, + maxLength: 6, + ), + Country( + name: "Uganda", + nameTranslations: { + "sk": "Uganda", + "se": "Uganda", + "pl": "Uganda", + "no": "Uganda", + "ja": "ウガンダ", + "it": "Uganda", + "zh": "乌干达", + "nl": "Oeganda", + "de": "Uganda", + "fr": "Ouganda", + "es": "Uganda", + "en": "Uganda", + "pt_BR": "Uganda", + "sr-Cyrl": "Уганда", + "sr-Latn": "Uganda", + "zh_TW": "烏干達", + "tr": "Uganda", + "ro": "Uganda", + "ar": "أوغندا", + "fa": "اوگاندا", + "yue": "烏干達" + }, + flag: "🇺🇬", + code: "UG", + dialCode: "256", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Ukraine", + nameTranslations: { + "sk": "Ukrajina", + "se": "Ukraina", + "pl": "Ukraina", + "no": "Ukraina", + "ja": "ウクライナ", + "it": "Ucraina", + "zh": "乌克兰", + "nl": "Oekraïne", + "de": "Ukraine", + "fr": "Ukraine", + "es": "Ucrania", + "en": "Ukraine", + "pt_BR": "Ucrânia", + "sr-Cyrl": "Украјина", + "sr-Latn": "Ukrajina", + "zh_TW": "烏克蘭", + "tr": "Ukrayna", + "ro": "Ucraína", + "ar": "أوكرانيا", + "fa": "اوکراین", + "yue": "烏克蘭" + }, + flag: "🇺🇦", + code: "UA", + dialCode: "380", + minLength: 9, + maxLength: 9, + ), + Country( + name: "United Arab Emirates", + nameTranslations: { + "sk": "Spojené arabské emiráty", + "se": "Ovttastuvvan Arábaemiráhtat", + "pl": "Zjednoczone Emiraty Arabskie", + "no": "De forente arabiske emirater", + "ja": "アラブ首長国連邦", + "it": "Emirati Arabi Uniti", + "zh": "阿拉伯联合酋长国", + "nl": "Verenigde Arabische Emiraten", + "de": "Vereinigte Arabische Emirate", + "fr": "Émirats arabes unis", + "es": "Emiratos Árabes Unidos", + "en": "United Arab Emirates", + "pt_BR": "Emirados Árabes Unidos", + "sr-Cyrl": "Уједињени Арапски Емирати", + "sr-Latn": "Ujedinjeni Arapski Emirati", + "zh_TW": "阿拉伯聯合大公國", + "tr": "Birleşik Arap Emirlikleri", + "ro": "Emiratele Arabe Unite", + "ar": "الإمارات العربية المتحدة", + "fa": "امارات متحده عربی", + "yue": "阿拉伯聯合酋長國" + }, + flag: "🇦🇪", + code: "AE", + dialCode: "971", + minLength: 9, + maxLength: 9, + ), + Country( + name: "United Kingdom", + nameTranslations: { + "sk": "Spojené kráľovstvo", + "se": "Stuorra-Británnia", + "pl": "Wielka Brytania", + "no": "Storbritannia", + "ja": "イギリス", + "it": "Regno Unito", + "zh": "英国", + "nl": "Verenigd Koninkrijk", + "de": "Vereinigtes Königreich", + "fr": "Royaume-Uni", + "es": "Reino Unido", + "en": "United Kingdom", + "pt_BR": "Reino Unido", + "sr-Cyrl": "Уједињено Краљевство", + "sr-Latn": "Ujedinjeno Kraljevstvo", + "zh_TW": "英國", + "tr": "Büyük Britanya ve Kuzey İrlanda Birleşik Krallığ", + "ro": "Regatul Unit al Marii Britanii și Irlandei de Nord", + "ar": "المملكة المتحدة", + "fa": "بریتانیا", + "yue": "大不列顛及北愛爾蘭聯合王國" + }, + flag: "🇬🇧", + code: "GB", + dialCode: "44", + minLength: 10, + maxLength: 10, + ), + Country( + name: "United States", + nameTranslations: { + "sk": "Spojené štáty", + "se": "Amerihká ovttastuvvan stáhtat", + "pl": "Stany Zjednoczone", + "no": "USA", + "ja": "アメリカ合衆国", + "it": "Stati Uniti", + "zh": "美国", + "nl": "Verenigde Staten", + "de": "Vereinigte Staaten", + "fr": "États-Unis", + "es": "Estados Unidos", + "en": "United States", + "pt_BR": "Estados Unidos", + "sr-Cyrl": "Сједињене Америчке Државе", + "sr-Latn": "Sjedinjene Američke Države", + "zh_TW": "美國", + "tr": "Amerika Birleşik Devletleri", + "ro": "Statele Unite ale Americii", + "ar": "الولايات المتحدة", + "fa": "ایالات متحده آمریکا", + "yue": "美利堅郃眾囯" + }, + flag: "🇺🇸", + code: "US", + dialCode: "1", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Uruguay", + nameTranslations: { + "sk": "Uruguaj", + "se": "Uruguay", + "pl": "Urugwaj", + "no": "Uruguay", + "ja": "ウルグアイ", + "it": "Uruguay", + "zh": "乌拉圭", + "nl": "Uruguay", + "de": "Uruguay", + "fr": "Uruguay", + "es": "Uruguay", + "en": "Uruguay", + "pt_BR": "Uruguai", + "sr-Cyrl": "Уругвај", + "sr-Latn": "Urugvaj", + "zh_TW": "烏拉圭", + "tr": "Uruguay", + "ro": "Uruguay", + "ar": "الأوروغواي", + "fa": "اروگوئه", + "yue": "烏拉圭" + }, + flag: "🇺🇾", + code: "UY", + dialCode: "598", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Uzbekistan", + nameTranslations: { + "sk": "Uzbekistan", + "se": "Usbekistan", + "pl": "Uzbekistan", + "no": "Usbekistan", + "ja": "ウズベキスタン", + "it": "Uzbekistan", + "zh": "乌兹别克斯坦", + "nl": "Oezbekistan", + "de": "Usbekistan", + "fr": "Ouzbékistan", + "es": "Uzbekistán", + "en": "Uzbekistan", + "pt_BR": "Uzbequistão", + "sr-Cyrl": "Узбекистан", + "sr-Latn": "Uzbekistan", + "zh_TW": "烏玆別克", + "tr": "Özbekistan", + "ro": "Uzbekistan", + "ar": "أوزبكستان", + "fa": "ازبکستان", + "yue": "月即別" + }, + flag: "🇺🇿", + code: "UZ", + dialCode: "998", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Vanuatu", + nameTranslations: { + "sk": "Vanuatu", + "se": "Vanuatu", + "pl": "Vanuatu", + "no": "Vanuatu", + "ja": "バヌアツ", + "it": "Vanuatu", + "zh": "瓦努阿图", + "nl": "Vanuatu", + "de": "Vanuatu", + "fr": "Vanuatu", + "es": "Vanuatu", + "en": "Vanuatu", + "pt_BR": "Vanuatu", + "sr-Cyrl": "Вануату", + "sr-Latn": "Vanuatu", + "zh_TW": "瓦努阿圖", + "tr": "Vanuatu", + "ro": "Vanuatu", + "ar": "فانواتو", + "fa": "وانواتو", + "yue": "瓦努阿圖" + }, + flag: "🇻🇺", + code: "VU", + dialCode: "678", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Venezuela, Bolivarian Republic of Venezuela", + nameTranslations: { + "sk": "Venezuela", + "se": "Venezuela", + "pl": "Wenezuela", + "no": "Venezuela", + "ja": "ベネズエラ", + "it": "Venezuela", + "zh": "委内瑞拉", + "nl": "Venezuela", + "de": "Venezuela", + "fr": "Venezuela", + "es": "Venezuela", + "en": "Venezuela", + "pt_BR": "Venezuela", + "sr-Cyrl": "Венецуела", + "sr-Latn": "Venecuela", + "zh_TW": "委內瑞拉", + "tr": "Venezuela", + "ro": "Venezuela", + "ar": "فنزويلا", + "fa": "ونزوئلا", + "yue": "委內瑞拉(玻利瓦爾共和國)" + }, + flag: "🇻🇪", + code: "VE", + dialCode: "58", + minLength: 10, + maxLength: 10, + ), + Country( + name: "Vietnam", + nameTranslations: { + "sk": "Vietnam", + "se": "Vietnam", + "pl": "Wietnam", + "no": "Vietnam", + "ja": "ベトナム", + "it": "Vietnam", + "zh": "越南", + "nl": "Vietnam", + "de": "Vietnam", + "fr": "Vietnam", + "es": "Vietnam", + "en": "Vietnam", + "pt_BR": "Vietnã", + "sr-Cyrl": "Вијетнам", + "sr-Latn": "Vijetnam", + "zh_TW": "越南", + "tr": "Vietnam", + "ro": "Vietnam", + "ar": "فيتنام", + "fa": "ویتنام", + "yue": "越南" + }, + flag: "🇻🇳", + code: "VN", + dialCode: "84", + minLength: 11, + maxLength: 11, + ), + Country( + name: "Virgin Islands, British", + nameTranslations: { + "sk": "Britské Panenské ostrovy", + "se": "Brittania Virgin-sullot", + "pl": "Brytyjskie Wyspy Dziewicze", + "no": "De britiske jomfruøyene", + "ja": "英領ヴァージン諸島", + "it": "Isole Vergini Britanniche", + "zh": "英属维尔京群岛", + "nl": "Britse Maagdeneilanden", + "de": "Britische Jungferninseln", + "fr": "Îles Vierges britanniques", + "es": "Islas Vírgenes Británicas", + "en": "British Virgin Islands", + "pt_BR": "Ilhas Virgens Britânicas", + "sr-Cyrl": "Британска Девичанска Острва", + "sr-Latn": "Britanska Devičanska Ostrva", + "zh_TW": "英屬維京群島", + "tr": "Britanya Virjin Adaları", + "ro": "Insulele Virgine Britanice", + "ar": "جزر العذراء البريطانية", + "fa": "جزایر ویرجین بریتانیا", + "yue": "維爾京群島(英國)" + }, + flag: "🇻🇬", + code: "VG", + dialCode: "1284", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Virgin Islands, U.S.", + nameTranslations: { + "sk": "Americké Panenské ostrovy", + "se": "AOS Virgin-sullot", + "pl": "Wyspy Dziewicze Stanów Zjednoczonych", + "no": "De amerikanske jomfruøyene", + "ja": "米領ヴァージン諸島", + "it": "Isole Vergini Americane", + "zh": "美属维尔京群岛", + "nl": "Amerikaanse Maagdeneilanden", + "de": "Amerikanische Jungferninseln", + "fr": "Îles Vierges des États-Unis", + "es": "Islas Vírgenes de EE. UU.", + "en": "U.S. Virgin Islands", + "pt_BR": "Ilhas Virgens Americanas", + "sr-Cyrl": "Амепичка Девичанска Острва", + "sr-Latn": "Američka Devičanska Ostrva", + "zh_TW": "美屬維京群島", + "tr": "Amerika Birleşik Devletleri Virjin Adaları", + "ro": "Insulele Virgine Americane", + "ar": "جزر العذراء الأمريكية", + "fa": "جزایر ویرجین ایالات متحده آمریکا", + "yue": "維爾京群島(美國)" + }, + flag: "🇻🇮", + code: "VI", + dialCode: "1340", + minLength: 7, + maxLength: 7, + ), + Country( + name: "Wallis and Futuna", + nameTranslations: { + "sk": "Wallis a Futuna", + "se": "Wallis ja Futuna", + "pl": "Wallis i Futuna", + "no": "Wallis og Futuna", + "ja": "ウォリス・フツナ", + "it": "Wallis e Futuna", + "zh": "瓦利斯和富图纳", + "nl": "Wallis en Futuna", + "de": "Wallis und Futuna", + "fr": "Wallis-et-Futuna", + "es": "Wallis y Futuna", + "en": "Wallis & Futuna", + "pt_BR": "Wallis e Futuna", + "sr-Cyrl": "Валис и Футуна", + "sr-Latn": "Valis i Futuna", + "zh_TW": "瓦利斯和富圖那", + "tr": "Wallis ve Futuna", + "ro": "Wallis și Futuna", + "ar": "والس وفوتونا", + "fa": "والیس و فوتونا", + "yue": "瓦利斯同富图纳" + }, + flag: "🇼🇫", + code: "WF", + dialCode: "681", + minLength: 6, + maxLength: 6, + ), + Country( + name: "Yemen", + nameTranslations: { + "sk": "Jemen", + "se": "Jemen", + "pl": "Jemen", + "no": "Jemen", + "ja": "イエメン", + "it": "Yemen", + "zh": "也门", + "nl": "Jemen", + "de": "Jemen", + "fr": "Yémen", + "es": "Yemen", + "en": "Yemen", + "pt_BR": "Iémen", + "sr-Cyrl": "Јемен", + "sr-Latn": "Jemen", + "zh_TW": "葉門", + "tr": "Yemen", + "ro": "Yemen", + "ar": "اليمن", + "fa": "یمن", + "yue": "也門" + }, + flag: "🇾🇪", + code: "YE", + dialCode: "967", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Zambia", + nameTranslations: { + "sk": "Zambia", + "se": "Zambia", + "pl": "Zambia", + "no": "Zambia", + "ja": "ザンビア", + "it": "Zambia", + "zh": "赞比亚", + "nl": "Zambia", + "de": "Sambia", + "fr": "Zambie", + "es": "Zambia", + "en": "Zambia", + "pt_BR": "Zâmbia", + "sr-Cyrl": "Замбија", + "sr-Latn": "Zambija", + "zh_TW": "贊比亞", + "tr": "Zambiya", + "ro": "Zambia", + "ar": "زامبيا", + "fa": "زامبیا", + "yue": "贊比亞" + }, + flag: "🇿🇲", + code: "ZM", + dialCode: "260", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Zimbabwe", + nameTranslations: { + "sk": "Zimbabwe", + "se": "Zimbabwe", + "pl": "Zimbabwe", + "no": "Zimbabwe", + "ja": "ジンバブエ", + "it": "Zimbabwe", + "zh": "津巴布韦", + "nl": "Zimbabwe", + "de": "Simbabwe", + "fr": "Zimbabwe", + "es": "Zimbabue", + "en": "Zimbabwe", + "pt_BR": "Zimbábue", + "sr-Cyrl": "Зимбабве", + "sr-Latn": "Zimbabve", + "zh_TW": "辛巴威", + "tr": "Zimbabve", + "ro": "Zimbabwe", + "ar": "زيمبابوي", + "fa": "زیمبابوه", + "yue": "津巴布韋" + }, + flag: "🇿🇼", + code: "ZW", + dialCode: "263", + minLength: 9, + maxLength: 9) +]; + +class Country { + final String name; + final Map nameTranslations; + final String flag; + final String code; + final String dialCode; + final String regionCode; + final int minLength; + final int maxLength; + + const Country({ + required this.name, + required this.flag, + required this.code, + required this.dialCode, + required this.nameTranslations, + required this.minLength, + required this.maxLength, + this.regionCode = "", + }); + + String get fullCountryCode { + return dialCode + regionCode; + } + + String get displayCC { + if (regionCode != "") { + return "$dialCode $regionCode"; + } + return dialCode; + } + + String localizedName(String languageCode) { + return nameTranslations[languageCode] ?? name; + } +} diff --git a/lib/controller/local/phone_intel/country_picker_dialog.dart b/lib/controller/local/phone_intel/country_picker_dialog.dart new file mode 100644 index 0000000..e6d2523 --- /dev/null +++ b/lib/controller/local/phone_intel/country_picker_dialog.dart @@ -0,0 +1,168 @@ +import 'package:SEFER/controller/local/phone_intel/helpers.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter/material.dart'; + +import 'countries.dart'; + +class PickerDialogStyle { + final Color? backgroundColor; + + final TextStyle? countryCodeStyle; + + final TextStyle? countryNameStyle; + + final Widget? listTileDivider; + + final EdgeInsets? listTilePadding; + + final EdgeInsets? padding; + + final Color? searchFieldCursorColor; + + final InputDecoration? searchFieldInputDecoration; + + final EdgeInsets? searchFieldPadding; + + final double? width; + + PickerDialogStyle({ + this.backgroundColor, + this.countryCodeStyle, + this.countryNameStyle, + this.listTileDivider, + this.listTilePadding, + this.padding, + this.searchFieldCursorColor, + this.searchFieldInputDecoration, + this.searchFieldPadding, + this.width, + }); +} + +class CountryPickerDialog extends StatefulWidget { + final List countryList; + final Country selectedCountry; + final ValueChanged onCountryChanged; + final String searchText; + final List filteredCountries; + final PickerDialogStyle? style; + final String languageCode; + + const CountryPickerDialog({ + Key? key, + required this.searchText, + required this.languageCode, + required this.countryList, + required this.onCountryChanged, + required this.selectedCountry, + required this.filteredCountries, + this.style, + }) : super(key: key); + + @override + State createState() => _CountryPickerDialogState(); +} + +class _CountryPickerDialogState extends State { + late List _filteredCountries; + late Country _selectedCountry; + + @override + void initState() { + _selectedCountry = widget.selectedCountry; + _filteredCountries = widget.filteredCountries.toList() + ..sort( + (a, b) => a + .localizedName(widget.languageCode) + .compareTo(b.localizedName(widget.languageCode)), + ); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + final mediaWidth = MediaQuery.of(context).size.width; + final width = widget.style?.width ?? mediaWidth; + const defaultHorizontalPadding = 40.0; + const defaultVerticalPadding = 24.0; + return Dialog( + insetPadding: EdgeInsets.symmetric( + vertical: defaultVerticalPadding, + horizontal: mediaWidth > (width + defaultHorizontalPadding * 2) + ? (mediaWidth - width) / 2 + : defaultHorizontalPadding), + backgroundColor: widget.style?.backgroundColor, + child: Container( + padding: widget.style?.padding ?? const EdgeInsets.all(10), + child: Column( + children: [ + Padding( + padding: + widget.style?.searchFieldPadding ?? const EdgeInsets.all(0), + child: TextField( + cursorColor: widget.style?.searchFieldCursorColor, + decoration: widget.style?.searchFieldInputDecoration ?? + InputDecoration( + suffixIcon: const Icon(Icons.search), + labelText: widget.searchText, + ), + onChanged: (value) { + _filteredCountries = widget.countryList.stringSearch(value) + ..sort( + (a, b) => a + .localizedName(widget.languageCode) + .compareTo(b.localizedName(widget.languageCode)), + ); + if (mounted) setState(() {}); + }, + ), + ), + const SizedBox(height: 20), + Expanded( + child: ListView.builder( + shrinkWrap: true, + itemCount: _filteredCountries.length, + itemBuilder: (ctx, index) => Column( + children: [ + ListTile( + leading: kIsWeb + ? Image.asset( + 'assets/flags/${_filteredCountries[index].code.toLowerCase()}.png', + package: 'intl_phone_field', + width: 32, + ) + : Text( + _filteredCountries[index].flag, + style: const TextStyle(fontSize: 18), + ), + contentPadding: widget.style?.listTilePadding, + title: Text( + _filteredCountries[index] + .localizedName(widget.languageCode), + style: widget.style?.countryNameStyle ?? + const TextStyle(fontWeight: FontWeight.w700), + ), + trailing: Text( + '+${_filteredCountries[index].dialCode}', + style: widget.style?.countryCodeStyle ?? + const TextStyle(fontWeight: FontWeight.w700), + ), + onTap: () { + _selectedCountry = _filteredCountries[index]; + widget.onCountryChanged(_selectedCountry); + Navigator.of(context).pop(); + }, + ), + widget.style?.listTileDivider ?? + const Divider(thickness: 1), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/controller/local/phone_intel/helpers.dart b/lib/controller/local/phone_intel/helpers.dart new file mode 100644 index 0000000..c2b3957 --- /dev/null +++ b/lib/controller/local/phone_intel/helpers.dart @@ -0,0 +1,31 @@ +import 'countries.dart'; + +bool isNumeric(String s) => + s.isNotEmpty && int.tryParse(s.replaceAll("+", "")) != null; + +String removeDiacritics(String str) { + var withDia = + 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž'; + var withoutDia = + 'AAAAAAaaaaaaOOOOOOOooooooEEEEeeeeeCcDIIIIiiiiUUUUuuuuNnSsYyyZz'; + + for (int i = 0; i < withDia.length; i++) { + str = str.replaceAll(withDia[i], withoutDia[i]); + } + + return str; +} + +extension CountryExtensions on List { + List stringSearch(String search) { + search = removeDiacritics(search.toLowerCase()); + return where( + (country) => isNumeric(search) || search.startsWith("+") + ? country.dialCode.contains(search) + : removeDiacritics(country.name.replaceAll("+", "").toLowerCase()) + .contains(search) || + country.nameTranslations.values.any((element) => + removeDiacritics(element.toLowerCase()).contains(search)), + ).toList(); + } +} diff --git a/lib/controller/local/phone_intel/intl_phone_field.dart b/lib/controller/local/phone_intel/intl_phone_field.dart new file mode 100644 index 0000000..b918bc6 --- /dev/null +++ b/lib/controller/local/phone_intel/intl_phone_field.dart @@ -0,0 +1,521 @@ +library intl_phone_field; + +import 'dart:async'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import './countries.dart'; +import './phone_number.dart'; +import 'country_picker_dialog.dart'; +import 'helpers.dart'; + +class IntlPhoneField extends StatefulWidget { + /// The TextFormField key. + final GlobalKey? formFieldKey; + + /// Whether to hide the text being edited (e.g., for passwords). + final bool obscureText; + + /// How the text should be aligned horizontally. + final TextAlign textAlign; + + /// How the text should be aligned vertically. + final TextAlignVertical? textAlignVertical; + final VoidCallback? onTap; + + /// {@macro flutter.widgets.editableText.readOnly} + final bool readOnly; + final FormFieldSetter? onSaved; + + /// {@macro flutter.widgets.editableText.onChanged} + /// + /// See also: + /// + /// * [inputFormatters], which are called before [onChanged] + /// runs and can validate and change ("format") the input value. + /// * [onEditingComplete], [onSubmitted], [onSelectionChanged]: + /// which are more specialized input change notifications. + final ValueChanged? onChanged; + + final ValueChanged? onCountryChanged; + + /// An optional method that validates an input. Returns an error string to display if the input is invalid, or null otherwise. + /// + /// A [PhoneNumber] is passed to the validator as argument. + /// The validator can handle asynchronous validation when declared as a [Future]. + /// Or run synchronously when declared as a [Function]. + /// + /// By default, the validator checks whether the input number length is between selected country's phone numbers min and max length. + /// If `disableLengthCheck` is not set to `true`, your validator returned value will be overwritten by the default validator. + /// But, if `disableLengthCheck` is set to `true`, your validator will have to check phone number length itself. + final FutureOr Function(PhoneNumber?)? validator; + + /// {@macro flutter.widgets.editableText.keyboardType} + final TextInputType keyboardType; + + /// Controls the text being edited. + /// + /// If null, this widget will create its own [TextEditingController]. + final TextEditingController? controller; + + /// Defines the keyboard focus for this widget. + /// + /// The [focusNode] is a long-lived object that's typically managed by a + /// [StatefulWidget] parent. See [FocusNode] for more information. + /// + /// To give the keyboard focus to this widget, provide a [focusNode] and then + /// use the current [FocusScope] to request the focus: + /// + /// ```dart + /// FocusScope.of(context).requestFocus(myFocusNode); + /// ``` + /// + /// This happens automatically when the widget is tapped. + /// + /// To be notified when the widget gains or loses the focus, add a listener + /// to the [focusNode]: + /// + /// ```dart + /// focusNode.addListener(() { print(myFocusNode.hasFocus); }); + /// ``` + /// + /// If null, this widget will create its own [FocusNode]. + /// + /// ## Keyboard + /// + /// Requesting the focus will typically cause the keyboard to be shown + /// if it's not showing already. + /// + /// On Android, the user can hide the keyboard - without changing the focus - + /// with the system back button. They can restore the keyboard's visibility + /// by tapping on a text field. The user might hide the keyboard and + /// switch to a physical keyboard, or they might just need to get it + /// out of the way for a moment, to expose something it's + /// obscuring. In this case requesting the focus again will not + /// cause the focus to change, and will not make the keyboard visible. + /// + /// This widget builds an [EditableText] and will ensure that the keyboard is + /// showing when it is tapped by calling [EditableTextState.requestKeyboard()]. + final FocusNode? focusNode; + + /// {@macro flutter.widgets.editableText.onSubmitted} + /// + /// See also: + /// + /// * [EditableText.onSubmitted] for an example of how to handle moving to + /// the next/previous field when using [TextInputAction.next] and + /// [TextInputAction.previous] for [textInputAction]. + final void Function(String)? onSubmitted; + + /// If false the widget is "disabled": it ignores taps, the [TextFormField]'s + /// [decoration] is rendered in grey, + /// [decoration]'s [InputDecoration.counterText] is set to `""`, + /// and the drop down icon is hidden no matter [showDropdownIcon] value. + /// + /// If non-null this property overrides the [decoration]'s + /// [Decoration.enabled] property. + final bool enabled; + + /// The appearance of the keyboard. + /// + /// This setting is only honored on iOS devices. + /// + /// If unset, defaults to the brightness of [ThemeData.brightness]. + final Brightness? keyboardAppearance; + + /// Initial Value for the field. + /// This property can be used to pre-fill the field. + final String? initialValue; + + final String languageCode; + + /// 2 letter ISO Code or country dial code. + /// + /// ```dart + /// initialCountryCode: 'IN', // India + /// initialCountryCode: '+225', // Côte d'Ivoire + /// ``` + final String? initialCountryCode; + + /// List of Country to display see countries.dart for format + final List? countries; + + /// The decoration to show around the text field. + /// + /// By default, draws a horizontal line under the text field but can be + /// configured to show an icon, label, hint text, and error text. + /// + /// Specify null to remove the decoration entirely (including the + /// extra padding introduced by the decoration to save space for the labels). + final InputDecoration decoration; + + /// The style to use for the text being edited. + /// + /// This text style is also used as the base style for the [decoration]. + /// + /// If null, defaults to the `subtitle1` text style from the current [Theme]. + final TextStyle? style; + + /// Disable view Min/Max Length check + final bool disableLengthCheck; + + /// Won't work if [enabled] is set to `false`. + final bool showDropdownIcon; + + final BoxDecoration dropdownDecoration; + + /// The style use for the country dial code. + final TextStyle? dropdownTextStyle; + + /// {@macro flutter.widgets.editableText.inputFormatters} + final List? inputFormatters; + + /// The text that describes the search input field. + /// + /// When the input field is empty and unfocused, the label is displayed on top of the input field (i.e., at the same location on the screen where text may be entered in the input field). + /// When the input field receives focus (or if the field is non-empty), the label moves above (i.e., vertically adjacent to) the input field. + final String searchText; + + /// Position of an icon [leading, trailing] + final IconPosition dropdownIconPosition; + + /// Icon of the drop down button. + /// + /// Default is [Icon(Icons.arrow_drop_down)] + final Icon dropdownIcon; + + /// Whether this text field should focus itself if nothing else is already focused. + final bool autofocus; + + /// Autovalidate mode for text form field. + /// + /// If [AutovalidateMode.onUserInteraction], this FormField will only auto-validate after its content changes. + /// If [AutovalidateMode.always], it will auto-validate even without user interaction. + /// If [AutovalidateMode.disabled], auto-validation will be disabled. + /// + /// Defaults to [AutovalidateMode.onUserInteraction]. + final AutovalidateMode? autovalidateMode; + + /// Whether to show or hide country flag. + /// + /// Default value is `true`. + final bool showCountryFlag; + + /// Message to be displayed on autoValidate error + /// + /// Default value is `Invalid Mobile Number`. + final String? invalidNumberMessage; + + /// The color of the cursor. + final Color? cursorColor; + + /// How tall the cursor will be. + final double? cursorHeight; + + /// How rounded the corners of the cursor should be. + final Radius? cursorRadius; + + /// How thick the cursor will be. + final double cursorWidth; + + /// Whether to show cursor. + final bool? showCursor; + + /// The padding of the Flags Button. + /// + /// The amount of insets that are applied to the Flags Button. + /// + /// If unset, defaults to [EdgeInsets.zero]. + final EdgeInsetsGeometry flagsButtonPadding; + + /// The type of action button to use for the keyboard. + final TextInputAction? textInputAction; + + /// Optional set of styles to allow for customizing the country search + /// & pick dialog + final PickerDialogStyle? pickerDialogStyle; + + /// The margin of the country selector button. + /// + /// The amount of space to surround the country selector button. + /// + /// If unset, defaults to [EdgeInsets.zero]. + final EdgeInsets flagsButtonMargin; + + /// Enable the autofill hint for phone number. + final bool disableAutoFillHints; + + /// If null, default magnification configuration will be used. + final TextMagnifierConfiguration? magnifierConfiguration; + + const IntlPhoneField({ + Key? key, + this.formFieldKey, + this.initialCountryCode, + this.languageCode = 'en', + this.disableAutoFillHints = false, + this.obscureText = false, + this.textAlign = TextAlign.left, + this.textAlignVertical, + this.onTap, + this.readOnly = false, + this.initialValue, + this.keyboardType = TextInputType.phone, + this.controller, + this.focusNode, + this.decoration = const InputDecoration(), + this.style, + this.dropdownTextStyle, + this.onSubmitted, + this.validator, + this.onChanged, + this.countries, + this.onCountryChanged, + this.onSaved, + this.showDropdownIcon = true, + this.dropdownDecoration = const BoxDecoration(), + this.inputFormatters, + this.enabled = true, + this.keyboardAppearance, + @Deprecated('Use searchFieldInputDecoration of PickerDialogStyle instead') + this.searchText = 'Search country', + this.dropdownIconPosition = IconPosition.leading, + this.dropdownIcon = const Icon(Icons.arrow_drop_down), + this.autofocus = false, + this.textInputAction, + this.autovalidateMode = AutovalidateMode.onUserInteraction, + this.showCountryFlag = true, + this.cursorColor, + this.disableLengthCheck = false, + this.flagsButtonPadding = EdgeInsets.zero, + this.invalidNumberMessage = 'Invalid Mobile Number', + this.cursorHeight, + this.cursorRadius = Radius.zero, + this.cursorWidth = 2.0, + this.showCursor = true, + this.pickerDialogStyle, + this.flagsButtonMargin = EdgeInsets.zero, + this.magnifierConfiguration, + }) : super(key: key); + + @override + State createState() => _IntlPhoneFieldState(); +} + +class _IntlPhoneFieldState extends State { + late List _countryList; + late Country _selectedCountry; + late List filteredCountries; + late String number; + + String? validatorMessage; + + @override + void initState() { + super.initState(); + _countryList = widget.countries ?? countries; + filteredCountries = _countryList; + number = widget.initialValue ?? ''; + if (widget.initialCountryCode == null && number.startsWith('+')) { + number = number.substring(1); + // parse initial value + _selectedCountry = countries.firstWhere( + (country) => number.startsWith(country.fullCountryCode), + orElse: () => _countryList.first); + + // remove country code from the initial number value + number = number.replaceFirst( + RegExp("^${_selectedCountry.fullCountryCode}"), ""); + } else { + _selectedCountry = _countryList.firstWhere( + (item) => item.code == (widget.initialCountryCode ?? 'US'), + orElse: () => _countryList.first); + + // remove country code from the initial number value + if (number.startsWith('+')) { + number = number.replaceFirst( + RegExp("^\\+${_selectedCountry.fullCountryCode}"), ""); + } else { + number = number.replaceFirst( + RegExp("^${_selectedCountry.fullCountryCode}"), ""); + } + } + + if (widget.autovalidateMode == AutovalidateMode.always) { + final initialPhoneNumber = PhoneNumber( + countryISOCode: _selectedCountry.code, + countryCode: '+${_selectedCountry.dialCode}', + number: widget.initialValue ?? '', + ); + + final value = widget.validator?.call(initialPhoneNumber); + + if (value is String) { + validatorMessage = value; + } else { + (value as Future).then((msg) { + validatorMessage = msg; + }); + } + } + } + + Future _changeCountry() async { + filteredCountries = _countryList; + await showDialog( + context: context, + useRootNavigator: false, + builder: (context) => StatefulBuilder( + builder: (ctx, setState) => CountryPickerDialog( + languageCode: widget.languageCode.toLowerCase(), + style: widget.pickerDialogStyle, + filteredCountries: filteredCountries, + searchText: widget.searchText, + countryList: _countryList, + selectedCountry: _selectedCountry, + onCountryChanged: (Country country) { + _selectedCountry = country; + widget.onCountryChanged?.call(country); + setState(() {}); + }, + ), + ), + ); + if (mounted) setState(() {}); + } + + @override + Widget build(BuildContext context) { + return TextFormField( + key: widget.formFieldKey, + initialValue: (widget.controller == null) ? number : null, + autofillHints: widget.disableAutoFillHints + ? null + : [AutofillHints.telephoneNumberNational], + readOnly: widget.readOnly, + obscureText: widget.obscureText, + textAlign: widget.textAlign, + textAlignVertical: widget.textAlignVertical, + cursorColor: widget.cursorColor, + onTap: widget.onTap, + controller: widget.controller, + focusNode: widget.focusNode, + cursorHeight: widget.cursorHeight, + cursorRadius: widget.cursorRadius, + cursorWidth: widget.cursorWidth, + showCursor: widget.showCursor, + onFieldSubmitted: widget.onSubmitted, + magnifierConfiguration: widget.magnifierConfiguration, + decoration: widget.decoration.copyWith( + prefixIcon: _buildFlagsButton(), + counterText: !widget.enabled ? '' : null, + ), + style: widget.style, + onSaved: (value) { + widget.onSaved?.call( + PhoneNumber( + countryISOCode: _selectedCountry.code, + countryCode: + '+${_selectedCountry.dialCode}${_selectedCountry.regionCode}', + number: value!, + ), + ); + }, + onChanged: (value) async { + final phoneNumber = PhoneNumber( + countryISOCode: _selectedCountry.code, + countryCode: '+${_selectedCountry.fullCountryCode}', + number: value, + ); + + if (widget.autovalidateMode != AutovalidateMode.disabled) { + validatorMessage = await widget.validator?.call(phoneNumber); + } + + widget.onChanged?.call(phoneNumber); + }, + validator: (value) { + if (value == null || !isNumeric(value)) return validatorMessage; + if (!widget.disableLengthCheck) { + return value.length >= _selectedCountry.minLength && + value.length <= _selectedCountry.maxLength + ? null + : widget.invalidNumberMessage; + } + + return validatorMessage; + }, + maxLength: widget.disableLengthCheck ? null : _selectedCountry.maxLength, + keyboardType: widget.keyboardType, + inputFormatters: widget.inputFormatters, + enabled: widget.enabled, + keyboardAppearance: widget.keyboardAppearance, + autofocus: widget.autofocus, + textInputAction: widget.textInputAction, + autovalidateMode: widget.autovalidateMode, + ); + } + + Container _buildFlagsButton() { + return Container( + margin: widget.flagsButtonMargin, + child: DecoratedBox( + decoration: widget.dropdownDecoration, + child: InkWell( + borderRadius: widget.dropdownDecoration.borderRadius as BorderRadius?, + onTap: widget.enabled ? _changeCountry : null, + child: Padding( + padding: widget.flagsButtonPadding, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox( + width: 4, + ), + if (widget.enabled && + widget.showDropdownIcon && + widget.dropdownIconPosition == IconPosition.leading) ...[ + widget.dropdownIcon, + const SizedBox(width: 4), + ], + if (widget.showCountryFlag) ...[ + kIsWeb + ? Image.asset( + 'assets/flags/${_selectedCountry.code.toLowerCase()}.png', + package: 'intl_phone_field', + width: 32, + ) + : Text( + _selectedCountry.flag, + style: const TextStyle(fontSize: 18), + ), + const SizedBox(width: 8), + ], + FittedBox( + child: Text( + '+${_selectedCountry.dialCode}', + style: widget.dropdownTextStyle, + ), + ), + if (widget.enabled && + widget.showDropdownIcon && + widget.dropdownIconPosition == IconPosition.trailing) ...[ + const SizedBox(width: 4), + widget.dropdownIcon, + ], + const SizedBox(width: 8), + ], + ), + ), + ), + ), + ); + } +} + +enum IconPosition { + leading, + trailing, +} diff --git a/lib/controller/local/phone_intel/phone_number.dart b/lib/controller/local/phone_intel/phone_number.dart new file mode 100644 index 0000000..912cb07 --- /dev/null +++ b/lib/controller/local/phone_intel/phone_number.dart @@ -0,0 +1,79 @@ +import 'countries.dart'; + +class NumberTooLongException implements Exception {} + +class NumberTooShortException implements Exception {} + +class InvalidCharactersException implements Exception {} + +class PhoneNumber { + String countryISOCode; + String countryCode; + String number; + + PhoneNumber({ + required this.countryISOCode, + required this.countryCode, + required this.number, + }); + + factory PhoneNumber.fromCompleteNumber({required String completeNumber}) { + if (completeNumber == "") { + return PhoneNumber(countryISOCode: "", countryCode: "", number: ""); + } + + try { + Country country = getCountry(completeNumber); + String number; + if (completeNumber.startsWith('+')) { + number = completeNumber.substring(1 + country.dialCode.length + country.regionCode.length); + } else { + number = completeNumber.substring(country.dialCode.length + country.regionCode.length); + } + return PhoneNumber( + countryISOCode: country.code, countryCode: country.dialCode + country.regionCode, number: number); + } on InvalidCharactersException { + rethrow; + // ignore: unused_catch_clause + } on Exception catch (e) { + return PhoneNumber(countryISOCode: "", countryCode: "", number: ""); + } + } + + bool isValidNumber() { + Country country = getCountry(completeNumber); + if (number.length < country.minLength) { + throw NumberTooShortException(); + } + + if (number.length > country.maxLength) { + throw NumberTooLongException(); + } + return true; + } + + String get completeNumber { + return countryCode + number; + } + + static Country getCountry(String phoneNumber) { + if (phoneNumber == "") { + throw NumberTooShortException(); + } + + final validPhoneNumber = RegExp(r'^[+0-9]*[0-9]*$'); + + if (!validPhoneNumber.hasMatch(phoneNumber)) { + throw InvalidCharactersException(); + } + + if (phoneNumber.startsWith('+')) { + return countries + .firstWhere((country) => phoneNumber.substring(1).startsWith(country.dialCode + country.regionCode)); + } + return countries.firstWhere((country) => phoneNumber.startsWith(country.dialCode + country.regionCode)); + } + + @override + String toString() => 'PhoneNumber(countryISOCode: $countryISOCode, countryCode: $countryCode, number: $number)'; +} diff --git a/lib/controller/local/translations.dart b/lib/controller/local/translations.dart index 335be84..c0d2313 100644 --- a/lib/controller/local/translations.dart +++ b/lib/controller/local/translations.dart @@ -4,7 +4,52 @@ class MyTranslation extends Translations { @override Map> get keys => { "ar": { + "Home Page": "الصفحة الرئيسية", + "To change Language the App": "لتغيير لغة التطبيق", + "Learn more about our app and mission": + "تعرف على المزيد حول تطبيقنا ورسالتنا", + "Promos For Today": "عروض اليوم", + 'Bonus gift': 'بونص', "Pay": "ادفع", + "Get": "احصل على", + "Send to Driver Again": "إرسال إلى السائق مرة أخرى", + "Driver Name:": "اسم السائق:", + 'No trip data available': "لا توجد بيانات رحلة متاحة", + "Car Plate:": "رقم اللوحة:", "remaining": "متبقي", + "Order Cancelled": "تم إلغاء الطلب", + 'You canceled VIP trip': "ألغيت الرحلة", + "Passenger cancelled order": "الراكب قام بإلغاء الطلب", + "Your trip is scheduled": "رحلتك مجدولة", + "Don't forget your ride!": "لا تنسَ رحلتك!", + "Trip updated successfully": "تم تحديث الرحلة بنجاح", + "Car Make:": "ماركة السيارة:", + "Car Model:": "طراز السيارة:", "Car Color:": "لون السيارة:", + "Driver Phone:": "رقم هاتف السائق:", + 'Pre-booking': 'احجز مسبقًا', "Waiting VIP": "انتظار VIP", + "Driver List": "قائمة السائقين", "Confirm Trip": "تأكيد الرحلة", + "Select date and time of trip": "حدد تاريخ ووقت الرحلة", + "Date and Time Picker": "اختيار التاريخ والوقت", + "Trip Status:": "حالة الرحلة:", "pending": "قيد الانتظار", + "accepted": "تم القبول", + "rejected": "تم الرفض", + "Scheduled Time:": "الوقت المحدد:", + "No drivers available": "لا يوجد سائقين متاحين", + "Please try again in a few moments": + "يرجى المحاولة مرة أخرى بعد قليل", + "Unknown Driver": "سائق غير معروف", + "rides": "الرحلات", + "The reason is": "السبب هو", + "User does not have a wallet #1652": "المستخدم ليس لديه محفظة ", + "Price of trip": "سعر الرحلة", + "For Speed and Delivery trips, the price is calculated dynamically. For Comfort trips, the price is based on time and distance": + "بالنسبة لرحلات السرعة والتوصيل، يتم حساب السعر ديناميكياً. بالنسبة لرحلات الراحة، يتم حساب السعر بناءً على الوقت والمسافة", + "Phone Wallet Saved Successfully": "تم حفظ المحفظة الهاتفية بنجاح", + "Add wallet phone you use": "أضف محفظة الهاتف التي تستخدمها", "Update Available": "تحديث متوفر", + 'Phone number must be exactly 11 digits long': + "رقم الهاتف يجب أن يكون بطول 11 رقماً", + 'Insert Wallet phone number': 'أدخل رقم هاتف المحفظة', + "Phone number isn't an Egyptian phone number": + "رقم الهاتف ليس رقم هاتف مصري", "A new version of the app is available. Please update to the latest version.": "تتوفر نسخة جديدة من التطبيق. يرجى التحديث إلى أحدث إصدار.", "We use location to get accurate and nearest passengers for you": @@ -41,14 +86,20 @@ class MyTranslation extends Translations { "Pick from map destination": "حدد وجهتك على الخريطة", "Pick or Tap to confirm": "حدد أو انقر للتأكيد", "Select Order Type": "حدد نوع الطلب", + 'Accepted your order': "تم قبول طلبك", "Choose who this order is for": "اختر لمن هذا الطلب", + "Order Accepted": "تم قبول الطلب", "with type": "مع النوع", + "accepted your order at price": "قبل طلبك بالسعر", "I want to order for myself": "أريد أن أطلب لنفسي", "I want to order for someone else": "أريد أن أطلب لشخص آخر", "Cancel Trip from driver": "إلغاء الرحلة من السائق", + "Order Cancelled": "تم إلغاء الطلب", + "you canceled order": "لقد قمت بإلغاء الطلب", "If you want order to another person": "إذا كنت تريد الطلب لشخص آخر", - "We will look for a new driver.\nPlease wait.": - "سنبحث عن سائق جديد.\nمن فضلك انتظر.", + "Ok I will go now.": "حسنًا، سأذهب الآن.", + "Hi, I will go now": "مرحبًا، سأذهب الآن.", "upgrade price": "رفع السعر", + 'Please enter a correct phone': 'يرجى إدخال رقم هاتف صحيح', 'airport': 'مطار', "Best choice for a comfortable car with a flexible route and stop points. This airport offers visa entry at this price.": "أفضل اختيار لسيارة مريحة مع طريق ونقاط توقف مرنة. يقدم هذا المطار تأشيرة دخول بهذا السعر.", @@ -224,15 +275,76 @@ iOS [https://getapp.cc/app/6458734951] "Capture an Image of Your ID Document front": "التقط صورة للواجهة الأمامية لوثيقة هويتك", "NationalID": "الرقم القومي", + 'You can share the SEFER App with your friends and earn rewards for rides they take using your code': + 'يمكنك مشاركة تطبيق SEFER مع أصدقائك وكسب مكافآت من الرحلات التي يقومون بها باستخدام كودك.', "FullName": "الاسم الكامل", + "No invitation found yet!": "لم يتم العثور على دعوات حتى الآن!", "InspectionResult": "نتيجة الفحص", - "Criminal Record": "السجل الجنائي", + "Criminal Record": "السجل الجنائي", 'Share App': 'شارك التطبيق', "The email or phone number is already registered.": "البريد الإلكتروني أو رقم الهاتف مسجل بالفعل.", 'To become a ride-sharing driver on the Sefer app, you need to upload your driver\'s license, ID document, and car registration document. Our AI system will instantly review and verify their authenticity in just 2-3 minutes. If your documents are approved, you can start working as a driver on the Sefer app. Please note, submitting fraudulent documents is a serious offense and may result in immediate termination and legal consequences.': 'لِتُصْبِحَ سَائِقَاً لِلرُّكوبِ المُشْتَرَكِ عَلَى تَطْبِيق سَفَر، يَجِبُ عَلَيْكَ تَحْمِيل رُخْصَةِ القِيَادَةِ، وَثِيقَةِ الهُوِيَّةِ، وَوَثِيقَةَ تَسْجِيل السَّيَّارَةِ. سَيَقُومُ نِظَامُ الذَّكَاءِ الاِصْطِنَاعِيِّ لَدَيْنَا بِمُرَاجَعَةِ وَتَحْقِيقِ صِحَّةِ الوَثَائِقِ فِي غُضُونِ ٢-٣ دَقَائِقَ فَقَطْ. إِذَا تَمَّتْ المُوَافَقَةُ عَلَى وَثَائِقِكَ، يُمْكِنُكَ البَدْءُ فِي العَمَلِ كَسَائِقٍ عَلَى تَطْبِيق سَفَر. يُرْجَى مُلَاحَظَةُ، تَقْدِيمُ وَثَائِقَ مُزَورَةٍ يُعَدُّ جَرِيمَةً خَطِيرَةً وَقَدْ يَتَرَتَّبُ عَلَيْهِ اِنهَاءُ الحِسَابِ فَوْرِيَّاً وَعَوَاقِبُ قَانُونِيَّة.', "Documents check": "فحص الوثائق", "Driver's License": "رخصة القيادة", + + "for your first registration!": "للتسجيل الأول!", + "Get it Now!": "احصل عليه الآن!", + "before": "قبل", + "Code not approved": "الرمز غير موافق عليه", + "3000 LE": "3000 جنيه مصري", + "Do you have an invitation code from another driver?": + "هل لديك كود دعوة من سائق آخر؟", + "Paste the code here": "الصق الكود هنا", + "No, I don't have a code": "لا، لا أملك كودا", + "Code approved": "تمت الموافقة على الكود", + "Install our app:": "قم بتثبيت تطبيقنا:", + "Invite another driver and both get a gift after he completes 100 trips!": + "ادع صديقًا ليكون سائقًا واحصلا على هدية بعد إكماله 100 مشوار!", + "Share App": "شارك التطبيق", + "Invite": "دعوة", "Are you sure?": "هل أنت متأكد؟", + "This will delete all recorded files from your device.": + "سيؤدي هذا إلى حذف جميع الملفات المسجلة من جهازك.", + "Select a file": "اختر ملفاً", + "Select a File": "اختر ملفاً", "Delete": "حذف", + 'attach audio of complain': 'إرفاق صوت للشكوى', + "Phone Number Check": "فحص رقم الهاتف", + "Drivers received orders": "السائقون استقبلوا الطلبات", + 'No audio files recorded.': 'لا توجد ملفات صوتية مسجلة.', + 'This is for delivery or a motorcycle.': + "هذا للتوصيل أو للدراجة النارية.", + "We will look for a new driver.\nPlease wait.": + "سوف نبحث عن سائق جديد.\nيرجى الانتظار", + "Sefer Reminder": "تطبيق سفر", + "It's time to check the Sefer app!": "حان وقت استخدام تطبيق سفر", + "The email or phone number is already registered.": + "البريد الإلكتروني أو رقم الهاتف مسجل بالفعل.", + "you must insert token code": "يجب إدخال رمز التحقق.", + "Something went wrong. Please try again.": + "حدث خطأ ما. يرجى المحاولة مرة أخرى.", + "This is for delivery or a motorcycle.": + "هذا للتوصيل أو للدراجة النارية.", + "Trip Details": "تفاصيل الرحلة", + 'The context does not provide any complaint details, so I cannot provide a solution to this issue. Please provide the necessary information, and I will be happy to assist you.': + "لا تتوفر تفاصيل الشكوى في السياق، لذا لا أستطيع تقديم حل لهذه المشكلة. يرجى تقديم المعلومات اللازمة، وسأكون سعيدًا بمساعدتك", + 'Submit Your Complaint': "أرسل شكواك", + "Date": "التاريخ", + "Price": "السعر", + "Status": "الحالة", + "Choose from contact": "اختر من جهات الاتصال", + 'attach correct audio': "إرفاق صوت للشكوى", + 'be sure': 'كن متأكدًا', + 'Audio uploaded successfully.': 'تم رفع الصوت بنجاح', + "Perfect for passengers seeking the latest car models with the freedom to choose any route they desire": + "مثالي للركاب الذين يبحثون عن أحدث موديلات السيارات مع حرية اختيار أي طريق يرغبون به", + "Share this code with your friends and earn rewards when they use it!": + "شارك هذا الرمز مع أصدقائك واحصل على مكافآت عند استخدامهم له!", + "Enter phone": "أدخل رقم الهاتف", + 'You deserve the gift': "أنت تستحق الهدية", + "complete, you can claim your gift": " يمكنك المطالبة بهديتك", + "When": "‏عندما يكمل", + "Enter driver's phone": "أدخل رقم هاتف السائق", + "Send Invite": "أرسل الدعوة", "Show Invitations": "عرض الدعوات", "License Type": "نوع الرخصة", "National Number": "الرقم الوطني", "Name (Arabic)": "الاسم بالعربي", @@ -369,7 +481,7 @@ iOS [https://getapp.cc/app/6458734951] "Go to this Target": "الانْتِقَال إِلَى هَذَا الهَدَف", "My Profile": "مَلَفِي الشَّخْصِي", "Sign Out": "تَسْجِيل الخُرُوج", - "Home Page": "الصَّفْحَة الرَّئِيسِيَّة", + "Are you want to go to this site": "هَل تَرْغَب فِي الانْتِقَال إِلَى هَذَا المَوْقِع", "MyLocation": "مَوْقِعِي", @@ -446,11 +558,11 @@ iOS [https://getapp.cc/app/6458734951] "Bachelor's Degree": "بَكَالُورِيُوس", "Master's Degree": "مَاجِسْتِير", "Doctoral Degree": "دُكْتُورَاه", - "Promos For today": "الْعُرُوض التَّرْوِيجِيَّة لِلْيَوْم", + "Copy this Promo to use it in your Ride!": "انْسَخْ هَذَا الْعَرْض لِاسْتِخْدَامِهِ فِي رِحْلَتِك!", "To change some Settings": "لِتَغْيِير بَعْض الإِعْدَادَات", - "To change Language the App": "لِتَغْيِير لُغَة التَّطْبِيق", + "Order Request Page": "صَفْحَة طَلَب الطَّلَب", "Rouats of Trip": "طُرُق الرِّحْلَة", "Passenger Name is ": "اسْم الرَّاكِب هُوَ ", @@ -503,6 +615,30 @@ iOS [https://getapp.cc/app/6458734951] "Do you want to pay Tips for this Driver": "هَل تُرِيد دَفْع أُكْرَامِيَّة لِهَذَا السَّائِق؟", "Tip is ": " مَبْلَغ الأُكْرَامِيَّة هُوَ", + "Are you sure to delete this location?": + "هل أنت متأكد من حذف هذا الموقع؟", + 'Are you want to wait drivers to accept your order': + 'هل تريد الانتظار حتى يقبل السائقون طلبك؟', + + "deleted": "تم الحذف", + 'Trip is begin': "الرحلة قد بدأت", + 'This price is fixed even if the route changes for the driver.': + "هذا السعر مثبت حتى لو تغير المسار للسائق", + 'The price may increase if the route changes.': + "احتمالية زيادة السعر عند تغيير المسار", + "The captain is responsible for the route.": + "الكابتن مسؤول عن المسار", + 'Your order is being prepared': "جاري تجهيز الطلب", + 'The drivers are reviewing your request': 'يدرس السائقين طلبك', + 'Your order sent to drivers': 'تم إرسال طلبك إلى السائقين', + "يمكنك الاتصال أو تسجيل صوت لهذه الرحلة": + "You can call or record audio of this trip", + + 'The trip has started! Feel free to contact emergency numbers, share your trip, or activate voice recording for the journey': + "بدأت الرحلة! لا تتردد في الاتصال بأرقام الطوارئ، مشاركة رحلتك، أو تفعيل التسجيل الصوتي للرحلة", + 'Please make sure you have all your personal belongings and that any remaining fare, if applicable, has been added to your wallet before leaving. Thank you for choosing the Sefer app': + 'الرجاء التأكد من جميع أغراضك الشخصية وأنه تم إضافة باقي الأجرة إن وجد إلى محفظتك قبل النزول. شكرا لاختيارك تطبيق سفر', + 'Don’t forget your personal belongings.': "لا تنسى متعلقاتك الشخصية", "Tip is": " مَبْلَغ الأُكْرَامِيَّة هُوَ", "Camera Access Denied.": "تَمَّ رَفْض الْوُصُول إِلَى الْكَامِيرَا.", "Open Settings": "افْتَحْ الإِعْدَادَات", @@ -596,6 +732,45 @@ iOS [https://getapp.cc/app/6458734951] 'الرَّجَاء التَّحَرُّك إِلَى السَّيَّارَة الآن', 'You will receive a code in WhatsApp Messenger': "سوف تتلقى رمزًا في واتساب ماسنجر", + 'If you need assistance, contact us': + "إذا كنت بحاجة إلى المساعدة، تواصل معنا", + "Promo Ended": "انتهى العرض", + 'Enter the promo code and get': 'أدخل رمز الترويج واحصل على', + 'DISCOUNT': 'خصم', + 'No wallet record found': 'لم يتم العثور على سجل محفظة', + 'for': 'لمدة', + "SEFER is the safest ride-sharing app that introduces many features for both captains and passengers. We offer the lowest commission rate of just 8%, ensuring you get the best value for your rides. Our app includes insurance for the best captains, regular maintenance of cars with top engineers, and on-road services to ensure a respectful and high-quality experience for all users.": + "سفر هو التطبيق الأكثر أمانًا لمشاركة الركوب الذي يقدم العديد من الميزات لكل من السائقين والركاب. نحن نقدم أقل عمولة بنسبة 8% فقط، مما يضمن حصولك على أفضل قيمة لرحلاتك. يتضمن تطبيقنا التأمين لأفضل السائقين، الصيانة المنتظمة للسيارات مع أفضل المهندسين، والخدمات على الطريق لضمان تجربة محترمة وعالية الجودة لجميع المستخدمين.", + "You can contact us during working hours from 12:00 - 19:00.": + "يمكنك الاتصال بنا خلال ساعات العمل من 12:00 - 7:00.", + "Contact Us": "اتصل بنا", + 'Choose a contact option': 'اختر خيار الاتصال', + 'Work time is from 12:00 - 19:00.\nYou can send a WhatsApp message or email.': + 'ساعات العمل من 12:00 - 19:00.\nيمكنك إرسال رسالة عبر واتساب أو بريد إلكتروني.', + 'Promo code copied to clipboard!': "'تم نسخ رمز العرض إلى الحافظة!'", + 'Copy Code': 'نسخ الرمز', + "Your invite code was successfully applied!": + "تم تطبيق رمز الدعوة بنجاح!", + "Payment Options": " خيارات الدفع", + "wait 1 minute to receive message": + "انتظر دقيقة واحدة لاستلام الرسالة", + 'Promo Copied!': 'تم نسخ العرض!', + 'You have copied the promo code.': 'لقد قمت بنسخ رمز العرض.', + 'Valid Until:': 'لمدة:', + "Select Payment Amount": " اختر مبلغ الدفع", + "The promotion period has ended.": "انتهت فترة العرض.", + "Promo Code Accepted": "تم قبول كود العرض", + 'Tap on the promo code to copy it!': 'اضغط على رمز العرض لنسخه!', + "Lowest Price Achieved": "تم الوصول إلى أدنى سعر", + "Cannot apply further discounts.": + "لا يمكن تطبيق المزيد من الخصومات.", + "Promo Already Used": "تم استخدام كود العرض بالفعل", + 'Invitation Used': "تم استخدام الدعوة", + "You have already used this promo code.": + "لقد استخدمت هذا الكود بالفعل.", + "Insert Your Promo Code": "أدخل كود العرض الخاص بك", + "Enter promo code here": "أدخل كود العرض هنا", + "Please enter a valid promo code": "يرجى إدخال كود عرض صالح", 'Awfar Car': 'أوفر كار', "Old and affordable, perfect for budget rides.": "سيارة ميسورة التكلفة، مثالية للرحلات الاقتصادية.", @@ -656,7 +831,7 @@ iOS [https://getapp.cc/app/6458734951] 'Selected Date and Time': "التاريخ والوقت المحددان", "Lets check Car license ": "دَعْنَا نَتَحَقَّق مِن رُخْصَة السَّيَّارَة ", - 'Driver List': 'قائمة السائقين', + // 'Driver List': 'قائمة السائقين', 'Car': 'السيارة', 'Plate': 'لوحة السيارة', 'N/A': 'غير متوفر', diff --git a/lib/controller/notification/passenger_notification_controller.dart b/lib/controller/notification/passenger_notification_controller.dart index 39a725b..062e162 100644 --- a/lib/controller/notification/passenger_notification_controller.dart +++ b/lib/controller/notification/passenger_notification_controller.dart @@ -1,13 +1,12 @@ import 'dart:convert'; +import 'package:SEFER/views/widgets/my_dialog.dart'; import 'package:get/get.dart'; import 'package:SEFER/controller/firebase/firbase_messge.dart'; import '../../constant/box_name.dart'; import '../../constant/links.dart'; -import '../../constant/style.dart'; import '../../main.dart'; -import '../../views/widgets/elevated_btn.dart'; import '../functions/crud.dart'; class PassengerNotificationController extends GetxController { @@ -21,22 +20,17 @@ class PassengerNotificationController extends GetxController { link: AppLink.getNotificationPassenger, payload: {'passenger_id': box.read(BoxName.passengerID)}); if (res == "failure") { - Get.defaultDialog( - title: 'There is no notification yet'.tr, - titleStyle: AppStyle.title, - middleText: '', - confirm: MyElevatedButton( - title: 'Back', - onPressed: () { - Get.back(); - Get.back(); - })); + MyDialog().getDialog('There is no notification yet'.tr, '', () { + Get.back(); + Get.back(); + }); + } else { + notificationData = jsonDecode(res); + isloading = false; + update(); } - notificationData = jsonDecode(res); - // sql.insertData(notificationData['message'], TableName.captainNotification); - isloading = false; - update(); + // sql.insertData(notificationData['message'], TableName.captainNotification); } updateNotification(String id) async { diff --git a/lib/controller/payment/passenger_wallet_history_controller.dart b/lib/controller/payment/passenger_wallet_history_controller.dart index 3ca4db3..0f7abd6 100644 --- a/lib/controller/payment/passenger_wallet_history_controller.dart +++ b/lib/controller/payment/passenger_wallet_history_controller.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:SEFER/constant/style.dart'; import 'package:SEFER/views/widgets/elevated_btn.dart'; +import 'package:SEFER/views/widgets/my_dialog.dart'; import 'package:get/get.dart'; import 'package:SEFER/constant/box_name.dart'; import 'package:SEFER/constant/links.dart'; @@ -23,17 +24,10 @@ class PassengerWalletHistoryController extends GetxController { isLoading = false; update(); } else { - Get.defaultDialog( - barrierDismissible: false, - title: 'No wallet record found'.tr, - titleStyle: AppStyle.title, - middleText: '', - confirm: MyElevatedButton( - title: 'OK'.tr, - onPressed: () { - Get.back(); - Get.back(); - })); + MyDialog().getDialog('No wallet record found'.tr, '', () { + Get.back(); + Get.back(); + }); } } diff --git a/lib/controller/payment/payment_controller.dart b/lib/controller/payment/payment_controller.dart index 4eb96d4..b4dde90 100644 --- a/lib/controller/payment/payment_controller.dart +++ b/lib/controller/payment/payment_controller.dart @@ -19,6 +19,7 @@ import '../../constant/colors.dart'; import '../../constant/info.dart'; import '../../constant/links.dart'; import '../../main.dart'; +import '../../print.dart'; import '../functions/crud.dart'; import '../functions/toast.dart'; import 'paymob/paymob_wallet.dart'; @@ -31,6 +32,7 @@ class PaymentController extends GetxController { bool isPromoSheetDialogue = false; final formKey = GlobalKey(); final promo = TextEditingController(); + final walletphoneController = TextEditingController(); double totalPassenger = Get.find().totalPassenger; int? selectedAmount = 0; List totalPassengerWalletDetails = []; @@ -166,7 +168,7 @@ class PaymentController extends GetxController { ? '1140' : '0'); - getPassengerWallet(); + // getPassengerWallet(); isLoading = false; update(); @@ -696,25 +698,31 @@ class PaymentController extends GetxController { billingData: PaymobBillingDataWallet(), onPayment: (PaymobResponseWallet response) {}, ); + // Log.print('response.message!: ${response!.responseCode!}'); + // if (response!.success == true && response.responseCode == '200') { - if (response!.success == true && response.responseCode == '200') { - Get.defaultDialog( - barrierDismissible: false, - title: 'Payment Successful'.tr, - titleStyle: AppStyle.title, - content: Text( - 'The payment was approved.'.tr, - style: AppStyle.title, - ), - confirm: MyElevatedButton( - title: 'OK'.tr, - kolor: AppColor.greenColor, - onPressed: () async { - Get.back(); - method(); - }, - ), - ); + if (response!.success == true && + response!.message.toString() == 'Approved') { + // Log.print('transactionID wewer: ${response.transactionID}'); + Toast.show(context, 'Payment Successful'.tr, AppColor.greenColor); + method(); + // Get.defaultDialog( + // barrierDismissible: false, + // title: 'Payment Successful'.tr, + // titleStyle: AppStyle.title, + // content: Text( + // 'The payment was approved.'.tr, + // style: AppStyle.title, + // ), + // confirm: MyElevatedButton( + // title: 'OK'.tr, + // kolor: AppColor.greenColor, + // onPressed: () async { + // Get.back(); + // method(); + // }, + // ), + // ); } else { Get.defaultDialog( barrierDismissible: false, @@ -762,26 +770,29 @@ class PaymentController extends GetxController { billingData: PaymobBillingData(), onPayment: (PaymobResponse response) {}, ); - - if (response!.responseCode == 'APPROVED') { - Get.defaultDialog( - barrierDismissible: false, - title: 'Payment Successful'.tr, - titleStyle: AppStyle.title, - // backgroundColor: AppColor.greenColor, - content: Text( - 'The payment was approved.'.tr, - style: AppStyle.title, - ), - confirm: MyElevatedButton( - kolor: AppColor.greenColor, - title: 'OK'.tr, - onPressed: () async { - Get.back(); - method(); - }, - ), - ); + if (response!.responseCode == '200' && response.success == true) { + // if (response!.success == true && response.responseCode == '200') { + // if (response!.responseCode == 'APPROVED') { + Toast.show(context, 'Payment Successful'.tr, AppColor.greenColor); + method(); + // Get.defaultDialog( + // barrierDismissible: false, + // title: 'Payment Successful'.tr, + // titleStyle: AppStyle.title, + // // backgroundColor: AppColor.greenColor, + // content: Text( + // 'The payment was approved.'.tr, + // style: AppStyle.title, + // ), + // confirm: MyElevatedButton( + // kolor: AppColor.greenColor, + // title: 'OK'.tr, + // onPressed: () async { + // Get.back(); + // method(); + // }, + // ), + // ); } else { Get.defaultDialog( barrierDismissible: false, @@ -801,6 +812,7 @@ class PaymentController extends GetxController { ), ); } + // } } } catch (e) { Get.defaultDialog( diff --git a/lib/controller/payment/paymob/paymob_wallet.dart b/lib/controller/payment/paymob/paymob_wallet.dart index 3e8c3c7..e743bee 100644 --- a/lib/controller/payment/paymob/paymob_wallet.dart +++ b/lib/controller/payment/paymob/paymob_wallet.dart @@ -144,7 +144,7 @@ class PaymobPaymentWallet { }) async { final Map data = { "source": { - "identifier": box.read(BoxName.phone).toString(), + "identifier": box.read(BoxName.phoneWallet).toString(), "subtype": "WALLET", }, "payment_token": paymentToken, @@ -269,7 +269,7 @@ class PaymobBillingDataWallet { "email": box.read(BoxName.email) ?? box.read(BoxName.emailDriver), "first_name": box.read(BoxName.name) ?? box.read(BoxName.nameDriver), "last_name": box.read(BoxName.name) ?? box.read(BoxName.nameDriver), - "phone_number": box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver), + "phone_number": box.read(BoxName.phoneWallet), "apartment": apartment ?? "NA", "floor": floor ?? "NA", "building": building ?? "NA", diff --git a/lib/controller/profile/profile_controller.dart b/lib/controller/profile/profile_controller.dart index 49b759f..e5b893c 100644 --- a/lib/controller/profile/profile_controller.dart +++ b/lib/controller/profile/profile_controller.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'package:SEFER/constant/colors.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:SEFER/constant/box_name.dart'; @@ -48,40 +50,41 @@ class ProfileController extends GetxController { } updatField(String columnName, TextInputType type) async { - Get.defaultDialog( - title: '${'Update'.tr} $columnName', - content: Column( - children: [ - SizedBox( - width: Get.width * .7, - child: MyTextForm( - controller: txtController, - label: 'type here'.tr, - hint: 'type here', - type: type) - // TextField( - // controller: txtController, - // decoration: const InputDecoration( - // border: OutlineInputBorder(), hintText: 'type here'), - // ), - + Get.dialog( + CupertinoAlertDialog( + title: Text('${'Update'.tr} $columnName'), + content: Column( + children: [ + const SizedBox(height: 16), // Add spacing between title and input + CupertinoTextField( + controller: txtController, + placeholder: 'type here'.tr, + keyboardType: type, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + decoration: BoxDecoration( + border: Border.all(color: CupertinoColors.lightBackgroundGray), + borderRadius: BorderRadius.circular(8), ), - MyElevatedButton( - title: 'Update'.tr, - onPressed: () async { - Get.back(); - await updateColumn({ - 'id': box.read(BoxName.passengerID), - columnName: txtController.text, - }); - if (columnName == 'first_name') { - box.write(BoxName.name, txtController.text); - } + ), + const SizedBox(height: 20), + CupertinoButton( + color: AppColor.blueColor, + onPressed: () async { + Get.back(); + await updateColumn({ + 'id': box.read(BoxName.passengerID), + columnName: txtController.text, + }); + if (columnName == 'first_name') { + box.write(BoxName.name, txtController.text); + } - txtController.clear(); - }, - ) - ], + txtController.clear(); + }, + child: Text('Update'.tr), + ), + ], + ), ), ); } diff --git a/lib/controller/rate/rate_conroller.dart b/lib/controller/rate/rate_conroller.dart index aa130b0..4d5ef26 100644 --- a/lib/controller/rate/rate_conroller.dart +++ b/lib/controller/rate/rate_conroller.dart @@ -70,24 +70,39 @@ class RateController extends GetxController { 'token': token1, }); if (res != 'failure') { - FirebaseMessagesController().sendNotificationToAnyWithoutData( + FirebaseMessagesController().sendNotificationToDriverMAP( 'You Have Tips'.tr, '${'${tip.toString()}\$${' tips\nTotal is'.tr}'} ${tip + (Get.find().totalPassenger)}', Get.find().driverToken.toString(), + [], 'ding.wav', ); } } } - await CRUD().post(link: AppLink.addRateToDriver, payload: { - 'passenger_id': box.read(BoxName.passengerID).toString(), - 'driver_id': driverId, - 'ride_id': rideId, - 'rating': selectedRateItemId.toString(), - 'comment': comment.text, - }).then((value) { - Get.find().restCounter(); - Get.offAll(const MapPagePassenger()); - }); + await CRUD().post( + link: "${AppLink.seferCairoServer}/ride/rate/addRateToDriver.php", + payload: { + 'passenger_id': box.read(BoxName.passengerID).toString(), + 'driver_id': driverId.toString(), + 'ride_id': rideId.toString(), + 'rating': selectedRateItemId.toString(), + 'comment': comment.text, + }); + + if (AppLink.endPoint != AppLink.seferCairoServer) { + CRUD().post( + link: "${AppLink.endPoint}/ride/rate/addRateToDriver.php", + payload: { + 'passenger_id': box.read(BoxName.passengerID).toString(), + 'driver_id': driverId.toString(), + 'ride_id': rideId.toString(), + 'rating': selectedRateItemId.toString(), + 'comment': comment.text, + }); + } + + Get.find().restCounter(); + Get.offAll(const MapPagePassenger()); } } diff --git a/lib/main.dart b/lib/main.dart index 7ffd4a8..534e1a2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:math'; import 'package:SEFER/controller/payment/paymob/paymob_response.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -12,6 +13,7 @@ import 'package:flutter/services.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'constant/api_key.dart'; import 'constant/info.dart'; +import 'constant/notification.dart'; import 'controller/firebase/firbase_messge.dart'; import 'controller/firebase/local_notification.dart'; import 'controller/local/local_controller.dart'; @@ -40,10 +42,12 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); WakelockPlus.enable(); // await LocationController().startLocationUpdates(); - if (Platform.isAndroid) { - await NotificationController().initNotifications(); - } + // if (Platform.isAndroid) { + NotificationController notificationController = + Get.put(NotificationController()); + // await NotificationController().initNotifications(); + // } await GetStorage.init(); // Get.put(DriverCallController()); // await AC().gAK(); @@ -77,20 +81,25 @@ void main() async { userTokenExpiration: 200, iFrameID: 837992, ); + PaymobPaymentWallet.instance.initialize( - apiKey: AK - .payMobApikey, // from dashboard Select Settings -> Account Info -> API Key + apiKey: AK.payMobApikey, integrationID: int.parse(AK.integrationIdPayMobWallet), userTokenExpiration: 200, iFrameID: 837992, ); - // Get device information - // List> deviceDataList = - // await DeviceInfoPlus.getDeviceInfo(); - // - // // Print all device data - // DeviceInfoPlus.printDeviceInfo(); + await notificationController.initNotifications(); + // Generate a random index to pick a message + final random = Random(); + final randomMessage = messages[random.nextInt(messages.length)]; + + // Schedule the notification with the random message + notificationController.scheduleNotificationsForSevenDays( + randomMessage.split(':')[0], + randomMessage.split(':')[1], + "tone1", + ); runApp(const MyApp()); } @@ -104,20 +113,20 @@ class MyApp extends StatelessWidget { LocaleController localController = Get.put(LocaleController()); return GetMaterialApp( - title: AppInformation.appName, - translations: MyTranslation(), - debugShowCheckedModeBanner: false, - locale: localController.language, - theme: localController.appTheme, - key: UniqueKey(), - // routes: {'/':const HomePage()}, - home: SplashScreen() - // initialRoute: '/', - // getPages: [ - // GetPage(name: '/', page: () => SplashScreen()), - // GetPage(name: '/tripmonitor', page: () => const TripMonitor()), - // ], - // home: SplashScreen() - ); + title: AppInformation.appName, + translations: MyTranslation(), + debugShowCheckedModeBanner: false, + locale: localController.language, + theme: localController.appTheme, + key: UniqueKey(), + // routes: {'/':const HomePage()}, + // home: LoginCaptin()); + initialRoute: '/', + getPages: [ + GetPage(name: '/', page: () => SplashScreen()), + GetPage(name: '/tripmonitor', page: () => const TripMonitor()), + ], + // home: SplashScreen() + ); } } diff --git a/lib/models/db_sql.dart b/lib/models/db_sql.dart index 0caa829..4178e68 100644 --- a/lib/models/db_sql.dart +++ b/lib/models/db_sql.dart @@ -37,18 +37,21 @@ class DbSql { latitude REAL, longitude REAL, name TEXT UNIQUE, - rate TEXT + rate TEXT, + createdAt TEXT ) '''); + await db.execute('DROP TABLE IF EXISTS ${TableName.recentLocations}'); await db.execute(''' - CREATE TABLE IF NOT EXISTS ${TableName.recentLocations}( - id INTEGER PRIMARY KEY AUTOINCREMENT, - latitude REAL, - longitude REAL, - name TEXT , - rate TEXT - ) - '''); + CREATE TABLE ${TableName.recentLocations}( + id INTEGER PRIMARY KEY AUTOINCREMENT, + latitude REAL, + longitude REAL, + name TEXT, + rate TEXT, + createdAt TEXT + ) +'''); await db.execute(''' CREATE TABLE IF NOT EXISTS ${TableName.driverOrdersRefuse}( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -97,6 +100,34 @@ class DbSql { return await db.insert(table, map); } + Future insertMapLocation(Map map, String table) async { + Database db = await instance.database; + + // Check if the record already exists (based on latitude, longitude, and name) + var existing = await db.query( + table, + where: 'latitude = ? AND longitude = ? AND name = ?', + whereArgs: [map['latitude'], map['longitude'], map['name']], + ); + + if (existing.isNotEmpty) { + // If record exists, update the createdAt field with the current timestamp + var updatedMap = Map.from(map); + updatedMap['createdAt'] = + DateTime.now().toIso8601String(); // Update timestamp + return await db.update( + table, + updatedMap, + where: 'id = ?', + whereArgs: [existing.first['id']], // Update the existing row + ); + } else { + // If record doesn't exist, insert new record with the current timestamp + map['createdAt'] = DateTime.now().toIso8601String(); + return await db.insert(table, map); + } + } + Future updateData(Map map, String table, int id) async { Database db = await instance.database; diff --git a/lib/models/model/painter_copoun.dart b/lib/models/model/painter_copoun.dart new file mode 100644 index 0000000..6149857 --- /dev/null +++ b/lib/models/model/painter_copoun.dart @@ -0,0 +1,276 @@ +import 'dart:math' as math; +import 'package:SEFER/constant/box_name.dart'; +import 'package:SEFER/main.dart'; +import 'package:SEFER/splash_screen_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; + +class CouponPainter extends CustomPainter { + final Color primaryColor; + final Color secondaryColor; + + CouponPainter({ + required this.primaryColor, + required this.secondaryColor, + }); + + @override + void paint(Canvas canvas, Size size) { + final Paint primaryPaint = Paint() + ..color = primaryColor + ..style = PaintingStyle.fill; // + + final Paint secondaryPaint = Paint() + ..color = secondaryColor + ..style = PaintingStyle.fill; + + final Path path = Path(); + + // Draw the main ticket shape + path.moveTo(0, size.height * 0.1); + path.lineTo(size.width * 0.93, size.height * 0.1); + path.arcToPoint( + Offset(size.width, size.height * 0.2), + radius: const Radius.circular(20), + clockwise: false, + ); + path.lineTo(size.width, size.height * 0.8); + path.arcToPoint( + Offset(size.width * 0.93, size.height * 0.9), + radius: const Radius.circular(20), + clockwise: false, + ); + path.lineTo(0, size.height * 0.9); + path.close(); + + canvas.drawPath(path, primaryPaint); + + // Draw decorative circles on the left side + for (int i = 0; i < 5; i++) { + canvas.drawCircle( + Offset(0, size.height * (0.2 + i * 0.15)), + 10, + secondaryPaint, + ); + } + + // Draw a wavy pattern on the right side + final wavePaint = Paint() + ..color = secondaryColor.withOpacity(0.3) + ..style = PaintingStyle.stroke + ..strokeWidth = 2; + + for (int i = 0; i < 20; i++) { + canvas.drawLine( + Offset(size.width * 0.8, i * 10.0), + Offset( + size.width, + i * 10.0 + math.sin(i * 0.5) * 10, + ), + wavePaint, + ); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +class PromoBanner extends StatelessWidget { + final String promoCode; + final String discountPercentage; + final String validity; + + const PromoBanner({ + Key? key, + required this.promoCode, + required this.discountPercentage, + required this.validity, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return CustomPaint( + painter: CouponPainter( + primaryColor: Colors.blue[800]!, + secondaryColor: Colors.white, + ), + child: Container( + width: 320, + height: 240, + padding: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const SizedBox( + height: 10, + ), + Text( + 'Enter the promo code and get'.tr, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + Text( + '${'DISCOUNT'.tr} $discountPercentage ${'for'.tr} $validity' + .toUpperCase(), + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Text( + promoCode, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.blue[800], + ), + ), + ), + const SizedBox(height: 10), + ElevatedButton( + onPressed: () { + // Copy promo code to clipboard + Clipboard.setData(ClipboardData(text: promoCode)); + // Show a Snackbar or other feedback to the user + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Promo code copied to clipboard!'.tr)), + ); + box.write(BoxName.isGiftToken, '1'); + box.write(BoxName.isFirstTime, '1'); + Get.back(); + }, + style: ElevatedButton.styleFrom( + foregroundColor: Colors.blue[800], // Customize the color + backgroundColor: Colors.white, // Customize the background color + ), + child: Text('Copy Code'.tr), + ) + ], + ), + ), + ); + } +} + + + + + +// import 'package:SEFER/constant/colors.dart'; +// import 'package:flutter/cupertino.dart'; +// import 'package:flutter/material.dart'; + +// class CouponPainter extends CustomPainter { +// @override +// void paint(Canvas canvas, Size size) { +// final Paint paint = Paint() +// ..color = AppColor.blueColor +// ..style = PaintingStyle.fill; + +// final Path path = Path(); + +// // Draw the ticket shape (like the image) +// path.moveTo(0, 0); +// path.lineTo(size.width * 0.7, 0); // top left to top right edge + +// // Draw curve for the cut on the right side (ticket look) +// path.arcToPoint(Offset(size.width, size.height * 0.15), +// radius: const Radius.circular(15), clockwise: false); +// path.lineTo(size.width, size.height * 0.85); +// path.arcToPoint(Offset(size.width * 0.7, size.height), +// radius: const Radius.circular(15), clockwise: false); +// path.lineTo(0, size.height); + +// canvas.drawPath(path, paint); +// } + +// @override +// bool shouldRepaint(CustomPainter oldDelegate) { +// return false; +// } +// } + +// class PromoBanner extends StatelessWidget { +// final String promoCode; +// final String discountPercentage; +// final String validity; + +// const PromoBanner({ +// required this.promoCode, +// required this.discountPercentage, +// required this.validity, +// }); + +// @override +// Widget build(BuildContext context) { +// return CustomPaint( +// painter: CouponPainter(), +// child: Container( +// width: 300, // Fixed width for the promo banner +// height: 180, // Set the desired height for your banner +// padding: const EdgeInsets.all(16), +// child: Column( +// mainAxisAlignment: MainAxisAlignment.spaceAround, +// children: [ +// Text( +// 'Enter the promo code and get'.toUpperCase(), +// style: const TextStyle( +// fontSize: 16, +// fontWeight: FontWeight.bold, +// color: Colors.white, +// ), +// textAlign: TextAlign.center, +// ), +// Text( +// '$discountPercentage OFF for $validity'.toUpperCase(), +// style: const TextStyle( +// fontSize: 18, +// fontWeight: FontWeight.bold, +// color: Colors.white, +// ), +// textAlign: TextAlign.center, +// ), +// const SizedBox(height: 10), +// Container( +// decoration: BoxDecoration( +// color: Colors.white, +// borderRadius: BorderRadius.circular(10), +// ), +// padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), +// child: Text( +// promoCode, +// style: TextStyle( +// fontSize: 20, +// fontWeight: FontWeight.bold, +// color: Colors.blue[800], +// ), +// ), +// ), +// ], +// ), +// ), +// ); +// } +// } diff --git a/lib/views/Rate/rate_captain.dart b/lib/views/Rate/rate_captain.dart index b680eb3..e3be4bd 100644 --- a/lib/views/Rate/rate_captain.dart +++ b/lib/views/Rate/rate_captain.dart @@ -40,7 +40,7 @@ class RateDriverFromPassenger extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( - '${'Total price to '.tr}${Get.find().firstName}', + '${'Total price to '.tr}${Get.find().driverName}', style: AppStyle.title, ), Row( diff --git a/lib/views/auth/login_page.dart b/lib/views/auth/login_page.dart index b8d14ce..8cceb25 100644 --- a/lib/views/auth/login_page.dart +++ b/lib/views/auth/login_page.dart @@ -12,11 +12,13 @@ import 'package:SEFER/constant/style.dart'; import 'package:SEFER/main.dart'; import 'package:SEFER/views/widgets/elevated_btn.dart'; import 'package:SEFER/views/widgets/my_scafold.dart'; +import 'package:permission_handler/permission_handler.dart'; import '../../constant/info.dart'; import '../../controller/auth/apple_signin_controller.dart'; import '../../controller/auth/google_sign.dart'; import '../../controller/auth/login_controller.dart'; +import '../home/HomePage/contact_us.dart'; import '../home/profile/passenger_profile_page.dart'; import '../widgets/mycircular.dart'; @@ -218,7 +220,7 @@ class LoginPage extends StatelessWidget { // ), InkWell( onTap: () async { - await GoogleSignInHelper.signInFromLogin(); + await GoogleSignInHelper().signInFromLogin(); }, child: Container( padding: const EdgeInsets.symmetric( @@ -303,6 +305,14 @@ class LoginPage extends StatelessWidget { SizedBox( height: Get.height * .1, ), + GestureDetector( + onTap: () => Get.to(() => ContactUsPage()), + child: Text( + 'If you need assistance, contact us' + .tr, // Improved wording + style: AppStyle.subtitle, + ), + ), // Text( // 'if you dont have account'.tr, // style: AppStyle.subtitle, @@ -447,6 +457,16 @@ class LoginPage extends StatelessWidget { textAlign: TextAlign.center, style: AppStyle.title, ), + TextButton( + onPressed: () { + // Optionally, navigate to app settings for manual permission control + openAppSettings(); + }, + child: Text( + "Open Settings".tr, + style: const TextStyle(color: AppColor.blueColor), + ), + ), MyElevatedButton( title: 'Next'.tr, onPressed: () async { diff --git a/lib/views/auth/sms_verfy_page.dart b/lib/views/auth/sms_verfy_page.dart index e9669ad..837c85f 100644 --- a/lib/views/auth/sms_verfy_page.dart +++ b/lib/views/auth/sms_verfy_page.dart @@ -1,11 +1,17 @@ import 'package:SEFER/constant/style.dart'; import 'package:SEFER/controller/auth/register_controller.dart'; import 'package:SEFER/views/widgets/elevated_btn.dart'; +import 'package:SEFER/views/widgets/my_dialog.dart'; import 'package:SEFER/views/widgets/my_scafold.dart'; import 'package:SEFER/views/widgets/my_textField.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import '../../controller/local/phone_intel/intl_phone_field.dart'; +import '../../print.dart'; +import '../widgets/mycircular.dart'; +// import 'package:intl_phone_field/intl_phone_field.dart'; + class SmsSignupEgypt extends StatelessWidget { SmsSignupEgypt({super.key}); @@ -13,7 +19,7 @@ class SmsSignupEgypt extends StatelessWidget { Widget build(BuildContext context) { Get.put(RegisterController()); return MyScafolld( - title: 'Phone Check'.tr, + title: "Phone Number Check".tr, body: [ GetBuilder(builder: (registerController) { return ListView( @@ -37,62 +43,43 @@ class SmsSignupEgypt extends StatelessWidget { style: AppStyle.title, ), ), - // Enter phone number text - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - child: Text( - 'Enter your phone number'.tr, - textAlign: TextAlign.center, - style: AppStyle.title, - ), - ), // Phone number input field with country code dropdown Padding( padding: const EdgeInsets.all(16.0), - child: !registerController.isSent - ? Form( - key: registerController.formKey3, - child: Row( - children: [ - // Country Code Dropdown - DropdownButton( - value: registerController.selectedCountryCode, - items: registerController.countryCodes - .map((String code) { - return DropdownMenuItem( - value: code, - child: Text(code), - ); - }).toList(), - onChanged: (String? newValue) { - registerController.updateCountryCode(newValue!); - }, - ), - // Spacer - const SizedBox(width: 10), - // Phone Number Input Field - Expanded( - child: MyTextForm( - controller: - registerController.phoneController, - label: 'Enter your phone number'.tr, - hint: 'Enter your phone number'.tr, - type: TextInputType.phone), - ), - ], - ), - ) - : Container( - decoration: AppStyle.boxDecoration1, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - registerController.phoneController.text, - style: AppStyle.title, - ), - ), - ), + child: IntlPhoneField( + decoration: InputDecoration( + labelText: 'Phone Number'.tr, + border: const OutlineInputBorder( + borderSide: BorderSide(), + ), + ), + initialCountryCode: 'EG', + onChanged: (phone) { + // Properly concatenate country code and number + registerController.phoneController.text = + phone.completeNumber.toString(); + Log.print(' phone.number: ${phone.number}'); + print( + "Formatted phone number: ${registerController.phoneController.text}"); + }, + validator: (phone) { + // Check if the phone number is not null and is valid + 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: 10, @@ -110,14 +97,19 @@ class SmsSignupEgypt extends StatelessWidget { ), ), // Submit button - MyElevatedButton( - onPressed: () async { - !registerController.isSent - ? await registerController.sendOtpMessage() - : await registerController.verifySMSCode(); - }, - title: 'Submit'.tr, - ), + registerController.isLoading + ? const MyCircularProgressIndicator() + : Padding( + padding: const EdgeInsets.all(16.0), + child: MyElevatedButton( + onPressed: () async { + !registerController.isSent + ? await registerController.sendOtpMessage() + : await registerController.verifySMSCode(); + }, + title: 'Submit'.tr, + ), + ), ], ); }), diff --git a/lib/views/home/HomePage/about_page.dart b/lib/views/home/HomePage/about_page.dart index 99347f8..7b487ca 100644 --- a/lib/views/home/HomePage/about_page.dart +++ b/lib/views/home/HomePage/about_page.dart @@ -1,101 +1,153 @@ import 'package:SEFER/constant/box_name.dart'; -import 'package:SEFER/constant/style.dart'; import 'package:SEFER/main.dart'; -import 'package:SEFER/views/widgets/my_scafold.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:flutter/cupertino.dart'; class AboutPage extends StatelessWidget { const AboutPage({super.key}); @override Widget build(BuildContext context) { - return MyScafolld( - title: 'About Us'.tr, - body: [ - // Company Logo (consider adding an image asset) - ListView( + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text('About Us'.tr), + ), + child: SafeArea( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, children: [ + // Company Logo Center( - child: Image.asset( - 'assets/images/logo.png', // Replace with your logo image asset path - height: 100.0, - width: 100.0, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Image.asset( + 'assets/images/logo.png', // Replace with your logo image asset path + height: 100.0, + width: 100.0, + ), ), - ), // Company Name and Location + ), + + // Company Name and Location Padding( padding: const EdgeInsets.all(16.0), child: Text( 'SEFER LLC\n${box.read(BoxName.countryCode).toString().tr}', - style: AppStyle.headTitle2, + style: + CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 22.0, + fontWeight: FontWeight.bold, + ), textAlign: TextAlign.center, ), ), + + // About Us Description Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Text( 'SEFER is a ride-sharing app designed with your safety and affordability in mind. We connect you with reliable drivers in your area, ensuring a convenient and stress-free travel experience.\n\nHere are some of the key features that set us apart:' .tr, - style: AppStyle.title, + style: + CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 16.0, + ), textAlign: TextAlign.center, ), - ), // Security Features List - const SizedBox( - height: 20, ), + + const SizedBox(height: 20), + + // Security Features Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0), child: Column( children: [ Row( children: [ - const Icon(Icons.lock, color: Colors.blue), + const Icon(CupertinoIcons.lock_fill, + color: CupertinoColors.activeBlue), const SizedBox(width: 8.0), - Text( - 'Most Secure Methods'.tr, - style: AppStyle.title, + Expanded( + child: Text( + 'Most Secure Methods'.tr, + style: CupertinoTheme.of(context) + .textTheme + .textStyle + .copyWith( + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), ), ], ), const SizedBox(height: 8.0), Row( children: [ - const Icon(Icons.phone, color: Colors.blue), + const Icon(CupertinoIcons.phone_fill, + color: CupertinoColors.activeBlue), const SizedBox(width: 8.0), - Text( - 'In-App VOIP Calls'.tr, - style: AppStyle.title, + Expanded( + child: Text( + 'In-App VOIP Calls'.tr, + style: CupertinoTheme.of(context) + .textTheme + .textStyle + .copyWith( + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), ), ], ), const SizedBox(height: 8.0), Row( children: [ - const Icon(Icons.videocam, color: Colors.blue), + const Icon(CupertinoIcons.videocam_fill, + color: CupertinoColors.activeBlue), const SizedBox(width: 8.0), - Text( - 'Recorded Trips for Safety'.tr, - style: AppStyle.title, + Expanded( + child: Text( + 'Recorded Trips for Safety'.tr, + style: CupertinoTheme.of(context) + .textTheme + .textStyle + .copyWith( + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), ), ], ), ], ), - ), // Affordability Highlight + ), + + // Affordability Highlight Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Text( '\nWe also prioritize affordability, offering competitive pricing to make your rides accessible.' .tr, - style: AppStyle.title, + style: + CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), textAlign: TextAlign.center, ), ), + + const SizedBox(height: 20), ], ), - - // About Us Text - ], - isleading: true); + ), + ), + ); } } diff --git a/lib/views/home/HomePage/contact_us.dart b/lib/views/home/HomePage/contact_us.dart new file mode 100644 index 0000000..e7012dc --- /dev/null +++ b/lib/views/home/HomePage/contact_us.dart @@ -0,0 +1,102 @@ +import 'package:SEFER/constant/colors.dart'; +import 'package:SEFER/constant/style.dart'; +// ignore: unused_import +import 'package:SEFER/controller/functions/launch.dart'; +import 'package:SEFER/views/widgets/my_scafold.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_font_icons/flutter_font_icons.dart'; +import 'package:get/get.dart'; + +import '../../../controller/functions/tts.dart'; +import '../../../controller/home/contact_us_controller.dart'; + +class ContactUsPage extends StatelessWidget { + ContactUsPage({super.key}); + + @override + Widget build(BuildContext context) { + Get.put(ContactUsController()); + return GetBuilder(builder: (controller) { + return MyScafolld( + title: "Contact Us".tr, + body: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + // crossAxisAlignment: CrossAxisAlignment.center, + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + decoration: AppStyle.boxDecoration1, + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(15), + child: Image.asset('assets/images/logo.gif')), + IconButton( + onPressed: () async { + Get.put(TextToSpeechController()).speakText( + 'SEFER is the safest ride-sharing app that introduces many features for both captains and passengers. We offer the lowest commission rate of just 8%, ensuring you get the best value for your rides. Our app includes insurance for the best captains, regular maintenance of cars with top engineers, and on-road services to ensure a respectful and high-quality experience for all users.' + .tr); + }, + icon: const Icon(Icons.headphones), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'SEFER is the safest ride-sharing app that introduces many features for both captains and passengers. We offer the lowest commission rate of just 8%, ensuring you get the best value for your rides. Our app includes insurance for the best captains, regular maintenance of cars with top engineers, and on-road services to ensure a respectful and high-quality experience for all users.' + .tr, + style: AppStyle.title, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + const SizedBox( + height: 30, + ), + Container( + decoration: AppStyle.boxDecoration1, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "You can contact us during working hours from 12:00 - 19:00." + .tr, + style: AppStyle.title, + textAlign: TextAlign.center, + ), + ), + ), + InkWell( + onTap: () => controller.showContactDialog(context), + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Icon( + Icons.phone, + color: AppColor.blueColor, + ), + Icon( + FontAwesome.whatsapp, + color: AppColor.greenColor, + ), + Icon( + Icons.email, + color: AppColor.redColor, + ), + ], + ), + ), + const SizedBox( + height: 30, + ) + ], + ), + ) + ], + isleading: true); + }); + } +} diff --git a/lib/views/home/HomePage/share_app_page.dart b/lib/views/home/HomePage/share_app_page.dart new file mode 100644 index 0000000..c5b28d2 --- /dev/null +++ b/lib/views/home/HomePage/share_app_page.dart @@ -0,0 +1,572 @@ +import 'package:SEFER/constant/style.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../../constant/box_name.dart'; +import '../../../constant/colors.dart'; +import '../../../controller/home/profile/invit_controller.dart'; +import '../../../main.dart'; +import '../../../print.dart'; + +class ShareAppPage extends StatelessWidget { + final InviteController controller = Get.put(InviteController()); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: CupertinoColors.systemBackground, + appBar: AppBar( + backgroundColor: CupertinoColors.systemBackground, + elevation: 0, + title: Text( + 'Invite'.tr, + style: const TextStyle(color: CupertinoColors.label), + ), + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios, color: AppColor.blueColor), + onPressed: () => Get.back(), + ), + ), + body: SafeArea( + child: GetBuilder( + builder: (controller) { + return Column( + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: _buildPassengerTab(context), + ), + ), + ], + ); + }, + ), + ), + ); + } + + Widget _buildPassengerTab(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: CupertinoColors.systemGrey6, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + Text( + "Share this code with your friends and earn rewards when they use it!" + .tr, + textAlign: TextAlign.center, + style: const TextStyle( + color: CupertinoColors.secondaryLabel, + fontSize: 13, + ), + ), + ], + ), + ), + const SizedBox(height: 20), + _buildPhoneInput(), + const SizedBox(height: 20), + _buildActionButtonsPassengers(), + const SizedBox(height: 20), + const SizedBox(height: 20), + _buildInvitationsListPassengers(context), + ], + ); + } + + // Widget _buildPhoneInput() { + // return Container( + // decoration: BoxDecoration( + // color: CupertinoColors.systemGrey6, + // borderRadius: BorderRadius.circular(8), + // ), + // child: Row( + // children: [ + // Expanded( + // child: CupertinoTextField.borderless( + // controller: controller.invitePhoneController, + // placeholder: 'Enter phone'.tr, + // padding: const EdgeInsets.all(12), + // keyboardType: TextInputType.phone, + // ), + // ), + // CupertinoButton( + // child: const Icon(CupertinoIcons.person_badge_plus, + // color: AppColor.blueColor), + // onPressed: () async { + // await controller.pickContacts(); + // if (controller.contacts.isNotEmpty) { + // if (box.read(BoxName.isSavedPhones) == null) { + // controller.savePhoneToServer(); + // box.write(BoxName.isSavedPhones, true); + // } + // _showContactsDialog(Get.context!); + // } + // }, + // ), + // ], + // ), + // ); + // } + Widget _buildPhoneInput() { + return Container( + decoration: BoxDecoration( + color: CupertinoColors.systemGrey6, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Expanded( + child: CupertinoTextField.borderless( + controller: controller.invitePhoneController, + placeholder: 'Enter phone'.tr, + padding: const EdgeInsets.all(12), + keyboardType: TextInputType.phone, + ), + ), + CupertinoButton( + child: const Icon(CupertinoIcons.person_badge_plus, + color: AppColor.blueColor), + onPressed: () async { + await controller.pickContacts(); + Log.print('contacts: ${controller.contacts}'); + if (controller.contacts.isNotEmpty) { + _showContactsDialog(Get + .context!); // Show contacts dialog after loading contacts + } else { + Get.snackbar( + 'No contacts available'.tr, + 'Please add contacts to your phone.'.tr, + ); + } + }, + ), + ], + ), + ); + } + + Widget _buildActionButtonsPassengers() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0), + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 6, + offset: const Offset(0, 3), + ), + ], + ), + child: CupertinoButton( + color: AppColor.blueColor, + borderRadius: BorderRadius.circular(10), + padding: const EdgeInsets.symmetric(vertical: 14), + onPressed: controller.sendInviteToPassenger, + child: Text( + 'Send Invite'.tr, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: CupertinoColors.white, + ), + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 6, + offset: const Offset(0, 3), + ), + ], + ), + child: CupertinoButton( + color: AppColor.blueColor, + borderRadius: BorderRadius.circular(10), + padding: const EdgeInsets.symmetric(vertical: 14), + child: Text( + 'Show Invitations'.tr, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: CupertinoColors.white, + ), + ), + onPressed: () async { + controller.fetchDriverStatsPassengers(); + }, + ), + ), + ), + ], + ), + ); + } + + Widget _buildInvitationsListPassengers(BuildContext context) { + return SizedBox( + height: Get.height * .4, + child: controller.driverInvitationDataToPassengers.isEmpty + ? Center( + child: Text( + "No invitation found yet!".tr, + style: const TextStyle( + color: CupertinoColors.secondaryLabel, + fontSize: 17, + ), + ), + ) + : ListView.builder( + itemCount: controller.driverInvitationDataToPassengers.length, + itemBuilder: (context, index) { + return _buildInvitationItemPassengers(context, index); + }, + ), + ); + } + + Widget _buildInvitationItemPassengers(BuildContext context, int index) { + // Extracting the data from the sample JSON-like structure + var invitation = controller.driverInvitationDataToPassengers[index]; + + int countOfInvitDriver = + int.tryParse(invitation['countOfInvitDriver']?.toString() ?? '0') ?? 0; + double progressValue = (countOfInvitDriver / 10.0).clamp(0.0, 1.0); + + return GestureDetector( + onTap: () { + controller.onSelectPassengerInvitation(index); + }, + child: Container( + margin: const EdgeInsets.symmetric(vertical: 8.0), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: CupertinoColors.systemGrey6, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + invitation['passengerName'] + .toString(), // Handle null or missing data + style: const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w600, + color: CupertinoColors.label, + ), + ), + const SizedBox(height: 8), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: progressValue, + backgroundColor: CupertinoColors.systemGrey4, + valueColor: + const AlwaysStoppedAnimation(AppColor.blueColor), + minHeight: 6, + ), + ), + const SizedBox(height: 4), + Text( + '$countOfInvitDriver / 2 ${'Trip'.tr}', // Show trips completed + style: const TextStyle( + fontSize: 13, + color: CupertinoColors.secondaryLabel, + ), + ), + ], + ), + ), + ); + } + + Widget _buildPassengerStats(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: CupertinoColors.systemGrey6, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Your Rewards".tr, + style: const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w600, + color: CupertinoColors.label, + ), + ), + const SizedBox(height: 16), + _buildStatItem( + context, + "Total Invites".tr, + controller.driverInvitationDataToPassengers[0]['countOfInvitDriver'] + .toString(), + ), + _buildStatItem( + context, + "Active Users".tr, + controller.driverInvitationDataToPassengers[0]['passengerName'] + .toString(), + ), + ], + ), + ); + } + + Widget _buildStatItem(BuildContext context, String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: const TextStyle( + color: CupertinoColors.label, + fontSize: 15, + ), + ), + Text( + value, + style: const TextStyle( + fontWeight: FontWeight.w600, + color: AppColor.blueColor, + fontSize: 15, + ), + ), + ], + ), + ); + } + + void _showContactsDialog(BuildContext context) { + Get.defaultDialog( + title: 'Choose from contact'.tr, + content: SizedBox( + height: 400, + width: 400, + child: Column( + children: [ + // Header with cancel and title + // Container( + // padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + // decoration: const BoxDecoration( + // borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + // color: CupertinoColors.systemGrey6, + // ), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // CupertinoButton( + // padding: EdgeInsets.zero, + // child: Text( + // 'Cancel'.tr, + // style: const TextStyle(color: CupertinoColors.systemBlue), + // ), + // onPressed: () => Navigator.pop(context), + // ), + // Container( + // child: Text('Choose from contact'.tr, + // style: AppStyle.title)), + // const SizedBox(width: 60), // Balance for Cancel button + // ], + // ), + // ), + + // Contact list + Expanded( + child: ListView.builder( + itemCount: controller.contactMaps.length, + itemBuilder: (context, index) { + final contact = controller.contactMaps[index]; + return CupertinoButton( + padding: EdgeInsets.zero, + onPressed: () { + controller.selectPhone(contact['phones'].toString()); + }, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: CupertinoColors.systemBackground, + border: Border( + bottom: BorderSide( + color: CupertinoColors.separator.withOpacity(0.5), + ), + ), + ), + child: Row( + children: [ + // Display contact name and phone number + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + contact['name'], + style: const TextStyle( + color: CupertinoColors.label, + fontSize: 17, + fontWeight: FontWeight.w500, + ), + ), + Text( + controller.formatPhoneNumber( + contact['phones'][0].toString()), + style: const TextStyle( + color: CupertinoColors.secondaryLabel, + fontSize: 15, + ), + ), + ], + ), + ), + // Chevron icon for selection + const Icon( + CupertinoIcons.chevron_forward, + color: CupertinoColors.systemGrey2, + ), + ], + ), + ), + ); + }, + ), + ), + ], + ), + ), + ); + // showCupertinoModalPopup( + // context: context, + // builder: (BuildContext context) => Container( + // height: 400, + // decoration: BoxDecoration( + // color: CupertinoColors.systemBackground, + // borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), + // boxShadow: [ + // BoxShadow( + // color: CupertinoColors.black.withOpacity(0.2), + // offset: const Offset(0, -4), + // blurRadius: 10, + // ), + // ], + // ), + // child: Column( + // children: [ + // // Header with cancel and title + // Container( + // padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + // decoration: const BoxDecoration( + // borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + // color: CupertinoColors.systemGrey6, + // ), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // CupertinoButton( + // padding: EdgeInsets.zero, + // child: Text( + // 'Cancel'.tr, + // style: const TextStyle(color: CupertinoColors.systemBlue), + // ), + // onPressed: () => Navigator.pop(context), + // ), + // Container( + // child: Text('Choose from contact'.tr, + // style: AppStyle.title)), + // const SizedBox(width: 60), // Balance for Cancel button + // ], + // ), + // ), + + // // Contact list + // Expanded( + // child: ListView.builder( + // itemCount: controller.contactMaps.length, + // itemBuilder: (context, index) { + // final contact = controller.contactMaps[index]; + // return CupertinoButton( + // padding: EdgeInsets.zero, + // onPressed: () { + // controller.selectPhone(contact['phones'].toString()); + // }, + // child: Container( + // padding: const EdgeInsets.symmetric( + // horizontal: 16, vertical: 12), + // decoration: BoxDecoration( + // color: CupertinoColors.systemBackground, + // border: Border( + // bottom: BorderSide( + // color: CupertinoColors.separator.withOpacity(0.5), + // ), + // ), + // ), + // child: Row( + // children: [ + // // Display contact name and phone number + // Expanded( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // contact['name'], + // style: const TextStyle( + // color: CupertinoColors.label, + // fontSize: 17, + // fontWeight: FontWeight.w500, + // ), + // ), + // Text( + // controller.formatPhoneNumber( + // contact['phones'][0].toString()), + // style: const TextStyle( + // color: CupertinoColors.secondaryLabel, + // fontSize: 15, + // ), + // ), + // ], + // ), + // ), + // // Chevron icon for selection + // const Icon( + // CupertinoIcons.chevron_forward, + // color: CupertinoColors.systemGrey2, + // ), + // ], + // ), + // ), + // ); + // }, + // ), + // ), + // ], + // ), + // ), + // ); + } +} diff --git a/lib/views/home/HomePage/trip_record_page.dart b/lib/views/home/HomePage/trip_record_page.dart index d98674e..c3d9ee5 100644 --- a/lib/views/home/HomePage/trip_record_page.dart +++ b/lib/views/home/HomePage/trip_record_page.dart @@ -1,14 +1,11 @@ import 'package:SEFER/views/widgets/my_scafold.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:share/share.dart'; import 'package:path/path.dart' as path; -import '../../../constant/colors.dart'; -import '../../../constant/style.dart'; import '../../../controller/functions/audio_record1.dart'; -import '../../../controller/functions/tts.dart'; -import '../../widgets/elevated_btn.dart'; class TripsRecordedPage extends StatelessWidget { const TripsRecordedPage({ @@ -21,132 +18,193 @@ class TripsRecordedPage extends StatelessWidget { title: 'Trips recorded'.tr, body: [ GetBuilder(builder: (audio) { - return Column( - children: [ - FutureBuilder>( - future: audio.getRecordedFiles(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const CircularProgressIndicator(); - } else if (snapshot.hasData) { - final recordedFiles = snapshot.data!; - return DropdownButton( - value: audio.selectedFilePath, - onChanged: (value) { - audio.selectedFilePath = value; - audio.playRecordedFile(value!); - audio.update(); - }, - items: recordedFiles - .map((file) => DropdownMenuItem( - value: file, - child: Text(path.basename(file)), - )) - .toList(), - ); - } else { - return Text('Error: ${snapshot.error}'); - } - }, - ), - Slider( - value: audio.currentPosition, - max: audio.totalDuration, - inactiveColor: AppColor.accentColor, - label: audio.currentPosition.toString(), - onChanged: (value) { - audio.currentPosition = value; - audio.audioPlayer.seek(Duration(seconds: value.toInt())); - }, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - IconButton( - icon: Icon( - audio.isPlaying ? Icons.pause : Icons.play_arrow), - onPressed: () { - if (audio.isPlaying) { - audio.pausePlayback(); - } else { - audio.resumePlayback(); - } - audio.update(); - }, - ), - IconButton( - icon: const Icon(Icons.stop), - onPressed: () { - audio.stopPlayback(); - audio.update(); - }, - ), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () async { - Get.defaultDialog( - title: 'Are you sure to delete recorded files'.tr, - content: Column( - children: [ - IconButton( - onPressed: () { - Get.find().speakText( - 'this will delete all files from your device' - .tr); - }, - icon: const Icon(Icons.headphones), - ), - Text( - 'this will delete all files from your device' - .tr, - textAlign: TextAlign.center, - style: AppStyle.title, - ), - ], - ), - titleStyle: AppStyle.title, - confirm: MyElevatedButton( - title: 'Delete'.tr, - kolor: AppColor.redColor, + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + FutureBuilder>( + future: audio.getRecordedFiles(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center( + child: CupertinoActivityIndicator()); + } else if (snapshot.hasData) { + final recordedFiles = snapshot.data!; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: CupertinoButton( + padding: EdgeInsets.zero, onPressed: () async { - await audio.deleteAllRecordedFiles(); - Get.back(); - Get.back(); + String? selectedFile = + await showCupertinoModalPopup( + context: context, + builder: (BuildContext context) { + return CupertinoActionSheet( + title: Text('Select a File'.tr), + actions: recordedFiles + .map( + (file) => CupertinoActionSheetAction( + child: Text(path.basename(file)), + onPressed: () { + Navigator.of(context).pop(file); + }, + ), + ) + .toList(), + ); + }, + ); + if (selectedFile != null) { + audio.selectedFilePath = selectedFile; + audio.playRecordedFile(selectedFile); + audio.update(); + } }, + child: Text( + audio.selectedFilePath != null + ? path.basename(audio.selectedFilePath!) + : 'Select a File'.tr, + style: CupertinoTheme.of(context) + .textTheme + .actionTextStyle + .copyWith(color: CupertinoColors.activeBlue), + ), ), ); + } else { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Text('Error: ${snapshot.error}'), + ); + } + }, + ), + + // Cupertino-style slider for seeking audio + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: CupertinoSlider( + value: audio.totalDuration > 0 + ? audio.currentPosition / audio.totalDuration + : 0.0, // Normalize to a value between 0.0 and 1.0 + min: 0.0, + max: 1.0, // Maximum value is now 1.0 + activeColor: CupertinoColors.activeBlue, + onChanged: (value) { + final newPosition = value * audio.totalDuration; + audio.currentPosition = newPosition; + audio.audioPlayer + .seek(Duration(seconds: newPosition.toInt())); + audio.update(); }, ), - ], - ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - padding: const EdgeInsets.all(16.0), - color: Colors.grey[200], + ), + + // iOS-style playback controls + Padding( + padding: const EdgeInsets.symmetric( + vertical: 16.0, horizontal: 16.0), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Text( - audio.selectedFilePath != null - ? '${'Selected file:'.tr} ${path.basename(audio.selectedFilePath!)}' - : 'No file selected'.tr, - style: AppStyle.subtitle, - ), - if (audio.selectedFilePath != null) - IconButton( - icon: const Icon(Icons.share), - onPressed: () { - Share.shareFiles([audio.selectedFilePath!]); - }, + CupertinoButton( + padding: EdgeInsets.zero, + child: Icon( + audio.isPlaying + ? CupertinoIcons.pause + : CupertinoIcons.play_arrow, + color: CupertinoColors.activeBlue, ), + onPressed: () { + if (audio.isPlaying) { + audio.pausePlayback(); + } else { + audio.resumePlayback(); + } + audio.update(); + }, + ), + CupertinoButton( + padding: EdgeInsets.zero, + child: const Icon(CupertinoIcons.stop, + color: CupertinoColors.destructiveRed), + onPressed: () { + audio.stopPlayback(); + audio.update(); + }, + ), + CupertinoButton( + padding: EdgeInsets.zero, + child: const Icon(CupertinoIcons.delete, + color: CupertinoColors.destructiveRed), + onPressed: () async { + showCupertinoModalPopup( + context: context, + builder: (BuildContext context) { + return CupertinoActionSheet( + title: Text('Are you sure?'.tr), + message: Text( + 'This will delete all recorded files from your device.' + .tr, + textAlign: TextAlign.center, + ), + actions: [ + CupertinoActionSheetAction( + isDestructiveAction: true, + onPressed: () async { + await audio.deleteAllRecordedFiles(); + Navigator.pop(context); + audio.update(); + }, + child: Text('Delete'.tr), + ), + ], + cancelButton: CupertinoActionSheetAction( + onPressed: () { + Navigator.pop(context); + }, + child: Text('Cancel'.tr), + ), + ); + }, + ); + }, + ), ], ), ), - ), - ], + + // File selection and sharing + if (audio.selectedFilePath != null) + Align( + alignment: Alignment.bottomCenter, + child: Container( + padding: const EdgeInsets.all(16.0), + color: CupertinoColors.systemGrey6, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Selected file: ${path.basename(audio.selectedFilePath!)}', + style: CupertinoTheme.of(context) + .textTheme + .textStyle, + ), + CupertinoButton( + padding: EdgeInsets.zero, + child: const Icon(CupertinoIcons.share), + onPressed: () { + Share.shareFiles([audio.selectedFilePath!]); + }, + ), + ], + ), + ), + ), + ], + ), ); - }), + }) ], isleading: true); } diff --git a/lib/views/home/home_page.dart b/lib/views/home/home_page.dart index 1455201..3b9949d 100644 --- a/lib/views/home/home_page.dart +++ b/lib/views/home/home_page.dart @@ -2,17 +2,11 @@ import 'package:SEFER/controller/home/home_page_controller.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:SEFER/constant/colors.dart'; -import 'package:SEFER/constant/style.dart'; import 'package:SEFER/views/lang/languages.dart'; -import 'package:SEFER/views/widgets/my_scafold.dart'; -import '../../constant/box_name.dart'; -import '../../controller/profile/profile_controller.dart'; -import '../../main.dart'; -import '../widgets/elevated_btn.dart'; import 'HomePage/about_page.dart'; import 'HomePage/frequentlyQuestionsPage.dart'; +import 'HomePage/share_app_page.dart'; import 'HomePage/trip_record_page.dart'; import 'profile/passenger_profile_page.dart'; @@ -22,104 +16,108 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { Get.put(HomePageController()); - final List countryOptions = [ - 'Jordan', - 'USA', - 'Egypt', - 'Turkey', - 'Saudi Arabia', - 'Qatar', - 'Bahrain', - 'Kuwait', - ]; - return MyScafolld( - isleading: true, - title: 'Home Page'.tr, - body: [ - Column( + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text('Home Page'.tr), + leading: CupertinoButton( + padding: EdgeInsets.zero, + child: const Icon(CupertinoIcons.back), + onPressed: () { + Navigator.pop(context); + }, + ), + ), + child: SafeArea( + child: ListView( children: [ - ListTile( + CupertinoListTile( onTap: () { Get.to(() => const Language()); }, - title: Text( - 'Language'.tr, - style: AppStyle.headTitle2, - ), - subtitle: Text( - 'To change Language the App'.tr, - style: AppStyle.title, - ), - trailing: const Icon( - Icons.arrow_forward_ios, - size: 30, - color: AppColor.primaryColor, - ), - leading: const Icon( - Icons.language_sharp, - color: AppColor.primaryColor, - ), + leading: const Icon(CupertinoIcons.globe, + color: CupertinoColors.activeBlue), + title: Text('Language'.tr), + subtitle: Text('To change Language the App'.tr), + trailing: const CupertinoListTileChevron(), ), - changeCountry(countryOptions), - ListTile( - leading: const Icon(Icons.question_answer), - title: Text( - 'Frequently Questions'.tr, - style: AppStyle.headTitle2, - ), - subtitle: Text( - 'You can change the Country to get all features'.tr, - style: AppStyle.title, - ), - onTap: () => Get.to(() => const FrequentlyQuestionsPage()), - ), - ListTile( - leading: const Icon(Icons.vibration), - title: GetBuilder(builder: (controller) { - return SwitchListTile( - title: Text( - 'Vibration'.tr, - style: AppStyle.headTitle2, + CupertinoListTile( + onTap: () { + Get.to(CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text('Change Country'.tr), ), - value: controller.isVibrate, - onChanged: controller.changeVibrateOption, - activeColor: AppColor.primaryColor, - ); - }), - subtitle: Text( - "You can change the vibration feedback for all buttons".tr, - style: AppStyle.title, - ), - onTap: () => Get.to(() => const FrequentlyQuestionsPage()), + child: SafeArea( + child: CountryPickerFromSetting(), + ), + )); + }, + leading: const Icon(CupertinoIcons.location, + color: CupertinoColors.activeBlue), + title: Text('Change Country'.tr), + subtitle: + Text('You can change the Country to get all features'.tr), + trailing: const CupertinoListTileChevron(), ), - ListTile( - leading: const Icon(Icons.record_voice_over_outlined), - title: Text( - 'Trips recorded'.tr, - style: AppStyle.headTitle2, - ), - subtitle: Text( - 'Here recorded trips audio'.tr, - style: AppStyle.title, - ), - onTap: () async { - Get.to(() => TripsRecordedPage()); - }), - ListTile( - leading: const Icon(Icons.account_balance_outlined), - title: Text( - 'About Us'.tr, - style: AppStyle.headTitle2, + CupertinoListTile( + onTap: () { + Get.to(() => const FrequentlyQuestionsPage()); + }, + leading: const Icon(CupertinoIcons.question, + color: CupertinoColors.activeBlue), + title: Text('Frequently Questions'.tr), + subtitle: Text('Find answers to common questions'.tr), + trailing: const CupertinoListTileChevron(), + ), + CupertinoListTile( + leading: const Icon(Icons.vibration, + color: CupertinoColors.activeBlue), + title: Text('Vibration'.tr), + trailing: GetBuilder( + builder: (controller) { + return CupertinoSwitch( + value: controller.isVibrate, + onChanged: controller.changeVibrateOption, + ); + }, ), subtitle: Text( - 'You can change the Country to get all features'.tr, - style: AppStyle.title, - ), - onTap: () => Get.to(() => const AboutPage()), + 'You can change the vibration feedback for all buttons'.tr), + ), + CupertinoListTile( + onTap: () { + Get.to(() => const TripsRecordedPage()); + }, + leading: const Icon(CupertinoIcons.mic_circle, + color: CupertinoColors.activeBlue), + title: Text('Trips recorded'.tr), + subtitle: Text('Here recorded trips audio'.tr), + trailing: const CupertinoListTileChevron(), + ), + CupertinoListTile( + onTap: () { + Get.to(() => const AboutPage()); + }, + leading: const Icon(CupertinoIcons.info_circle, + color: CupertinoColors.activeBlue), + title: Text('About Us'.tr), + subtitle: Text('Learn more about our app and mission'.tr), + trailing: const CupertinoListTileChevron(), + ), + CupertinoListTile( + onTap: () { + Get.to(() => ShareAppPage()); + }, + leading: const Icon(CupertinoIcons.share, + color: CupertinoColors.activeBlue), + title: Text('Share App'.tr), + subtitle: Text( + 'You can share the SEFER App with your friends and earn rewards for rides they take using your code' + .tr), + trailing: const CupertinoListTileChevron(), ), ], ), - ], + ), ); } } diff --git a/lib/views/home/map_page_passenger.dart b/lib/views/home/map_page_passenger.dart index 29d8a81..040f669 100644 --- a/lib/views/home/map_page_passenger.dart +++ b/lib/views/home/map_page_passenger.dart @@ -23,6 +23,7 @@ import 'map_widget.dart/payment_method.page.dart'; import 'map_widget.dart/points_page_for_rider.dart'; import 'map_widget.dart/ride_from_start_app.dart'; import 'map_widget.dart/searching_captain_window.dart'; +import 'map_widget.dart/vip_begin.dart'; class MapPagePassenger extends StatelessWidget { const MapPagePassenger({super.key}); @@ -51,6 +52,7 @@ class MapPagePassenger extends StatelessWidget { CarDetailsTypeToChoose(), const HeaderDestination(), const BurcMoney(), + const PromoCode(), const ApplyOrderWidget(), const MapMenuWidget(), // hexagonClipper(), const CancelRidePageShow(), @@ -62,7 +64,9 @@ class MapPagePassenger extends StatelessWidget { // const TimerToPassengerFromDriver(), const PassengerRideLocationWidget(), const RideBeginPassenger(), + const VipRideBeginPassenger(), const RideFromStartApp(), + cancelRidePage(), const MenuIconMapPageWidget(), PointsPageForRider() @@ -82,7 +86,7 @@ class CancelRidePageShow extends StatelessWidget { Widget build(BuildContext context) { return GetBuilder( builder: (controller) => - (controller.data.isNotEmpty && controller.remainingTime > 0) + (controller.data.isNotEmpty && controller.statusRide != 'Begin') // || // controller.timeToPassengerFromDriverAfterApplied == 0 ? Positioned( diff --git a/lib/views/home/map_widget.dart/apply_order_widget.dart b/lib/views/home/map_widget.dart/apply_order_widget.dart index b850503..2a974d8 100644 --- a/lib/views/home/map_widget.dart/apply_order_widget.dart +++ b/lib/views/home/map_widget.dart/apply_order_widget.dart @@ -17,6 +17,21 @@ class ApplyOrderWidget extends StatelessWidget { @override Widget build(BuildContext context) { + Color _parseColor(String colorHex) { + if (colorHex.isEmpty) { + return Colors.grey; // Fallback for empty color + } + + // Ensure the string starts with '0xff' for ARGB format + String processedHex = colorHex.replaceFirst('#', '0xff').trim(); + + if (!processedHex.startsWith('0xff')) { + processedHex = '0xff$processedHex'; // Add '0xff' if missing + } + + return Color(int.parse(processedHex)); + } + return GetBuilder(builder: (controller) { if (controller.statusRide == 'Apply' && controller.isSearchingWindow == false) { @@ -27,12 +42,13 @@ class ApplyOrderWidget extends StatelessWidget { right: 0, child: Container( decoration: AppStyle.boxDecoration, - height: Get.height * .35, + height: Get.height * .36, child: ListView( children: [ InkWell( onTap: () { if (box.read(BoxName.carType) == 'Speed' || + box.read(BoxName.carType) == 'Awfar Car' || box.read(BoxName.carType) == 'Delivery') { Get.snackbar( 'This price is'.tr + @@ -86,7 +102,7 @@ class ApplyOrderWidget extends StatelessWidget { width: 10, ), Container( - height: Get.height * .3, + height: Get.height * .31, width: Get.width * .9, decoration: AppStyle.boxDecoration, child: Column( @@ -107,28 +123,43 @@ class ApplyOrderWidget extends StatelessWidget { ], ), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Image.asset( - box.read(BoxName.carType) == 'Comfort' - ? 'assets/images/blob.png' - : box.read(BoxName.carType) == 'Lady' - ? 'assets/images/lady.png' // Assuming there's an image for Lady - : box.read(BoxName.carType) == 'Speed' - ? 'assets/images/carspeed.png' - : box.read(BoxName.carType) == - 'Delivery' - ? 'assets/images/moto.png' - : box.read(BoxName.carType) == - 'Mashwari' - ? 'assets/images/freeRide.png' - : box.read(BoxName - .carType) == - 'Rayeh Gai' - ? 'assets/images/roundtrip.png' - : 'assets/images/carspeed.png', // Default image if none of the above - width: 80, - ), + // ColorFiltered( + // colorFilter: ColorFilter.mode( + // _parseColor(controller.colorHex), + // BlendMode.srcIn, + // ), + // child: Image.asset( + // box.read(BoxName.carType) == 'Comfort' + // ? 'assets/images/blob.png' + // : box.read(BoxName.carType) == 'Lady' + // ? 'assets/images/lady.png' // Assuming there's an image for Lady + // : box.read(BoxName.carType) == 'Speed' + // ? 'assets/images/carspeed.png' + // : box.read(BoxName.carType) == + // 'Scooter' + // ? 'assets/images/moto.png' + // : box.read(BoxName.carType) == + // 'Mishwar Vip' + // ? 'assets/images/freeRide.png' + // : box.read(BoxName + // .carType) == + // 'Awfar Car' + // ? 'assets/images/balash.png' + // : box.read(BoxName + // .carType) == + // 'Pink Bike' + // ? 'assets/images/pinkBike.png' + // : box.read(BoxName + // .carType) == + // 'Rayeh Gai' + // ? 'assets/images/roundtrip.png' + // : 'assets/images/carspeed.png', // Default image if none of the above + // width: 80, + // ), + // ), + Column( children: [ Text( @@ -143,14 +174,31 @@ class ApplyOrderWidget extends StatelessWidget { ), ], ), + const SizedBox( + width: 10, + ), Text( // 'Black', - controller.carColor, + controller.carColor.toString(), style: AppStyle.title, ), const SizedBox( width: 10, ), + ColorFiltered( + colorFilter: ColorFilter.mode( + _parseColor(controller.colorHex), + BlendMode.srcIn, + ), + child: Image.asset( + box.read(BoxName.carType) == 'Scooter' || + box.read(BoxName.carType) == + 'Pink Bike' + ? 'assets/images/moto.png' + : 'assets/images/car3.png', + width: 80, + ), + ), ], ), Padding( @@ -159,17 +207,57 @@ class ApplyOrderWidget extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ CircleAvatar( - radius: 30, + radius: 25, backgroundImage: NetworkImage( - // '', - // ), - '${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'), + '${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg', + ), + child: Builder( + builder: (context) { + return Image.network( + '${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg', + fit: BoxFit.cover, + loadingBuilder: (BuildContext context, + Widget child, + ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; // Image is loaded + } else { + return Center( + child: CircularProgressIndicator( + value: loadingProgress + .expectedTotalBytes != + null + ? loadingProgress + .cumulativeBytesLoaded / + (loadingProgress + .expectedTotalBytes ?? + 1) + : null, + ), + ); + } + }, + errorBuilder: (BuildContext context, + Object error, + StackTrace? stackTrace) { + return const Icon( + Icons + .person, // Icon to show when image fails to load + size: + 25, // Adjust the size as needed + color: AppColor + .blueColor, // Color for the error icon + ); + }, + ); + }, + ), ), Column( children: [ Text( // 'fadi ahmad', - controller.firstName, + controller.driverName, style: AppStyle.title, ), Text( @@ -182,7 +270,7 @@ class ApplyOrderWidget extends StatelessWidget { IconButton( onPressed: () async { Get.defaultDialog( - title: 'Select one message'.tr, + title: 'Select one message', titleStyle: AppStyle.title, content: SizedBox( width: 300, @@ -192,11 +280,13 @@ class ApplyOrderWidget extends StatelessWidget { InkWell( onTap: () { FirebaseMessagesController() - .sendNotificationToAnyWithoutData( + .sendNotificationToDriverMAP( 'message From passenger', 'Hello, I\'m at the agreed-upon location' .tr, - controller.driverToken, + controller.driverToken + .toString(), + [], 'ding.wav', ); Get.back(); @@ -222,11 +312,12 @@ class ApplyOrderWidget extends StatelessWidget { InkWell( onTap: () { FirebaseMessagesController() - .sendNotificationToAnyWithoutData( - 'message From passenger'.tr, + .sendNotificationToDriverMAP( + 'message From passenger', 'My location is correct. You can search for me using the navigation app' .tr, controller.driverToken, + [], 'ding.wav', ); Get.back(); @@ -252,11 +343,12 @@ class ApplyOrderWidget extends StatelessWidget { InkWell( onTap: () { FirebaseMessagesController() - .sendNotificationToAnyWithoutData( + .sendNotificationToDriverMAP( 'message From passenger', 'My location is correct. You can search for me using the navigation app' .tr, controller.driverToken, + [], 'ding.wav', ); Get.back(); @@ -281,11 +373,12 @@ class ApplyOrderWidget extends StatelessWidget { InkWell( onTap: () { FirebaseMessagesController() - .sendNotificationToAnyWithoutData( + .sendNotificationToDriverMAP( 'message From passenger', "How much longer will you be?" .tr, controller.driverToken, + [], 'ding.wav', ); Get.back(); @@ -335,13 +428,14 @@ class ApplyOrderWidget extends StatelessWidget { IconButton( onPressed: () { FirebaseMessagesController() - .sendNotificationToAnyWithoutData( + .sendNotificationToDriverMAP( 'message From passenger', controller .messageToDriver .text, controller .driverToken, + [], 'ding.wav'); controller .messageToDriver @@ -463,7 +557,7 @@ class TimeDriverToPassenger extends StatelessWidget { Container( decoration: AppStyle.boxDecoration, width: Get.width * .7, - height: 35, + height: 15, // color: AppColor.yellowColor, ), Stack( diff --git a/lib/views/home/map_widget.dart/cancel_raide_page.dart b/lib/views/home/map_widget.dart/cancel_raide_page.dart index 8901377..286e2e6 100644 --- a/lib/views/home/map_widget.dart/cancel_raide_page.dart +++ b/lib/views/home/map_widget.dart/cancel_raide_page.dart @@ -3,11 +3,11 @@ import 'package:get/get.dart'; import 'package:SEFER/constant/colors.dart'; import 'package:SEFER/constant/style.dart'; import 'package:SEFER/controller/home/map_passenger_controller.dart'; - import '../../widgets/elevated_btn.dart'; GetBuilder cancelRidePage() { Get.put(MapPassengerController()); + final List reasons = [ "I don't need a ride anymore".tr, "I was just trying the application".tr, @@ -16,80 +16,74 @@ GetBuilder cancelRidePage() { "I don't have a reason".tr, "Other".tr, ]; + return GetBuilder( builder: (controller) => controller.isCancelRidePageShown ? Positioned( - left: Get.width * .1, - top: Get.width * .2, - right: Get.width * .1, - bottom: Get.width * .15, + left: 20, + top: Get.height * 0.15, + right: 20, + bottom: Get.height * 0.15, child: Container( + padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: AppColor.secondaryColor, + color: Colors.white, boxShadow: [ - const BoxShadow( - color: AppColor.accentColor, - offset: Offset(2, 2), - blurRadius: 5), BoxShadow( - color: AppColor.accentColor.withOpacity(.4), - offset: const Offset(-2, -2), - blurRadius: 5) + color: Colors.black.withOpacity(0.2), + offset: const Offset(0, 8), + blurRadius: 16, + ), ], - borderRadius: const BorderRadius.all(Radius.circular(15)), + borderRadius: BorderRadius.circular(20), ), - height: Get.height * .7, - width: Get.width * .7, child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Text( - 'Can we know why you want to cancel Ride ?'.tr, - style: AppStyle.title, - textAlign: TextAlign.center, - ), + Text( + 'Can we know why you want to cancel Ride ?'.tr, + style: AppStyle.title + .copyWith(fontSize: 18, fontWeight: FontWeight.bold), + textAlign: TextAlign.center, ), - SizedBox( - height: 380, - width: 300, - child: ListView.builder( + const SizedBox(height: 20), + Expanded( + child: ListView.separated( itemCount: reasons.length, + separatorBuilder: (context, index) => const Divider(), itemBuilder: (context, index) { return ListTile( - title: InkWell( - onTap: () { - controller.selectReason( - index, - reasons[index].toString(), - ); - }, - child: Text( - reasons[index], - style: AppStyle.title, - )), + title: Text( + reasons[index], + style: AppStyle.title.copyWith(fontSize: 16), + ), leading: Radio( value: index, groupValue: controller.selectedReason, onChanged: (int? value) { - controller.selectReason( - value!, - reasons[index].toString(), - ); + controller.selectReason(value!, reasons[index]); }, + activeColor: AppColor.primaryColor, ), + onTap: () { + controller.selectReason(index, reasons[index]); + }, ); }, ), ), + const SizedBox(height: 20), MyElevatedButton( title: 'Cancel Ride'.tr, onPressed: () { if (controller.selectedReason == -1) { - Get.snackbar('You Should be select reason.'.tr, '', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: AppColor.redColor); + Get.snackbar( + 'You Should be select reason.'.tr, + '', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: AppColor.redColor, + colorText: Colors.white, + ); } else { controller.cancelRide(); } diff --git a/lib/views/home/map_widget.dart/car_details_widget_to_go.dart b/lib/views/home/map_widget.dart/car_details_widget_to_go.dart index f1964d4..eb9840e 100644 --- a/lib/views/home/map_widget.dart/car_details_widget_to_go.dart +++ b/lib/views/home/map_widget.dart/car_details_widget_to_go.dart @@ -1,12 +1,14 @@ import 'package:SEFER/constant/box_name.dart'; import 'package:SEFER/constant/colors.dart'; import 'package:SEFER/constant/style.dart'; +import 'package:SEFER/controller/home/blinking_promo_controller.dart.dart'; import 'package:SEFER/main.dart'; import 'package:SEFER/views/home/profile/passenger_profile_page.dart'; import 'package:SEFER/views/widgets/elevated_btn.dart'; import 'package:SEFER/views/widgets/my_dialog.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:SEFER/views/widgets/my_textField.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_confetti/flutter_confetti.dart'; import 'package:get/get.dart'; import '../../../constant/info.dart'; @@ -47,30 +49,33 @@ List carTypes = [ ), CarType( carType: 'Scooter', - carDetail: 'Scooter service'.tr, + carDetail: 'Delivery service'.tr, image: 'assets/images/moto.png', ), CarType( carType: 'Mishwar Vip', - carDetail: 'Mishwar Vip without end point'.tr, + carDetail: 'Mashwari without end point'.tr, image: 'assets/images/freeRide.png', ), CarType( - carType: 'Scooter Lady', - carDetail: 'Scooter Lady'.tr, - image: 'assets/images/moto.png', + carType: 'Pink Bike', + carDetail: "Best choice for cities".tr, + image: 'assets/images/pinkBike.png', + ), + CarType( + carType: 'Rayeh Gai', + carDetail: "Best choice for cities".tr, + image: 'assets/images/roundtrip.png', ), ]; class CarDetailsTypeToChoose extends StatelessWidget { CarDetailsTypeToChoose({super.key}); final textToSpeechController = Get.put(TextToSpeechController()); - @override Widget build(BuildContext context) { return GetBuilder( builder: (mapPassengerController) { - // Add a new CarType if the distance condition is met and the list has fewer than 6 items if (mapPassengerController.distance > 40) { carTypes.add( CarType( @@ -91,7 +96,6 @@ class CarDetailsTypeToChoose extends StatelessWidget { // Convert the Set back to a List carTypes = uniqueCarTypes.toList(); - return mapPassengerController.data.isNotEmpty && mapPassengerController.isBottomSheetShown && mapPassengerController.rideConfirm == false @@ -157,36 +161,37 @@ class CarDetailsTypeToChoose extends StatelessWidget { carType.carType == 'Comfort' ? mapPassengerController .totalPassengerComfort - .toStringAsFixed(2) + .toStringAsFixed(1) : carType.carType == 'Speed' ? mapPassengerController .totalPassengerSpeed - .toStringAsFixed(2) + .toStringAsFixed(1) : carType.carType == 'Awfar Car' ? mapPassengerController .totalPassengerBalash - .toStringAsFixed(2) + .toStringAsFixed(1) : carType.carType == 'Scooter' ? mapPassengerController .totalPassengerScooter - .toStringAsFixed(2) + .toStringAsFixed(1) : carType.carType == 'Lady' ? mapPassengerController .totalPassengerLady - .toStringAsFixed(2) + .toStringAsFixed(1) : carType.carType == - 'Scooter' + 'Pink Bike' ? mapPassengerController .totalPassengerScooter .toStringAsFixed( - 2) + 1) : carType.carType == 'Rayeh Gai' ? mapPassengerController - .totalPassengerRayehGaiBalash + .totalPassengerRayehGai .toStringAsFixed( - 2) - : '50', + 1) + : 'Pre-booking' + .tr, style: AppStyle.title.copyWith(fontSize: 20), ), @@ -202,7 +207,18 @@ class CarDetailsTypeToChoose extends StatelessWidget { decoration: AppStyle.boxDecoration1, child: Text( - '-12%', + mapPassengerController + .promoTaken + ? mapPassengerController + .totalPassengerComfortDiscount + .toStringAsFixed( + 1) + : (mapPassengerController + .totalPassengerComfortDiscount - + mapPassengerController + .totalPassengerComfort) + .toStringAsFixed( + 1), style: AppStyle.subtitle .copyWith( color: AppColor @@ -213,8 +229,15 @@ class CarDetailsTypeToChoose extends StatelessWidget { ), Text( mapPassengerController - .totalPassengerComfortDiscount - .toStringAsFixed(2), + .promoTaken + ? (mapPassengerController + .totalPassengerComfortDiscount + + mapPassengerController + .totalPassengerComfort) + .toStringAsFixed(1) + : mapPassengerController + .totalPassengerComfortDiscount + .toStringAsFixed(1), style: AppStyle.title.copyWith( color: AppColor.redColor, @@ -234,7 +257,18 @@ class CarDetailsTypeToChoose extends StatelessWidget { decoration: AppStyle .boxDecoration1, child: Text( - '-10%', + mapPassengerController + .promoTaken + ? mapPassengerController + .totalPassengerSpeedDiscount + .toStringAsFixed( + 1) + : (mapPassengerController + .totalPassengerSpeedDiscount - + mapPassengerController + .totalPassengerSpeed) + .toStringAsFixed( + 1), style: AppStyle .subtitle .copyWith( @@ -246,8 +280,17 @@ class CarDetailsTypeToChoose extends StatelessWidget { ), Text( mapPassengerController - .totalPassengerSpeedDiscount - .toStringAsFixed(2), + .promoTaken + ? (mapPassengerController + .totalPassengerSpeedDiscount + + mapPassengerController + .totalPassengerSpeed) + .toStringAsFixed( + 1) + : mapPassengerController + .totalPassengerSpeedDiscount + .toStringAsFixed( + 1), style: AppStyle.title .copyWith( color: @@ -269,7 +312,18 @@ class CarDetailsTypeToChoose extends StatelessWidget { decoration: AppStyle .boxDecoration1, child: Text( - '-10%', + mapPassengerController + .promoTaken + ? mapPassengerController + .totalPassengerBalashDiscount + .toStringAsFixed( + 1) + : (mapPassengerController + .totalPassengerBalashDiscount - + mapPassengerController + .totalPassengerBalash) + .toStringAsFixed( + 1), style: AppStyle .subtitle .copyWith( @@ -281,9 +335,17 @@ class CarDetailsTypeToChoose extends StatelessWidget { ), Text( mapPassengerController - .totalPassengerBalashDiscount - .toStringAsFixed( - 2), + .promoTaken + ? (mapPassengerController + .totalPassengerBalashDiscount + + mapPassengerController + .totalPassengerBalash) + .toStringAsFixed( + 1) + : mapPassengerController + .totalPassengerBalashDiscount + .toStringAsFixed( + 1), style: AppStyle.title .copyWith( color: AppColor @@ -305,7 +367,15 @@ class CarDetailsTypeToChoose extends StatelessWidget { decoration: AppStyle .boxDecoration1, child: Text( - '-10%', + mapPassengerController + .promoTaken + ? mapPassengerController + .totalPassengerLadyDiscount + .toStringAsFixed( + 1) + : (mapPassengerController.totalPassengerLadyDiscount - + mapPassengerController.totalPassengerLady) + .toStringAsFixed(0), style: AppStyle .subtitle .copyWith( @@ -317,9 +387,17 @@ class CarDetailsTypeToChoose extends StatelessWidget { ), Text( mapPassengerController - .totalPassengerLadyDiscount - .toStringAsFixed( - 2), + .promoTaken + ? (mapPassengerController + .totalPassengerLadyDiscount + + mapPassengerController + .totalPassengerLady) + .toStringAsFixed( + 0) + : mapPassengerController + .totalPassengerLadyDiscount + .toStringAsFixed( + 1), style: AppStyle .title .copyWith( @@ -443,7 +521,41 @@ class CarDetailsTypeToChoose extends StatelessWidget { textToSpeechController, image: 'assets/images/moto.png', text: - 'This is for Scooter or a motorcycle.' + 'This is for delivery or a motorcycle.' + .tr), + confirm: MyElevatedButton( + kolor: AppColor.greenColor, + title: 'Next'.tr, + onPressed: () { + Get.back(); + mapPassengerController + .isBottomSheetShown = false; + mapPassengerController.update(); + mapPassengerController + .changeCashConfirmPageShown(); + }), + cancel: MyElevatedButton( + title: 'Cancel'.tr, + kolor: AppColor.redColor, + onPressed: () { + Get.back(); + })); + } else if (mapPassengerController + .selectedIndex == + 6) { + box.write(BoxName.carType, 'Pink Bike'); + mapPassengerController.totalPassenger = + mapPassengerController + .totalPassengerScooter; + Get.defaultDialog( + title: 'Pink Bike'.tr, + titleStyle: AppStyle.title, + content: CarDialogue( + textToSpeechController: + textToSpeechController, + image: 'assets/images/pinkBike.png', + text: + 'This is for delivery or a motorcycle.' .tr), confirm: MyElevatedButton( kolor: AppColor.greenColor, @@ -475,7 +587,7 @@ class CarDetailsTypeToChoose extends StatelessWidget { textToSpeechController, image: 'assets/images/freeRide.png', text: - 'A trip with a prior reservation, allowing you to choose the best captains and cars.' + "Perfect for passengers seeking the latest car models with the freedom to choose any route they desire" .tr), confirm: MyElevatedButton( kolor: AppColor.greenColor, @@ -494,7 +606,7 @@ class CarDetailsTypeToChoose extends StatelessWidget { } else if (mapPassengerController .selectedIndex == 2) { - box.write(BoxName.carType, 'Balash'); + box.write(BoxName.carType, 'Awfar Car'); mapPassengerController.totalPassenger = mapPassengerController .totalPassengerBalash; @@ -545,8 +657,10 @@ class CarDetailsTypeToChoose extends StatelessWidget { title: 'Next'.tr, onPressed: () { Get.back(); - if (box.read(BoxName.gender) != - null) { + if (box + .read(BoxName.gender) + .toString() != + '') { mapPassengerController .isBottomSheetShown = false; mapPassengerController.update(); @@ -570,88 +684,148 @@ class CarDetailsTypeToChoose extends StatelessWidget { })); } else if (mapPassengerController .selectedIndex == - 4) { - box.write(BoxName.carType, 'ScooterLady'); + 7) { + box.write(BoxName.carType, 'Rayeh Gai'); mapPassengerController.totalPassenger = - mapPassengerController - .totalPassengerScooter; + mapPassengerController.totalPassengerLady; Get.defaultDialog( - title: 'Scooter Lady'.tr, + title: 'Rayeh Gai'.tr, titleStyle: AppStyle.title, content: CarDialogue( textToSpeechController: textToSpeechController, - image: 'assets/images/moto.png', - text: 'This is only for Scooter Lady.' - .tr), + image: 'assets/images/roundtrip.png', + text: + "Rayeh Gai: Round trip service for convenient travel between cities, easy and reliable." + .tr), confirm: MyElevatedButton( kolor: AppColor.greenColor, title: 'Next'.tr, onPressed: () { Get.back(); - mapPassengerController - .isBottomSheetShown = false; - mapPassengerController.update(); - mapPassengerController - .changeCashConfirmPageShown(); - }), - cancel: MyElevatedButton( - title: 'Cancel'.tr, - kolor: AppColor.redColor, - onPressed: () { - Get.back(); - })); - } else if (mapPassengerController - .selectedIndex == - 6) { - box.write(BoxName.carType, 'Rayeh Gai'); - // mapPassengerController.totalPassenger = - // mapPassengerController.totalPassengerRayehGai; - Get.defaultDialog( - title: 'Rayeh Gai'.tr, - titleStyle: AppStyle.title, - content: Column( - children: [ - CarDialogue( - textToSpeechController: - textToSpeechController, - image: - 'assets/images/roundtrip.png', - text: - "Rayeh Gai: Round trip service for convenient travel between cities, easy and reliable." - .tr), - const SizedBox( - height: 16, - ), - Container( - decoration: AppStyle.boxDecoration, - child: Padding( - padding: - const EdgeInsets.all(8.0), - child: Text( - 'Choose between those Type Cars' - .tr), - ), - ), - const SizedBox( - height: 16, - ), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - Container( - decoration: - AppStyle.boxDecoration, - child: InkWell( - onTap: () { - box.write(BoxName.carType, - 'RayehGaiComfort'); - mapPassengerController - .totalPassenger = - mapPassengerController - .totalPassengerRayehGaiComfort; - Get.back(); + + Get.defaultDialog( + barrierDismissible: false, + title: + "Select betweeen types".tr, + content: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + GestureDetector( + onTap: () { + Get.back(); + mapPassengerController + .totalPassenger = + mapPassengerController + .totalPassengerRayehGaiBalash; + mapPassengerController + .isBottomSheetShown = + false; + mapPassengerController + .update(); + mapPassengerController + .changeCashConfirmPageShown(); + }, + child: Container( + decoration: AppStyle + .boxDecoration1, + child: Padding( + padding: + const EdgeInsets + .all(8.0), + child: Column( + children: [ + Text('Awfar Car' + .tr), + Text(mapPassengerController + .totalPassengerRayehGaiBalash + .toStringAsFixed( + 0)), + ], + ), + )), + ), + GestureDetector( + onTap: () { + Get.back(); + mapPassengerController + .totalPassenger = + mapPassengerController + .totalPassengerRayehGai; + mapPassengerController + .isBottomSheetShown = + false; + mapPassengerController + .update(); + mapPassengerController + .changeCashConfirmPageShown(); + }, + child: Container( + decoration: AppStyle + .boxDecoration1, + child: Padding( + padding: + const EdgeInsets + .all(8.0), + child: Column( + children: [ + Text( + 'Speed'.tr), + Text(mapPassengerController + .totalPassengerRayehGai + .toStringAsFixed( + 0)), + ], + ), + )), + ), + GestureDetector( + onTap: () { + Get.back(); + mapPassengerController + .totalPassenger = + mapPassengerController + .totalPassengerRayehGaiComfort; + mapPassengerController + .isBottomSheetShown = + false; + mapPassengerController + .update(); + mapPassengerController + .changeCashConfirmPageShown(); + }, + child: Container( + decoration: AppStyle + .boxDecoration1, + child: Padding( + padding: + const EdgeInsets + .all(8.0), + child: Column( + children: [ + Text('Comfort' + .tr), + Text(mapPassengerController + .totalPassengerRayehGaiComfort + .toStringAsFixed( + 0)), + ], + ), + )), + ) + ], + ), + cancel: MyElevatedButton( + kolor: AppColor.redColor, + title: 'Cancel'.tr, + onPressed: () => + Get.back()), + confirm: MyElevatedButton( + kolor: AppColor.greenColor, + title: 'Next'.tr, + onPressed: () { mapPassengerController .isBottomSheetShown = false; @@ -659,121 +833,8 @@ class CarDetailsTypeToChoose extends StatelessWidget { .update(); mapPassengerController .changeCashConfirmPageShown(); - }, - child: Padding( - padding: - const EdgeInsets.all( - 8.0), - child: Column( - children: [ - Text('Comfort'.tr), - Text(mapPassengerController - .totalPassengerRayehGaiComfort - .toString() + - r'$'), - ], - ), - ), - )), - const SizedBox( - width: 10, - ), - Container( - decoration: - AppStyle.boxDecoration, - child: InkWell( - onTap: () { - box.write(BoxName.carType, - 'Speed'); - mapPassengerController - .totalPassenger = - mapPassengerController - .totalPassengerRayehGai; - Get.back(); - mapPassengerController - .isBottomSheetShown = - false; - mapPassengerController - .update(); - mapPassengerController - .changeCashConfirmPageShown(); - }, - child: Padding( - padding: - const EdgeInsets.all( - 8.0), - child: Column( - children: [ - Text('Speed'.tr), - Text(mapPassengerController - .totalPassengerRayehGai - .toString() + - r'$'), - ], - ), - ), - ), - ), - const SizedBox( - width: 10, - ), - Container( - decoration: - AppStyle.boxDecoration, - child: InkWell( - onTap: () { - box.write(BoxName.carType, - 'Balash'); - mapPassengerController - .totalPassenger = - mapPassengerController - .totalPassengerRayehGaiBalash; - Get.back(); - mapPassengerController - .isBottomSheetShown = - false; - mapPassengerController - .update(); - mapPassengerController - .changeCashConfirmPageShown(); - }, - child: Padding( - padding: - const EdgeInsets.all( - 8.0), - child: Column( - children: [ - Text('Awfar Car'.tr), - Text(mapPassengerController - .totalPassengerRayehGaiBalash - .toString() + - r'$'), - ], - ), - ), - ), - ), - ], - ), - const SizedBox( - height: 16, - ), - const SizedBox( - height: 16, - ), - ], - ), - // confirm: MyElevatedButton( - // kolor: AppColor.greenColor, - // title: 'Next'.tr, - // onPressed: () { - // Get.back(); - // mapPassengerController - // .isBottomSheetShown = false; - // mapPassengerController.update(); - // mapPassengerController - // .changeCashConfirmPageShown(); - // }), + })); + }), cancel: MyElevatedButton( title: 'Cancel'.tr, kolor: AppColor.redColor, @@ -798,110 +859,86 @@ class CarDetailsTypeToChoose extends StatelessWidget { // ; }); } +} - Future comfortDialougRayehgai( - MapPassengerController mapPassengerController) { - return Get.defaultDialog( - title: 'Comfort'.tr, - content: GetBuilder( - builder: (mapPassengerController) { - return Column( - children: [ - SizedBox(height: 60, child: HourPickerExample()), - const SizedBox( - height: 16, - ), - SizedBox( - height: 30, - child: Text(box.read(BoxName.hourWait).toString() + 'hour'.tr ?? - '1${'hour'.tr}'), - ), - ], - ); - }), - confirm: MyElevatedButton( - kolor: AppColor.greenColor, - title: 'Next'.tr, - onPressed: () { - Get.back(); - mapPassengerController.isBottomSheetShown = false; - mapPassengerController.update(); - mapPassengerController.changeCashConfirmPageShown(); - }), - cancel: MyElevatedButton( - title: 'Cancel'.tr, - kolor: AppColor.redColor, - onPressed: () { - Get.back(); - })); - } +class PromoCode extends StatelessWidget { + const PromoCode({super.key}); - Future speedDialougRayehGai( - MapPassengerController mapPassengerController) { - return Get.defaultDialog( - title: 'Speed'.tr, - content: Column( - children: [ - SizedBox(height: 60, child: HourPickerExample()), - const SizedBox( - height: 16, - ), - SizedBox( - height: 30, - child: Text(box.read(BoxName.hourWait).toString() + 'hour'.tr ?? - '1${'hour'.tr}'), - ), - ], - ), - confirm: MyElevatedButton( - kolor: AppColor.greenColor, - title: 'Next'.tr, - onPressed: () { - Get.back(); - mapPassengerController.isBottomSheetShown = false; - mapPassengerController.update(); - mapPassengerController.changeCashConfirmPageShown(); - }), - cancel: MyElevatedButton( - title: 'Cancel'.tr, - kolor: AppColor.redColor, - onPressed: () { - Get.back(); - })); - } - - Future balashDialougRayehGai( - MapPassengerController mapPassengerController) { - return Get.defaultDialog( - title: 'Awfar Car'.tr, - content: Column( - children: [ - SizedBox(height: 60, child: HourPickerExample()), - const SizedBox( - height: 16, - ), - SizedBox( - height: 30, - child: Text(box.read(BoxName.hourWait).toString() + 'hour'.tr ?? - '1${'hour'.tr}'), - ), - ], - ), - confirm: MyElevatedButton( - kolor: AppColor.greenColor, - title: 'Next'.tr, - onPressed: () { - Get.back(); - mapPassengerController.isBottomSheetShown = false; - mapPassengerController.update(); - mapPassengerController.changeCashConfirmPageShown(); - }), - cancel: MyElevatedButton( - title: 'Cancel'.tr, - kolor: AppColor.redColor, - onPressed: () { - Get.back(); - })); + @override + Widget build(BuildContext context) { + Get.put(BlinkingController()); + return GetBuilder( + builder: (mapPassengerController) { + return mapPassengerController.data.isNotEmpty && + mapPassengerController.isBottomSheetShown && + mapPassengerController.rideConfirm == false && + mapPassengerController.promoTaken == false + ? GetBuilder(builder: (blinkingController) { + blinkingController.startBlinking(); + return Positioned( + right: 5, + bottom: Get.height * 0.5, + child: Obx(() { + return AnimatedContainer( + duration: const Duration(milliseconds: 500), + width: 70, // Circle size + height: 70, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: blinkingController.isLightOn.value + ? Colors.yellow + : Colors.grey, // Light on/off effect + border: Border.all( + color: blinkingController + .borderColor.value, // Animated border color + width: 3, + ), + ), + child: IconButton( + onPressed: () { + Get.defaultDialog( + title: 'Insert Your Promo Code'.tr, + content: Form( + key: mapPassengerController.promoFormKey, + child: MyTextForm( + controller: mapPassengerController.promo, + label: 'Insert Your Promo Code'.tr, + hint: 'Enter promo code here'.tr, + type: TextInputType.name, + ), + ), + confirm: MyElevatedButton( + title: 'Ok'.tr, + onPressed: () { + if (mapPassengerController + .promoFormKey.currentState! + .validate()) { + mapPassengerController + .applyPromoCodeToPassenger(context); + Get.back(); + } + })); + }, + icon: const Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.local_offer, size: 24), // Promo icon + SizedBox(height: 4), + Text( + "Promo", + style: TextStyle( + fontSize: 12, fontWeight: FontWeight.bold), + ), + ], + ), + ), + ); + }), + ); + }) + : const SizedBox(); + }, + ); } } @@ -966,9 +1003,16 @@ class BurcMoney extends StatelessWidget { } } -class HeaderDestination extends StatelessWidget { +class HeaderDestination extends StatefulWidget { const HeaderDestination({super.key}); + @override + _HeaderDestinationState createState() => _HeaderDestinationState(); +} + +class _HeaderDestinationState extends State { + bool _isExpanded = false; + @override Widget build(BuildContext context) { return GetBuilder( @@ -980,95 +1024,64 @@ class HeaderDestination extends StatelessWidget { top: Get.height * .08, left: 5, right: 5, - child: Container( - decoration: AppStyle.boxDecoration1, - height: Get.height * .15, - width: Get.width * .8, - child: InkWell( - onTap: () { - // mapPassengerController - // .getDialog('Are you want to change'.tr, '', () { - // Get.back(); - // mapPassengerController.cancelRide(); - // }); - MyDialog().getDialog( - "Change Route".tr, - 'You can change the destination by long-pressing any point on the map' - .tr, () { - Get.back(); - }); - }, + child: GestureDetector( + onTap: () { + setState(() { + _isExpanded = !_isExpanded; + }); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + decoration: AppStyle.boxDecoration1, + height: _isExpanded ? Get.height * .13 : Get.height * .06, + width: Get.width * .9, + padding: const EdgeInsets.all(8), child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, vertical: 2), - child: SizedBox( - height: Get.height * .08, - child: ListView( - // crossAxisAlignment: CrossAxisAlignment.start, - // - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - '🟢 ', + _isExpanded + ? Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text('🟢 ', style: AppStyle.subtitle), + Expanded( + child: Text( + mapPassengerController.startNameAddress, style: AppStyle.subtitle, + overflow: TextOverflow.ellipsis, ), - SizedBox( - // height: Get.height * .03, - width: Get.width * .8, - child: Text( - mapPassengerController.startNameAddress, - style: AppStyle.subtitle, - ), - ), - ], - ), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - '🔴 ', - style: AppStyle.subtitle, - ), - SizedBox( - // height: Get.height * .03, - width: Get.width * .8, - child: Text( - mapPassengerController.endNameAddress, - style: AppStyle.subtitle, - ), - ), - ], - ), - ], + ), + ], + ) + : const SizedBox(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('🔴 ', style: AppStyle.subtitle), + Expanded( + child: Text( + mapPassengerController.endNameAddress, + style: AppStyle.subtitle, + overflow: TextOverflow.ellipsis, + ), ), - ), + ], ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Row( + if (_isExpanded) + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - '📍 ', - style: AppStyle.subtitle, - ), - SizedBox( - width: Get.width * .8, + Text('📍', style: AppStyle.subtitle), + Expanded( child: Text( '${mapPassengerController.distance} ${'KM'.tr} ⌛ ${mapPassengerController.hours > 0 ? '${'Your Ride Duration is '.tr}${mapPassengerController.hours} ${'H and'.tr} ${mapPassengerController.minutes} ${'m'.tr}' : '${'Your Ride Duration is '.tr} ${mapPassengerController.minutes} ${'m'.tr}'}', style: AppStyle.subtitle, + overflow: TextOverflow.ellipsis, ), ), ], ), - ), ], ), ), @@ -1120,72 +1133,3 @@ class CarDialogue extends StatelessWidget { ); } } - -class HourPickerExample extends StatelessWidget { - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () async { - int? selectedHour = await showCupertinoModalPopup( - context: context, - builder: (BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - height: 200, - color: Colors.white, - child: Column( - children: [ - CupertinoButton( - child: Text('Done'.tr), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - SizedBox( - height: 140, - child: CupertinoPicker( - itemExtent: 32.0, - onSelectedItemChanged: (int index) { - // Handle the selection - box.write(BoxName.hourWait, index); - // Navigator.pop(context, index + 1); - }, - children: List.generate(5, (int index) { - return Center( - child: Text('${index + 1} ${'hour'.tr}'), - ); - }), - ), - ), - ], - ), - ), - ); - }, - ); - - if (selectedHour != null) { - // Do something with the selected hour - print('Selected hour: $selectedHour'); - } - }, - child: Container( - // height: 70, - decoration: BoxDecoration( - border: Border.all( - color: AppColor.blueColor, - width: 3, - ), - borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'How many hours would you like to wait?'.tr, - style: TextStyle(fontSize: 18), - ), - ), - ), - ); - } -} diff --git a/lib/views/home/map_widget.dart/cash_confirm_bottom_page.dart b/lib/views/home/map_widget.dart/cash_confirm_bottom_page.dart index 0db1089..63a997d 100644 --- a/lib/views/home/map_widget.dart/cash_confirm_bottom_page.dart +++ b/lib/views/home/map_widget.dart/cash_confirm_bottom_page.dart @@ -29,7 +29,8 @@ class CashConfirmPageShown extends StatelessWidget { ? controller.cashConfirmPageShown : 0, decoration: BoxDecoration( - color: box.read(BoxName.carType) == 'Lady' + color: box.read(BoxName.carType) == 'Lady' || + box.read(BoxName.carType) == 'Pink Bike' ? Colors.pink.shade100 : AppColor.secondaryColor, borderRadius: BorderRadius.circular(15)), @@ -168,7 +169,8 @@ class CashConfirmPageShown extends StatelessWidget { paymentController.update(); controller.changeCashConfirmPageShown(); controller.isSearchingWindow = true; - controller.confirmRideForFirstDriver(); + controller + .confirmRideForAllDriverAvailable(); controller.update(); }, ), @@ -179,7 +181,7 @@ class CashConfirmPageShown extends StatelessWidget { onPressed: () { controller.changeCashConfirmPageShown(); controller.isSearchingWindow = true; - controller.confirmRideForFirstDriver(); + controller.confirmRideForAllDriverAvailable(); controller.update(); }, ), // Add a fallback widget if none of the conditions are met diff --git a/lib/views/home/map_widget.dart/form_search_places_destenation.dart b/lib/views/home/map_widget.dart/form_search_places_destenation.dart index 525e642..83217c3 100644 --- a/lib/views/home/map_widget.dart/form_search_places_destenation.dart +++ b/lib/views/home/map_widget.dart/form_search_places_destenation.dart @@ -242,109 +242,127 @@ GetBuilder formSearchPlacesDestenation() { // ) // : const SizedBox(), Container( - height: controller.placesDestination.isNotEmpty - ? controller.height - : 0, - color: AppColor.secondaryColor, - child: ListView.builder( - itemCount: controller.placesDestination.length, - itemBuilder: (BuildContext context, int index) { - var res = controller.placesDestination[index]; - return InkWell( - onTap: () async { - controller.changeHeightPlaces(); - await sql.insertData({ - 'latitude': res['geometry']['location']['lat'], - 'longitude': res['geometry']['location']['lng'], - 'name': res['name'].toString(), - 'rate': res['rating'].toString(), - }, TableName.recentLocations); + height: controller.placesDestination.isNotEmpty + ? controller.height + : 0, + color: AppColor.secondaryColor, + child: ListView.builder( + itemCount: controller.placesDestination.length, + itemBuilder: (BuildContext context, int index) { + var res = controller.placesDestination[index]; - controller.changeHeightPlaces(); + // Extract fields with null safety + var title = res['title']?.toString() ?? 'Unknown Place'; + var position = res['position']; + var latitude = position?['lat']; + var longitude = position?['lng']; + var address = + res['address']?['label'] ?? 'Unknown Address'; + var categories = res['categories'] ?? []; + var primaryCategory = categories.isNotEmpty + ? categories[0]['name'] + : 'Unknown Category'; - controller.passengerLocation = controller.newMyLocation; - controller.myDestination = LatLng( - double.parse( - res['geometry']['location']['lat'].toString()), - double.parse( - res['geometry']['location']['lng'].toString()), - ); - controller.convertHintTextDestinationNewPlaces(index); + return InkWell( + onTap: () async { + if (latitude != null && longitude != null) { + sql.insertMapLocation({ + 'latitude': latitude, + 'longitude': longitude, + 'name': title, + 'rate': 'N/A', + 'createdAt': DateTime.now().toIso8601String(), + // No rating in this structure, adjust as needed + }, TableName.recentLocations); - controller.placesDestination = []; - controller.placeDestinationController.clear(); - controller.changeMainBottomMenuMap(); - controller.passengerStartLocationFromMap = true; - controller.isPickerShown = true; - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - children: [ - Image.network( - res['icon'], - width: 20, + controller.passengerLocation = + controller.newMyLocation; + controller.myDestination = + LatLng(latitude, longitude); + controller + .convertHintTextDestinationNewPlaces(index); + + controller.placesDestination = []; + controller.placeDestinationController.clear(); + controller.changeMainBottomMenuMap(); + controller.passengerStartLocationFromMap = true; + controller.isPickerShown = true; + } else { + Toast.show( + context, + 'Invalid location data', + AppColor.redColor, + ); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + const Icon(Icons.place, + size: 20), // Fallback icon for places + IconButton( + onPressed: () async { + if (latitude != null && + longitude != null) { + await sql.insertMapLocation({ + 'latitude': latitude, + 'longitude': longitude, + 'name': title, + 'rate': 'N/A', + }, TableName.placesFavorite); + Toast.show( + context, + '$title ${'Saved Successfully'.tr}', + AppColor.primaryColor, + ); + } else { + Toast.show( + context, + 'Invalid location data', + AppColor.redColor, + ); + } + }, + icon: const Icon(Icons.favorite_border), + ), + ], + ), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppStyle.title, + ), + Text( + address, + style: AppStyle.subtitle, + ), + Text( + primaryCategory, + style: AppStyle.subtitle, + ), + ], ), - IconButton( - onPressed: () async { - await sql.insertData({ - 'latitude': res['geometry'] - ['location']['lat'], - 'longitude': res['geometry'] - ['location']['lng'], - 'name': res['name'].toString(), - 'rate': res['rating'].toString(), - }, TableName.placesFavorite); - Toast.show( - context, - '${res['name']} ${'Saved Sucssefully'.tr}', - AppColor.primaryColor); - }, - icon: const Icon(Icons.favorite_border), - ), - ], - ), - Column( - children: [ - Text( - res['name'].toString(), - style: AppStyle.title, - ), - Text( - res['vicinity'].toString(), - style: AppStyle.subtitle, - ), - ], - ), - Column( - children: [ - Text( - 'rate', - style: AppStyle.subtitle, - ), - Text( - res['rating'].toString(), - style: AppStyle.subtitle, - ), - ], - ), - ], - ), - const Divider( - thickness: 1, - ) - ], + ), + ], + ), + const Divider(thickness: 1), + ], + ), ), - ), - ); - }, - ), - ) + ); + }, + )) ], )); } diff --git a/lib/views/home/map_widget.dart/form_search_start.dart b/lib/views/home/map_widget.dart/form_search_start.dart index 171ec03..ac7d2d2 100644 --- a/lib/views/home/map_widget.dart/form_search_start.dart +++ b/lib/views/home/map_widget.dart/form_search_start.dart @@ -103,7 +103,7 @@ GetBuilder formSearchPlacesStart() { // controller.myLocation = // controller.newStartPointLocation; // } - await sql.insertData({ + await sql.insertMapLocation({ 'latitude': res['geometry']['location']['lat'], 'longitude': res['geometry']['location']['lng'], 'name': res['name'].toString(), @@ -130,7 +130,7 @@ GetBuilder formSearchPlacesStart() { ), IconButton( onPressed: () async { - await sql.insertData({ + await sql.insertMapLocation({ 'latitude': res['geometry'] ['location']['lat'], 'longitude': res['geometry'] diff --git a/lib/views/home/map_widget.dart/form_serch_multiy_point.dart b/lib/views/home/map_widget.dart/form_serch_multiy_point.dart index 0347790..fed2274 100644 --- a/lib/views/home/map_widget.dart/form_serch_multiy_point.dart +++ b/lib/views/home/map_widget.dart/form_serch_multiy_point.dart @@ -109,7 +109,7 @@ GetBuilder formSearchPlaces(int index) { ), IconButton( onPressed: () async { - await sql.insertData({ + await sql.insertMapLocation({ 'latitude': res['geometry'] ['location']['lat'], 'longitude': res['geometry'] diff --git a/lib/views/home/map_widget.dart/google_map_passenger_widget.dart b/lib/views/home/map_widget.dart/google_map_passenger_widget.dart index 3b439b9..a7bfdcc 100644 --- a/lib/views/home/map_widget.dart/google_map_passenger_widget.dart +++ b/lib/views/home/map_widget.dart/google_map_passenger_widget.dart @@ -318,7 +318,7 @@ class GoogleMapPassengerWidget extends StatelessWidget { // icon: controller.endIcon, // ), // }, - + polygons: controller.polygons, polylines: { Polyline( zIndex: 2, @@ -419,7 +419,7 @@ class GoogleMapPassengerWidget extends StatelessWidget { }, mapType: - controller.mapType ? MapType.satellite : MapType.normal, + controller.mapType ? MapType.satellite : MapType.terrain, myLocationButtonEnabled: true, // liteModeEnabled: true, tiltGesturesEnabled: false, diff --git a/lib/views/home/map_widget.dart/left_main_menu_icons.dart b/lib/views/home/map_widget.dart/left_main_menu_icons.dart index fad231a..09b3346 100644 --- a/lib/views/home/map_widget.dart/left_main_menu_icons.dart +++ b/lib/views/home/map_widget.dart/left_main_menu_icons.dart @@ -1,22 +1,14 @@ -import 'package:SEFER/constant/api_key.dart'; -import 'package:SEFER/constant/box_name.dart'; -import 'package:SEFER/main.dart'; -import 'package:SEFER/views/auth/sms_verfy_page.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_font_icons/flutter_font_icons.dart'; import 'package:get/get.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; -import '../../../constant/char_map.dart'; import '../../../constant/colors.dart'; -import '../../../constant/credential.dart'; -import '../../../constant/links.dart'; -import '../../../controller/firebase/firbase_messge.dart'; -import '../../../controller/functions/audio_record1.dart'; -import '../../../controller/functions/crud.dart'; import '../../../controller/functions/tts.dart'; import '../../../controller/home/map_passenger_controller.dart'; +import '../../../controller/home/vip_waitting_page.dart'; GetBuilder leftMainMenuIcons() { - final textToSpeechController = Get.put(TextToSpeechController()); + Get.put(TextToSpeechController()); return GetBuilder( builder: (controller) => Positioned( top: Get.height * .008, @@ -99,20 +91,10 @@ GetBuilder leftMainMenuIcons() { borderRadius: BorderRadius.circular(15)), child: IconButton( onPressed: () async { - FirebaseMessagesController().sendNotificationToAnyWithoutData( - 'Order'.tr, - 'from: ', - // jsonDecode(value)['message'].toString(), - 'fBJObfCd9kHxnzMsEzeh2R:APA91bEE435Fvg1ixHs2_GPJJzz5CztswczqAi-PJfS6gSzg5U0eHvOi_v2J3imqPeWvkic-Dhhq2Pzrva2LncvS3MofCTJyM8AVScktGUuB6NvgyeK_5er8yDPrp2-2fqUz7VOXflni', - 'order.wav' - - // polylineCoordinates.toString() - ); - // print(box.read(BoxName.tokenFCM)); - // + Get.to(() => VipWaittingPage()); }, icon: const Icon( - Icons.voice_chat, + Octicons.watch, // Replace this with your desired VIP icon size: 29, ), ), @@ -126,54 +108,13 @@ GetBuilder leftMainMenuIcons() { // borderRadius: BorderRadius.circular(15)), // child: IconButton( // onPressed: () async { - // Get.to(SmsSignupEgypt()); + // controller.statusRide == 'Apply' && + // controller.isSearchingWindow == false; + // controller.update(); // }, // icon: const Icon( - // Icons.chat, - // size: 29, - // ), - // ), - // ), - // AnimatedContainer( - // duration: const Duration(microseconds: 200), - // width: controller.widthMapTypeAndTraffic, - // decoration: BoxDecoration( - // color: AppColor.secondaryColor, - // border: Border.all(), - // borderRadius: BorderRadius.circular(15)), - // child: IconButton( - // onPressed: () async { - // await CRUD().allMethodForAI( - // 'name,fullName,address,idNumber,cardId,dob', - // AppLink.uploadEgypt, - // 'idFront'); - - // await ImageController().choosImage( - // 'https://api.sefer.live/sefer/uploadEgypt.php', - // 'FrontId'); - // AC credentials = AC(); - // String apiKey = - // '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'; - // String convertedStringN = credentials.c( - // credentials.c(credentials.c(apiKey, cs), cC), cn); - - // String retrievedStringS = credentials.r( - // credentials.r(credentials.r(convertedStringN, cn), cC), - // cs); - // // - // if (retrievedStringS == apiKey) { - // print('convertedStringN --- $convertedStringN'); - // print('retrievedStringS ---$retrievedStringS'); - // print('same'); - // } - - // await Get.find() - // .payWithPayMob(context, '1100', 'EGP'); - // Initiates a payment with a card using the FlutterPaymob instance - // }, - // icon: const Icon( - // // Get.put(AudioRecorderController()).isRecording - // Icons.start, + // Octicons + // .telescope, // Replace this with your desired VIP icon // size: 29, // ), // ), diff --git a/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart b/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart index 1d34d3f..65591c5 100644 --- a/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart +++ b/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart @@ -1,4 +1,6 @@ +import 'package:SEFER/views/widgets/my_dialog.dart'; import 'package:SEFER/views/widgets/my_textField.dart'; +import 'package:SEFER/views/widgets/mysnakbar.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -14,6 +16,7 @@ import '../../../constant/colors.dart'; import '../../../constant/table_names.dart'; import '../../../controller/functions/toast.dart'; import '../../../controller/functions/tts.dart'; +import '../../../print.dart'; import 'form_search_start.dart'; class MainBottomMenuMap extends StatelessWidget { @@ -46,24 +49,34 @@ class MainBottomMenuMap extends StatelessWidget { child: Container( width: Get.width * .8, height: Get.height * .1, - decoration: const BoxDecoration( - boxShadow: [ - BoxShadow( - color: Color.fromARGB( - 255, 237, 230, 230), - blurRadius: 5, - offset: Offset(2, 4)), - BoxShadow( - color: Color.fromARGB( - 255, 242, 237, 237), - blurRadius: 5, - offset: Offset(-2, -2)) - ], - color: AppColor.blueColor, - borderRadius: BorderRadius.all( - Radius.elliptical(15, 30), + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.blueColor.withOpacity(0.8), + AppColor.blueColor.withOpacity(0.6), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, ), + boxShadow: const [ + BoxShadow( + color: Color.fromARGB( + 255, 237, 230, 230), + blurRadius: 8, + offset: Offset(4, 8), + ), + BoxShadow( + color: Color.fromARGB( + 255, 242, 237, 237), + blurRadius: 8, + offset: Offset(-4, -4), + ), + ], + borderRadius: BorderRadius.circular(30), ), + // decoration: AppStyle.boxDecoration1, child: DefaultTextStyle( style: AppStyle.title.copyWith( @@ -338,7 +351,7 @@ class MainBottomMenuMap extends StatelessWidget { title: 'Yes'.tr, onPressed: () async { Get.back(); - controller.getLocation(); + await controller.getLocation(); await controller.getMap( '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', '${controller.recentPlaces[index]['latitude']},${controller.recentPlaces[index]['longitude']}', @@ -350,6 +363,18 @@ class MainBottomMenuMap extends StatelessWidget { }, )); }, + onLongPress: () { + MyDialog().getDialog( + "Are you sure to delete this location?".tr, '', () { + sql.deleteData(TableName.recentLocations, + controller.recentPlaces[index]['id']); + + controller.getFavioratePlaces(); + controller.update(); + Get.back(); + mySnackbarSuccess('deleted'.tr); + }); + }, child: Container( decoration: AppStyle.boxDecoration1, child: Padding( @@ -606,7 +631,7 @@ class FaviouratePlacesDialog extends StatelessWidget { TextButton( onPressed: () async { Get.back(); - controller.getLocation(); + await controller.getLocation(); await controller.getMap( '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', '${favoritePlaces[index]['latitude']},${favoritePlaces[index]['longitude']}', diff --git a/lib/views/home/map_widget.dart/map_menu_widget.dart b/lib/views/home/map_widget.dart/map_menu_widget.dart index 01971a6..8082df7 100644 --- a/lib/views/home/map_widget.dart/map_menu_widget.dart +++ b/lib/views/home/map_widget.dart/map_menu_widget.dart @@ -1,3 +1,4 @@ +import 'package:SEFER/views/home/HomePage/contact_us.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_font_icons/flutter_font_icons.dart'; @@ -110,10 +111,10 @@ class MapMenuWidget extends StatelessWidget { ), IconMainPageMap( onTap: () { - Get.to(() => const TaarifPage()); + Get.to(() => ContactUsPage()); }, - title: 'Tariff'.tr, - icon: Icons.money, + title: "Contact Us".tr, + icon: Icons.contact_page, ), ], ), diff --git a/lib/views/home/map_widget.dart/ride_begin_passenger.dart b/lib/views/home/map_widget.dart/ride_begin_passenger.dart index 984c2a7..cf919a1 100644 --- a/lib/views/home/map_widget.dart/ride_begin_passenger.dart +++ b/lib/views/home/map_widget.dart/ride_begin_passenger.dart @@ -10,6 +10,7 @@ import 'package:SEFER/main.dart'; import '../../../constant/colors.dart'; import '../../../constant/style.dart'; import '../../../controller/functions/audio_record1.dart'; +import '../../../controller/functions/launch.dart'; import '../../../controller/functions/toast.dart'; import '../../../controller/home/map_passenger_controller.dart'; @@ -29,7 +30,7 @@ class RideBeginPassenger extends StatelessWidget { return Positioned( left: 10, right: 10, - bottom: 4, + bottom: 10, child: Container( decoration: AppStyle.boxDecoration, height: controller.statusRide == 'Begin' ? Get.height * .33 : 0, @@ -52,7 +53,7 @@ class RideBeginPassenger extends StatelessWidget { Container( decoration: AppStyle.boxDecoration, child: Text( - controller.firstName, + controller.driverName, style: AppStyle.title, ), ), @@ -248,8 +249,8 @@ class RideBeginPassenger extends StatelessWidget { profileController.prfoileData['sosPhone']); } } else { - controller - .sendSMS(box.read(BoxName.sosPhonePassenger)); + makePhoneCall('122'); + // box.read(BoxName.sosPhonePassenger)); } }, icon: const Icon( diff --git a/lib/views/home/map_widget.dart/searching_captain_window.dart b/lib/views/home/map_widget.dart/searching_captain_window.dart index e9d5342..d98c834 100644 --- a/lib/views/home/map_widget.dart/searching_captain_window.dart +++ b/lib/views/home/map_widget.dart/searching_captain_window.dart @@ -8,6 +8,121 @@ import 'package:SEFER/views/widgets/my_textField.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import '../../../constant/links.dart'; + +// class SearchingCaptainWindow extends StatelessWidget { +// const SearchingCaptainWindow({super.key}); + +// Widget _buildDriverAvatars(MapPassengerController controller) { +// // If no drivers yet, show loading indicator +// if (controller.isSearchingWindow) { +// // Check if dataCarsLocationByPassenger or its 'data' is null +// if (controller.dataCarsLocationByPassenger == null || +// controller.dataCarsLocationByPassenger['data'] == null || +// controller.dataCarsLocationByPassenger['data'].isEmpty) { +// return const SizedBox( +// height: 60, +// child: Center( +// child: CircularProgressIndicator( +// valueColor: +// AlwaysStoppedAnimation(AppColor.secondaryColor), +// ), +// ), +// ); +// } +// } + +// return SizedBox( +// height: 60, +// child: ListView.builder( +// scrollDirection: Axis.horizontal, +// itemCount: controller.dataCarsLocationByPassenger['data'].length, +// padding: const EdgeInsets.symmetric(horizontal: 16), +// itemBuilder: (context, index) { +// final driver = controller.dataCarsLocationByPassenger['data'][index]; +// return Padding( +// padding: const EdgeInsets.only(right: 8), +// child: Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// CircleAvatar( +// radius: 25, +// backgroundColor: AppColor.secondaryColor, +// child: ClipOval( +// child: Image.network( +// '${AppLink.server}/portrate_captain_image/${driver['driver_id']}.jpg', +// width: 50, +// height: 50, +// fit: BoxFit.cover, +// errorBuilder: (context, error, stackTrace) { +// return const Icon( +// Icons.person, +// color: Colors.white, +// size: 30, +// ); +// }, +// ), +// ), +// ), +// ], +// ), +// ); +// }, +// ), +// ); +// } + +// @override +// Widget build(BuildContext context) { +// return GetBuilder( +// builder: (mapPassengerController) { +// return mapPassengerController.isSearchingWindow +// ? Positioned( +// bottom: 0, +// left: 0, +// right: 0, +// child: Container( +// decoration: AppStyle.boxDecoration1, +// height: Get.height * +// .3, // Increased height to accommodate avatars +// child: Column( +// mainAxisAlignment: MainAxisAlignment.spaceEvenly, +// children: [ +// SizedBox( +// width: Get.width * .7, +// child: const LinearProgressIndicator( +// minHeight: 6, +// backgroundColor: AppColor.yellowColor, +// color: AppColor.secondaryColor, +// ), +// ), +// mapPassengerController.driverOrderStatus == 'recive' +// ? Text( +// "Drivers received orders".tr, +// style: AppStyle.title, +// ) +// : Text( +// "We are searching for the nearest driver to you" +// .tr, +// style: AppStyle.title, +// ), +// Text( +// 'please wait till driver accept your order'.tr, +// style: AppStyle.title, +// ), +// // New: Driver avatars section +// _buildDriverAvatars(mapPassengerController), +// _buildTimer(mapPassengerController), +// ], +// ), +// ), +// ) +// : const SizedBox(); +// }, +// ); +// } +// } + class SearchingCaptainWindow extends StatelessWidget { const SearchingCaptainWindow({super.key}); @@ -22,11 +137,13 @@ class SearchingCaptainWindow extends StatelessWidget { right: 0, child: Container( decoration: AppStyle.boxDecoration1, - height: Get.height * .2, + height: Get.height * .25, child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, // Use Stack for overlapping widgets children: [ // Text elements + SizedBox( width: Get.width * .7, child: const LinearProgressIndicator( @@ -36,9 +153,13 @@ class SearchingCaptainWindow extends StatelessWidget { ), ), Text( - "We are searching for the nearest driver to you".tr, + mapPassengerController.driversStatusForSearchWindow, style: AppStyle.title, ), + // Text( + // "We are searching for the nearest driver to you".tr, + // style: AppStyle.title, + // ), Text( 'please wait till driver accept your order'.tr, style: AppStyle.title, diff --git a/lib/views/home/map_widget.dart/select_driver_mishwari.dart b/lib/views/home/map_widget.dart/select_driver_mishwari.dart index 0f7f335..dfaee4b 100644 --- a/lib/views/home/map_widget.dart/select_driver_mishwari.dart +++ b/lib/views/home/map_widget.dart/select_driver_mishwari.dart @@ -1,13 +1,13 @@ import 'package:SEFER/constant/colors.dart'; import 'package:SEFER/constant/style.dart'; import 'package:SEFER/controller/home/map_passenger_controller.dart'; -import 'package:SEFER/env/env.dart'; import 'package:SEFER/views/widgets/elevated_btn.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../constant/api_key.dart'; +import '../../../constant/links.dart'; import '../../../print.dart'; class CupertinoDriverListWidget extends StatelessWidget { @@ -17,154 +17,214 @@ class CupertinoDriverListWidget extends StatelessWidget { Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - middle: Text('Driver List'.tr), + middle: Text('Driver List'.tr), // Ensure text is properly localized ), child: SafeArea( - child: ListView.separated( - itemCount: mapPassengerController.driversForMishwari.length, - separatorBuilder: (context, index) => const Divider(height: 1), - itemBuilder: (context, index) { - var driver = mapPassengerController.driversForMishwari[index]; - return Container( - decoration: AppStyle.boxDecoration1, - child: CupertinoListTile( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), - leading: CircleAvatar( - radius: 25, - backgroundImage: NetworkImage( - '${Env.seferCairoServer}/portrate_captain_image/${driver['id']}.jpg', - ), - backgroundColor: CupertinoColors.systemGrey5, - ), - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '${driver['NAME'].toString().split(' ')[0]} ${driver['NAME'].toString().split(' ')[1]}', - style: const TextStyle(fontWeight: FontWeight.bold), + child: mapPassengerController.driversForMishwari.isEmpty + ? Center( + child: Text( + 'No drivers available at the moment. Please try again later.' + .tr, + style: const TextStyle( + fontSize: 18, // Adjust the size as needed + fontWeight: FontWeight.w600, + color: CupertinoColors.inactiveGray, // Customize color + ), + textAlign: TextAlign.center, // Center-align the text ), - Text('${'Age'.tr}: ${driver['age'].toString()}'), - Row( - children: [ - const Icon(CupertinoIcons.star_fill, - size: 16, color: CupertinoColors.systemYellow), - const SizedBox(width: 4), - Text(driver['rating']?.toStringAsFixed(1) ?? 'N/A'.tr), - const SizedBox(width: 8), - Text('${'Rides'.tr}: ${driver['countRide']}'), - ], - ), - ], - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '${'Car'.tr}: ${driver['make']} ${driver['model']} (${driver['year']})'), - Text('${'Plate'.tr}: ${driver['car_plate']}'), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('${'Education'.tr}: ${driver['education']}'), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox( - // width: Get.width * .3, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('${'Color'.tr}: ${driver['color']}'), - const SizedBox(width: 8), - Container( - width: 20, - height: 20, - decoration: BoxDecoration( - color: hexToColor( - driver['color_hex'].toString()) ?? - Colors.amber, - borderRadius: BorderRadius.circular(4), - border: Border.all(), - ), - ), - ], + ) + : ListView.separated( + itemCount: mapPassengerController.driversForMishwari.length, + separatorBuilder: (context, index) => + const Divider(height: 1), + itemBuilder: (context, index) { + var driver = + mapPassengerController.driversForMishwari[index]; + return Container( + decoration: AppStyle.boxDecoration1, + child: CupertinoListTile( + padding: const EdgeInsets.symmetric( + vertical: 4, horizontal: 8), + leading: CircleAvatar( + radius: 25, + backgroundImage: NetworkImage( + '${AppLink.seferCairoServer}/portrate_captain_image/${driver['id']}.jpg', + ), + child: Builder( + builder: (context) { + return Image.network( + '${AppLink.seferCairoServer}/portrate_captain_image/${driver['id']}.jpg', + fit: BoxFit.cover, + loadingBuilder: (BuildContext context, + Widget child, + ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; // Image is loaded + } else { + return Center( + child: CircularProgressIndicator( + value: loadingProgress + .expectedTotalBytes != + null + ? loadingProgress + .cumulativeBytesLoaded / + (loadingProgress + .expectedTotalBytes ?? + 1) + : null, + ), + ); + } + }, + errorBuilder: (BuildContext context, + Object error, StackTrace? stackTrace) { + return const Icon( + Icons + .person, // Icon to show when image fails to load + size: 25, // Adjust the size as needed + color: AppColor + .blueColor, // Color for the error icon + ); + }, + ); + }, + ), ), - ), - ], - ), - ], - ), - onTap: () { - // Handle driver selection - Get.defaultDialog( - title: '${'Selected driver'.tr}: ${driver['NAME']}', - content: Column( - children: [ - Column( + title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '${'Car'.tr}: ${driver['make']} ${driver['model']} (${driver['year']})'), - Text('${'Plate'.tr}: ${driver['car_plate']}'), + '${driver['NAME'].toString().split(' ')[0]} ${driver['NAME'].toString().split(' ')[1]}', + style: + const TextStyle(fontWeight: FontWeight.bold), + ), + Text('${'Age'.tr}: ${driver['age'].toString()}'), Row( - mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('${'Color'.tr}: ${driver['color']}'), + const Icon(CupertinoIcons.star_fill, + size: 16, + color: CupertinoColors.systemYellow), + const SizedBox(width: 4), + Text(driver['rating']?.toStringAsFixed(1) ?? + 'N/A'.tr), const SizedBox(width: 8), - Container( - width: 20, - height: 20, - decoration: BoxDecoration( - color: hexToColor( - driver['color_hex'].toString()) ?? - AppColor.bronze, - borderRadius: BorderRadius.circular(4), - border: Border.all(), + Text('${'Rides'.tr}: ${driver['ride_count']}'), + ], + ), + ], + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${'Car'.tr}: ${driver['make']} ${driver['model']} (${driver['year']})'), + Text('${'Plate'.tr}: ${driver['car_plate']}'), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + // width: Get.width * .3, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text('${'Color'.tr}: ${driver['color']}'), + const SizedBox(width: 8), + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: driver['color_hex'] + .toString() == + 'null' + ? Colors.amber + : hexToColor(driver['color_hex'] + .toString()), + borderRadius: + BorderRadius.circular(4), + border: Border.all(), + ), + ), + ], ), ), ], ), ], ), - ], - ), - confirm: MyElevatedButton( - title: 'OK'.tr, - onPressed: () { - Get.back(); - showDateTimePickerDialog(driver); - Log.print('driver: ${driver}'); - })); - print('${'Selected driver'.tr}: ${driver['NAME']}'); - // Get.back(); // Close the dialog - }, - ), - ); - }, - )), + onTap: () { + Log.print(' driver["id"]: ${driver['driver_id']}'); + Get.find().driverIdVip = + driver['driver_id']; + + // Handle driver selection + Get.defaultDialog( + title: + '${'Selected driver'.tr}: ${driver['NAME']}', + content: Column( + children: [ + Column( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + '${'Car'.tr}: ${driver['make']} ${driver['model']} (${driver['year']})'), + Text( + '${'Plate'.tr}: ${driver['car_plate']}'), + Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Text( + '${'Color'.tr}: ${driver['color']}'), + const SizedBox(width: 8), + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: driver['color_hex'] + .toString() == + 'null' + ? Colors.amber + : hexToColor( + driver['color_hex'] + .toString()), + borderRadius: + BorderRadius.circular(4), + border: Border.all(), + ), + ), + ], + ), + ], + ), + ], + ), + confirm: MyElevatedButton( + title: 'OK'.tr, + onPressed: () { + Get.back(); + showDateTimePickerDialog(driver); + })); + print('${'Selected driver'.tr}: ${driver['NAME']}'); + // Get.back(); // Close the dialog + }, + ), + ); + }, + )), ); } Color hexToColor(String hexColor) { - if (hexColor == null || hexColor.isEmpty || hexColor == 'null') { - // Return a default color if the hex color is invalid - return Colors.grey; - } hexColor = hexColor.replaceAll("#", ""); - if (hexColor.length == 6) { - hexColor = "ff$hexColor"; - } else if (hexColor.length != 8) { - // Return a default color if the hex color is not in the valid format - return Colors.grey; - } - return Color(int.parse(hexColor, radix: 16)); + String colorString = "ff$hexColor"; + return Color(int.parse(colorString, radix: 16)); } void showDriverSelectionDialog(Map driver) { @@ -185,7 +245,9 @@ class CupertinoDriverListWidget extends StatelessWidget { width: 20, height: 20, decoration: BoxDecoration( - color: hexToColor(driver['color_hex'].toString()), + color: driver['color_hex'].toString() == 'null' + ? Colors.amber + : hexToColor(driver['color_hex'].toString()), borderRadius: BorderRadius.circular(4), border: Border.all(), ), @@ -204,28 +266,12 @@ class CupertinoDriverListWidget extends StatelessWidget { ); } - Future confirmTripData( - Map driver, DateTime selectedDateTime) async { - try { - // Save trip data and set up notifications - // Log.print('selectedDateTime: $selectedDateTime'); - // Log.print('driver: $driver'); - await mapPassengerController.saveTripData(driver, selectedDateTime); - Get.back(); // Close the dialog - } catch (e) { - // Handle any errors that occur during the save process - Log.print('Error saving trip data: $e'); - Get.snackbar('Error', 'Failed to save trip data'); - } - } - void showDateTimePickerDialog(Map driver) { - Log.print('driver: ${driver}'); DateTime selectedDateTime = DateTime.now(); Get.defaultDialog( barrierDismissible: false, - title: 'select date and time of trip'.tr, + title: "Select date and time of trip".tr, content: SizedBox( // height: 400, // Adjust height as needed width: double.maxFinite, @@ -237,10 +283,19 @@ class CupertinoDriverListWidget extends StatelessWidget { ), confirm: MyElevatedButton( title: 'Confirm Trip'.tr, - onPressed: () { + onPressed: () async { DateTime selectedDateTime = mapPassengerController.selectedDateTime.value; - confirmTripData(driver, selectedDateTime); + // Save trip data and set up notifications + Get.back(); + await mapPassengerController.saveTripData(driver, selectedDateTime); + }, + ), + cancel: MyElevatedButton( + kolor: AppColor.redColor, + title: 'Cancel'.tr, + onPressed: () { + Get.back(); }, ), ); @@ -253,10 +308,10 @@ class DateTimePickerWidget extends StatelessWidget { @override Widget build(BuildContext context) { return CupertinoPageScaffold( - navigationBar: const CupertinoNavigationBar( + navigationBar: CupertinoNavigationBar( transitionBetweenRoutes: false, automaticallyImplyLeading: false, - middle: Text('Date and Time Picker'), + middle: Text('Date and Time Picker'.tr), ), child: SafeArea( child: Column( diff --git a/lib/views/home/map_widget.dart/vip_begin.dart b/lib/views/home/map_widget.dart/vip_begin.dart new file mode 100644 index 0000000..b124710 --- /dev/null +++ b/lib/views/home/map_widget.dart/vip_begin.dart @@ -0,0 +1,319 @@ +import 'package:SEFER/constant/links.dart'; +import 'package:SEFER/views/home/profile/complaint_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_font_icons/flutter_font_icons.dart'; +import 'package:get/get.dart'; +import 'package:SEFER/constant/box_name.dart'; +import 'package:SEFER/controller/profile/profile_controller.dart'; +import 'package:SEFER/main.dart'; + +import '../../../constant/colors.dart'; +import '../../../constant/style.dart'; +import '../../../controller/functions/audio_record1.dart'; +import '../../../controller/functions/launch.dart'; +import '../../../controller/functions/toast.dart'; +import '../../../controller/home/map_passenger_controller.dart'; + +class VipRideBeginPassenger extends StatelessWidget { + const VipRideBeginPassenger({ + super.key, + }); + + @override + Widget build(BuildContext context) { + ProfileController profileController = Get.put(ProfileController()); + AudioRecorderController audioController = + Get.put(AudioRecorderController()); + // Get.put(MapPassengerController()); + return GetBuilder(builder: (controller) { + if (controller.statusRideVip == 'Begin' || + !controller.statusRideFromStart) { + return Positioned( + left: 10, + right: 10, + bottom: 10, + child: Container( + decoration: AppStyle.boxDecoration, + height: controller.statusRideVip == 'Begin' ? Get.height * .33 : 0, + // width: 100, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CircleAvatar( + radius: 30, + backgroundImage: NetworkImage( + '${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg', + ), + onBackgroundImageError: (_, __) { + // Handle error here + }, + backgroundColor: Colors.grey, + child: const Icon( + Icons.person, // Default icon or placeholder + size: 30, + color: Colors.white, + ), // Placeholder background color + ), + Column( + children: [ + Container( + decoration: AppStyle.boxDecoration, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + child: Text( + controller.driverName, + style: AppStyle.title, + ), + ), + ), + const SizedBox( + height: 10, + ), + Container( + decoration: AppStyle.boxDecoration, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + Text( + controller.make, + style: AppStyle.title, + ), + const SizedBox( + width: 10, + ), + Text( + controller.model, + style: AppStyle.title, + ), + ], + ), + ), + ), + ], + ), + Column( + children: [ + Container( + decoration: AppStyle.boxDecoration, + child: Padding( + padding: const EdgeInsets.all(3), + child: Text( + 'vip', + style: AppStyle.title, + ), + ), + ), + Text( + '${controller.driverRate} 📈', + style: AppStyle.title, + ), + ], + ), + ], + ), + // SizedBox( + // height: 5, + // ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Container( + width: Get.width * .15, + decoration: AppStyle.boxDecoration, + child: IconButton( + onPressed: () => Get.to( + () => ComplaintPage(), + transition: Transition.downToUp, + ), + icon: const Icon( + Icons.note_add, + color: AppColor.redColor, + ), + tooltip: ' Add Note', // Optional tooltip for clarity + ), + ), + Container( + width: Get.width * .15, + decoration: AppStyle.boxDecoration, + child: audioController.isRecording == false + ? IconButton( + onPressed: () async { + await audioController.startRecording(); + Toast.show(context, 'Start Record'.tr, + AppColor.greenColor); + }, + icon: const Icon( + Icons.play_circle_fill_outlined, + color: AppColor.greenColor, + ), + tooltip: + ' Add Note', // Optional tooltip for clarity + ) + : IconButton( + onPressed: () async { + await audioController.stopRecording(); + Toast.show(context, 'Record saved'.tr, + AppColor.greenColor); + }, + icon: const Icon( + Icons.stop_circle, + color: AppColor.greenColor, + ), + tooltip: + ' Add Note', // Optional tooltip for clarity + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Container( + decoration: AppStyle.boxDecoration, + width: Get.width * .15, + child: IconButton( + onPressed: () async { + if (box.read(BoxName.sosPhonePassenger) == null) { + { + await profileController.updatField( + 'sosPhone', TextInputType.phone); + box.write(BoxName.sosPhonePassenger, + profileController.prfoileData['sosPhone']); + } + } else { + makePhoneCall('122'); + // box.read(BoxName.sosPhonePassenger)); + } + }, + icon: const Icon( + Icons.sos_rounded, + color: AppColor.redColor, + ), + ), + ), + Container( + decoration: AppStyle.boxDecoration, + width: Get.width * .15, + child: IconButton( + onPressed: () async { + if (box.read(BoxName.sosPhonePassenger) == null || + box.read(BoxName.sosPhonePassenger) == 'sos') { + { + await profileController.updatField( + 'sosPhone', TextInputType.phone); + box.write(BoxName.sosPhonePassenger, + profileController.prfoileData['sosPhone']); + } + } else { + String phoneNumber = box + .read(BoxName.sosPhonePassenger) + .toString(); + // phoneNumber = phoneNumber.replaceAll('0', ''); + var phone = box.read(BoxName.countryCode) == + 'Egypt' + ? '+2${box.read(BoxName.sosPhonePassenger)}' + : '+962${box.read(BoxName.sosPhonePassenger)}'; + controller.sendWhatsapp(phone); + } + }, + icon: const Icon( + FontAwesome.whatsapp, + color: AppColor.greenColor, + ), + ), + ), + Container( + decoration: AppStyle.boxDecoration, + width: Get.width * .15, + child: IconButton( + onPressed: () async { + await controller.getTokenForParent(); + }, + icon: const Icon( + AntDesign.Safety, + color: AppColor.blueColor, + ), + ), + ), + ], + ), + Stack( + children: [ + // StreamCounter(), + LinearProgressIndicator( + backgroundColor: AppColor.accentColor, + color: + // controller.remainingTimeTimerRideBegin < 60 + // ? AppColor.redColor + // : + AppColor.greenColor, + minHeight: 25, + borderRadius: BorderRadius.circular(15), + value: + 24 //controller.progressTimerRideBegin.toDouble(), + ), + Center( + child: Text( + controller.stringElapsedTimeRideBeginVip, + style: AppStyle.title, + ), + ) + ], + ) + ], + ), + ), + ), + ); + } else { + return const SizedBox(); + } + }); + } +} + +class StreamCounter extends StatelessWidget { + const StreamCounter({Key? key}) : super(key: key); + + @override + // Build the UI based on the timer value + Widget build(BuildContext context) { + Get.put(MapPassengerController()); + return GetBuilder(builder: (controller) { + return StreamBuilder( + initialData: 0, + stream: controller.timerController.stream, + builder: (context, snapshot) { + // Calculate the remaining time based on the current tick + final remainingTime = controller.durationToRide - snapshot.data!; + + // Format the remaining time as a string + final formattedRemainingTime = + '${(remainingTime / 60).floor()}:${(remainingTime % 60).toString().padLeft(2, '0')}'; + + // Return the UI widgets based on the remaining time + return Column( + children: [ + Text(formattedRemainingTime), + // ElevatedButton( + // onPressed: () { + // // Handle button press here + // }, + // ), + ], + ); + }, + ); + }); + } +} diff --git a/lib/views/home/my_wallet/passenger_wallet.dart b/lib/views/home/my_wallet/passenger_wallet.dart index d3e9f3c..ce13291 100644 --- a/lib/views/home/my_wallet/passenger_wallet.dart +++ b/lib/views/home/my_wallet/passenger_wallet.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -6,6 +7,7 @@ import '../../../constant/box_name.dart'; import '../../../constant/colors.dart'; import '../../../constant/info.dart'; import '../../../constant/style.dart'; +import '../../../controller/functions/toast.dart'; import '../../../controller/home/payment/credit_card_controller.dart'; import '../../../controller/payment/payment_controller.dart'; import '../../../main.dart'; @@ -27,27 +29,51 @@ class PassengerWallet extends StatelessWidget { GetBuilder( builder: (controller) => Column( mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const CardSeferWallet(), const SizedBox( height: 20, ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: Row( - children: [ - MyElevatedButton( - kolor: AppColor.blueColor, - title: 'Payment History'.tr, - onPressed: () { - Get.to(() => const PaymentHistoryPassengerPage(), - transition: Transition.size); - }, - ), - ], + padding: + const EdgeInsets.symmetric(horizontal: 80, vertical: 10), + child: MyElevatedButton( + kolor: AppColor.blueColor, + title: 'Payment History'.tr, + onPressed: () { + Get.to(() => const PaymentHistoryPassengerPage(), + transition: Transition.size); + }, ), - ) + ), + // Padding( + // padding: + // const EdgeInsets.symmetric(horizontal: 80, vertical: 10), + // child: MyElevatedButton( + // kolor: AppColor.yellowColor, + // title: 'Bonus gift'.tr, + // onPressed: () { + // Get.dialog( + // AlertDialog( + // contentPadding: EdgeInsets + // .zero, // Removes the padding around the content + // content: SizedBox( + // width: 300, // Match the width of PromoBanner + // // height: 250, // Match the height of PromoBanner + // child: PromoBanner( + // promoCode: box.read(BoxName.promo), + // discountPercentage: box.read(BoxName.discount), + // validity: box.read(BoxName.validity), + // ), + // ), + // ), + // ); + // Log.print( + // 'box.read(BoxName.isGiftToken).toString(): ${box.read(BoxName.isGiftToken).toString()}'); + // }, + // ), + // ) ], ), ), @@ -62,11 +88,77 @@ class PassengerWallet extends StatelessWidget { bottom: Get.height * .2, left: Get.width * .2, right: Get.width * .2, - child: MyElevatedButton( - title: 'Show Promos to Charge'.tr, - onPressed: () { - controller.changePromoSheetDialogue(); - }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + MyElevatedButton( + title: 'Show Promos to Charge'.tr, + onPressed: () { + // controller.changePromoSheetDialogue(); + showPaymentBottomSheet(context); + }, + ), + const SizedBox( + height: 20, + ), + MyElevatedButton( + kolor: AppColor.deepPurpleAccent, + title: "Add wallet phone you use".tr, + onPressed: () { + Get.dialog( + CupertinoAlertDialog( + title: Text('Insert Wallet phone number'.tr), + content: Column( + children: [ + const SizedBox(height: 10), + Form( + key: controller.formKey, + child: CupertinoTextField( + controller: + controller.walletphoneController, + placeholder: + 'Insert Wallet phone number'.tr, + keyboardType: TextInputType.phone, + padding: const EdgeInsets.symmetric( + vertical: 12, horizontal: 10), + ), + ), + ], + ), + actions: [ + CupertinoDialogAction( + child: Text('Cancel'.tr, + style: const TextStyle( + color: CupertinoColors + .destructiveRed)), + onPressed: () { + Get.back(); // Dismiss the dialog + }, + ), + CupertinoDialogAction( + child: Text('OK'.tr, + style: const TextStyle( + color: + CupertinoColors.activeGreen)), + onPressed: () async { + Get.back(); // Close the dialog + box.write( + BoxName.phoneWallet, + controller + .walletphoneController.text); + Toast.show( + context, + 'Phone Wallet Saved Successfully'.tr, + AppColor.greenColor); + }, + ), + ], + ), + barrierDismissible: + false, // Set to prevent dismissing by tapping outside + ); + }) + ], ), )), const PassengerWalletDialog(), @@ -76,70 +168,71 @@ class PassengerWallet extends StatelessWidget { } class CardSeferWallet extends StatelessWidget { - const CardSeferWallet({ - super.key, - }); + const CardSeferWallet({super.key}); @override Widget build(BuildContext context) { - return GetBuilder(builder: (paymentController) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: Get.width * .85, - height: Get.height * .3, - decoration: BoxDecoration( - color: AppColor.twitterColor.withOpacity(.8), - borderRadius: const BorderRadius.all(Radius.circular(12)), - gradient: const LinearGradient(colors: [ - AppColor.redColor, - AppColor.yellowColor, - AppColor.yellowColor, - ]), - ), + return GetBuilder( + builder: (paymentController) { + return Container( + width: Get.width * 0.9, + height: Get.height * 0.25, + margin: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: CupertinoColors.extraLightBackgroundGray, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + offset: const Offset(0, 10), + blurRadius: 20, + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(16.0), child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Text( - '${AppInformation.appName} Wallet', - style: AppStyle.headTitle - .copyWith(color: AppColor.writeColor), - ) - ], + // Wallet Title + Text( + '${AppInformation.appName} Wallet', + style: AppStyle.headTitle.copyWith( + color: CupertinoColors.label, + fontSize: 20, + fontWeight: FontWeight.bold, ), ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '${box.read(BoxName.passengerWalletTotal)} \$' ?? - '0.0 \$', - style: AppStyle.headTitle2, - ) - ], - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Text( - box.read(BoxName.name), - style: AppStyle.title, - ) - ], + + // Wallet Balance + Center( + child: Text( + '${box.read(BoxName.passengerWalletTotal) ?? '0.0'} ${'LE'.tr}', + style: AppStyle.headTitle2.copyWith( + color: CupertinoColors.label, + fontSize: 36, + fontWeight: FontWeight.w600, + ), ), - ) + ), + + // User Name (Bottom Right) + Align( + alignment: Alignment.bottomRight, + child: Text( + box.read(BoxName.name), + style: AppStyle.title.copyWith( + color: CupertinoColors.secondaryLabel, + fontSize: 16, + ), + ), + ), ], ), ), - ], - ); - }); + ); + }, + ); } } diff --git a/lib/views/home/my_wallet/passenger_wallet_dialoge.dart b/lib/views/home/my_wallet/passenger_wallet_dialoge.dart index bb11e11..af85f94 100644 --- a/lib/views/home/my_wallet/passenger_wallet_dialoge.dart +++ b/lib/views/home/my_wallet/passenger_wallet_dialoge.dart @@ -1,11 +1,11 @@ -import 'package:SEFER/constant/box_name.dart'; +import 'package:SEFER/constant/style.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:SEFER/constant/box_name.dart'; import 'package:SEFER/constant/colors.dart'; -import 'package:SEFER/constant/style.dart'; import 'package:SEFER/controller/functions/toast.dart'; import 'package:SEFER/controller/payment/payment_controller.dart'; -import 'package:SEFER/views/widgets/elevated_btn.dart'; import '../../../main.dart'; @@ -18,249 +18,344 @@ class PassengerWalletDialog extends StatelessWidget { Widget build(BuildContext context) { return GetBuilder( builder: (controller) => Positioned( - top: Get.height * .1, - right: Get.width * .15, - left: Get.width * .15, - bottom: Get.height * .1, - child: controller.isPromoSheetDialogue - ? Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(12)), - color: AppColor.secondaryColor, - boxShadow: [ - BoxShadow( - color: AppColor.accentColor, - offset: Offset(-1, -1), - blurRadius: 0, - spreadRadius: 0, - blurStyle: BlurStyle.normal), - BoxShadow( - color: AppColor.accentColor, - offset: Offset(3, 3), - blurRadius: 1, - spreadRadius: 0, - blurStyle: BlurStyle.normal) - ]), - child: Padding( - padding: const EdgeInsets.all(6), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - GestureDetector( - onTap: () { - controller.updateSelectedAmount(10); - }, - child: Row( - children: [ - Radio( - value: box.read(BoxName.countryCode) == 'Egypt' - ? 100 - : 10, - groupValue: controller.selectedAmount, - onChanged: (value) { - controller.updateSelectedAmount(value as int); - }, - ), - Text( - box.read(BoxName.countryCode) == 'Egypt' - ? '100 ${'LE'.tr}'.tr - : '10 ${'JOD'.tr}'.tr, - style: AppStyle.title, - ), - ], + top: Get.height * .1, + right: Get.width * .15, + left: Get.width * .15, + bottom: Get.height * .1, + child: controller.isPromoSheetDialogue + ? CupertinoActionSheet( + title: Text('Select Payment Amount'.tr), + actions: [ + CupertinoActionSheetAction( + onPressed: () { + controller.updateSelectedAmount( + box.read(BoxName.countryCode) == 'Egypt' ? 100 : 10, + ); + showPaymentOptions(context, controller); + }, + child: Text( + box.read(BoxName.countryCode) == 'Egypt' + ? '100 ${'LE'.tr}' + : '10 ${'JOD'.tr}', + ), + ), + CupertinoActionSheetAction( + onPressed: () { + controller.updateSelectedAmount( + box.read(BoxName.countryCode) == 'Egypt' ? 200 : 20, + ); + showPaymentOptions(context, controller); + }, + child: Text( + box.read(BoxName.countryCode) == 'Egypt' + ? '200 ${'LE'.tr} = 205 ${'LE'.tr}' + : '20 ${'JOD'.tr}', + ), + ), + CupertinoActionSheetAction( + onPressed: () { + controller.updateSelectedAmount( + box.read(BoxName.countryCode) == 'Egypt' ? 400 : 40, + ); + showPaymentOptions(context, controller); + }, + child: Text( + box.read(BoxName.countryCode) == 'Egypt' + ? '400 ${'LE'.tr} = 415 ${'LE'.tr}' + : '40 ${'JOD'.tr}', + ), + ), + CupertinoActionSheetAction( + onPressed: () { + controller.updateSelectedAmount( + box.read(BoxName.countryCode) == 'Egypt' ? 1000 : 50, + ); + showPaymentOptions(context, controller); + }, + child: Text( + box.read(BoxName.countryCode) == 'Egypt' + ? '1000 ${'LE'.tr} = 1100 ${'LE'.tr}' + : '50 ${'JOD'.tr}', + ), + ), + ], + cancelButton: CupertinoActionSheetAction( + onPressed: () { + controller.changePromoSheetDialogue(); + }, + child: Text('Cancel'.tr), + ), + ) + : const SizedBox(), + ), + ); + } +} +// class PassengerWalletDialog extends StatelessWidget { +// const PassengerWalletDialog({ +// super.key, +// }); + +// @override +// Widget build(BuildContext context) { +// return GetBuilder( +// builder: (controller) { +// return Positioned( +// top: Get.height * .1, +// right: Get.width * .15, +// left: Get.width * .15, +// bottom: Get.height * .1, +// child: controller.isPromoSheetDialogue +// ? Container() +// : SizedBox +// .shrink(), // If condition is false, return an empty widget +// ); +// }, +// ); +// } +// } +void showPaymentBottomSheet(BuildContext context) { + final controller = Get.find(); + + showModalBottomSheet( + context: context, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(15.0)), + ), + builder: (BuildContext context) { + return WillPopScope( + onWillPop: () async { + Get.back(); + return false; + }, + child: Container( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + 'Select Payment Amount'.tr, + style: AppStyle.headTitle2, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16.0), + + // Payment Options List + _buildPaymentOption( + context: context, + controller: controller, + amount: box.read(BoxName.countryCode) == 'Egypt' ? 100 : 10, + bonusAmount: 0, + currency: box.read(BoxName.countryCode) == 'Egypt' + ? 'LE'.tr + : 'JOD'.tr, + ), + + const SizedBox(height: 8.0), + _buildPaymentOption( + context: context, + controller: controller, + amount: box.read(BoxName.countryCode) == 'Egypt' ? 200 : 20, + bonusAmount: box.read(BoxName.countryCode) == 'Egypt' ? 5 : 1, + currency: box.read(BoxName.countryCode) == 'Egypt' + ? 'LE'.tr + : 'JOD'.tr, + ), + + const SizedBox(height: 8.0), + _buildPaymentOption( + context: context, + controller: controller, + amount: box.read(BoxName.countryCode) == 'Egypt' ? 400 : 40, + bonusAmount: + box.read(BoxName.countryCode) == 'Egypt' ? 15 : 2.5, + currency: box.read(BoxName.countryCode) == 'Egypt' + ? 'LE'.tr + : 'JOD'.tr, + ), + + const SizedBox(height: 8.0), + _buildPaymentOption( + context: context, + controller: controller, + amount: box.read(BoxName.countryCode) == 'Egypt' ? 1000 : 50, + bonusAmount: box.read(BoxName.countryCode) == 'Egypt' ? 100 : 6, + currency: box.read(BoxName.countryCode) == 'Egypt' + ? 'LE'.tr + : 'JOD'.tr, + ), + + const SizedBox(height: 16.0), + TextButton( + onPressed: () => Get.back(), + child: Text('Cancel'.tr), + ), + ], + ), + ), + ); + }, + ); +} + +Widget _buildPaymentOption({ + required BuildContext context, + required PaymentController controller, + required int amount, + required double bonusAmount, + required String currency, +}) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + controller.updateSelectedAmount(amount); + Get.back(); + showPaymentOptions(context, controller); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey[300]!), + borderRadius: BorderRadius.circular(8.0), + ), + child: Text( + bonusAmount > 0 + ? '${'Pay'.tr} $amount $currency, ${'Get'.tr} ${amount + bonusAmount} $currency' + : '$amount $currency', + style: AppStyle.title, + textAlign: TextAlign.center, + ), + ), + ), + ); +} + +void showPaymentOptions(BuildContext context, PaymentController controller) { + showCupertinoModalPopup( + context: context, + builder: (context) => CupertinoActionSheet( + title: Text('Payment Options'.tr), + actions: [ + box.read(BoxName.countryCode) == 'Egypt' + ? CupertinoActionSheetAction( + child: Text('💳 Pay with Credit Card'.tr), + onPressed: () { + if (controller.selectedAmount != 0) { + controller.payWithPayMob( + context, + controller.selectedAmount.toString(), + box.read(BoxName.countryCode) == 'Egypt' ? 'EGP' : 'JOD', + () async { + await controller.addPassengerWallet(); + controller.changePromoSheetDialogue(); + await controller.getPassengerWallet(); + }, + ); + } else { + Toast.show(context, '⚠️ You need to choose an amount!'.tr, + AppColor.redColor); + } + }, + ) + : const SizedBox(), + box.read(BoxName.countryCode) != 'Egypt' + ? CupertinoActionSheetAction( + child: Text('Pay with PayPal'.tr), + onPressed: () { + if (controller.selectedAmount != 0) { + controller.makePaymentPayPal(context); + } else { + Toast.show(context, 'You will choose one of above!'.tr, + AppColor.redColor); + } + }, + ) + : const SizedBox(), + box.read(BoxName.phoneWallet) != null + ? CupertinoActionSheetAction( + child: Text('💰 Pay with Wallet'.tr), + onPressed: () { + if (controller.selectedAmount != 0) { + controller.isLoading = true; + controller.update(); + controller.payWithPayMobWallet( + context, + controller.selectedAmount.toString(), + box.read(BoxName.countryCode) == 'Egypt' ? 'EGP' : 'JOD', + () async { + await controller.addPassengerWallet(); + controller.changePromoSheetDialogue(); + await controller.getPassengerWallet(); + }, + ); + controller.isLoading = false; + controller.update(); + } else { + Toast.show(context, '⚠️ You need to choose an amount!'.tr, + AppColor.redColor); + } + }, + ) + : CupertinoActionSheetAction( + child: Text('Add wallet phone you use'.tr), + onPressed: () { + Get.dialog( + CupertinoAlertDialog( + title: Text('Insert Wallet phone number'.tr), + content: Column( + children: [ + const SizedBox(height: 10), + CupertinoTextField( + controller: controller.walletphoneController, + placeholder: 'Insert Wallet phone number'.tr, + keyboardType: TextInputType.phone, + padding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 10, + ), ), - ), - GestureDetector( - onTap: () { - controller.updateSelectedAmount(20); - }, - child: Row( - children: [ - Radio( - value: - box.read(BoxName.countryCode) == 'Egypt' - ? 200 - : 20, - groupValue: controller.selectedAmount, - onChanged: (value) { - controller - .updateSelectedAmount(value as int); - }, - ), - Text( - box.read(BoxName.countryCode) == 'Egypt' - ? '200 ${'LE'.tr} '.tr - : '20 ${'JOD'.tr}'.tr, - style: AppStyle.title, - ), - ], - )), - GestureDetector( - onTap: () { - controller.updateSelectedAmount(40); - }, - child: Row( - children: [ - Radio( - value: - box.read(BoxName.countryCode) == 'Egypt' - ? 400 - : 40, - groupValue: controller.selectedAmount, - onChanged: (value) { - controller - .updateSelectedAmount(value as int); - }, - ), - Text( - box.read(BoxName.countryCode) == 'Egypt' - ? '400 ${'LE'.tr} '.tr - : '40 ${'JOD'.tr}'.tr, - style: AppStyle.title, - ), - ], - )), - GestureDetector( - onTap: () { - controller.updateSelectedAmount(100); - }, - child: Row( - children: [ - Radio( - value: - box.read(BoxName.countryCode) == 'Egypt' - ? 1000 - : 50, - groupValue: controller.selectedAmount, - onChanged: (value) { - controller - .updateSelectedAmount(value as int); - }, - ), - Text( - box.read(BoxName.countryCode) == 'Egypt' - ? '1000 ${'LE'.tr} '.tr - : '50 ${'JOD'.tr}'.tr, - style: AppStyle.title, - ), - ], - )), - const Spacer(), - box.read(BoxName.countryCode) == 'Egypt' - ? const SizedBox() - : MyElevatedButton( - kolor: AppColor.blueColor, - title: '${'Pay with Your'.tr} PayPal', - onPressed: () { - if (controller.selectedAmount != 0) { - controller.makePaymentPayPal(context); - } else { - Toast.show( - context, - 'You will choose one of above !'.tr, - AppColor.redColor); - } - }, - ), - box.read(BoxName.countryCode) == 'Egypt' - ? Column( - children: [ - MyElevatedButton( - title: '💳 Pay with Credit Card'.tr, - onPressed: () { - if (controller.selectedAmount != 0) { - controller.payWithPayMob( - context, - controller.selectedAmount - .toString(), // Convert int to double - box.read(BoxName.countryCode) == - 'Egypt' - ? 'EGP' - : 'JOD', - () async { - await controller - .addPassengerWallet(); - controller - .changePromoSheetDialogue(); - await controller - .getPassengerWallet(); - }, - ); - } else { - Toast.show( - context, - '⚠️ You need to choose an amount!'.tr, - AppColor.redColor, - ); - } - }, - ), - // Add some spacing between buttons - MyElevatedButton( - kolor: AppColor.yellowColor, - title: '💰 Pay with Wallet'.tr, - onPressed: () { - if (controller.selectedAmount != 0) { - controller.payWithPayMobWallet( - context, - controller.selectedAmount - .toString(), // Convert int to double - box.read(BoxName.countryCode) == - 'Egypt' - ? 'EGP' - : 'JOD', - () async { - await controller - .addPassengerWallet(); - controller - .changePromoSheetDialogue(); - await controller - .getPassengerWallet(); - }, - ); - } else { - Toast.show( - context, - '⚠️ You need to choose an amount!'.tr, - AppColor.redColor, - ); - } - }, - ), - ], - ) - : MyElevatedButton( - title: 'Pay with Credit Card'.tr, - onPressed: () { - if (controller.selectedAmount != 0) { - controller.makePaymentStripe( - controller.selectedAmount! - .toDouble(), // Convert int to double - box.read(BoxName.countryCode) != 'Egypt' - ? 'usd' - : 'jod', () { - controller.addPassengerWallet(); - controller.changePromoSheetDialogue(); - controller.getPassengerWallet(); - }); - } else { - Toast.show( - context, - 'You will choose one of above !'.tr, - AppColor.redColor); - } - }), - MyElevatedButton( - title: 'Cancel'.tr, - kolor: AppColor.redColor, + ], + ), + actions: [ + CupertinoDialogAction( + child: Text('Cancel'.tr, + style: const TextStyle( + color: CupertinoColors.destructiveRed)), onPressed: () { - controller.changePromoSheetDialogue(); + Get.back(); + }, + ), + CupertinoDialogAction( + child: Text('OK'.tr, + style: const TextStyle( + color: CupertinoColors.activeGreen)), + onPressed: () async { + Get.back(); + box.write(BoxName.phoneWallet, + controller.walletphoneController.text); + Toast.show( + context, + 'Phone Wallet Saved Successfully'.tr, + AppColor.greenColor); }, ), ], ), - )) - : const SizedBox()), - ); - } + barrierDismissible: false, + ); + }, + ), + ], + cancelButton: CupertinoActionSheetAction( + child: Text('Cancel'.tr), + onPressed: () { + // controller.changePromoSheetDialogue(); + Get.back(); + }, + ), + ), + ); } diff --git a/lib/views/home/my_wallet/payment_history_passenger_page.dart b/lib/views/home/my_wallet/payment_history_passenger_page.dart index 5a33d50..f3ab954 100644 --- a/lib/views/home/my_wallet/payment_history_passenger_page.dart +++ b/lib/views/home/my_wallet/payment_history_passenger_page.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:SEFER/constant/colors.dart'; @@ -17,7 +18,7 @@ class PaymentHistoryPassengerPage extends StatelessWidget { body: [ GetBuilder( builder: (controller) => controller.isLoading - ? const MyCircularProgressIndicator() + ? const MyCircularProgressIndicator() // iOS-style loading indicator : controller.archive.isEmpty ? Center( child: Text( @@ -25,37 +26,33 @@ class PaymentHistoryPassengerPage extends StatelessWidget { style: AppStyle.title, ), ) - : ListView.builder( - itemCount: controller.archive.length, - itemBuilder: (BuildContext context, int index) { - var list = controller.archive[index]; - return Padding( - padding: const EdgeInsets.all(4), - child: Container( - decoration: BoxDecoration( - color: double.parse(list['balance']) < 0 - ? AppColor.redColor.withOpacity(.4) - : AppColor.greenColor.withOpacity(.4)), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - list['balance'], - style: AppStyle.title, - ), - Text( - list['created_at'], - style: AppStyle.title, - ), - ], + : CupertinoListSection.insetGrouped( + children: List.generate( + controller.archive.length, + (index) { + var list = controller.archive[index]; + return CupertinoListTile( + backgroundColor: double.parse(list['balance']) < 0 + ? AppColor.redColor.withOpacity(.2) + : AppColor.greenColor.withOpacity(.2), + title: Text( + list['balance'], + style: AppStyle.title.copyWith( + color: CupertinoColors.black, ), ), - ), - ); - }, + additionalInfo: Text( + list['created_at'], + style: AppStyle.title.copyWith( + fontSize: 12, + color: CupertinoColors.systemGrey, + ), + ), + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 16), + ); + }, + ), ), ) ], diff --git a/lib/views/home/profile/complaint_page.dart b/lib/views/home/profile/complaint_page.dart index a9f137b..eae4907 100644 --- a/lib/views/home/profile/complaint_page.dart +++ b/lib/views/home/profile/complaint_page.dart @@ -1,64 +1,199 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:SEFER/views/widgets/my_scafold.dart'; -import 'package:SEFER/views/widgets/mycircular.dart'; +import 'dart:convert'; -import '../../../controller/home/profile/complaint_controller.dart'; -import '../../widgets/elevated_btn.dart'; +import 'package:SEFER/constant/colors.dart'; +import 'package:SEFER/constant/style.dart'; +import 'package:SEFER/controller/functions/crud.dart'; +import 'package:SEFER/controller/home/profile/complaint_controller.dart'; +import 'package:SEFER/views/widgets/my_dialog.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:get/get.dart'; +import 'dart:io'; + +import '../../../controller/functions/audio_record1.dart'; class ComplaintPage extends StatelessWidget { - // Rename class - ComplaintPage({super.key}); - ComplaintController complaintController = - Get.put(ComplaintController()); // Update controller instance + final ComplaintController complaintController = + Get.put(ComplaintController()); + final AudioRecorderController audioRecorderController = + Get.put(AudioRecorderController()); @override Widget build(BuildContext context) { - return MyScafolld( - title: 'Complaint'.tr, - body: [ - Padding( - padding: const EdgeInsets.all(26), - child: Form( - key: complaintController.formKey, - child: Column( - children: [ - TextFormField( - controller: complaintController.complaintController, - decoration: InputDecoration( - border: const OutlineInputBorder(), - hintText: 'Enter your complaint here'.tr, - labelText: 'Complaint'.tr, // Update label - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter your complaint.'.tr; - } - return null; - }, - ), - const SizedBox(height: 20), - complaintController.isLoading - ? const MyCircularProgressIndicator() - : MyElevatedButton( - onPressed: () { - if (complaintController.formKey.currentState! - .validate()) { - complaintController - .addComplaint(); // Update method name - - // Clear the complaint form - complaintController.formKey.currentState!.reset(); - } - }, - title: 'Submit'.tr, - ), - ], - ), - ), + return GetBuilder(builder: (complaintController) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text('Complaint'.tr, style: AppStyle.title), ), - ], - isleading: true, - ); + child: complaintController.isLoading + ? const Center(child: CupertinoActivityIndicator()) + : SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: Form( + key: complaintController.formKey, + child: ListView( + children: [ + // Complaint Text Field + CupertinoFormSection( + header: Text('Submit Your Complaint'.tr), + children: [ + CupertinoTextField( + controller: + complaintController.complaintController, + placeholder: 'Enter your complaint here'.tr, + padding: const EdgeInsets.symmetric( + vertical: 12, horizontal: 16), + maxLines: 5, + decoration: BoxDecoration( + border: Border.all( + color: CupertinoColors.systemGrey4), + borderRadius: BorderRadius.circular(10), + color: CupertinoColors.white, + ), + style: AppStyle.subtitle, + ), + ], + ), + const SizedBox(height: 24), + + // FutureBuilder to load recorded audio files + FutureBuilder>( + future: audioRecorderController.getRecordedFiles(), + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return const Center( + child: CupertinoActivityIndicator()); + } else if (snapshot.hasError) { + return Text('Error: ${snapshot.error}', + style: AppStyle.subtitle); + } else if (snapshot.hasData && + snapshot.data!.isEmpty) { + return Text('No audio files recorded.'.tr, + style: AppStyle.subtitle); + } + + // List of recorded audio files + return CupertinoFormSection( + header: Text('attach audio of complain'.tr), + children: snapshot.data!.map((audioFilePath) { + final audioFile = File(audioFilePath); + return CupertinoListTile( + title: Text(audioFilePath.split('/').last, + style: AppStyle.title), + trailing: const Icon( + CupertinoIcons.play_arrow, + color: AppColor.accentColor), + onTap: () async { + MyDialogContent().getDialog( + 'be sure'.tr, + Text('attach correct audio'.tr), + () async { + await complaintController + .uploadAudioFile(audioFile); + }, + ); + }, + ); + }).toList(), + ); + }, + ), + const SizedBox(height: 24), + + // Trip Details Section + CupertinoFormSection( + header: Text('Trip Details'.tr), + children: [ + CupertinoListTile( + title: complaintController.feedBack.isEmpty + ? Text('No Ride found yet'.tr) + : Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + '${'Date'.tr}: ${complaintController.feedBack[0]['date']}', + style: AppStyle.title), + Text( + '${'Price'.tr}: ${complaintController.feedBack[0]['price']}', + style: AppStyle.title), + ], + ), + ), + ], + ), + const SizedBox(height: 24), + CupertinoFormSection( + header: Text('SEFER answer'.tr), + children: [ + SizedBox( + height: 100, + child: ListView( + children: [ + // Check if passengerReport is not null + if (complaintController.passengerReport != + null) + // Access the 'solution' key safely + Text( + complaintController + .passengerReport!['solution'] + ?.toString() ?? + 'No solution available', + style: AppStyle.title, + ) + else + const SizedBox(), // Fallback if passengerReport is null + ], + ), + ), + ], + ), + const SizedBox(height: 24), + // Submit Button + CupertinoButton( + color: AppColor.blueColor, + padding: const EdgeInsets.symmetric( + vertical: 14, horizontal: 30), + onPressed: () async { + if (complaintController.formKey.currentState! + .validate()) { + if (complaintController.audioLink.toString() == + '') { + MyDialogContent().getDialog( + 'title', + Text( + 'the audio file not uploaded yet \nDo you want to upload without audio file' + .tr), () async { + await complaintController.geminiAudio( + jsonEncode(complaintController.feedBack), + complaintController.audioLink, + complaintController + .complaintController.text); + complaintController.formKey.currentState! + .reset(); + }); + Get.back(); + } else { + await complaintController.geminiAudio( + jsonEncode(complaintController.feedBack), + complaintController.audioLink, + complaintController + .complaintController.text); + complaintController.formKey.currentState! + .reset(); + } + complaintController.addComplaint(); + } + }, + child: Text('Submit'.tr, style: AppStyle.title), + ), + ], + ), + ), + ), + ), + ); + }); } } diff --git a/lib/views/home/profile/passenger_profile_page.dart b/lib/views/home/profile/passenger_profile_page.dart index 152929e..04e2083 100644 --- a/lib/views/home/profile/passenger_profile_page.dart +++ b/lib/views/home/profile/passenger_profile_page.dart @@ -393,92 +393,6 @@ class CountryPicker extends StatelessWidget { } } -ListTile changeCountry(List countryOptions) { - return ListTile( - leading: const Icon(Icons.location_city_outlined), - title: Text( - 'Change Country'.tr, - style: AppStyle.headTitle2, - ), - subtitle: Text( - 'You can change the Country to get all features'.tr, - style: AppStyle.title, - ), - onTap: () => Get.to(MyScafolld( - title: 'Change Country'.tr, - // body: [], - body: [ - GetBuilder(builder: (controller) { - return Padding( - padding: const EdgeInsets.all(20.0), - child: ListView( - children: [ - const SizedBox( - height: 20, - ), - Text( - "Select Your Country".tr, - style: AppStyle.headTitle2, - textAlign: TextAlign.center, - ), - // const SizedBox( - // height: 20, - // ), - Padding( - padding: const EdgeInsets.all(10), - child: Text( - "To ensure you receive the most accurate information for your location, please select your country below. This will help tailor the app experience and content to your country." - .tr, - style: AppStyle.title, - textAlign: TextAlign.center, - ), - ), - SizedBox( - height: 200, - child: CupertinoPicker( - itemExtent: 32, - onSelectedItemChanged: (int index) { - controller.setCountry(countryOptions[index]); - box.write(BoxName.countryCode, - countryOptions[index]); // Save in English - }, - children: List.generate( - countryOptions.length, - (index) => Center( - child: Text( - countryOptions[index] - .tr, // Display translated if not English - style: AppStyle.title, - ), - ), - ), - ), - ), - - MyElevatedButton( - title: - 'Select Country'.tr, // Use translated text for button - onPressed: () async { - // No conversion needed - box.write( - BoxName.countryCode, // - controller - .selectedCountry); // Already saved in English - Get.snackbar(controller.selectedCountry.toString().tr, '', - backgroundColor: AppColor.greenColor); - // Get.back();// - // Get.back(); - }, - ) - ], - ), - ); - }) - ], - isleading: true)), - ); -} - class CountryPickerFromSetting extends StatelessWidget { final ProfileController controller = Get.put(ProfileController()); final LoginController loginController = Get.put(LoginController()); @@ -548,7 +462,8 @@ class CountryPickerFromSetting extends StatelessWidget { MyElevatedButton( title: 'Select Country'.tr, // Use translated text for button onPressed: () async { - // No conversion needed + loginController.saveCountryCode(controller.selectedCountry + .toString()); // No conversion needed box.write( BoxName.countryCode, // controller.selectedCountry); // Already saved in English diff --git a/lib/views/home/profile/promos_passenger_page.dart b/lib/views/home/profile/promos_passenger_page.dart index e22b8ce..923478b 100644 --- a/lib/views/home/profile/promos_passenger_page.dart +++ b/lib/views/home/profile/promos_passenger_page.dart @@ -1,5 +1,7 @@ import 'package:animated_text_kit/animated_text_kit.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:SEFER/controller/home/profile/promos_controller.dart'; import 'package:SEFER/views/widgets/my_scafold.dart'; @@ -18,160 +20,211 @@ class PromosPassengerPage extends StatelessWidget { Widget build(BuildContext context) { Get.put(PromosController()); return MyScafolld( - title: 'Promos For today'.tr, + title: "Promos For Today".tr, isleading: true, body: [ GetBuilder( builder: (orderHistoryController) => orderHistoryController.isLoading ? const MyCircularProgressIndicator() : ListView.builder( - itemCount: orderHistoryController.promoList.length + - 1, // Adding 1 for the ad + itemCount: orderHistoryController + .promoList.length, // Adding 1 for the ad itemBuilder: (BuildContext context, int index) { - if (index == 0) { - // Ad at the beginning - return Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - height: 120, // Adjust the height of the ad container - decoration: BoxDecoration( - color: - Colors.grey[200], // Background color for the ad - borderRadius: BorderRadius.circular(10), - ), - child: Center( - child: Container( - decoration: AppStyle.boxDecoration, - height: Get.height * .19, - child: ListView( - scrollDirection: Axis.horizontal, - children: [ - PointsCaptain( - kolor: AppColor.greyColor, - pricePoint: - box.read(BoxName.countryCode) == - 'Jordan' - ? 5 - : 100, - countPoint: - box.read(BoxName.countryCode) == - 'Jordan' - ? '300' - : '100', - ), - PointsCaptain( - kolor: AppColor.bronze, - pricePoint: - box.read(BoxName.countryCode) == - 'Jordan' - ? 10 - : 200, - countPoint: - box.read(BoxName.countryCode) == - 'Jordan' - ? '1040' - : '210', - ), - PointsCaptain( - kolor: AppColor.goldenBronze, - pricePoint: - box.read(BoxName.countryCode) == - 'Jordan' - ? 22 - : 400, - countPoint: - box.read(BoxName.countryCode) == - 'Jordan' - ? '2300' - : '450', - ), - PointsCaptain( - kolor: AppColor.gold, - pricePoint: - box.read(BoxName.countryCode) == - 'Jordan' - ? 50 - : 1000, - countPoint: - box.read(BoxName.countryCode) == - 'Jordan' - ? '55000' - : '1200', - ), - ], - )), - ), + // if (index == 0) { + // // Ad at the beginning + // return Padding( + // padding: const EdgeInsets.all(8.0), + // child: Container( + // height: 120, // Adjust the height of the ad container + // decoration: BoxDecoration( + // color: + // Colors.grey[200], // Background color for the ad + // borderRadius: BorderRadius.circular(10), + // ), + // child: Center( + // child: Container( + // decoration: AppStyle.boxDecoration, + // height: Get.height * .19, + // child: ListView( + // scrollDirection: Axis.horizontal, + // children: [ + // PointsCaptain( + // kolor: AppColor.greyColor, + // pricePoint: + // box.read(BoxName.countryCode) == + // 'Jordan' + // ? 5 + // : 100, + // countPoint: + // box.read(BoxName.countryCode) == + // 'Jordan' + // ? '300' + // : '100', + // ), + // PointsCaptain( + // kolor: AppColor.bronze, + // pricePoint: + // box.read(BoxName.countryCode) == + // 'Jordan' + // ? 10 + // : 200, + // countPoint: + // box.read(BoxName.countryCode) == + // 'Jordan' + // ? '1040' + // : '210', + // ), + // PointsCaptain( + // kolor: AppColor.goldenBronze, + // pricePoint: + // box.read(BoxName.countryCode) == + // 'Jordan' + // ? 22 + // : 400, + // countPoint: + // box.read(BoxName.countryCode) == + // 'Jordan' + // ? '2300' + // : '450', + // ), + // PointsCaptain( + // kolor: AppColor.gold, + // pricePoint: + // box.read(BoxName.countryCode) == + // 'Jordan' + // ? 50 + // : 1000, + // countPoint: + // box.read(BoxName.countryCode) == + // 'Jordan' + // ? '55000' + // : '1200', + // ), + // ], + // )), + // ), + // ), + // ); + // } else { + // Promo items + final rides = orderHistoryController.promoList[index]; + + return Padding( + padding: const EdgeInsets.all(12.0), + child: Container( + decoration: BoxDecoration( + color: CupertinoColors.systemGrey6, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: + CupertinoColors.systemGrey.withOpacity(0.5), + blurRadius: 8, + offset: Offset(0, 4), + ), + ], ), - ); - } else { - // Promo items - final rides = orderHistoryController.promoList[index - 1]; - return Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - decoration: AppStyle.boxDecoration, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - AnimatedTextKit( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: () {}, + child: AnimatedTextKit( animatedTexts: [ ScaleAnimatedText( - rides['promo_code'], - textStyle: AppStyle.title), + rides['promo_code'], + textStyle: + AppStyle.title.copyWith( + fontSize: + 32, // Increased font size for emphasis + color: + CupertinoColors.activeBlue, + fontWeight: FontWeight.bold, + ), + ), WavyAnimatedText( - rides['promo_code'], - textStyle: AppStyle.title), - FlickerAnimatedText( - rides['promo_code'], - textStyle: AppStyle.title), - WavyAnimatedText( - rides['promo_code'], - textStyle: AppStyle.title), + rides['promo_code'], + textStyle: + AppStyle.title.copyWith( + fontSize: + 32, // Increased font size for emphasis + color: + CupertinoColors.activeBlue, + fontWeight: FontWeight.bold, + ), + ), ], isRepeatingAnimation: true, - onTap: () {}, ), - Text( - rides['description'], - style: AppStyle.title, + ), + const SizedBox(height: 8), + // Description Text + Text( + rides['description'], + style: AppStyle.title.copyWith( + fontSize: 22, + color: CupertinoColors.systemGrey, ), - ], + ), + ], + ), + Column( + children: [ + // Only displaying end date + Text( + '${'Valid Until:'.tr} ${rides['validity_end_date']}', + style: AppStyle.subtitle.copyWith( + fontWeight: FontWeight.bold, + fontSize: 20, + color: CupertinoColors.systemGrey, + ), + ), + ], + ), + ], + ), + // const SizedBox(height: 16), + // Copy Promo Text + Center( + child: GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: rides['promo_code'])); + Get.snackbar( + 'Promo Copied!'.tr, + 'You have copied the promo code.'.tr, + snackPosition: SnackPosition.BOTTOM, + backgroundColor: + CupertinoColors.systemGrey, + colorText: CupertinoColors.white, + ); + }, + child: Text( + 'Copy Code'.tr, + textAlign: TextAlign.center, + style: AppStyle.headTitle2.copyWith( + color: CupertinoColors.systemBlue, + fontWeight: FontWeight.bold, ), - Column( - children: [ - Text( - rides['validity_start_date'], - style: AppStyle.title, - ), - Text( - rides['validity_end_date'], - style: AppStyle.title, - ), - ], - ), - ], + ), ), - Text( - 'Copy this Promo to use it in your Ride!'.tr, - textAlign: TextAlign.center, - style: AppStyle.headTitle2 - .copyWith(color: AppColor.accentColor), - ) - ], - ), + ), + ], ), ), - ); - } + ), + ); + // } }, ), ) diff --git a/lib/views/notification/notification_page.dart b/lib/views/notification/notification_page.dart index 873479a..5752137 100644 --- a/lib/views/notification/notification_page.dart +++ b/lib/views/notification/notification_page.dart @@ -1,10 +1,10 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:SEFER/constant/colors.dart'; import 'package:SEFER/constant/style.dart'; import '../../controller/notification/passenger_notification_controller.dart'; -import '../widgets/elevated_btn.dart'; import '../widgets/my_scafold.dart'; import '../widgets/mycircular.dart'; @@ -19,64 +19,82 @@ class NotificationPage extends StatelessWidget { title: 'Notifications', body: [ GetBuilder( - builder: (notificationCaptainController) => - notificationCaptainController.isloading - ? const MyCircularProgressIndicator() - : SafeArea( - child: ListView.builder( - itemCount: notificationCaptainController - .notificationData['message'].length, - itemBuilder: (BuildContext context, int index) { - if (notificationCaptainController - .notificationData['message'] == - "No notification data found") { - Get.defaultDialog(); - } - var res = notificationCaptainController - .notificationData['message'][index]; - return Card( - elevation: 4, - color: res['isShown'] == 'true' - ? AppColor.secondaryColor.withOpacity(.5) - : AppColor.secondaryColor.withOpacity(.9), - child: ListTile( - onTap: () { - Get.defaultDialog( - title: res['title'], - titleStyle: AppStyle.title, - content: SizedBox( - width: Get.width * .8, - // height: Get.height * .4, - child: Text( - res['body'], - style: AppStyle.title, - ), - ), - confirm: MyElevatedButton( - title: 'Ok', - onPressed: () { - notificationCaptainController - .updateNotification( - res['id'].toString()); - })); - }, - leading: res['isShown'] == 'true' - ? const Icon( - Icons.notifications_off_outlined) - : const Icon(Icons.notifications_active), - title: Text( - res['title'], - style: AppStyle.title, - ), - subtitle: Text( - res['body'], - style: AppStyle.subtitle, - ), - ), + builder: (notificationCaptainController) => notificationCaptainController + .isloading + ? const MyCircularProgressIndicator() // iOS-style loading indicator + : SafeArea( + child: ListView.builder( + itemCount: notificationCaptainController + .notificationData['message'].length, + itemBuilder: (BuildContext context, int index) { + if (notificationCaptainController + .notificationData['message'] == + "No notification data found") { + Get.defaultDialog( + title: 'No Notifications'.tr, + content: Text( + 'No notification data found.'.tr, + ), + ); + } + var res = notificationCaptainController + .notificationData['message'][index]; + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + child: CupertinoListTile( + backgroundColor: res['isShown'] == 'true' + ? AppColor.secondaryColor.withOpacity(.2) + : AppColor.secondaryColor.withOpacity(.8), + leading: res['isShown'] == 'true' + ? const Icon(CupertinoIcons.bell_slash_fill) + : const Icon(CupertinoIcons.bell_fill), + title: Text( + res['title'], + style: AppStyle.title.copyWith( + color: CupertinoColors.black, + ), + ), + subtitle: Text( + res['body'], + style: AppStyle.subtitle.copyWith( + color: CupertinoColors.systemGrey, + ), + ), + onTap: () { + showCupertinoDialog( + context: context, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: Text( + res['title'], + style: AppStyle.title, + ), + content: Text( + res['body'], + style: AppStyle.subtitle, + ), + actions: [ + CupertinoDialogAction( + child: const Text('Ok'), + onPressed: () { + notificationCaptainController + .updateNotification( + res['id'].toString()); + Get.back(); + }, + ), + ], + ); + }, ); }, ), - )) + ); + }, + ), + ), + ) ], ); } diff --git a/lib/views/widgets/my_dialog.dart b/lib/views/widgets/my_dialog.dart index 0608a88..add3cc1 100644 --- a/lib/views/widgets/my_dialog.dart +++ b/lib/views/widgets/my_dialog.dart @@ -1,5 +1,6 @@ import 'dart:ui'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -11,34 +12,98 @@ import 'elevated_btn.dart'; class MyDialog extends GetxController { void getDialog(String title, String? midTitle, VoidCallback onPressed) { final textToSpeechController = Get.put(TextToSpeechController()); - Get.defaultDialog( - title: title, - titleStyle: AppStyle.title, - barrierDismissible: false, - middleTextStyle: AppStyle.title, - content: Column( - children: [ - IconButton( + + Get.dialog( + BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: CupertinoAlertDialog( + title: Text( + title, + style: AppStyle.title.copyWith( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + content: Column( + children: [ + CupertinoButton( onPressed: () async { await textToSpeechController.speakText(title ?? midTitle!); }, - icon: const Icon(Icons.headphones)), - Text( - midTitle!, - style: AppStyle.title, - ) + child: const Icon(CupertinoIcons.headphones, + color: AppColor.primaryColor), + ), + Text( + midTitle!, + style: AppStyle.title.copyWith(fontSize: 16), + ), + ], + ), + actions: [ + CupertinoDialogAction( + child: const Text('Cancel', + style: TextStyle(color: AppColor.redColor)), + onPressed: () { + Get.back(); + }, + ), + CupertinoDialogAction( + onPressed: onPressed, + child: Text('OK'.tr, + style: const TextStyle(color: AppColor.greenColor)), + ), ], ), - confirm: MyElevatedButton( - title: 'Ok'.tr, - onPressed: onPressed, - kolor: AppColor.greenColor, - ), - cancel: MyElevatedButton( - title: 'Cancel', - kolor: AppColor.redColor, - onPressed: () { - Get.back(); - })); + ), + barrierDismissible: false, + ); + } +} + +class MyDialogContent extends GetxController { + void getDialog(String title, Widget? content, VoidCallback onPressed) { + final textToSpeechController = Get.put(TextToSpeechController()); + + Get.dialog( + BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: CupertinoAlertDialog( + title: Text( + title, + style: AppStyle.title.copyWith( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + content: Column( + children: [ + CupertinoButton( + onPressed: () async { + await textToSpeechController.speakText(title); + }, + child: const Icon(CupertinoIcons.speaker_2, + color: AppColor.primaryColor), + ), + content! + ], + ), + actions: [ + CupertinoDialogAction( + child: const Text('Cancel', + style: TextStyle(color: AppColor.redColor)), + onPressed: () { + Get.back(); + }, + ), + CupertinoDialogAction( + onPressed: onPressed, + child: Text('OK'.tr, + style: const TextStyle(color: AppColor.greenColor)), + ), + ], + ), + ), + barrierDismissible: false, + ); } } diff --git a/lib/views/widgets/my_textField.dart b/lib/views/widgets/my_textField.dart index 2e4f060..15fc4c0 100644 --- a/lib/views/widgets/my_textField.dart +++ b/lib/views/widgets/my_textField.dart @@ -27,7 +27,7 @@ class MyTextForm extends StatelessWidget { children: [ Text( label.tr, - style: TextStyle( + style: const TextStyle( color: CupertinoColors.label, fontSize: 16, fontWeight: FontWeight.w600, diff --git a/lib/views/widgets/mycircular.dart b/lib/views/widgets/mycircular.dart index 2fe8e35..af68047 100644 --- a/lib/views/widgets/mycircular.dart +++ b/lib/views/widgets/mycircular.dart @@ -12,8 +12,8 @@ class MyCircularProgressIndicator extends StatelessWidget { Widget build(BuildContext context) { return Center( child: Container( - width: 110, - height: 110, + width: 140, + height: 140, decoration: BoxDecoration( color: backgroundColor, shape: BoxShape.circle, @@ -21,13 +21,11 @@ class MyCircularProgressIndicator extends StatelessWidget { child: Stack( children: [ const Center(child: CircularProgressIndicator()), - Column( - children: [ - Align( - alignment: Alignment.center, - child: Image.asset('assets/images/logo.gif'), - ), - ], + Image.asset( + 'assets/images/logo.gif', + width: 140, + height: 140, + fit: BoxFit.contain, ), ], ), diff --git a/lib/views/widgets/mysnakbar.dart b/lib/views/widgets/mysnakbar.dart new file mode 100644 index 0000000..8b7e73b --- /dev/null +++ b/lib/views/widgets/mysnakbar.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../constant/colors.dart'; + +SnackbarController mySnackeBarError(String message) { + return Get.snackbar( + 'Error'.tr, + message, + backgroundColor: AppColor.redColor.withOpacity(0.9), + colorText: AppColor.secondaryColor, + icon: const Icon(Icons.error, color: AppColor.secondaryColor), + shouldIconPulse: true, + snackPosition: SnackPosition.TOP, + margin: const EdgeInsets.all(10), + borderRadius: 10, + animationDuration: const Duration(milliseconds: 500), + forwardAnimationCurve: Curves.easeOutBack, + reverseAnimationCurve: Curves.easeInBack, + titleText: Text( + 'Error'.tr, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + fontSize: 16, + ), + ), + messageText: Text( + message, + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 14, + ), + ), + ); +} + +SnackbarController mySnackbarSuccess(String message) { + return Get.snackbar( + 'Success'.tr, + message, + backgroundColor: AppColor.greenColor + .withOpacity(0.9), // Assuming green color for success + colorText: AppColor.secondaryColor, + icon: const Icon(Icons.check_circle, color: AppColor.secondaryColor), + shouldIconPulse: true, + snackPosition: SnackPosition.TOP, + margin: const EdgeInsets.all(10), + borderRadius: 10, + animationDuration: const Duration(milliseconds: 500), + forwardAnimationCurve: Curves.easeOutBack, + reverseAnimationCurve: Curves.easeInBack, + titleText: Text( + 'Success'.tr, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + fontSize: 16, + ), + ), + messageText: Text( + message, + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 14, + ), + ), + ); +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 01687f7..b54e01e 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -27,6 +27,7 @@ import sqflite import url_launcher_macos import video_player_avfoundation import wakelock_plus +import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) @@ -51,4 +52,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) + FLTWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "FLTWebViewFlutterPlugin")) } diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index d53ef64..8e02df2 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/pubspec.lock b/pubspec.lock index 591c4b7..7c2a472 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "72.0.0" _flutterfire_internals: dependency: transitive description: @@ -17,14 +17,19 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.41" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.7.0" animated_text_kit: dependency: "direct main" description: @@ -109,18 +114,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.4.11" + version: "2.4.12" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.dev" source: hosted - version: "7.3.1" + version: "7.3.2" built_collection: dependency: transitive description: @@ -141,26 +146,26 @@ packages: dependency: transitive description: name: cached_network_image - sha256: "4a5d8d2c728b0f3d0245f69f921d7be90cae4c2fd5288f773088672c0893f819" + sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.3.1" cached_network_image_platform_interface: dependency: transitive description: name: cached_network_image_platform_interface - sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" url: "https://pub.dev" source: hosted - version: "4.1.1" + version: "4.0.0" cached_network_image_web: dependency: transitive description: name: cached_network_image_web - sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996" + sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.0" calendar_builder: dependency: "direct main" description: @@ -189,10 +194,10 @@ packages: dependency: transitive description: name: chewie - sha256: "8210c6e8702ddae9b3337aad0d539a9ff128cc4a5baaadc0174edbd0f99b0125" + sha256: e53da939709efb9aad0f3d72a69a8d05f889168b7a138af60ce78bab5c94b135 url: "https://pub.dev" source: hosted - version: "1.8.4" + version: "1.8.1" cli_util: dependency: transitive description: @@ -269,10 +274,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.7" dbus: dependency: transitive description: @@ -482,10 +487,26 @@ packages: dependency: transitive description: name: flutter_cache_manager - sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + sha256: ceff65d74d907b1b772e22cf04daad60fb472461638977d9fae8b00a63e01e3d url: "https://pub.dev" source: hosted - version: "3.4.1" + version: "3.3.3" + flutter_confetti: + dependency: "direct main" + description: + name: flutter_confetti + sha256: "22fc66984c17aea73e3cd300666609c4581176c00c31d4513b1e16758f75cb08" + url: "https://pub.dev" + source: hosted + version: "0.3.4" + flutter_contacts: + dependency: "direct main" + description: + name: flutter_contacts + sha256: "388d32cd33f16640ee169570128c933b45f3259bddbfae7a100bb49e5ffea9ae" + url: "https://pub.dev" + source: hosted + version: "1.1.9+2" flutter_font_icons: dependency: "direct main" description: @@ -836,10 +857,10 @@ packages: dependency: transitive description: name: google_maps_flutter_android - sha256: "075f550650a907a85d00d1e48f135e9cc326f1519652eb5f8caafebacabf7727" + sha256: bccf64ccbb2ea672dc62a61177b315a340af86b0228564484b023657544a3fd5 url: "https://pub.dev" source: hosted - version: "2.14.5" + version: "2.14.11" google_maps_flutter_ios: dependency: transitive description: @@ -852,10 +873,10 @@ packages: dependency: transitive description: name: google_maps_flutter_platform_interface - sha256: "8ba5daee8b5d2230fea17a7903047ce4dc45fb1f0726356a1d8eb541e30c7d05" + sha256: a951981c22d790848efb9f114f81794945bc5c06bc566238a419a92f110af6cb url: "https://pub.dev" source: hosted - version: "2.9.1" + version: "2.9.5" google_maps_flutter_web: dependency: transitive description: @@ -961,7 +982,7 @@ packages: source: hosted version: "3.2.1" http_parser: - dependency: transitive + dependency: "direct main" description: name: http_parser sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" @@ -1012,10 +1033,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "8c5abf0dcc24fe6e8e0b4a5c0b51a5cf30cefdf6407a3213dae61edc75a70f56" + sha256: a26dc9a03fe042440c1e4be554fb0fceae2bf6d887d7467fc48c688fa4a81889 url: "https://pub.dev" source: hosted - version: "0.8.12+12" + version: "0.8.12+7" image_picker_for_web: dependency: transitive description: @@ -1164,10 +1185,10 @@ packages: dependency: transitive description: name: local_auth_android - sha256: e99c44ca0bce08f26f25e2a2e07d3b443d69986e1c3acf67c1449f7d847e3625 + sha256: "33fcebe9c3cf1bb0033bc85caed354c1e75ff7f7670918a571bd3152a2b65bf4" url: "https://pub.dev" source: hosted - version: "1.0.43" + version: "1.0.42" local_auth_darwin: dependency: transitive description: @@ -1224,6 +1245,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" matcher: dependency: transitive description: @@ -1249,7 +1278,7 @@ packages: source: hosted version: "1.15.0" mime: - dependency: transitive + dependency: "direct main" description: name: mime sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" @@ -1396,10 +1425,10 @@ packages: dependency: transitive description: name: permission_handler_platform_interface - sha256: fe0ffe274d665be8e34f9c59705441a7d248edebbe5d9e3ec2665f88b79358ea + sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20" url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.2.1" permission_handler_windows: dependency: transitive description: @@ -1640,10 +1669,10 @@ packages: dependency: transitive description: name: sqflite_common - sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" + sha256: "7b41b6c3507854a159e24ae90a8e3e9cc01eb26a477c118d6dca065b5f55453e" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.4+2" stack_trace: dependency: transitive description: @@ -1704,10 +1733,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + sha256: a824e842b8a054f91a728b783c177c1e4731f6b124f9192468457a8913371255 url: "https://pub.dev" source: hosted - version: "3.1.0+1" + version: "3.2.0" term_glyph: dependency: transitive description: @@ -1725,7 +1754,7 @@ packages: source: hosted version: "0.7.2" timezone: - dependency: "direct main" + dependency: transitive description: name: timezone sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" @@ -1784,10 +1813,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 + sha256: "95d8027db36a0e52caf55680f91e33ea6aa12a3ce608c90b06f4e429a21067ac" url: "https://pub.dev" source: hosted - version: "6.3.9" + version: "6.3.5" url_launcher_ios: dependency: transitive description: @@ -1904,10 +1933,10 @@ packages: dependency: transitive description: name: video_player_android - sha256: e343701aa890b74a863fa460f5c0e628127ed06a975d7d9af6b697133fb25bdf + sha256: fdc0331ce9f808cc2714014cb8126bd6369943affefd54f8fdab0ea0bb617b7f url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.5.2" video_player_avfoundation: dependency: transitive description: @@ -1992,10 +2021,10 @@ packages: dependency: "direct main" description: name: webview_flutter - sha256: "6869c8786d179f929144b4a1f86e09ac0eddfe475984951ea6c634774c16b522" + sha256: ec81f57aa1611f8ebecf1d2259da4ef052281cb5ad624131c93546c79ccc7736 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.9.0" webview_flutter_android: dependency: transitive description: @@ -2016,10 +2045,10 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "9c62cc46fa4f2d41e10ab81014c1de470a6c6f26051a2de32111b2ee55287feb" + sha256: "1942a12224ab31e9508cf00c0c6347b931b023b8a4f0811e5dec3b06f94f117d" url: "https://pub.dev" source: hosted - version: "3.14.0" + version: "3.15.0" win32: dependency: transitive description: @@ -2032,10 +2061,10 @@ packages: dependency: transitive description: name: win32_registry - sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" + sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.1.3" xdg_directories: dependency: transitive description: @@ -2061,5 +2090,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 923aa1b..5e19f7c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -61,7 +61,11 @@ dependencies: package_info_plus: ^8.0.0 uni_links: ^0.5.1 googleapis_auth: ^1.6.0 - timezone: ^0.9.4 + flutter_confetti: ^0.3.0 + # intl_phone_field: ^3.1.0 + flutter_contacts: ^1.1.8 + mime: ^1.0.6 + http_parser: ^4.0.2 dev_dependencies: flutter_test: diff --git a/ride9-11.zip b/ride9-11.zip new file mode 100644 index 0000000..a979ff0 Binary files /dev/null and b/ride9-11.zip differ diff --git a/shorebird.yaml b/shorebird.yaml index aaa3af7..10c3a35 100644 --- a/shorebird.yaml +++ b/shorebird.yaml @@ -1,11 +1,11 @@ # This file is used to configure the Shorebird updater used by your app. # Learn more at https://docs.shorebird.dev -# This file should be checked into version control. +# This file does not contain any sensitive information and should be checked into version control. -# This is the unique identifier assigned to your app. -# Your app_id is not a secret and is just used to identify your app -# when requesting patches from Shorebird's servers. -app_id: 8a571d7f-dfbf-4a65-be62-17eed08cbd5c +# Your app_id is the unique identifier assigned to your app. +# It is used to identify your app when requesting patches from Shorebird's servers. +# It is not a secret and can be shared publicly. +app_id: 496cb3ac-c25c-455d-9d2e-5ebbea13ab03 # auto_update controls if Shorebird should automatically update in the background on launch. # If auto_update: false, you will need to use package:shorebird_code_push to trigger updates.