diff --git a/android/app/build.gradle b/android/app/build.gradle index 6c13091..4386811 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -148,8 +148,8 @@ android { // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdk = 23 targetSdk = flutter.targetSdkVersion - versionCode = 82 - versionName = '1.5.82' + versionCode = 93 + versionName = '1.5.93' multiDexEnabled =true // manifestPlaceholders can be specified here if needed diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6759204..cbffd52 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -10,6 +10,12 @@ + + + + + + @@ -59,6 +65,17 @@ + + + + + + + + + 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): @@ -221,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`) @@ -292,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: @@ -360,6 +365,7 @@ SPEC CHECKSUMS: FirebaseInstallations: 771177d89d6c451dc6e50085ec82e2fc77ed0a4a FirebaseMessaging: d2d1d9c62c46dd2db49a952f7deb5b16ad2c9742 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_contacts: edb1c5ce76aa433e20e6cb14c615f4c0b66e0983 flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 flutter_tts: 0f492aab6accf87059b72354fcb4ba934304771d diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 9c0b524..ad2e15e 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,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 59 + 67 CFBundleSignature ???? CFBundleVersion - 4.3.59 + 4.3.67 NSHumanReadableCopyright FirebaseAppDelegateProxyEnabled diff --git a/lib/constant/box_name.dart b/lib/constant/box_name.dart index 7db29cd..0113597 100644 --- a/lib/constant/box_name.dart +++ b/lib/constant/box_name.dart @@ -12,6 +12,7 @@ class BoxName { static const String packagInfo = "packagInfo"; 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"; diff --git a/lib/constant/links.dart b/lib/constant/links.dart index 78c4f36..803a4bb 100644 --- a/lib/constant/links.dart +++ b/lib/constant/links.dart @@ -57,17 +57,21 @@ class AppLink { 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 = "$server/ride/cancelRide/add.php"; static String cancelRide = "$server/ride/cancelRide/get.php"; //-----------------ridessss------------------ static String addRides = "$ride/rides/add.php"; - static String getRides = "$server/ride/rides/get.php"; - static String getRideOrderID = "$server/ride/rides/getRideOrderID.php"; - static String getRideStatus = "$server/ride/rides/getRideStatus.php"; + static String getRides = "$endPoint/ride/rides/get.php"; + static String getRideOrderID = "$endPoint/ride/rides/getRideOrderID.php"; + static String getRideStatus = "$endPoint/ride/rides/getRideStatus.php"; static String getRideStatusBegin = - "$server/ride/rides/getRideStatusBegin.php"; + "$endPoint/ride/rides/getRideStatusBegin.php"; static String getRideStatusFromStartApp = "$ride/rides/getRideStatusFromStartApp.php"; static String updateRides = "$server/ride/rides/update.php"; @@ -116,6 +120,17 @@ class AppLink { "$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"; @@ -125,6 +140,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"; 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..10975c7 100644 --- a/lib/constant/style.dart +++ b/lib/constant/style.dart @@ -43,7 +43,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/controller/auth/login_controller.dart b/lib/controller/auth/login_controller.dart index 851ffbd..d2f4745 100644 --- a/lib/controller/auth/login_controller.dart +++ b/lib/controller/auth/login_controller.dart @@ -3,6 +3,7 @@ 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'; @@ -15,6 +16,7 @@ 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 { @@ -90,23 +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, jsonDecoeded['data'][0]['package']); - box.write(BoxName.promo, jsonDecoeded['data'][0]['promo']); - box.write(BoxName.discount, jsonDecoeded['data'][0]['discount']); - box.write(BoxName.validity, jsonDecoeded['data'][0]['validity']); - box.write(BoxName.isInstall, - jsonDecoeded['data'][0]['isInstall'] ?? 'none'); - box.write(BoxName.isGiftToken, - jsonDecoeded['data'][0]['isGiftToken'] ?? 'none'); - box.write(BoxName.inviteCode, - jsonDecoeded['data'][0]['inviteCode'] ?? 'none'); + 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() @@ -114,11 +115,11 @@ 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)); @@ -153,10 +154,10 @@ class LoginController extends GetxController { Get.offAll(() => const MapPagePassenger()); }, ); + } else { + print('same'); } } // Logging to check if inviteCode is written correctly - print("Invite Code in Box: ${box.read(BoxName.inviteCode)}"); - print("Is Install: ${box.read(BoxName.isInstall)}"); if (box.read(BoxName.inviteCode).toString() != 'none' && box.read(BoxName.isInstall).toString() != '1') { @@ -171,15 +172,22 @@ class LoginController extends GetxController { middleText: "Your invite code was successfully applied!" .tr, // Automatically translates based on the current locale onConfirm: () { - 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' - }); - Get.offAll(() => - const MapPagePassenger()); // Navigate to MapPagePassenger after confirmation + 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 ); @@ -200,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(); diff --git a/lib/controller/auth/register_controller.dart b/lib/controller/auth/register_controller.dart index cb03dcc..2cd19d4 100644 --- a/lib/controller/auth/register_controller.dart +++ b/lib/controller/auth/register_controller.dart @@ -4,6 +4,7 @@ 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'; @@ -134,66 +135,51 @@ class RegisterController extends GetxController { String phoneNumber = phoneController.text; // Check if the phone number is from Egypt (Assuming Egyptian numbers start with +20) - bool isEgyptianNumber = phoneNumber.startsWith('+20'); - // print('dfdf${phoneNumber.toString().split('+2')[1]}'); - 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 (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 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, phoneNumber); - Get.offAll(const MapPagePassenger()); + 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, phoneNumber); + Get.offAll(const MapPagePassenger()); + } else { + await sendOtp(phoneNumber, randomNumber, isEgyptianNumber, + smsEgyptController); + } } else { - // If the phone number is not verified, send OTP - // if (isEgyptianNumber) { - // if (isValidEgyptianPhoneNumber( - // phoneNumber.toString().split('+2')[1])) { await sendOtp(phoneNumber, randomNumber, isEgyptianNumber, smsEgyptController); - // } - // } } } else { - // If verification check fails, still send OTP - // if (isEgyptianNumber) { - // if (isValidEgyptianPhoneNumber( - // phoneNumber.toString().split('+2')[1])) { - await sendOtp( + sendOtp( phoneNumber, randomNumber, isEgyptianNumber, smsEgyptController); - // } else { - // MyDialog().getDialog( - // 'Error'.tr, "Phone number isn't an Egyptian phone number".tr, - // () { - // Get.back(); - // }); - // } - // } } } else { - // MyDialog().getDialog( - // 'Error'.tr, 'Phone number must be exactly 11 digits long'.tr, () { - // Get.back(); - // }); - sendOtp( - phoneNumber, randomNumber, isEgyptianNumber, smsEgyptController); - print(phoneNumber); + MyDialog().getDialog( + 'Error'.tr, 'Phone number must be exactly 11 digits long'.tr, () { + Get.back(); + }); + // sendOtp( + // phoneNumber, randomNumber, isEgyptianNumber, smsEgyptController); } } catch (e) { // Handle error - print('Error: $e'); } finally { isLoading = false; update(); @@ -205,34 +191,14 @@ class RegisterController extends GetxController { SmsEgyptController controller) async { // Trim any leading or trailing whitespace from the phone number phoneNumber = phoneNumber.trim(); - Log.print('phoneNumber: ${phoneNumber}'); - + await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: { + 'phone_number': phoneNumber, + 'token': otp.toString(), + }); if (isEgyptian) { - // // Check if the phone number has exactly 11 digits - // if (phoneNumber.length == 11 && - // RegExp(r'^\d{11}$').hasMatch(phoneNumber)) { - // Send SMS for Egyptian phone numbers - await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: { - 'phone_number': phoneNumber, - 'token': otp.toString(), - // 'urlImage': box.read(BoxName.passengerPhotoUrl), - // 'name': box.read(BoxName.name), - }); - await controller.sendSmsEgypt(phoneNumber, otp.toString()); - print('SMS sent to Egyptian phone number: $phoneNumber'); } else { - // // Show error dialog if phone number is invalid - // MyDialog().getDialog('Invalid Phone Number', - // 'The phone number must be exactly 11 digits long.', () { - // Get.back(); - // }); - // } - - // else { - // Send WhatsApp message for non-Egyptian phone numbers await CRUD().sendWhatsAppAuth(phoneNumber, otp.toString()); - print('WhatsApp message sent to non-Egyptian phone number: $phoneNumber'); } isLoading = false; @@ -243,98 +209,72 @@ class RegisterController extends GetxController { } verifySMSCode() async { - // if (formKey3.currentState!.validate()) { - Log.print('phoneController.text: ${phoneController.text}'); - // if (isValidEgyptianPhoneNumber(phoneController.text)) { - 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': box.read(BoxName.passengerPhotoUrl) ?? 'unknown', - 'first_name': box.read(BoxName.name).toString().split(' ')[0], - 'last_name': box.read(BoxName.name).toString().split(' ')[1], - }; + try { + if (formKey3.currentState!.validate()) { + var res = await CRUD().post(link: AppLink.verifyOtpMessage, payload: { + 'phone_number': phoneController.text, + 'token': verifyCode.text.toString(), + }); - var res1 = await CRUD().post( - link: AppLink.signUp, - 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.isFirstTime, '0'); - box.write(BoxName.phone, phoneController.text); - // Get.offAll(const MapPagePassenger()); - Get.put(LoginController()).loginUsingCredentials( - box.read(BoxName.passengerID).toString(), - box.read(BoxName.email).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, + ); + + 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, "you must insert token code".tr, + backgroundColor: AppColor.redColor); } - } else { - Get.snackbar( - 'Error'.tr, "The email or phone number is already registered.".tr, + } catch (e) { + addError(e.toString(), 'passenger sign up '); + Get.snackbar('Error'.tr, "Something went wrong. Please try again.".tr, backgroundColor: Colors.redAccent); } - // } 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], - // }; - - // var res1 = await CRUD().post( - // link: AppLink.signUp, - // 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, '+${phoneController.text}'); - // Get.offAll(const MapPagePassenger()); - // } - // } else { - // Get.snackbar( - // 'Error'.tr, "The email or phone number is already registered.".tr, - // backgroundColor: Colors.redAccent); - // } - // } } sendVerifications() async { diff --git a/lib/controller/firebase/firbase_messge.dart b/lib/controller/firebase/firbase_messge.dart index 60ec7e2..a8227a3 100644 --- a/lib/controller/firebase/firbase_messge.dart +++ b/lib/controller/firebase/firbase_messge.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:io'; +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'; @@ -65,6 +66,9 @@ class FirebaseMessagesController extends GetxController { } } + NotificationController notificationController = + Get.find(); + Future getTokens() async { String? basicAuthCredentials = await storage.read(key: BoxName.basicAuthCredentials); @@ -118,6 +122,10 @@ class FirebaseMessagesController extends GetxController { void fireBaseTitles(RemoteMessage message) { if (message.notification!.title! == 'Order'.tr) { + if (Platform.isAndroid) { + notificationController.showNotification( + 'Order', message.notification!.body!, 'Order'); + } } else if (message.notification!.title! == 'Apply Ride'.tr) { var passengerList = message.data['passengerList']; @@ -128,20 +136,22 @@ class FirebaseMessagesController extends GetxController { Get.find().isSearchingWindow == false; Get.find().update(); if (Platform.isAndroid) { - NotificationController().showNotification( + notificationController.showNotification( 'Apply Order'.tr, 'Driver Applied the Ride for You'.tr, 'order1'); + //notificationController.showNotification( + // 'Apply Order'.tr, 'Driver Applied the Ride for You'.tr, 'order1'); } // driverAppliedTripSnakBar(); } else if (message.notification!.title! == 'Promo'.tr) { if (Platform.isAndroid) { - NotificationController() - .showNotification('Promo', 'Show latest promo'.tr, 'promo'); + notificationController.showNotification( + 'Promo', 'Show latest promo'.tr, 'promo'); } Get.to(const PromosPassengerPage()); } else if (message.notification!.title! == 'Trip Monitoring'.tr) { if (Platform.isAndroid) { - NotificationController() - .showNotification('Trip Monitoring'.tr, '', 'iphone_ringtone'); + notificationController.showNotification( + 'Trip Monitoring'.tr, '', 'iphone_ringtone'); } var myListString = message.data['passengerList']; var myList = jsonDecode(myListString) as List; @@ -151,65 +161,67 @@ class FirebaseMessagesController extends GetxController { }); } else if (message.notification!.title! == 'token change'.tr) { if (Platform.isAndroid) { - NotificationController() - .showNotification('token change'.tr, 'token change'.tr, 'cancel'); + 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(); if (Platform.isAndroid) { - NotificationController().showNotification('Driver is Going To You'.tr, + 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') { if (Platform.isAndroid) { - NotificationController() - .showNotification('message From passenger'.tr, ''.tr, 'tone2'); + notificationController.showNotification( + 'message From passenger'.tr, ''.tr, 'tone2'); } passengerDialog(message.notification!.body!); update(); } else if (message.notification!.title! == 'message From Driver') { - passengerDialog(message.notification!.body!); if (Platform.isAndroid) { - NotificationController() - .showNotification('message From passenger'.tr, ''.tr, 'tone2'); + notificationController.showNotification( + 'message From passenger'.tr, ''.tr, 'tone2'); } + 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'); update(); - if (Platform.isAndroid) { - NotificationController() - .showNotification('Trip is Begin'.tr, ''.tr, 'start'); - } } else if (message.notification!.title! == 'Hi ,I will go now'.tr) { // Get.snackbar('Hi ,I will go now', '', // backgroundColor: AppColor.greenColor); if (Platform.isAndroid) { - NotificationController().showNotification( + notificationController.showNotification( 'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'tone2'); } update(); } else if (message.notification!.title! == 'Hi ,I Arrive your site'.tr) { driverArrivePassengerDialoge(); if (Platform.isAndroid) { - NotificationController() - .showNotification('Hi ,I Arrive your site'.tr, ''.tr, 'tone2'); + notificationController.showNotification( + 'Hi ,I Arrive your site'.tr, ''.tr, 'tone2'); } update(); } else if (message.notification!.title! == "Cancel Trip from driver".tr) { Get.back(); + 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(); @@ -219,6 +231,7 @@ class FirebaseMessagesController extends GetxController { ), cancel: MyElevatedButton( title: 'Cancel'.tr, + kolor: AppColor.redColor, onPressed: () { Get.offAll(const MapPagePassenger()); }, @@ -230,7 +243,7 @@ class FirebaseMessagesController extends GetxController { var myListString = message.data['passengerList']; var driverList = jsonDecode(myListString) as List; if (Platform.isAndroid) { - NotificationController().showNotification( + notificationController.showNotification( 'Driver Finish Trip'.tr, 'you will pay to Driver'.tr + ' ${driverList[3].toString()} \$'.tr, 'tone1'); @@ -267,7 +280,7 @@ class FirebaseMessagesController extends GetxController { var driverList = jsonDecode(myListString) as List; // if (Platform.isAndroid) { if (Platform.isAndroid) { - NotificationController().showNotification( + notificationController.showNotification( 'Call Income'.tr, message.notification!.body!, 'iphone_ringtone', @@ -287,7 +300,7 @@ class FirebaseMessagesController extends GetxController { var driverList = jsonDecode(myListString) as List; // if (Platform.isAndroid) { if (Platform.isAndroid) { - NotificationController().showNotification( + notificationController.showNotification( 'Call Income'.tr, message.notification!.body!, 'iphone_ringtone', @@ -305,7 +318,7 @@ 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', @@ -321,7 +334,7 @@ class FirebaseMessagesController extends GetxController { // 'message', // backgroundColor: AppColor.redColor); if (Platform.isAndroid) { - NotificationController().showNotification( + notificationController.showNotification( 'Driver Cancel Your Trip'.tr, 'you will pay to Driver you will be pay the cost of driver time look to your SEFER Wallet' .tr, @@ -346,7 +359,7 @@ class FirebaseMessagesController extends GetxController { else if (message.notification!.title! == 'Order Applied'.tr) { if (Platform.isAndroid) { - NotificationController().showNotification( + notificationController.showNotification( 'The order Accepted by another Driver'.tr, 'We regret to inform you that another driver has accepted this order.' .tr, @@ -604,120 +617,6 @@ class FirebaseMessagesController extends GetxController { } } - void sendNotificationToAnyWithoutData( - String title, String body, String token, String tone) 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(); - // Log.print('accessToken: ${accessToken}'); - - // 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({ - 'message': { - 'token': token, - 'notification': { - 'title': title, - '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, - }, - }, - }, - }, - }), - ); - - if (response.statusCode == 200) { - 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}'); - } - } 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, {int retryCount = 2}) async { @@ -809,79 +708,6 @@ class FirebaseMessagesController extends GetxController { } } } - - void sendNotificationToDriverMapPolyline(String title, String body, - String token, List data, String polylineJson, String tone) 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({ - 'message': { - 'token': token, - 'notification': { - 'title': title, - 'body': body, - }, - 'data': { - '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, - }, - }, - }, - }, - }), - ); - - if (response.statusCode == 200) { - // Notification sent successfully - } else { - // Handle error response - 'Failed to send notification. Status code: ${response.statusCode}'; - } - } catch (e) { - // Handle other exceptions - } - } } class DriverTipWidget extends StatelessWidget { diff --git a/lib/controller/firebase/local_notification.dart b/lib/controller/firebase/local_notification.dart index 5fc0484..b6d8c93 100644 --- a/lib/controller/firebase/local_notification.dart +++ b/lib/controller/firebase/local_notification.dart @@ -1,29 +1,219 @@ +// import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +// import 'package:get/get.dart'; +// import 'package:timezone/data/latest.dart' as tz; +// import 'package:timezone/timezone.dart' as tz; + +// class NotificationController extends GetxController { +// final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = +// FlutterLocalNotificationsPlugin(); + +// // Initializes the local notifications plugin +// Future initNotifications() async { +// const AndroidInitializationSettings android = +// AndroidInitializationSettings('@mipmap/launcher_icon'); +// const InitializationSettings initializationSettings = +// InitializationSettings(android: android); +// await _flutterLocalNotificationsPlugin.initialize(initializationSettings); +// } + +// // Displays a notification with the given title and message +// void showNotification(String title, String message, String tone) async { +// AndroidNotificationDetails android = AndroidNotificationDetails( +// 'high_importance_channel', +// 'High Importance Notifications', +// importance: Importance.max, +// priority: Priority.high, +// showWhen: false, +// sound: RawResourceAndroidNotificationSound(tone), +// ); +// DarwinNotificationDetails ios = const DarwinNotificationDetails( +// sound: 'default', +// presentAlert: true, +// presentBadge: true, +// presentSound: true, +// ); +// NotificationDetails details = +// NotificationDetails(android: android, iOS: ios); +// await _flutterLocalNotificationsPlugin.show(0, title, message, details); +// } + +// // Schedules a notification after 1 minute +// void scheduleNotificationAfter1Minute( +// String title, String message, String tone) async { +// AndroidNotificationDetails android = AndroidNotificationDetails( +// 'high_importance_channel', 'High Importance Notifications', +// importance: Importance.max, +// priority: Priority.high, +// showWhen: false, +// sound: RawResourceAndroidNotificationSound(tone)); + +// DarwinNotificationDetails ios = const DarwinNotificationDetails( +// sound: 'default', +// presentAlert: true, +// presentBadge: true, +// presentSound: true, +// ); + +// NotificationDetails details = +// NotificationDetails(android: android, iOS: ios); + +// // Schedule the notification to be shown after 1 minute +// final now = tz.TZDateTime.now(tz.local); +// final scheduledTime = now.add(const Duration(minutes: 1)); + +// await _flutterLocalNotificationsPlugin.zonedSchedule( +// 0, +// title, +// message, +// scheduledTime, +// details, +// androidAllowWhileIdle: true, +// uiLocalNotificationDateInterpretation: +// UILocalNotificationDateInterpretation.absoluteTime, +// matchDateTimeComponents: DateTimeComponents.time, +// ); +// } +// } +import 'dart:async'; +import 'dart:io'; + +import 'package:SEFER/constant/box_name.dart'; +import 'package:SEFER/main.dart'; 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; class NotificationController extends GetxController { final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + @override + void onInit() { + super.onInit(); + initNotifications(); + } + // 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( - 'high_importance_channel', 'High Importance Notifications', - 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'); + } + + void scheduleDailyNotifications( + 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 10:00 AM and 3:00 PM daily + await _scheduleNotificationForTime(8, 0, title, message, details); + await _scheduleNotificationForTime(15, 0, title, message, details); + await _scheduleNotificationForTime(20, 0, title, message, details); + // await _scheduleNotificationForTime(0, 22, title, message, details); + + print('Daily notifications scheduled successfully'); + } + +// Helper function to get the next instance of a specific hour and minute + Future _scheduleNotificationForTime(int hour, int minute, String title, + String message, NotificationDetails details) async { + // Initialize and set Cairo timezone + tz.initializeTimeZones(); + var cairoLocation; + if (box.read(BoxName.countryCode).toString() == 'Egypt') { + cairoLocation = tz.getLocation('Africa/Cairo'); + } else {} // todo get for location country + // Set Cairo timezone + + final now = tz.TZDateTime.now( + cairoLocation); // Use Cairo timezone for the current time + tz.TZDateTime scheduledDate = tz.TZDateTime( + cairoLocation, now.year, now.month, now.day, hour, minute); + + // If scheduled time is already past today, schedule it for 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( + 0, // Use unique IDs if you want to manage each notification separately + title, + message, + scheduledDate, + details, + androidAllowWhileIdle: true, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, + matchDateTimeComponents: DateTimeComponents.time, + ); + print('Notification scheduled successfully for Cairo timezone'); } } 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 1525659..8c0e3a3 100644 --- a/lib/controller/functions/crud.dart +++ b/lib/controller/functions/crud.dart @@ -1,15 +1,21 @@ import 'dart:convert'; +import 'dart:io'; +import 'dart:ui'; import 'package:SEFER/constant/box_name.dart'; import 'package:SEFER/constant/links.dart'; import 'package:SEFER/main.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:SEFER/env/env.dart'; +import 'package:http_parser/http_parser.dart'; +import 'package:mime/mime.dart'; import '../../constant/api_key.dart'; +import '../../constant/colors.dart'; import '../../print.dart'; import '../../views/widgets/elevated_btn.dart'; +import 'add_error.dart'; import 'upload_image.dart'; class CRUD { @@ -29,8 +35,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}'); @@ -44,7 +51,6 @@ class CRUD { return jsonData['status']; } - // } Future getTokenParent({ required String link, Map? payload, @@ -264,36 +270,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 } } diff --git a/lib/controller/functions/sms_controller.dart b/lib/controller/functions/sms_controller.dart index fc0ec9c..53e3422 100644 --- a/lib/controller/functions/sms_controller.dart +++ b/lib/controller/functions/sms_controller.dart @@ -29,7 +29,7 @@ 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', 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/map_passenger_controller.dart b/lib/controller/home/map_passenger_controller.dart index 36e5dbd..a500876 100644 --- a/lib/controller/home/map_passenger_controller.dart +++ b/lib/controller/home/map_passenger_controller.dart @@ -769,7 +769,8 @@ class MapPassengerController extends GetxController { if (res != 'failure') { var decode = jsonDecode(res); - if (decode['data']['status'] != 'Apply') { + // if (decode['data']['status'] != 'Apply') { + if (decode['data']['status'] == 'Begin') { timeToPassengerFromDriverAfterApplied = 0; remainingTime = 0; remainingTimeToPassengerFromDriverAfterApplied = 0; @@ -1077,7 +1078,7 @@ class MapPassengerController extends GetxController { // licensePlate = nearestDriverData['car_plate'].toString(); // startCarLocationSearch(box.read(BoxName.carType)); await getCarsLocationByPassengerAndReloadMarker( - box.read(BoxName.carType), 4500); + box.read(BoxName.carType), 3000); // await getCarsLocationByPassengerAndReloadMarker( // box.read(BoxName.carType), 7000); // await getNearestDriverByPassengerLocation(); @@ -1112,26 +1113,28 @@ class MapPassengerController extends GetxController { isDriversTokensSend = false; update(); - await CRUD() - .post(link: "${AppLink.endPoint}/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['data'][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) { + await CRUD().post( + link: "${AppLink.seferCairoServer}/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['data'][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 = [ @@ -1188,6 +1191,29 @@ class MapPassengerController extends GetxController { Log.print( 'dataCarsLocationByPassenger[data]: ${dataCarsLocationByPassenger['data'][carsOrder]['token']}'); }); + + if (AppLink.endPoint != AppLink.seferCairoServer) { + CRUD().post(link: "${AppLink.endPoint}/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['data'][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, box.read(BoxName.carType)); if (shouldFetch == false) { startTimer(); @@ -1211,10 +1237,11 @@ class MapPassengerController extends GetxController { } } + String driverOrderStatus = 'yet'; bool isDriversTokensSend = false; - confirmRideForAllDriverAvailable() async { + confirmRideForAllDriverAvailable0() async { await getCarsLocationByPassengerAndReloadMarker( - box.read(BoxName.carType), 4500); + box.read(BoxName.carType), 3500); if (dataCarsLocationByPassenger != 'failure') { // driversToken.remove(driverToken); PaymentController paymentController = Get.find(); @@ -1291,33 +1318,38 @@ class MapPassengerController extends GetxController { // Log.print('body: ${body}'); FirebaseMessagesController().sendNotificationToDriverMAP( - 'OrderSpeed', - rideId.toString(), - dataCarsLocationByPassenger['data'][i]['token'].toString(), - body, - 'order.wav'); + 'OrderSpeed', + rideId.toString(), + dataCarsLocationByPassenger['data'][i]['token'].toString(), + body, + 'order.wav', + ); + driverOrderStatus = 'recive'; } }); (rideId); // - CRUD().post(link: '${AppLink.endPoint}/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['data'][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(), - }); + if (AppLink.endPoint != AppLink.seferCairoServer) { + CRUD().post(link: '${AppLink.endPoint}/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['data'][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(); @@ -1330,6 +1362,344 @@ class MapPassengerController extends GetxController { } } + Set notifiedDrivers = {}; + + confirmRideForAllDriverAvailable() async { + // Fetch car locations + await getCarsLocationByPassengerAndReloadMarker( + box.read(BoxName.carType), 3000); + + // Ensure dataCarsLocationByPassenger is not 'failure' or null + if (dataCarsLocationByPassenger != 'failure' && + dataCarsLocationByPassenger != null) { + // Check if 'data' key exists and is not null + if (dataCarsLocationByPassenger.containsKey('data') && + dataCarsLocationByPassenger['data'] != null) { + 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['data'][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}'); + } + // Log.print('value: ${value}'); + // rideId = jsonDecode(value)['message']; + // rideId = jsonDecode(value)['message'].toString(); + + // Timer for 5 iterations, runs every 2 seconds + int iteration = 0; + Timer.periodic(const Duration(seconds: 2), (timer) async { + if (iteration >= 5) { + timer.cancel(); + return; + } + iteration++; + + // Reload driver locations + await getCarsLocationByPassengerAndReloadMarker( + box.read(BoxName.carType), 3000); + + // Ensure dataCarsLocationByPassenger and data key are still valid + if (dataCarsLocationByPassenger != null && + dataCarsLocationByPassenger.containsKey('data') && + dataCarsLocationByPassenger['data'] != null) { + // Notify only new drivers + for (var driverData in dataCarsLocationByPassenger['data']) { + String driverId = driverData['driver_id'].toString(); + if (!notifiedDrivers.contains(driverId)) { + notifiedDrivers.add(driverId); + + // Prepare body payload for notification + 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, + 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, + rideTimerBegin.toString(), + driverId, + 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), + ]; + + // Send notification to the driver + FirebaseMessagesController().sendNotificationToDriverMAP( + 'OrderSpeed', + rideId, + driverData['token'].toString(), + body, + 'order.wav', + ); + } + } + } + }); + }); + + // Check for additional server endpoint + 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['data'][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(); + } else { + // Show dialog if no drivers found in data key + 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()); + }); + } + } else { + // Show dialog if dataCarsLocationByPassenger is 'failure' or null + 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()); + }); + } + } + + confirmRideForAllDriverAvailable1() async { + int attempts = 0; + const int maxAttempts = 4; + const Duration delayDuration = Duration(seconds: 2); + + // Initial data fetch + await getCarsLocationByPassengerAndReloadMarker( + box.read(BoxName.carType), 3000); + + if (dataCarsLocationByPassenger != null && + dataCarsLocationByPassenger != 'failure') { + PaymentController paymentController = Get.find(); + rideConfirm = true; + shouldFetch = true; + isBottomSheetShown = false; + timeToPassengerFromDriverAfterApplied = 60; + + // Create a set to keep track of notified driver IDs + Set notifiedDriverIds = {}; + + // Send the initial ride request once + rideId = 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['data'][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) => jsonDecode(value)['message']); + + if (AppLink.endPoint != AppLink.seferCairoServer) { + CRUD().post(link: '${AppLink.endPoint}/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['data'][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(), + }); + } + // Add the initially available drivers to the notified set + for (var driver in dataCarsLocationByPassenger['data']) { + notifiedDriverIds.add(driver['driver_id'].toString()); + } + + // Periodically check for new drivers + Timer.periodic(delayDuration, (Timer timer) async { + attempts++; + + await getCarsLocationByPassengerAndReloadMarker( + box.read(BoxName.carType), 3000); + + if (dataCarsLocationByPassenger != 'failure') { + // Check for new drivers and notify them + for (var driver in dataCarsLocationByPassenger['data']) { + String driverId = driver['driver_id'].toString(); + + // Only notify new drivers + if (!notifiedDriverIds.contains(driverId)) { + notifiedDriverIds.add(driverId); + + // Prepare notification body + 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, + 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(), + driver['token'].toString(), + durationToPassenger.toString(), + rideId, + rideTimerBegin.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), + ]; + + // Send notification to the new driver + FirebaseMessagesController().sendNotificationToDriverMAP( + 'OrderSpeed', + rideId.toString(), + driver['token'].toString(), + body, + 'order.wav', + ); + } + } + } 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()); + }); + } + + // Stop after max attempts + if (attempts >= maxAttempts) { + timer.cancel(); + } + }); + } 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()); + }); + } + } + icreaseForSameRideAndDelay() { PaymentController paymentController = Get.find(); rideConfirm = true; @@ -1398,19 +1768,20 @@ class MapPassengerController extends GetxController { String res = await getRideStatus(rideId); Log.print('tick: $tick'); - - if ((res.toString() == 'waiting' || res.toString() == 'Refused') && + 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, 4500); + await getCarsLocationByPassengerAndReloadMarker(carType, 3000); // await getNearestDriverByPassengerLocationAPIGOOGLE(); // getCarForFirstConfirm(carType); confirmRideForAllDriverAvailable(); // delayAndFetchRideStatusForAllDriverAvailable(rideId); - } else if (res.toString() == 'Apply') { - Log.print('res.toString() == Apply: ${res.toString()}'); + } else if (rideStatusDelayed == 'Apply') { + Log.print('rideStatusDelayed == Apply: ${rideStatusDelayed}'); // todo play sound Get.find() .playSoundFromAssets('assets/start.wav'); @@ -1422,7 +1793,7 @@ class MapPassengerController extends GetxController { isSearchingWindow = false; update(); startTimerFromDriverToPassengerAfterApplied(); - } else if (res.toString() == 'Refused') { + } else if (rideStatusDelayed == 'Refused') { statusRide = 'Refused'; if (isDriversTokensSend == false) { confirmRideForAllDriverAvailable(); @@ -1473,17 +1844,22 @@ class MapPassengerController extends GetxController { attemptCounter++; tick++; var res = await getRideStatus(rideId); + String rideStatusDelayed = res.toString(); - if (res.toString() == 'Apply' || res.toString() == 'Applied') { + if (rideStatusDelayed == 'Apply' || rideStatusDelayed == 'Applied') { await getUpdatedRideForDriverApply(rideId); isApplied = true; shouldFetch = false; statusRide = 'Apply'; rideConfirm = false; isSearchingWindow = false; + + startTimer(); + update(); startTimerFromDriverToPassengerAfterApplied(); - } else if (attemptCounter >= maxAttempts && statusRide != 'Cancel') { + } else if (attemptCounter >= maxAttempts && + rideStatusDelayed != 'Cancel') { shouldFetch = false; // If the status is still not "Apply" after 15 attempts MyDialog().getDialog('upgrade price'.tr, @@ -1573,7 +1949,7 @@ class MapPassengerController extends GetxController { reSearchAfterCanceledFromDriver() async { await getCarsLocationByPassengerAndReloadMarker( - box.read(BoxName.carType), 4500); + box.read(BoxName.carType), 3000); confirmRideForAllDriverAvailable(); shouldFetch = true; // Stop further fetches @@ -1616,8 +1992,9 @@ class MapPassengerController extends GetxController { } 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}); return jsonDecode(response)['data']; } @@ -1638,7 +2015,7 @@ class MapPassengerController extends GetxController { make = response['data']['make']; licensePlate = response['data']['car_plate']; firstName = response['data']['first_name']; - driverName = response['data']['driverName']; + driverName = response['data']['driverName'].toString().split(' ')[0]; driverToken = response['data']['token']; Log.print('driverToken updated: $driverToken'); carYear = response['data']['year']; @@ -1646,10 +2023,11 @@ class MapPassengerController extends GetxController { } // driversToken.remove(driverToken); // for (var i = 1; i < driversToken.length; i++) { - FirebaseMessagesController().sendNotificationToAnyWithoutData( + FirebaseMessagesController().sendNotificationToDriverMAP( 'Order Applied'.tr, '$driverName Apply order\nTake attention in other order'.tr, driverToken, + [], 'start.wav', ); // } @@ -1871,6 +2249,7 @@ 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 && @@ -2380,12 +2759,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 && statusRide == 'Apply') { - await getDriverCarsLocationToPassengerAfterApplied(); - } + // if (rideTimerBegin == true && statusRide == 'Apply') { + await getDriverCarsLocationToPassengerAfterApplied(); + // } reloadMarkerDriverCarsLocationToPassengerAfterApplied(); } } @@ -2517,79 +2897,66 @@ class MapPassengerController extends GetxController { } Future cancelRide() async { - if (rideConfirm == false && statusRide == 'Apply' || - statusRide == 'Applied' || - statusRide == 'waiting') { - clearPlacesDestination(); - clearPolyline(); - // clearPolylineAll(); - data = []; - changeCancelRidePageShow(); - if (rideId != 'yet') { - await CRUD().post(link: AppLink.updateDriverOrder, payload: { - "order_id": rideId.toString(), // Convert to String - "status": 'Cancel' - }); - FirebaseMessagesController().sendNotificationToDriverMAP( - 'Cancel Trip', - 'Trip Cancelled'.tr, - driverToken, - [], - 'cancel.wav', - ); - 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' - }); - } - await CRUD().post(link: AppLink.updateRides, 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, + [], + 'cancel.wav', + ); + + await Future.wait([ + CRUD().post(link: AppLink.updateRides, payload: { "id": rideId.toString(), // Convert to String "status": 'Cancel' - }); - if (AppLink.endPoint != AppLink.seferCairoServer) { - CRUD().post( - link: "${AppLink.endPoint}/ride/rides/update.php", - payload: { - "id": rideId.toString(), // Convert to String - "status": 'Cancel' - }); - } - + }), + CRUD().post(link: AppLink.updateDriverOrder, payload: { + "order_id": rideId.toString(), // Convert to String + "status": 'Cancel' + }), 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' }); - if (AppLink.endPoint != AppLink.seferCairoServer) { - CRUD().post( - link: - "${AppLink.endPoint}/ride/notificationCaptain/updateWaitingTrip.php", - payload: { - "id": rideId.toString(), // Convert to String - "status": 'Cancel' - }); - } - - print('Cancel'); + 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() { @@ -2942,28 +3309,28 @@ 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), ); - - northeast = LatLng( - centerLat + (radius / 111000), - centerLng + (radius / (111000 * cos(centerLat))), - ); - - return LatLngBounds(southwest: southwest, northeast: northeast); } GoogleMapController? mapController; @@ -2992,8 +3359,6 @@ 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: 5), (timer) async { reloadCount++; @@ -3233,7 +3598,7 @@ class MapPassengerController extends GetxController { remainingTime = 25; //to make cancel every call // startCarLocationSearch(box.read(BoxName.carType)); await getCarsLocationByPassengerAndReloadMarker( - box.read(BoxName.carType), 7000); + box.read(BoxName.carType), 5000); // await getCarsLocationByPassengerAndReloadMarker(); var coordDestination = destination.split(','); double latPassengerDestination = double.parse(coordDestination[0]); @@ -4093,55 +4458,77 @@ class MapPassengerController extends GetxController { try { // Prepare trip data Map tripData = { - 'id': driver['id'], + 'id': driver['id'].toString(), // Ensure the id is a string 'phone': driver['phone'], 'gender': driver['gender'], - 'name': driver['name'], + 'name': driver['NAME'], 'name_english': driver['name_english'], 'address': driver['address'], 'religion': driver['religion'], - 'age': driver['age'], + 'age': driver['age'].toString(), // Convert age to String 'education': driver['education'], 'license_type': driver['license_type'], 'national_number': driver['national_number'], 'car_plate': driver['car_plate'], 'make': driver['make'], 'model': driver['model'], - 'year': driver['year'], + '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'], - 'countRide': driver['countRide'], + 'rating': driver['rating'].toString(), // Convert rating to String + 'countRide': + driver['countRide'].toString(), // Convert countRide to String 'passengerId': box.read(BoxName.passengerID), 'timeSelected': tripDateTime.toIso8601String(), - 'status': 'pending', // Or other appropriate status + 'status': 'pending', }; + // Log.print('tripData: $tripData'); // Send data to server var response = await CRUD().post(link: AppLink.addMishwari, payload: tripData); + // Log.print('response: $response'); if (response != 'failure') { // Trip saved successfully Get.snackbar('Success'.tr, 'Trip booked successfully'.tr); + var id = response['message'].toString(); if (AppLink.endPoint != AppLink.seferCairoServer) { - CRUD().post( + await CRUD().post( link: "${AppLink.endPoint}/ride/mishwari/add.php", payload: tripData); } - // Set up local notification + // Optionally, set up local notification or send a push notification // await setLocalNotification(tripDateTime); - - // Send notification to driver - // await FirebaseMessagesController().sendNotificationToDriverMAP(); + await FirebaseMessagesController().sendNotificationToDriverMAP( + 'OrderVIP', + rideId.toString(), + driver['token'].toString(), + [ + id, + driver['id'], + passengerLocation.latitude.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'); } else { throw Exception('Failed to save trip'); } } catch (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'); } } @@ -4253,8 +4640,8 @@ class MapPassengerController extends GetxController { addCustomStepIcon(); addCustomStartIcon(); addCustomEndIcon(); - addToken(); - getLocation(); + // addToken(); + await getLocation(); getPassengerLocationUniversity(); _initializePolygons(); // await addToken(); 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..50ed888 --- /dev/null +++ b/lib/controller/home/profile/invit_controller.dart @@ -0,0 +1,280 @@ +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/home/payment/captain_wallet_controller.dart'; +import 'package:SEFER/controller/payment/payment_controller.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 '../../../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 { + if (await FlutterContacts.requestPermission(readonly: true)) { + final List fetchedContacts = + await FlutterContacts.getContacts(withProperties: true); + contacts = fetchedContacts; + + // Convert contacts to a list of maps + contactMaps.value = fetchedContacts.map((contact) { + return { + 'name': contact.displayName, + 'phones': + contact.phones.map((phone) => phone.normalizedNumber).toList(), + 'emails': contact.emails.map((email) => email.address).toList(), + }; + }).toList(); + update(); + + if (contacts.isEmpty) { + 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) { + Get.snackbar('Error', 'Please enter an phone address'.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 = jsonDecode(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/local/local_controller.dart b/lib/controller/local/local_controller.dart index 8eb2a76..1f61cb8 100644 --- a/lib/controller/local/local_controller.dart +++ b/lib/controller/local/local_controller.dart @@ -94,7 +94,7 @@ class LocaleController extends GetxController { @override void onInit() { - String storedLang = box.read(BoxName.lang) ?? ""; + String storedLang = box.read(BoxName.lang) ?? "ar"; switch (storedLang) { case "ar": language = const Locale("ar"); diff --git a/lib/controller/local/translations.dart b/lib/controller/local/translations.dart index 5a8e001..64f8e7a 100644 --- a/lib/controller/local/translations.dart +++ b/lib/controller/local/translations.dart @@ -56,8 +56,7 @@ class MyTranslation extends Translations { "I want to order for someone else": "أريد أن أطلب لشخص آخر", "Cancel Trip from driver": "إلغاء الرحلة من السائق", "If you want order to another person": "إذا كنت تريد الطلب لشخص آخر", - "We will look for a new driver.\nPlease wait.": - "سنبحث عن سائق جديد.\nمن فضلك انتظر.", + "upgrade price": "رفع السعر", 'airport': 'مطار', "Best choice for a comfortable car with a flexible route and stop points. This airport offers visa entry at this price.": @@ -234,15 +233,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)": "الاسم بالعربي", 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 7a4531a..32f2b90 100644 --- a/lib/controller/rate/rate_conroller.dart +++ b/lib/controller/rate/rate_conroller.dart @@ -70,10 +70,11 @@ 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', ); } diff --git a/lib/main.dart b/lib/main.dart index 028dc86..4bf3b65 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,24 @@ 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(); + // 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.scheduleDailyNotifications( + randomMessage.split(':')[0], + randomMessage.split(':')[1], + "ding", + ); + + // await NotificationController().initNotifications(); + // } await GetStorage.init(); // Get.put(DriverCallController()); // await AC().gAK(); diff --git a/lib/views/auth/sms_verfy_page.dart b/lib/views/auth/sms_verfy_page.dart index 352b8b4..837c85f 100644 --- a/lib/views/auth/sms_verfy_page.dart +++ b/lib/views/auth/sms_verfy_page.dart @@ -19,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( @@ -99,13 +99,16 @@ class SmsSignupEgypt extends StatelessWidget { // Submit button registerController.isLoading ? const MyCircularProgressIndicator() - : MyElevatedButton( - onPressed: () async { - !registerController.isSent - ? await registerController.sendOtpMessage() - : await registerController.verifySMSCode(); - }, - title: 'Submit'.tr, + : 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/share_app_page.dart b/lib/views/home/HomePage/share_app_page.dart new file mode 100644 index 0000000..312afca --- /dev/null +++ b/lib/views/home/HomePage/share_app_page.dart @@ -0,0 +1,437 @@ +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'; + +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 _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) { + 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 7819ba6..9c58f41 100644 --- a/lib/views/home/home_page.dart +++ b/lib/views/home/home_page.dart @@ -8,6 +8,7 @@ import 'package:SEFER/views/widgets/my_scafold.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'; @@ -21,7 +22,7 @@ class HomePage extends StatelessWidget { isleading: true, title: 'Home Page'.tr, body: [ - Column( + ListView( children: [ ListTile( onTap: () { @@ -116,6 +117,19 @@ class HomePage extends StatelessWidget { ), onTap: () => Get.to(() => const AboutPage()), ), + ListTile( + leading: const Icon(Icons.share), + title: Text( + 'Share App'.tr, + style: AppStyle.headTitle2, + ), + subtitle: Text( + 'You can share the SEFER App with your friends and earn rewards for rides they take using your code' + .tr, + style: AppStyle.title, + ), + onTap: () => Get.to(() => ShareAppPage()), + ), ], ), ], 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 fdbe9c8..392c352 100644 --- a/lib/views/home/map_widget.dart/apply_order_widget.dart +++ b/lib/views/home/map_widget.dart/apply_order_widget.dart @@ -27,7 +27,7 @@ class ApplyOrderWidget extends StatelessWidget { right: 0, child: Container( decoration: AppStyle.boxDecoration, - height: Get.height * .35, + height: Get.height * .36, child: ListView( children: [ InkWell( @@ -87,11 +87,11 @@ class ApplyOrderWidget extends StatelessWidget { width: 10, ), Container( - height: Get.height * .3, + height: Get.height * .34, width: Get.width * .9, decoration: AppStyle.boxDecoration, child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisAlignment: MainAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.end, @@ -241,12 +241,13 @@ class ApplyOrderWidget extends StatelessWidget { InkWell( onTap: () { FirebaseMessagesController() - .sendNotificationToAnyWithoutData( - 'message From passenger', + .sendNotificationToDriverMAP( + 'message From passenger'.tr, 'Hello, I\'m at the agreed-upon location' .tr, controller.driverToken .toString(), + [], 'ding.wav', ); Get.back(); @@ -272,11 +273,12 @@ class ApplyOrderWidget extends StatelessWidget { InkWell( onTap: () { FirebaseMessagesController() - .sendNotificationToAnyWithoutData( + .sendNotificationToDriverMAP( 'message From passenger'.tr, 'My location is correct. You can search for me using the navigation app' .tr, controller.driverToken, + [], 'ding.wav', ); Get.back(); @@ -302,11 +304,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(); @@ -331,11 +334,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(); @@ -385,13 +389,14 @@ class ApplyOrderWidget extends StatelessWidget { IconButton( onPressed: () { FirebaseMessagesController() - .sendNotificationToAnyWithoutData( + .sendNotificationToDriverMAP( 'message From passenger', controller .messageToDriver .text, controller .driverToken, + [], 'ding.wav'); controller .messageToDriver @@ -466,7 +471,7 @@ class DriverArrivePassengerAndWaitMinute extends StatelessWidget { color: controller.remainingTimeDriverWaitPassenger5Minute < 60 ? AppColor.redColor : AppColor.greenColor, - minHeight: 25, + minHeight: 15, borderRadius: BorderRadius.circular(15), value: controller.progressTimerDriverWaitPassenger5Minute.toDouble(), @@ -513,7 +518,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 aa6b2ff..52d5a4c 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 @@ -586,7 +586,7 @@ class CarDetailsTypeToChoose extends StatelessWidget { textToSpeechController, image: 'assets/images/freeRide.png', text: - 'Perfect for adventure seekers who want to experience something new and exciting' + "Perfect for passengers seeking the latest car models with the freedom to choose any route they desire" .tr), confirm: MyElevatedButton( kolor: AppColor.greenColor, @@ -656,8 +656,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(); @@ -710,59 +712,108 @@ class CarDetailsTypeToChoose extends StatelessWidget { MainAxisAlignment .spaceBetween, children: [ - Container( - decoration: AppStyle - .boxDecoration1, - child: Padding( - padding: - const EdgeInsets - .all(8.0), - child: Column( - children: [ - Text('Awfar Car' - .tr), - Text(mapPassengerController - .totalPassengerRayehGaiBalash - .toStringAsFixed( - 0)), - ], - ), - )), - Container( - decoration: AppStyle - .boxDecoration1, - child: Padding( - padding: - const EdgeInsets - .all(8.0), - child: Column( - children: [ - Text('Speed'.tr), - Text(mapPassengerController - .totalPassengerRayehGai - .toStringAsFixed( - 0)), - ], - ), - )), - Container( - decoration: AppStyle - .boxDecoration1, - child: Padding( - padding: - const EdgeInsets - .all(8.0), - child: Column( - children: [ - Text( - 'Comfort'.tr), - Text(mapPassengerController - .totalPassengerRayehGaiComfort - .toStringAsFixed( - 0)), - ], - ), - )) + 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( @@ -951,9 +1002,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( @@ -965,95 +1023,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, ), ), ], ), - ), ], ), ), 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 f306cb1..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)), 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 bd2417b..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 @@ -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 c9a53d6..5d152bd 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,7 +1,13 @@ +import 'dart:math'; + +import 'package:SEFER/views/auth/login_page.dart'; +import 'package:SEFER/views/auth/sms_verfy_page.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import '../../../constant/colors.dart'; +import '../../../constant/notification.dart'; +import '../../../controller/firebase/local_notification.dart'; import '../../../controller/functions/tts.dart'; import '../../../controller/home/map_passenger_controller.dart'; @@ -89,17 +95,14 @@ GetBuilder leftMainMenuIcons() { // borderRadius: BorderRadius.circular(15)), // child: IconButton( // onPressed: () async { - // FirebaseMessagesController().sendNotificationToAnyWithoutData( - // 'Order'.tr, - // 'from: ', - // // jsonDecode(value)['message'].toString(), - // 'dEugS-JOT4Ka5riF4s5TEN:APA91bEDL_W7BuEQGbyL-RMaKiMWDlURXhFuaybe5WurTUV8K5eIooSGe22yY22_U2hEZcfPr46ig1v--l00dbOGiivazxvmTyhUyQQW6lJsuIN-wordGtBxtREyeYtEKvxIa1J4ApEu', - // 'order.wav' - - // // polylineCoordinates.toString() - // ); - // // print(box.read(BoxName.tokenFCM)); - // // + // final random = Random(); + // final randomMessage = + // messages[random.nextInt(messages.length)]; + // NotificationController().showNotification( + // randomMessage.split(':')[0], + // randomMessage.split(':')[1], + // "ding", + // ); // }, // icon: const Icon( // Icons.voice_chat, 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 abd71c7..3c71f0e 100644 --- a/lib/views/home/map_widget.dart/ride_begin_passenger.dart +++ b/lib/views/home/map_widget.dart/ride_begin_passenger.dart @@ -30,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, 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..5dc5d64 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}); 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 5e8d466..adda2c2 100644 --- a/lib/views/home/map_widget.dart/select_driver_mishwari.dart +++ b/lib/views/home/map_widget.dart/select_driver_mishwari.dart @@ -116,9 +116,10 @@ class CupertinoDriverListWidget extends StatelessWidget { width: 20, height: 20, decoration: BoxDecoration( - color: hexToColor( - driver['color_hex'].toString()) ?? - Colors.amber, + color: driver['color_hex'].toString() == 'null' + ? Colors.amber + : hexToColor( + driver['color_hex'].toString()), borderRadius: BorderRadius.circular(4), border: Border.all(), ), @@ -151,9 +152,11 @@ class CupertinoDriverListWidget extends StatelessWidget { width: 20, height: 20, decoration: BoxDecoration( - color: hexToColor( - driver['color_hex'].toString()) ?? - AppColor.bronze, + color: + driver['color_hex'].toString() == 'null' + ? Colors.amber + : hexToColor( + driver['color_hex'].toString()), borderRadius: BorderRadius.circular(4), border: Border.all(), ), @@ -204,7 +207,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(), ), 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/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/pubspec.lock b/pubspec.lock index b7ac582..e214496 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -499,6 +499,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.0" + 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: @@ -974,7 +982,7 @@ packages: source: hosted version: "3.2.1" http_parser: - dependency: transitive + dependency: "direct main" description: name: http_parser sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" @@ -1270,7 +1278,7 @@ packages: source: hosted version: "1.15.0" mime: - dependency: transitive + dependency: "direct main" description: name: mime sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" diff --git a/pubspec.yaml b/pubspec.yaml index cb6fd4d..5e19f7c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,6 +63,9 @@ dependencies: googleapis_auth: ^1.6.0 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