From 98a8a2ae3d93868ef0d7119191f481d14b1a24ff Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Thu, 25 Jun 2026 02:28:33 +0300 Subject: [PATCH] Update: 2026-06-25 02:28:33 --- siro_rider/lib/constant/credential.dart | 2 +- siro_rider/lib/constant/payment_tiers.dart | 66 +++ .../lib/controller/auth/login_controller.dart | 5 +- .../controller/auth/register_controller.dart | 28 +- .../auth/token_otp_change_controller.dart | 9 +- .../controller/auth/tokens_controller.dart | 3 +- .../controller/firebase/firbase_messge.dart | 24 +- siro_rider/lib/controller/functions/crud.dart | 3 +- .../lib/controller/functions/log_out.dart | 8 +- .../controller/functions/secure_storage.dart | 4 +- .../controller/functions/sms_controller.dart | 2 +- .../controller/functions/upload_image.dart | 4 +- .../home/blinking_promo_controller.dart.dart | 2 +- .../home/map/location_search_controller.dart | 35 +- .../home/map/nearby_drivers_controller.dart | 4 +- .../home/map/ride_lifecycle_controller.dart | 79 +-- .../home/map/ui_interactions_controller.dart | 6 +- .../payment/captain_wallet_controller.dart | 2 +- .../home/profile/complaint_controller.dart | 28 +- .../home/profile/invit_controller.dart | 71 +-- .../profile/invites_rewards_controller.dart | 13 +- .../home/trip_monitor_controller.dart | 2 +- .../controller/home/vip_waitting_page.dart | 5 +- .../notification_captain_controller.dart | 2 +- .../passenger_notification_controller.dart | 6 +- .../ride_available_controller.dart | 2 +- .../payment/driver_payment_controller.dart | 2 +- .../payment/payment_controller.dart | 32 +- .../profile/captain_profile_controller.dart | 27 +- .../lib/controller/voice_call_controller.dart | 16 +- .../lib/models/click_payment_schema.dart | 124 ++++ siro_rider/lib/splash_screen_page.dart | 557 ++++++++---------- .../views/home/HomePage/share_app_page.dart | 5 +- .../home/map_widget.dart/map_menu_widget.dart | 5 +- .../picker_animation_container.dart | 8 +- .../map_widget.dart/ride_from_start_app.dart | 4 +- .../home/my_wallet/cliq_payment_sheet.dart | 173 ++++++ .../my_wallet/passenger_wallet_dialoge.dart | 188 ++---- .../home/my_wallet/payment_screen_cliq.dart | 156 +++-- .../home/my_wallet/payment_screen_mtn.dart | 3 +- .../home/profile/passenger_profile_page.dart | 21 +- .../home/profile/promos_passenger_page.dart | 9 +- .../src/snackbar/snackbar_controller.dart | 59 +- 43 files changed, 992 insertions(+), 812 deletions(-) create mode 100644 siro_rider/lib/constant/payment_tiers.dart create mode 100644 siro_rider/lib/models/click_payment_schema.dart create mode 100644 siro_rider/lib/views/home/my_wallet/cliq_payment_sheet.dart diff --git a/siro_rider/lib/constant/credential.dart b/siro_rider/lib/constant/credential.dart index 00789a7..44686c9 100644 --- a/siro_rider/lib/constant/credential.dart +++ b/siro_rider/lib/constant/credential.dart @@ -12,7 +12,7 @@ class AC { gAK() async { if (box.read(BoxName.apiKeyRun).toString() != 'run') { var res = await CRUD().get(link: AppLink.getApiKey, payload: {}); - var decod = jsonDecode(res); + var decod = res; Log.print(decod); Map jsonData = {}; for (var i = 0; i < decod['message'].length; i++) { diff --git a/siro_rider/lib/constant/payment_tiers.dart b/siro_rider/lib/constant/payment_tiers.dart new file mode 100644 index 0000000..6a36e07 --- /dev/null +++ b/siro_rider/lib/constant/payment_tiers.dart @@ -0,0 +1,66 @@ +import 'package:get/get.dart'; +import 'package:siro_rider/constant/box_name.dart'; +import 'package:siro_rider/main.dart'; + +class PaymentTier { + final double amount; + final double bonus; + const PaymentTier(this.amount, this.bonus); +} + +class PaymentTierConfig { + final String currencyCode; + final List tiers; + const PaymentTierConfig({required this.currencyCode, required this.tiers}); + + static const Map _configs = { + 'Jordan': PaymentTierConfig( + currencyCode: 'JOD', + tiers: [ + PaymentTier(5, 0), + PaymentTier(10, 0.5), + PaymentTier(20, 1), + PaymentTier(50, 3), + ], + ), + 'Egypt': PaymentTierConfig( + currencyCode: 'EGP', + tiers: [ + PaymentTier(100, 0), + PaymentTier(200, 10), + PaymentTier(400, 20), + PaymentTier(1000, 50), + ], + ), + 'Syria': PaymentTierConfig( + currencyCode: 'SYP', + tiers: [ + PaymentTier(100, 0), + PaymentTier(200, 10), + PaymentTier(400, 20), + PaymentTier(1000, 50), + ], + ), + }; + + static PaymentTierConfig get current { + final country = box.read(BoxName.countryCode) ?? 'Jordan'; + return _configs[country] ?? _configs['Jordan']!; + } + + static List get currentTiers => current.tiers; + static String get currentCurrency => current.currencyCode; + + static String formatAmount(double v) { + return v == v.roundToDouble() ? v.toInt().toString() : v.toStringAsFixed(1); + } + + static String tierLabel(int index) { + final t = currentTiers[index]; + final cur = currentCurrency; + if (t.bonus > 0) { + return '${'Pay'.tr} ${formatAmount(t.amount)} $cur, ${'Get'.tr} ${formatAmount(t.amount + t.bonus)} $cur'; + } + return '${formatAmount(t.amount)} $cur'; + } +} diff --git a/siro_rider/lib/controller/auth/login_controller.dart b/siro_rider/lib/controller/auth/login_controller.dart index 6ebc718..5032b83 100644 --- a/siro_rider/lib/controller/auth/login_controller.dart +++ b/siro_rider/lib/controller/auth/login_controller.dart @@ -300,8 +300,7 @@ class LoginController extends GetxController { if (decoded is! Map || decoded.isEmpty) return; if (decoded['status'] == 'failure' || decoded['status'] == 'Failure') { - Get.snackbar("User does not exist.".tr, '', - backgroundColor: Colors.red); + mySnackeBarError("User does not exist.".tr); return; } @@ -445,7 +444,7 @@ class LoginController extends GetxController { Get.offAll(() => const MapPagePassenger()); } catch (e) { addError('$e', 'loginUsingCredentials'); - Get.snackbar('Error', e.toString(), backgroundColor: Colors.redAccent); + mySnackeBarError(e.toString()); } finally { isloading = false; update(); diff --git a/siro_rider/lib/controller/auth/register_controller.dart b/siro_rider/lib/controller/auth/register_controller.dart index e6c8f77..db4ccf3 100644 --- a/siro_rider/lib/controller/auth/register_controller.dart +++ b/siro_rider/lib/controller/auth/register_controller.dart @@ -14,6 +14,7 @@ import 'package:siro_rider/constant/style.dart'; import 'package:siro_rider/controller/functions/crud.dart'; import 'package:siro_rider/views/auth/login_page.dart'; import 'package:siro_rider/views/widgets/elevated_btn.dart'; +import 'package:siro_rider/views/widgets/error_snakbar.dart'; import '../../constant/box_name.dart'; import '../../main.dart'; @@ -145,13 +146,10 @@ class RegisterController extends GetxController { }, ); - if (responseChecker != 'failure') { - var data = jsonDecode(responseChecker); - + if (responseChecker is Map) { // 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); + if (responseChecker['message'][0]['verified'].toString() == '1') { + mySnackbarSuccess('Phone number is verified before'.tr); box.write(BoxName.isVerified, '1'); box.write(BoxName.phone, (phoneNumber)); Get.offAll(const MapPagePassenger()); @@ -310,22 +308,17 @@ class RegisterController extends GetxController { box.read(BoxName.email).toString(), ); } else { - Get.snackbar('Error'.tr, - "The email or phone number is already registered.".tr, - backgroundColor: Colors.redAccent); + mySnackeBarError("The email or phone number is already registered.".tr); } } else { - Get.snackbar('Error'.tr, "phone not verified".tr, - backgroundColor: Colors.redAccent); + mySnackeBarError("phone not verified".tr); } } else { - Get.snackbar('Error'.tr, "you must insert token code".tr, - backgroundColor: AppColor.redColor); + mySnackeBarError("you must insert token code".tr); } } catch (e) { addError(e.toString(), 'passenger sign up '); - Get.snackbar('Error'.tr, "Something went wrong. Please try again.".tr, - backgroundColor: Colors.redAccent); + mySnackeBarError("Something went wrong. Please try again.".tr); } } @@ -334,8 +327,7 @@ class RegisterController extends GetxController { 'email': emailController.text, 'token': verifyCode.text, }); - var dec = jsonDecode(res); - if (dec['status'] == 'success') { + if (res['status'] == 'success') { Get.offAll(() => LoginPage()); } } @@ -352,7 +344,7 @@ class RegisterController extends GetxController { 'site': siteController.text, 'birthdate': birthDate, }); - if (jsonDecode(res)['status'] == 'success') { + if (res['status'] == 'success') { int randomNumber = Random().nextInt(100000) + 1; await CRUD().post(link: AppLink.sendVerifyEmail, payload: { 'email': emailController.text, diff --git a/siro_rider/lib/controller/auth/token_otp_change_controller.dart b/siro_rider/lib/controller/auth/token_otp_change_controller.dart index 50b5596..e69cb8e 100644 --- a/siro_rider/lib/controller/auth/token_otp_change_controller.dart +++ b/siro_rider/lib/controller/auth/token_otp_change_controller.dart @@ -5,6 +5,7 @@ import 'package:siro_rider/constant/links.dart'; import 'package:siro_rider/controller/functions/crud.dart'; import 'package:siro_rider/main.dart'; import 'package:get/get.dart'; +import 'package:siro_rider/views/widgets/error_snakbar.dart'; import '../../print.dart'; import '../../views/home/map_page_passenger.dart'; @@ -68,10 +69,10 @@ class OtpVerificationController extends GetxController { isLoading.value = true; // بإمكانك عرض رسالة نجاح هنا } else { - Get.snackbar('Error'.tr, 'Failed to send OTP'.tr); + mySnackeBarError('Failed to send OTP'.tr); } } catch (e) { - Get.snackbar('Error', e.toString()); + mySnackeBarError(e.toString()); } finally { // isLoading.value = false; } @@ -107,10 +108,10 @@ class OtpVerificationController extends GetxController { Get.offAll(() => const MapPagePassenger()); } else { - Get.snackbar('Verification Failed', 'OTP is incorrect or expired'); + mySnackeBarError('OTP is incorrect or expired'); } } catch (e) { - Get.snackbar('Error', e.toString()); + mySnackeBarError(e.toString()); } finally { isVerifying.value = false; } diff --git a/siro_rider/lib/controller/auth/tokens_controller.dart b/siro_rider/lib/controller/auth/tokens_controller.dart index 930359b..67afdba 100644 --- a/siro_rider/lib/controller/auth/tokens_controller.dart +++ b/siro_rider/lib/controller/auth/tokens_controller.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; +import 'package:siro_rider/views/widgets/error_snakbar.dart'; import '../../constant/box_name.dart'; import '../../constant/links.dart'; @@ -32,7 +33,7 @@ class TokenController extends GetxController { update(); var jsonToken = jsonDecode(res.body); if (jsonToken['status'] == 'The token has been updated successfully.') { - Get.snackbar('token updated'.tr, ''); + mySnackbarInfo('token updated'.tr); } } } diff --git a/siro_rider/lib/controller/firebase/firbase_messge.dart b/siro_rider/lib/controller/firebase/firbase_messge.dart index 16e8b1d..416c396 100644 --- a/siro_rider/lib/controller/firebase/firbase_messge.dart +++ b/siro_rider/lib/controller/firebase/firbase_messge.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:siro_rider/controller/functions/toast.dart'; import 'package:siro_rider/views/widgets/elevated_btn.dart'; +import 'package:siro_rider/views/widgets/error_snakbar.dart'; import '../../constant/box_name.dart'; import '../../constant/colors.dart'; @@ -574,26 +575,9 @@ class FirebaseMessagesController extends GetxController { // } // } - SnackbarController driverAppliedTripSnakBar() { - return Get.snackbar( - 'Driver Applied the Ride for You'.tr, - '', - colorText: AppColor.greenColor, - duration: const Duration(seconds: 3), - snackPosition: SnackPosition.TOP, - titleText: Text( - 'Applied'.tr, - style: TextStyle(color: AppColor.redColor), - ), - messageText: Text( - 'Driver Applied the Ride for You'.tr, - style: AppStyle.title, - ), - icon: Icon(Icons.approval, color: AppColor.primaryColor), - shouldIconPulse: true, - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(16), - ); + SnackbarController? driverAppliedTripSnakBar() { + mySnackbarInfo('Driver Applied the Ride for You'.tr); + return null; } Future passengerDialog(String message) { diff --git a/siro_rider/lib/controller/functions/crud.dart b/siro_rider/lib/controller/functions/crud.dart index 8d99671..95e1c63 100644 --- a/siro_rider/lib/controller/functions/crud.dart +++ b/siro_rider/lib/controller/functions/crud.dart @@ -304,7 +304,8 @@ class CRUD { Future sendWhatsAppAuth(String to, String token) async { var res = await CRUD() .get(link: AppLink.getApiKey, payload: {'keyName': 'whatsapp_key'}); - var accesstoken = jsonDecode(res)['message']['whatsapp_key']; + if (res is! Map) return; + var accesstoken = res['message']['whatsapp_key']; var headers = { 'Authorization': 'Bearer $accesstoken', 'Content-Type': 'application/json' diff --git a/siro_rider/lib/controller/functions/log_out.dart b/siro_rider/lib/controller/functions/log_out.dart index 47f2706..a843e0a 100644 --- a/siro_rider/lib/controller/functions/log_out.dart +++ b/siro_rider/lib/controller/functions/log_out.dart @@ -10,6 +10,7 @@ import 'package:siro_rider/main.dart'; import 'package:siro_rider/onbording_page.dart'; import 'package:siro_rider/views/widgets/elevated_btn.dart'; import 'package:siro_rider/views/widgets/my_textField.dart'; +import 'package:siro_rider/views/widgets/error_snakbar.dart'; import '../../constant/style.dart'; import 'package:siro_rider/controller/home/map/map_socket_controller.dart'; @@ -30,8 +31,7 @@ class LogOutController extends GetxController { Future deleteMyAccountDriver(String id) async { await CRUD().post(link: AppLink.removeUser, payload: {'id': id}).then( - (value) => Get.snackbar('Deleted'.tr, 'Your Account is Deleted', - backgroundColor: AppColor.redColor)); + (value) => mySnackbarSuccess('Your Account is Deleted'.tr)); } checkBeforeDelete() async { @@ -198,9 +198,7 @@ class LogOutController extends GetxController { 'email': box.read(BoxName.email), }); } else { - Get.snackbar('Email Wrong'.tr, 'Email you inserted is Wrong.'.tr, - snackPosition: SnackPosition.BOTTOM, - backgroundColor: AppColor.redColor); + mySnackeBarError('Email you inserted is Wrong.'.tr); } } } diff --git a/siro_rider/lib/controller/functions/secure_storage.dart b/siro_rider/lib/controller/functions/secure_storage.dart index cb81fd1..116ea8c 100644 --- a/siro_rider/lib/controller/functions/secure_storage.dart +++ b/siro_rider/lib/controller/functions/secure_storage.dart @@ -58,7 +58,7 @@ class AppInitializer { var res = await CRUD().get(link: AppLink.getapiKey, payload: {"keyName": key1}); if (res != 'failure') { - var d = jsonDecode(res)['message']; + var d = res['message']; storage.write(key: key1, value: d[key1].toString()); } else {} } @@ -69,7 +69,7 @@ class AppInitializer { var res = await CRUD().get(link: AppLink.getLocationAreaLinks, payload: {}); if (res != 'failure') { - links = List>.from(jsonDecode(res)['message']); + links = List>.from(res['message']); await box.remove(BoxName.locationName); await box.remove(BoxName.basicLink); await box.remove(links[4]['name']); diff --git a/siro_rider/lib/controller/functions/sms_controller.dart b/siro_rider/lib/controller/functions/sms_controller.dart index 313f4be..3dac5e2 100644 --- a/siro_rider/lib/controller/functions/sms_controller.dart +++ b/siro_rider/lib/controller/functions/sms_controller.dart @@ -20,7 +20,7 @@ class SmsEgyptController extends GetxController { Future getSender() async { var res = await CRUD().get(link: AppLink.getSender, payload: {}); if (res != 'failure') { - var d = jsonDecode(res)['message'][0]['senderId'].toString(); + var d = res['message'][0]['senderId'].toString(); return d; } else { return "Sefer Egy"; diff --git a/siro_rider/lib/controller/functions/upload_image.dart b/siro_rider/lib/controller/functions/upload_image.dart index 9eeef1f..e251b74 100644 --- a/siro_rider/lib/controller/functions/upload_image.dart +++ b/siro_rider/lib/controller/functions/upload_image.dart @@ -8,6 +8,7 @@ import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart'; import 'package:secure_string_operations/secure_string_operations.dart'; +import 'package:siro_rider/views/widgets/error_snakbar.dart'; import '../../constant/box_name.dart'; import '../../constant/char_map.dart'; @@ -54,8 +55,7 @@ class ImageController extends GetxController { link, ); } catch (e) { - Get.snackbar('Image Upload Failed'.tr, e.toString(), - backgroundColor: AppColor.redColor); + mySnackeBarError(e.toString()); } finally { isloading = false; update(); diff --git a/siro_rider/lib/controller/home/blinking_promo_controller.dart.dart b/siro_rider/lib/controller/home/blinking_promo_controller.dart.dart index df0bc82..e706ed0 100644 --- a/siro_rider/lib/controller/home/blinking_promo_controller.dart.dart +++ b/siro_rider/lib/controller/home/blinking_promo_controller.dart.dart @@ -29,7 +29,7 @@ class BlinkingController extends GetxController { }, )); } - var decode = jsonDecode(value); + var decode = value; // if (decode["status"] == "success") { // var firstElement = decode["message"][0]; diff --git a/siro_rider/lib/controller/home/map/location_search_controller.dart b/siro_rider/lib/controller/home/map/location_search_controller.dart index 57f9116..220e65d 100644 --- a/siro_rider/lib/controller/home/map/location_search_controller.dart +++ b/siro_rider/lib/controller/home/map/location_search_controller.dart @@ -303,21 +303,7 @@ class LocationSearchController extends GetxController { rideLifecycle.resetNoRideSearch(); } - final bool isLoggedIn = box.read(BoxName.isVerified) == '1' && - box.read(BoxName.passengerID) != null; - - if (isLoggedIn) { - try { - getReverseGeocoding(passengerLocation).then((address) { - currentLocationString = address; - update(); - }); - } catch (e) { - Log.print('Error resolving current location: $e'); - } - } else { - Log.print('LocationSearchController: Skipping reverse geocoding call, user not logged in.'); - } + Log.print('LocationSearchController: Reverse geocoding deferred — will run when map is idle.'); OfflineMapService.instance .downloadRegion(passengerLocation, radiusKm: 10.0); @@ -493,10 +479,22 @@ class LocationSearchController extends GetxController { } } + bool _pendingGeocode = true; + void updateCurrentLocationFromCamera(LatLng target) { Log.print('📍 updateCurrentLocationFromCamera: $target'); newMyLocation = target; + if (_pendingGeocode) { + _pendingGeocode = false; + if (box.read(BoxName.isVerified) == '1') { + getReverseGeocoding(target).then((address) { + currentLocationString = address; + update(); + }); + } + } + if (startLocationFromMap == true) { Log.print('📍 Updating startLocationFromMap to $target'); newStartPointLocation = target; @@ -865,12 +863,7 @@ class LocationSearchController extends GetxController { mapEngine.heightBottomSheetShown = 250; update(); - Get.snackbar( - 'Location Received'.tr, - 'Route and prices have been calculated successfully!'.tr, - backgroundColor: AppColor.greenColor, - colorText: Colors.white, - ); + mySnackbarInfo('Route and prices have been calculated successfully!'.tr); } } else { Log.print('⚠️ Could not extract valid coordinates from link: $link'); diff --git a/siro_rider/lib/controller/home/map/nearby_drivers_controller.dart b/siro_rider/lib/controller/home/map/nearby_drivers_controller.dart index f989a30..6e948e1 100644 --- a/siro_rider/lib/controller/home/map/nearby_drivers_controller.dart +++ b/siro_rider/lib/controller/home/map/nearby_drivers_controller.dart @@ -49,7 +49,7 @@ class NearbyDriversController extends GetxController { }, ); - if (res == 'failure') { + if (res == 'failure' || res is! Map) { noCarString = true; dataCarsLocationByPassenger = 'failure'; update(); @@ -57,7 +57,7 @@ class NearbyDriversController extends GetxController { } noCarString = false; - var responseData = jsonDecode(res); + var responseData = res; dataCarsLocationByPassenger = responseData; List driversList = []; diff --git a/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart b/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart index 653d389..b22106b 100644 --- a/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart +++ b/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart @@ -1253,15 +1253,14 @@ class RideLifecycleController extends GetxController { 'data': {'status': 'NoRide', 'needsReview': false} }; isStartAppHasRide = false; - } else { - var decoded = jsonDecode(res); - if (decoded['status'] == 'failure') { + } else if (res is Map) { + if (res['status'] == 'failure') { rideStatusFromStartApp = { 'data': {'status': 'NoRide', 'needsReview': false} }; isStartAppHasRide = false; } else { - rideStatusFromStartApp = decoded; + rideStatusFromStartApp = res; } } @@ -1654,13 +1653,12 @@ class RideLifecycleController extends GetxController { payload: {'passengerID': box.read(BoxName.passengerID).toString()}); if (res != 'failure') { - var response = jsonDecode(res); - Log.print('getUpdatedRideForDriverApply Response: $response'); + Log.print('getUpdatedRideForDriverApply Response: $res'); - if (response['status'] == 'success' && - response['data'] != null && - response['data'] is Map) { - var data = response['data']; + if (res['status'] == 'success' && + res['data'] != null && + res['data'] is Map) { + var data = res['data']; driverId = data['driver_id']?.toString() ?? ''; driverPhone = data['phone']?.toString() ?? ''; @@ -1814,10 +1812,9 @@ class RideLifecycleController extends GetxController { 'country': box.read(BoxName.countryCode) ?? '', }); - if (res != 'failure') { - var response = jsonDecode(res); - if (response['status'] == 'success') { - var data = response['data']; + if (res is Map) { + if (res['status'] == 'success') { + var data = res['data']; totalPassengerSpeed = data['totalPassengerSpeed']?.toString() ?? '0'; totalPassengerBalash = data['totalPassengerBalash']?.toString() ?? '0'; @@ -1847,7 +1844,7 @@ class RideLifecycleController extends GetxController { } else { MyDialog().getDialog( 'Promo Error'.tr, - response['message']?.toString() ?? 'Invalid Promo'.tr, + res['message']?.toString() ?? 'Invalid Promo'.tr, () => Get.back()); return; } @@ -1855,8 +1852,7 @@ class RideLifecycleController extends GetxController { Get.back(); await Future.delayed(const Duration(milliseconds: 120)); } catch (e) { - Get.snackbar('Error'.tr, e.toString(), - backgroundColor: AppColor.redColor); + mySnackeBarError(e.toString()); } } @@ -1891,10 +1887,9 @@ class RideLifecycleController extends GetxController { 'country': box.read(BoxName.countryCode) ?? '', }); - if (res != 'failure') { - var response = jsonDecode(res); - if (response['status'] == 'success') { - var data = response['data']; + if (res is Map) { + if (res['status'] == 'success') { + var data = res['data']; totalPassengerSpeed = data['totalPassengerSpeed']?.toString() ?? '0'; totalPassengerBalash = data['totalPassengerBalash']?.toString() ?? '0'; @@ -1914,7 +1909,7 @@ class RideLifecycleController extends GetxController { data['totalPassengerRayehGaiBalash']?.toString() ?? '0'; // Save price_token from server response - priceToken = response['price_token']?.toString() ?? ''; + priceToken = res['price_token']?.toString() ?? ''; totalPassenger = totalPassengerSpeed; totalCostPassenger = totalPassenger; @@ -2194,12 +2189,10 @@ class RideLifecycleController extends GetxController { link: AppLink.getDriverCarsLocationToPassengerAfterApplied, payload: {'driver_id': driverId}); - if (res != 'failure') { - var datadriverLocation = jsonDecode(res); - - if (datadriverLocation['message'] != null && - datadriverLocation['message'].isNotEmpty) { - var _data = datadriverLocation['message'][0]; + if (res != 'failure' && res is Map) { + if (res['message'] != null && + (res['message'] as List).isNotEmpty) { + var _data = (res['message'] as List)[0]; LatLng newDriverPos = LatLng( double.parse(_data['latitude'].toString()), @@ -2233,7 +2226,7 @@ class RideLifecycleController extends GetxController { } mapEngine.clearMarkersExceptStartEndAndDriver(); reloadMarkerDriverCarsLocationToPassengerAfterApplied( - datadriverLocation); + res); } } update(); @@ -2641,8 +2634,7 @@ class RideLifecycleController extends GetxController { throw Exception('Failed to save trip'); } } catch (e) { - Get.snackbar('Error'.tr, 'Failed to book trip: $e'.tr, - backgroundColor: AppColor.redColor); + mySnackeBarError('Failed to book trip: $e'.tr); } } @@ -3639,21 +3631,14 @@ class RideLifecycleController extends GetxController { Future cancelRide() async { if (selectedReasonIndex == -1) { - Get.snackbar( - 'Attention'.tr, - 'Please select a reason first'.tr, - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange, - colorText: Colors.white, - ); + mySnackbarWarning('Please select a reason first'.tr); return; } String finalReason = selectedReasonText; if (finalReason == "Other".tr) { if (otherReasonController.text.trim().isEmpty) { - Get.snackbar("Attention".tr, "Please write the reason...".tr, - backgroundColor: Colors.red, colorText: Colors.white); + mySnackbarWarning("Please write the reason...".tr); return; } finalReason = otherReasonController.text.trim(); @@ -3697,8 +3682,7 @@ class RideLifecycleController extends GetxController { var res = await CRUD().get(link: AppLink.getapiKey, payload: {"keyName": key}); if (res != 'failure') { - var d = jsonDecode(res)['message']; - return d[key].toString(); + return res['message'][key].toString(); } return null; } @@ -3709,7 +3693,7 @@ class RideLifecycleController extends GetxController { payload: {'id': rideId}); Log.print(response); Log.print('2176'); - return jsonDecode(response)['data']; + return response['data']; } void handleActiveRideOnStartup(dynamic data) { @@ -4365,11 +4349,10 @@ class RideLifecycleController extends GetxController { var resPromo = await CRUD().get(link: AppLink.getPromoFirst, payload: { "passengerID": box.read(BoxName.passengerID).toString(), }); - if (resPromo != 'failure') { - var d1 = jsonDecode(resPromo); - promo = d1['message']['promo_code']; - discount = d1['message']['amount']; - validity = d1['message']['validity_end_date']; + if (resPromo is Map) { + promo = resPromo['message']['promo_code']; + discount = resPromo['message']['amount']; + validity = resPromo['message']['validity_end_date']; } box.write(BoxName.isFirstTime, '1'); Get.dialog( diff --git a/siro_rider/lib/controller/home/map/ui_interactions_controller.dart b/siro_rider/lib/controller/home/map/ui_interactions_controller.dart index 7dbfb4b..cd25c44 100644 --- a/siro_rider/lib/controller/home/map/ui_interactions_controller.dart +++ b/siro_rider/lib/controller/home/map/ui_interactions_controller.dart @@ -15,6 +15,7 @@ import '../../../print.dart'; import '../../../services/emergency_signal_service.dart'; import '../../../views/widgets/elevated_btn.dart'; import '../../../views/widgets/my_textField.dart'; +import '../../../views/widgets/error_snakbar.dart'; import '../../functions/launch.dart'; import '../../firebase/notification_service.dart'; import '../../functions/crud.dart'; @@ -255,7 +256,7 @@ class UiInteractionsController extends GetxController { String storedPhone = box.read(BoxName.sosPhonePassenger)!; if (rideLifecycle.rideId == 'yet' || rideLifecycle.driverId.isEmpty) { - Get.snackbar("Alert".tr, "Wait for the trip to start first".tr); + mySnackbarWarning("Wait for the trip to start first".tr); return; } @@ -374,8 +375,7 @@ Siro Team'''; } else if (res['status'] == 'success') { if (Get.isDialogOpen ?? false) Get.back(); - Get.snackbar("Success".tr, "The invitation was sent successfully".tr, - backgroundColor: AppColor.greenColor, colorText: Colors.white); + mySnackbarSuccess("The invitation was sent successfully".tr); List tokensData = res['data']; for (var device in tokensData) { diff --git a/siro_rider/lib/controller/home/payment/captain_wallet_controller.dart b/siro_rider/lib/controller/home/payment/captain_wallet_controller.dart index e477591..a483bc7 100644 --- a/siro_rider/lib/controller/home/payment/captain_wallet_controller.dart +++ b/siro_rider/lib/controller/home/payment/captain_wallet_controller.dart @@ -88,7 +88,7 @@ class CaptainWalletController extends GetxController { 'amount': amount.toString(), 'payment_method': paymentMethod.toString(), }); - var d = jsonDecode(res); + var d = res; paymentID = d['message'].toString(); } diff --git a/siro_rider/lib/controller/home/profile/complaint_controller.dart b/siro_rider/lib/controller/home/profile/complaint_controller.dart index e972267..7da1b45 100644 --- a/siro_rider/lib/controller/home/profile/complaint_controller.dart +++ b/siro_rider/lib/controller/home/profile/complaint_controller.dart @@ -9,6 +9,7 @@ import 'package:siro_rider/constant/colors.dart'; import 'package:siro_rider/constant/links.dart'; import 'package:siro_rider/controller/functions/crud.dart'; import 'package:siro_rider/main.dart'; +import 'package:siro_rider/views/widgets/error_snakbar.dart'; import 'package:http_parser/http_parser.dart'; import 'package:mime/mime.dart'; @@ -39,26 +40,11 @@ class ComplaintController extends GetxController { // --- دالة مخصصة لعرض إشعارات Snackbar بشكل جميل --- void _showCustomSnackbar(String title, String message, {bool isError = false}) { - Get.snackbar( - '', // العنوان سيتم التعامل معه عبر titleText - '', // الرسالة سيتم التعامل معها عبر messageText - titleText: Text(title.tr, - style: const TextStyle( - color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16)), - messageText: Text(message.tr, - style: const TextStyle(color: Colors.white, fontSize: 14)), - backgroundColor: isError - ? AppColor.redColor.withOpacity(0.95) - : const Color.fromARGB(255, 6, 148, 79).withOpacity(0.95), - icon: Icon(isError ? Icons.error_outline : Icons.check_circle_outline, - color: Colors.white, size: 28), - borderRadius: 12, - margin: const EdgeInsets.all(15), - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18), - snackPosition: SnackPosition.BOTTOM, - duration: const Duration(seconds: 4), - colorText: Colors.white, - ); + if (isError) { + mySnackeBarError(message.tr); + } else { + mySnackbarSuccess(message.tr); + } } // --- هذه الدالة تبقى كما هي لجلب بيانات الرحلة --- @@ -69,7 +55,7 @@ class ComplaintController extends GetxController { 'passengerId': box.read(BoxName.passengerID).toString(), }); if (res != 'failure') { - var d = jsonDecode(res)['message']; + var d = res['message']; feedBack = d; } isLoading = false; diff --git a/siro_rider/lib/controller/home/profile/invit_controller.dart b/siro_rider/lib/controller/home/profile/invit_controller.dart index 26674f8..b8b267e 100644 --- a/siro_rider/lib/controller/home/profile/invit_controller.dart +++ b/siro_rider/lib/controller/home/profile/invit_controller.dart @@ -1,8 +1,7 @@ -import 'dart:convert'; - import 'package:siro_rider/constant/box_name.dart'; import 'package:siro_rider/constant/colors.dart'; import 'package:siro_rider/constant/links.dart'; +import 'package:siro_rider/controller/functions/country_logic.dart'; import 'package:siro_rider/controller/functions/crud.dart'; import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; @@ -77,9 +76,8 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider var response = await CRUD().get(link: AppLink.getInviteDriver, payload: { "driverId": box.read(BoxName.driverID), }); - if (response != 'failure') { - var data = jsonDecode(response); - driverInvitationData = data['message']; + if (response != 'failure' && response is Map) { + driverInvitationData = response['message']; update(); } } catch (e) { @@ -93,9 +91,8 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider .get(link: AppLink.getDriverInvitationToPassengers, payload: { "driverId": box.read(BoxName.passengerID), }); - if (response != 'failure') { - var data = jsonDecode(response); - driverInvitationDataToPassengers = data['message']; + if (response != 'failure' && response is Map) { + driverInvitationDataToPassengers = response['message']; update(); } } catch (e) { @@ -167,17 +164,14 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider // snackPosition: SnackPosition.BOTTOM); } } else { - Get.snackbar('No contacts found'.tr, - 'No contacts with phone numbers were found on your device.'.tr); + mySnackbarWarning('No contacts with phone numbers were found on your device.'.tr); } } else { - Get.snackbar('Permission denied'.tr, - 'Contact permission is required to pick contacts'.tr); + mySnackbarWarning('Contact permission is required to pick contacts'.tr); } } catch (e) { Log.print('Error picking contacts: $e'); - Get.snackbar( - 'Error'.tr, 'An error occurred while picking contacts: $e'.tr); + mySnackeBarError('An error occurred while picking contacts: $e'.tr); } } @@ -187,28 +181,11 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider Get.back(); } - /// **IMPROVEMENT**: A new robust function to format phone numbers specifically for Syria (+963). - /// It handles various user inputs gracefully to produce a standardized international format. - String _formatSyrianPhoneNumber(String phone) { - // 1. Remove all non-digit characters to clean the input. - String digitsOnly = phone.replaceAll(RegExp(r'\D'), ''); - - // 2. If it already starts with the country code, we assume it's correct. - if (digitsOnly.startsWith('963')) { - return '$digitsOnly'; - } - - // 3. If it starts with '09' (common local format), remove the leading '0'. - if (digitsOnly.startsWith('09')) { - digitsOnly = digitsOnly.substring(1); - } - - // 4. Prepend the Syrian country code. - return '963$digitsOnly'; + /// Formats phone number based on the current country (Syria=963, Jordan=962, Egypt=20). + String _formatPhoneByCountry(String phone) { + return CountryLogic.formatCurrentCountryPhone(phone); } - /// **IMPROVEMENT**: This method now uses the new phone formatting logic and - /// sends a much-improved, user-friendly WhatsApp message. void sendInviteToPassenger() async { if (invitePhoneController.text.isEmpty || invitePhoneController.text.length < 9) { @@ -217,9 +194,8 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider } try { - // Use the new formatting function to ensure the number is correct. String formattedPhoneNumber = - _formatSyrianPhoneNumber(invitePhoneController.text); + _formatPhoneByCountry(invitePhoneController.text); var response = await CRUD().post(link: AppLink.addInvitationPassenger, payload: { @@ -229,13 +205,9 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider if (response != 'failure' && response is Map) { var d = response; - Get.snackbar('Success'.tr, 'Invite sent successfully'.tr, - backgroundColor: Colors.green, colorText: Colors.white); + mySnackbarSuccess('Invite sent successfully'.tr); - // التحقق الديناميكي من مكان البيانات (V1 vs V3) var payload = d['data'] ?? d['message']; - - // إذا كان الـ message نصاً وليس خريطة (Map)، نأخذ البيانات من المستوى الأعلى if (payload is String) { payload = d; } @@ -243,7 +215,6 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider String expirationTime = (payload['expirationTime'] ?? '').toString(); String inviteCode = (payload['inviteCode'] ?? '').toString(); - // New and improved WhatsApp message for better user engagement. String message = "👋 ${'Hello! I\'m inviting you to try Siro.'.tr}\n\n" "🎁 ${'Use my invitation code to get a special gift on your first ride!'.tr}\n\n" @@ -259,16 +230,11 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider invitePhoneController.clear(); update(); } else { - Get.snackbar( - 'Error'.tr, "This phone number has already been invited.".tr, - backgroundColor: AppColor.redColor, - duration: const Duration(seconds: 4)); + mySnackbarWarning("This phone number has already been invited.".tr); } } catch (e) { Log.print("Error sending invite: $e"); - Get.snackbar( - 'Error'.tr, 'An unexpected error occurred. Please try again.'.tr, - backgroundColor: AppColor.redColor); + mySnackeBarError('An unexpected error occurred. Please try again.'.tr); } } @@ -314,9 +280,8 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider }, ); - if (response != 'failure') { - var data = jsonDecode(response); - if (data['status'] == 'success') { + if (response != 'failure' && response is Map) { + if (response['status'] == 'success') { NotificationCaptainController().addNotificationCaptain( invitation['passengerInviterId'].toString(), "You have got a gift for invitation".tr, @@ -325,7 +290,7 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider ); fetchDriverStatsPassengers(); // Refresh list } else { - Get.snackbar('Error'.tr, data['message'] ?? 'Claim failed'.tr, backgroundColor: AppColor.redColor); + mySnackeBarError(response['message'] ?? 'Claim failed'.tr); } } }, diff --git a/siro_rider/lib/controller/home/profile/invites_rewards_controller.dart b/siro_rider/lib/controller/home/profile/invites_rewards_controller.dart index 667c079..9e80cd1 100644 --- a/siro_rider/lib/controller/home/profile/invites_rewards_controller.dart +++ b/siro_rider/lib/controller/home/profile/invites_rewards_controller.dart @@ -4,6 +4,7 @@ import 'package:siro_rider/constant/box_name.dart'; import 'package:siro_rider/constant/links.dart'; import 'package:siro_rider/controller/functions/crud.dart'; import 'package:siro_rider/main.dart'; +import 'package:siro_rider/views/widgets/error_snakbar.dart'; class InvitesRewardsController extends GetxController { bool isLoading = false; @@ -70,20 +71,20 @@ class InvitesRewardsController extends GetxController { } ); - Get.back(); // close loading + Navigator.maybeOf(Get.context!)?.pop(); // close loading dialog only if (response != 'failure') { if (response['status'] == 'success') { - Get.snackbar("Success".tr, "You have been successfully referred!".tr, backgroundColor: Colors.green, colorText: Colors.white); + mySnackbarSuccess("You have been successfully referred!".tr); } else { - Get.snackbar("Notice".tr, response['message'] ?? "Could not add invite".tr); + mySnackbarWarning(response['message'] ?? "Could not add invite".tr); } } else { - Get.snackbar("Error".tr, "Network error occurred".tr); + mySnackeBarError("Network error occurred".tr); } } catch (e) { - Get.back(); // close loading - Get.snackbar("Error".tr, "Network error occurred".tr); + Navigator.maybeOf(Get.context!)?.pop(); // close loading dialog only + mySnackeBarError("Network error occurred".tr); } } } diff --git a/siro_rider/lib/controller/home/trip_monitor_controller.dart b/siro_rider/lib/controller/home/trip_monitor_controller.dart index ebc582a..b1f3e8a 100644 --- a/siro_rider/lib/controller/home/trip_monitor_controller.dart +++ b/siro_rider/lib/controller/home/trip_monitor_controller.dart @@ -30,7 +30,7 @@ class TripMonitorController extends GetxController { var res = await CRUD().get( link: AppLink.getLocationParents, payload: {"driver_id": driverId}); if (res != 'failure') { - tripData = jsonDecode(res); + tripData = res; parentLocation = LatLng( double.parse(tripData['message'][0]['latitude'].toString()), double.parse(tripData['message'][0]['longitude'].toString())); diff --git a/siro_rider/lib/controller/home/vip_waitting_page.dart b/siro_rider/lib/controller/home/vip_waitting_page.dart index 0b437e1..22d34e5 100644 --- a/siro_rider/lib/controller/home/vip_waitting_page.dart +++ b/siro_rider/lib/controller/home/vip_waitting_page.dart @@ -53,9 +53,8 @@ class VipOrderController extends GetxController { ); if (res != 'failure') { - var decodedResponse = jsonDecode(res); - if (decodedResponse['message'] is List) { - tripData.value = decodedResponse['message']; + if (res['message'] is List) { + tripData.value = res['message']; } else { tripData.clear(); // Ensure empty list if no data // mySnackeBarError('No trip data found'); diff --git a/siro_rider/lib/controller/notification/notification_captain_controller.dart b/siro_rider/lib/controller/notification/notification_captain_controller.dart index 50958e2..51f8591 100644 --- a/siro_rider/lib/controller/notification/notification_captain_controller.dart +++ b/siro_rider/lib/controller/notification/notification_captain_controller.dart @@ -31,7 +31,7 @@ class NotificationCaptainController extends GetxController { Get.back(); })); } - notificationData = jsonDecode(res); + notificationData = res; // sql.insertData(notificationData['message'], TableName.captainNotification); isLoading = false; diff --git a/siro_rider/lib/controller/notification/passenger_notification_controller.dart b/siro_rider/lib/controller/notification/passenger_notification_controller.dart index 51f66a7..0cd8b03 100644 --- a/siro_rider/lib/controller/notification/passenger_notification_controller.dart +++ b/siro_rider/lib/controller/notification/passenger_notification_controller.dart @@ -26,11 +26,9 @@ class PassengerNotificationController extends GetxController { Get.back(); }); } else { - final decoded = jsonDecode(res); - // التحقق من وجود البيانات في 'data' أو 'message' - var rawData = decoded['data'] ?? decoded['message']; + var rawData = res['data'] ?? res['message']; - if (decoded['status'] == 'error' || decoded['status'] == 'failure' || rawData == "No notification data found") { + if (res['status'] == 'error' || res['status'] == 'failure' || rawData == "No notification data found") { notificationData = {'status': 'success', 'message': []}; // قائمة فارغة لمنع الخطأ في UI } else { // التأكد أننا نخزن قائمة diff --git a/siro_rider/lib/controller/notification/ride_available_controller.dart b/siro_rider/lib/controller/notification/ride_available_controller.dart index 7acb90f..df19a46 100644 --- a/siro_rider/lib/controller/notification/ride_available_controller.dart +++ b/siro_rider/lib/controller/notification/ride_available_controller.dart @@ -14,7 +14,7 @@ class RideAvailableController extends GetxController { isLoading = true; var res = await CRUD().get(link: AppLink.getRideWaiting, payload: {}); if (res != 'failure') { - rideAvailableMap = jsonDecode(res); + rideAvailableMap = res; isLoading = false; update(); } else { diff --git a/siro_rider/lib/controller/payment/driver_payment_controller.dart b/siro_rider/lib/controller/payment/driver_payment_controller.dart index f9b284c..ade817a 100644 --- a/siro_rider/lib/controller/payment/driver_payment_controller.dart +++ b/siro_rider/lib/controller/payment/driver_payment_controller.dart @@ -30,7 +30,7 @@ class DriverWalletHistoryController extends GetxController { }, )); } - archive = jsonDecode(res)['message']; + archive = res['message']; isLoading = false; update(); } diff --git a/siro_rider/lib/controller/payment/payment_controller.dart b/siro_rider/lib/controller/payment/payment_controller.dart index 92205e5..c3c0d9b 100644 --- a/siro_rider/lib/controller/payment/payment_controller.dart +++ b/siro_rider/lib/controller/payment/payment_controller.dart @@ -16,6 +16,7 @@ import '../functions/crud.dart'; import 'paymob/e_cash_screen.dart'; import '../../views/home/my_wallet/payment_screen_mtn.dart'; import '../../views/home/my_wallet/payment_screen_cliq.dart'; +import '../../views/widgets/error_snakbar.dart'; class PaymentController extends GetxController { bool isLoading = false; @@ -28,14 +29,14 @@ class PaymentController extends GetxController { final walletphoneController = TextEditingController(); double totalPassenger = double.parse( Get.find().totalPassenger.toString()); - int? selectedAmount = 0; + double? selectedAmount = 0; List totalPassengerWalletDetails = []; String passengerTotalWalletAmount = ''; String ip = '1'; DateTime now = DateTime.now(); late int timestamp; - void updateSelectedAmount(int value) { + void updateSelectedAmount(double value) { selectedAmount = value; update(); } @@ -80,8 +81,7 @@ class PaymentController extends GetxController { 'passengerId': box.read(BoxName.passengerID).toString(), 'amount': amount.toString(), }); - var d = jsonDecode(res); - return d['message']; + return res['message']; } Future generateTokenDriver(String amount) async { @@ -89,8 +89,7 @@ class PaymentController extends GetxController { 'driverID': Get.find().driverId, 'amount': amount.toString(), }); - var d = jsonDecode(res); - return d['message']; + return res['message']; } Future payToDriverForCancelAfterAppliedAndHeNearYou( @@ -430,14 +429,14 @@ class PaymentController extends GetxController { Future payWithClickWallet(BuildContext context, String amount, String currency) async { try { - final phone = walletphoneController.text.trim(); + final phone = box.read(BoxName.phoneWallet) ?? walletphoneController.text.trim(); if (phone.isEmpty) { - Get.defaultDialog(title: 'Error'.tr, content: Text('Please enter phone number'.tr)); + mySnackeBarError('Please enter a phone number'.tr); return; } - + Get.dialog(const Center(child: CircularProgressIndicator()), barrierDismissible: false); - + var res = await CRUD().postWalletMtn( link: AppLink.createCliqInvoice, payload: { @@ -447,9 +446,9 @@ class PaymentController extends GetxController { "click_phone": phone, }, ); - - Get.back(); // close loading - + + if (Get.isDialogOpen ?? false) Get.back(); + late final Map resMap; if (res is Map) { resMap = res; @@ -466,14 +465,11 @@ class PaymentController extends GetxController { amount: double.parse(amount), )); } else { - Get.defaultDialog( - title: 'Error'.tr, - content: Text(resMap['message']?.toString() ?? 'Failed to create invoice'.tr), - ); + mySnackeBarError(resMap['message']?.toString() ?? 'Failed to create invoice'.tr); } } catch (e) { if (Get.isDialogOpen ?? false) Get.back(); - Get.defaultDialog(title: 'Error'.tr, content: Text(e.toString())); + mySnackeBarError(e.toString()); } } diff --git a/siro_rider/lib/controller/profile/captain_profile_controller.dart b/siro_rider/lib/controller/profile/captain_profile_controller.dart index 26c3680..972e14d 100644 --- a/siro_rider/lib/controller/profile/captain_profile_controller.dart +++ b/siro_rider/lib/controller/profile/captain_profile_controller.dart @@ -48,7 +48,7 @@ class CaptainProfileController extends GetxController { var res = await CRUD().post(link: AppLink.updateRegisrationCar, payload: payload); - if (jsonDecode(res)['status'] == 'success') { + if (res is Map && res['status'] == 'success') { box.write(BoxName.vin, vin.text); box.write(BoxName.color, color.text); box.write(BoxName.model, model.text); @@ -65,20 +65,19 @@ class CaptainProfileController extends GetxController { var res = await CRUD().get( link: AppLink.getCaptainProfile, payload: {'id': box.read(BoxName.driverID)}); - if (res != 'failure') { - var d = jsonDecode(res); - captainProfileData = d['message']; + if (res is Map) { + captainProfileData = res['message']; update(); - box.write(BoxName.sexDriver, d['message']['gender']); - box.write(BoxName.dobDriver, d['message']['birthdate']); - box.write(BoxName.vin, d['message']['vin']); - box.write(BoxName.color, d['message']['color']); - box.write(BoxName.model, d['message']['model']); - box.write(BoxName.carPlate, d['message']['car_plate']); - box.write(BoxName.make, d['message']['make']); - box.write(BoxName.year, d['message']['year']); - box.write(BoxName.expirationDate, d['message']['expiration_date']); - // box.write(BoxName.acc, d['message']['accountBank']); + box.write(BoxName.sexDriver, captainProfileData['gender']); + box.write(BoxName.dobDriver, captainProfileData['birthdate']); + box.write(BoxName.vin, captainProfileData['vin']); + box.write(BoxName.color, captainProfileData['color']); + box.write(BoxName.model, captainProfileData['model']); + box.write(BoxName.carPlate, captainProfileData['car_plate']); + box.write(BoxName.make, captainProfileData['make']); + box.write(BoxName.year, captainProfileData['year']); + box.write(BoxName.expirationDate, captainProfileData['expiration_date']); + // box.write(BoxName.acc, captainProfileData['accountBank']); update(); } diff --git a/siro_rider/lib/controller/voice_call_controller.dart b/siro_rider/lib/controller/voice_call_controller.dart index f8a1667..a1ceddd 100644 --- a/siro_rider/lib/controller/voice_call_controller.dart +++ b/siro_rider/lib/controller/voice_call_controller.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:get/get.dart' hide Response; +import 'package:siro_rider/views/widgets/error_snakbar.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:just_audio/just_audio.dart'; @@ -258,10 +259,7 @@ class VoiceCallController extends GetxController with WidgetsBindingObserver { final permissionStatus = await Permission.microphone.request(); if (!permissionStatus.isGranted) { _endCallInternal("permission_denied"); - Get.snackbar( - "Error", - "Microphone permission is required for voice calls".tr, - ); + mySnackeBarError("Microphone permission is required for voice calls".tr); return; } @@ -276,10 +274,7 @@ class VoiceCallController extends GetxController with WidgetsBindingObserver { response == 'failure' || response['status'] != 'success') { _endCallInternal("session_creation_failed"); - Get.snackbar( - "Error", - "Failed to initiate call session. Please try again.".tr, - ); + mySnackeBarError("Microphone permission is required for voice calls".tr); return; } @@ -358,10 +353,7 @@ class VoiceCallController extends GetxController with WidgetsBindingObserver { final permissionStatus = await Permission.microphone.request(); if (!permissionStatus.isGranted) { declineCall(); - Get.snackbar( - "Error", - "Microphone permission is required for voice calls".tr, - ); + mySnackeBarError("Microphone permission is required for voice calls".tr); return; } diff --git a/siro_rider/lib/models/click_payment_schema.dart b/siro_rider/lib/models/click_payment_schema.dart new file mode 100644 index 0000000..6dd368c --- /dev/null +++ b/siro_rider/lib/models/click_payment_schema.dart @@ -0,0 +1,124 @@ +/// Database schema for click_payment table. +/// +/// Stores Cliq wallet payment transactions for manual verification. +/// +/// SQL (MySQL): +/// ```sql +/// CREATE TABLE click_payment ( +/// id INT AUTO_INCREMENT PRIMARY KEY, +/// user_id VARCHAR(64) NOT NULL COMMENT 'Passenger ID from passengers table', +/// user_type VARCHAR(20) NOT NULL DEFAULT 'passenger', +/// amount DECIMAL(10,2) NOT NULL, +/// currency VARCHAR(6) NOT NULL DEFAULT 'JOD', +/// click_phone VARCHAR(20) NOT NULL COMMENT 'Phone number used for Cliq invoice', +/// invoice_number VARCHAR(64) NOT NULL UNIQUE COMMENT 'Unique invoice reference', +/// cliq_alias VARCHAR(64) NOT NULL COMMENT 'Cliq wallet alias for payment', +/// invoice_status ENUM('pending','completed','failed','expired') NOT NULL DEFAULT 'pending', +/// proof_text TEXT DEFAULT NULL COMMENT 'Bank SMS proof pasted by user', +/// proof_image VARCHAR(256) DEFAULT NULL COMMENT 'Screenshot path (optional)', +/// verified_by VARCHAR(64) DEFAULT NULL COMMENT 'Admin who verified manually', +/// verified_at DATETIME DEFAULT NULL, +/// created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, +/// updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +/// INDEX idx_user (user_id), +/// INDEX idx_status (invoice_status), +/// INDEX idx_invoice (invoice_number) +/// ); +/// ``` +class ClickPaymentSchema { + static const String tableName = 'click_payment'; + + static const String createTable = ''' +CREATE TABLE click_payment ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id VARCHAR(64) NOT NULL, + user_type VARCHAR(20) NOT NULL DEFAULT 'passenger', + amount DECIMAL(10,2) NOT NULL, + currency VARCHAR(6) NOT NULL DEFAULT 'JOD', + click_phone VARCHAR(20) NOT NULL, + invoice_number VARCHAR(64) NOT NULL UNIQUE, + cliq_alias VARCHAR(64) NOT NULL, + invoice_status ENUM('pending','completed','failed','expired') NOT NULL DEFAULT 'pending', + proof_text TEXT DEFAULT NULL, + proof_image VARCHAR(256) DEFAULT NULL, + verified_by VARCHAR(64) DEFAULT NULL, + verified_at DATETIME DEFAULT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_user (user_id), + INDEX idx_status (invoice_status), + INDEX idx_invoice (invoice_number) +) +'''; + + /// Map a JSON response from the API to a typed object. + final int? id; + final String userId; + final String userType; + final double amount; + final String currency; + final String clickPhone; + final String invoiceNumber; + final String cliqAlias; + final String invoiceStatus; + final String? proofText; + final String? proofImage; + final String? verifiedBy; + final DateTime? verifiedAt; + final DateTime? createdAt; + final DateTime? updatedAt; + + const ClickPaymentSchema({ + this.id, + required this.userId, + this.userType = 'passenger', + required this.amount, + this.currency = 'JOD', + required this.clickPhone, + required this.invoiceNumber, + required this.cliqAlias, + this.invoiceStatus = 'pending', + this.proofText, + this.proofImage, + this.verifiedBy, + this.verifiedAt, + this.createdAt, + this.updatedAt, + }); + + factory ClickPaymentSchema.fromJson(Map json) { + return ClickPaymentSchema( + id: json['id'] is int ? json['id'] : int.tryParse(json['id']?.toString() ?? ''), + userId: json['user_id']?.toString() ?? '', + userType: json['user_type']?.toString() ?? 'passenger', + amount: (json['amount'] is num) ? (json['amount'] as num).toDouble() : double.tryParse(json['amount']?.toString() ?? '0') ?? 0, + currency: json['currency']?.toString() ?? 'JOD', + clickPhone: json['click_phone']?.toString() ?? '', + invoiceNumber: json['invoice_number']?.toString() ?? '', + cliqAlias: json['cliq_alias']?.toString() ?? '', + invoiceStatus: json['invoice_status']?.toString() ?? 'pending', + proofText: json['proof_text']?.toString(), + proofImage: json['proof_image']?.toString(), + verifiedBy: json['verified_by']?.toString(), + verifiedAt: json['verified_at'] != null ? DateTime.tryParse(json['verified_at'].toString()) : null, + createdAt: json['created_at'] != null ? DateTime.tryParse(json['created_at'].toString()) : null, + updatedAt: json['updated_at'] != null ? DateTime.tryParse(json['updated_at'].toString()) : null, + ); + } + + Map toJson() => { + if (id != null) 'id': id, + 'user_id': userId, + 'user_type': userType, + 'amount': amount, + 'currency': currency, + 'click_phone': clickPhone, + 'invoice_number': invoiceNumber, + 'cliq_alias': cliqAlias, + 'invoice_status': invoiceStatus, + if (proofText != null) 'proof_text': proofText, + if (proofImage != null) 'proof_image': proofImage, + if (verifiedBy != null) 'verified_by': verifiedBy, + if (verifiedAt != null) 'verified_at': verifiedAt!.toIso8601String(), + }; +} diff --git a/siro_rider/lib/splash_screen_page.dart b/siro_rider/lib/splash_screen_page.dart index 3af8c28..7bb59ca 100644 --- a/siro_rider/lib/splash_screen_page.dart +++ b/siro_rider/lib/splash_screen_page.dart @@ -1,9 +1,10 @@ -import 'dart:math'; +import 'dart:math' as math; import 'package:animated_text_kit/animated_text_kit.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:siro_rider/constant/style.dart'; import 'package:siro_rider/constant/box_name.dart'; +import 'package:siro_rider/constant/colors.dart'; +import 'package:siro_rider/constant/style.dart'; import 'package:siro_rider/main.dart'; import 'controller/home/splash_screen_controlle.dart'; @@ -13,307 +14,247 @@ class SplashScreen extends StatelessWidget { @override Widget build(BuildContext context) { - final SplashScreenController controller = Get.put(SplashScreenController()); + final controller = Get.put(SplashScreenController()); final size = MediaQuery.of(context).size; - - // ألوان الـ colorize — سيان كهربائي → أبيض → ذهبي عنبري - const colorizeColors = [ - Color(0xFF1DA1F2), - Colors.white, - Color(0xFFFFB700), - Color(0xFF1DA1F2), - ]; + final isDark = Get.isDarkMode; + final bg = isDark ? const Color(0xFF0A0F1E) : AppColor.primaryColor; + final accent = AppColor.secondaryColorStatic; + final gold = AppColor.gold; return SafeArea( child: Scaffold( - backgroundColor: const Color(0xFF060B18), + backgroundColor: bg, body: Stack( children: [ - // ── طبقة الشبكة الهندسية ────────────────────────────────── + // ── Animated gradient overlay ── Positioned.fill( - child: CustomPaint(painter: _GridPainter()), - ), - - // ── توهج سماوي — أعلى اليمين ───────────────────────────── - Positioned( - top: -size.height * 0.18, - right: -size.width * 0.25, - child: Container( - width: size.width * 0.85, - height: size.width * 0.85, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: RadialGradient(colors: [ - const Color(0xFF1DA1F2).withOpacity(0.11), - Colors.transparent, - ]), + child: AnimatedBuilder( + animation: controller.glowAnimation, + builder: (_, __) => Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + bg, + Color.lerp(bg, accent.withOpacity(0.1), + controller.glowAnimation.value)!, + bg, + ], + ), + ), ), ), ), - // ── توهج أزرق غامق — أسفل اليسار ──────────────────────── + // ── Top-right glow ── Positioned( - bottom: -size.height * 0.12, - left: -size.width * 0.22, + top: -size.height * 0.14, + right: -size.width * 0.18, child: Container( width: size.width * 0.75, height: size.width * 0.75, decoration: BoxDecoration( shape: BoxShape.circle, - gradient: RadialGradient(colors: [ - const Color(0xFF0052FF).withOpacity(0.09), - Colors.transparent, - ]), + gradient: RadialGradient( + colors: [ + accent.withOpacity(0.08), + Colors.transparent, + ], + ), ), ), ), - // ── المحتوى الرئيسي ─────────────────────────────────────── + // ── Bottom-left glow ── + Positioned( + bottom: -size.height * 0.08, + left: -size.width * 0.2, + child: Container( + width: size.width * 0.65, + height: size.width * 0.65, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + gold.withOpacity(0.05), + Colors.transparent, + ], + ), + ), + ), + ), + + // ── Subtle grid ── + Positioned.fill( + child: CustomPaint( + painter: _GridPainter(color: accent.withOpacity(0.035)), + ), + ), + + // ── Main content ── Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // ── حلقات مدارية + اسم التطبيق ─────────────────── - FadeTransition( - opacity: controller.titleFadeAnimation, - child: ScaleTransition( - scale: controller.titleScaleAnimation, - child: SizedBox( - width: 220, - height: 220, + child: FadeTransition( + opacity: controller.titleFadeAnimation, + child: ScaleTransition( + scale: controller.titleScaleAnimation, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 240, + height: 240, child: Stack( alignment: Alignment.center, children: [ - // الحلقة الخارجية — تدور ببطء + // Outer ring AnimatedBuilder( animation: controller.orbitAnimation, builder: (_, __) => Transform.rotate( - angle: controller.orbitAnimation.value * 2 * pi, + angle: controller.orbitAnimation.value * + 2 * + math.pi, child: CustomPaint( painter: _OrbitalRingPainter( - radius: 100, - dotColor: const Color(0xFF1DA1F2), - lineOpacity: 0.22, - dotSize: 5.5, + radius: 108, + dotColor: accent, + lineOpacity: 0.2, + dotSize: 5, ), - size: const Size(220, 220), + size: const Size(240, 240), ), ), ), - // الحلقة الداخلية — تدور عكسياً + // Inner ring AnimatedBuilder( animation: controller.orbitAnimation, builder: (_, __) => Transform.rotate( angle: -controller.orbitAnimation.value * 2 * - pi * + math.pi * 0.65, child: CustomPaint( painter: _OrbitalRingPainter( - radius: 73, - dotColor: const Color(0xFFFFB700), + radius: 76, + dotColor: gold, lineOpacity: 0.14, dotSize: 4, - dashCount: 20, + dashCount: 16, ), - size: const Size(220, 220), + size: const Size(240, 240), ), ), ), - // النقطة المركزية المضيئة + // Center glow AnimatedBuilder( animation: controller.glowAnimation, builder: (_, __) => Container( - width: 8, - height: 8, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: const Color(0xFF1DA1F2), - boxShadow: [ - BoxShadow( - color: const Color(0xFF1DA1F2) - .withOpacity(0.25 + - controller.glowAnimation.value * - 0.35), - blurRadius: 20 + - controller.glowAnimation.value * 20, - spreadRadius: 4, - ), - ], - ), - ), - ), - // ── اسم "Siro" مع توهج متنفّس ───────── - AnimatedBuilder( - animation: controller.glowAnimation, - builder: (_, child) => Container( + width: 80, + height: 80, decoration: BoxDecoration( shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: const Color(0xFF1DA1F2) - .withOpacity(0.08 + - controller.glowAnimation.value * - 0.10), - blurRadius: 40 + + color: accent.withOpacity(0.1 + + controller.glowAnimation.value * + 0.18), + blurRadius: 30 + controller.glowAnimation.value * 25, spreadRadius: 0, ), ], ), - child: child, ), - child: AnimatedTextKit( - animatedTexts: [ - ColorizeAnimatedText( - 'Siro', - textStyle: const TextStyle( - fontSize: 38, - fontWeight: FontWeight.w800, - letterSpacing: 3, - height: 1, - ), - colors: colorizeColors, - speed: const Duration(milliseconds: 380), + ), + // Siro text + AnimatedTextKit( + animatedTexts: [ + ColorizeAnimatedText( + 'Siro', + textStyle: const TextStyle( + fontSize: 40, + fontWeight: FontWeight.w800, + letterSpacing: 5, + height: 1, ), - ], - isRepeatingAnimation: false, - ), + colors: [ + Colors.white, + accent, + gold.withOpacity(0.85), + Colors.white, + ], + speed: const Duration(milliseconds: 500), + ), + ], + isRepeatingAnimation: false, ), ], ), ), - ), - ), - - const SizedBox(height: 28), - - // ── شريحة "AI-Powered" + الشعار النصي ─────────────── - FadeTransition( - opacity: controller.taglineFadeAnimation, - child: SlideTransition( - position: controller.taglineSlideAnimation, - child: Column( - children: [ - // شريحة الذكاء الاصطناعي - Container( - padding: const EdgeInsets.symmetric( - horizontal: 14, vertical: 5), - decoration: BoxDecoration( - border: Border.all( - color: - const Color(0xFF1DA1F2).withOpacity(0.35), - width: 1, + const SizedBox(height: 24), + // Tagline + FadeTransition( + opacity: controller.taglineFadeAnimation, + child: SlideTransition( + position: controller.taglineSlideAnimation, + child: Column( + children: [ + _TaglineBadge( + controller: controller, accent: accent), + const SizedBox(height: 14), + Text( + 'Your Journey Begins Here'.tr, + style: TextStyle( + color: Colors.white.withOpacity(0.4), + fontSize: 13, + letterSpacing: 1, + fontWeight: FontWeight.w300, + ), ), - borderRadius: BorderRadius.circular(20), - color: const Color(0xFF1DA1F2).withOpacity(0.06), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - // نقطة نبضية - AnimatedBuilder( - animation: controller.glowAnimation, - builder: (_, __) => Container( - width: 6, - height: 6, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: const Color(0xFF1DA1F2) - .withOpacity(0.5 + - controller.glowAnimation.value * - 0.5), - boxShadow: [ - BoxShadow( - color: const Color(0xFF1DA1F2) - .withOpacity(controller - .glowAnimation.value * - 0.6), - blurRadius: 6, - spreadRadius: 1, - ), - ], - ), - ), - ), - const SizedBox(width: 8), - Text( - 'AI-Powered Mobility', - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w600, - color: const Color(0xFF1DA1F2) - .withOpacity(0.85), - letterSpacing: 1.4, - ), - ), - ], - ), + ], ), - const SizedBox(height: 16), - // الشعار النصي - Text( - 'Your Journey Begins Here'.tr, - style: AppStyle.title.copyWith( - color: const Color(0xFF7A8FA8), - fontSize: 14, - letterSpacing: 0.6, - fontWeight: FontWeight.w400, - ), - ), - ], - ), - ), - ), - ], - ), - ), - - // ── القسم السفلي: شريط التقدم + الإصدار ───────────────── - Align( - alignment: Alignment.bottomCenter, - child: FadeTransition( - opacity: controller.footerFadeAnimation, - child: Padding( - padding: - const EdgeInsets.only(bottom: 44, left: 36, right: 36), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // شريط تقدم رفيع مضيء - Obx(() => _GlowProgressBar( - value: controller.progress.value, - )), - const SizedBox(height: 18), - // معلومات الإصدار - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'SIRO', - style: TextStyle( - fontSize: 9.5, - fontWeight: FontWeight.w700, - color: Colors.white.withOpacity(0.18), - letterSpacing: 3.5, - ), - ), - Text( - 'v${box.read(BoxName.packagInfo) ?? '1.0.0'}', - style: TextStyle( - fontSize: 9.5, - fontWeight: FontWeight.w500, - color: Colors.white.withOpacity(0.18), - letterSpacing: 1, - ), - ), - ], + ), ), ], ), ), ), ), + + // ── Bottom ── + Positioned( + left: 32, + right: 32, + bottom: 48, + child: FadeTransition( + opacity: controller.footerFadeAnimation, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Obx(() => _GlowProgressBar( + value: controller.progress.value, accent: accent)), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('SIRO', + style: TextStyle( + fontSize: 9, + fontWeight: FontWeight.w700, + color: Colors.white.withOpacity(0.12), + letterSpacing: 4.5)), + Text('v${box.read(BoxName.packagInfo) ?? '1.0.0'}', + style: TextStyle( + fontSize: 9, + fontWeight: FontWeight.w500, + color: Colors.white.withOpacity(0.12), + letterSpacing: 1.5)), + ], + ), + ], + ), + ), + ), ], ), ), @@ -321,40 +262,84 @@ class SplashScreen extends StatelessWidget { } } -// ── شريط التقدم المضيء ───────────────────────────────────────────────────── +// ── Tagline badge ── +class _TaglineBadge extends StatelessWidget { + final SplashScreenController controller; + final Color accent; + const _TaglineBadge({required this.controller, required this.accent}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 5), + decoration: BoxDecoration( + border: Border.all(color: accent.withOpacity(0.25), width: 1), + borderRadius: BorderRadius.circular(20), + color: accent.withOpacity(0.05), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + AnimatedBuilder( + animation: controller.glowAnimation, + builder: (_, __) => Container( + width: 6, + height: 6, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: accent + .withOpacity(0.4 + controller.glowAnimation.value * 0.6), + boxShadow: [ + BoxShadow( + color: accent + .withOpacity(controller.glowAnimation.value * 0.5), + blurRadius: 6, + spreadRadius: 1), + ], + ), + ), + ), + const SizedBox(width: 8), + Text('AI-Powered Mobility', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: accent.withOpacity(0.85), + letterSpacing: 1.4)), + ], + ), + ); + } +} + +// ── Progress bar ── class _GlowProgressBar extends StatelessWidget { final double value; - const _GlowProgressBar({required this.value}); + final Color accent; + const _GlowProgressBar({required this.value, required this.accent}); @override Widget build(BuildContext context) { return Stack( children: [ - // المسار Container( - height: 2, - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.07), - borderRadius: BorderRadius.circular(2), - ), - ), - // الملء المضيء + height: 2, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.06), + borderRadius: BorderRadius.circular(2))), FractionallySizedBox( widthFactor: value.clamp(0.0, 1.0), child: Container( height: 2, decoration: BoxDecoration( - gradient: const LinearGradient(colors: [ - Color(0xFF0052FF), - Color(0xFF1DA1F2), - ]), + gradient: + LinearGradient(colors: [accent.withOpacity(0.5), accent]), borderRadius: BorderRadius.circular(2), boxShadow: [ BoxShadow( - color: const Color(0xFF1DA1F2).withOpacity(0.55), - blurRadius: 8, - spreadRadius: 1, - ), + color: accent.withOpacity(0.4), + blurRadius: 8, + spreadRadius: 1) ], ), ), @@ -364,40 +349,30 @@ class _GlowProgressBar extends StatelessWidget { } } -// ── رسّام الشبكة الهندسية ───────────────────────────────────────────────── +// ── Grid painter ── class _GridPainter extends CustomPainter { + final Color color; + _GridPainter({required this.color}); + @override void paint(Canvas canvas, Size size) { - final linePaint = Paint() - ..color = const Color(0xFF1DA1F2).withOpacity(0.04) + final paint = Paint() + ..color = color ..strokeWidth = 0.5; - - const spacing = 36.0; - - for (double y = 0; y < size.height; y += spacing) { - canvas.drawLine(Offset(0, y), Offset(size.width, y), linePaint); + const step = 36.0; + for (double y = 0; y < size.height; y += step) { + canvas.drawLine(Offset(0, y), Offset(size.width, y), paint); } - for (double x = 0; x < size.width; x += spacing) { - canvas.drawLine(Offset(x, 0), Offset(x, size.height), linePaint); - } - - // نقاط التقاطع - final dotPaint = Paint() - ..color = const Color(0xFF1DA1F2).withOpacity(0.07) - ..style = PaintingStyle.fill; - - for (double y = 0; y < size.height; y += spacing) { - for (double x = 0; x < size.width; x += spacing) { - canvas.drawCircle(Offset(x, y), 0.9, dotPaint); - } + for (double x = 0; x < size.width; x += step) { + canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint); } } @override - bool shouldRepaint(_GridPainter old) => false; + bool shouldRepaint(_GridPainter old) => old.color != color; } -// ── رسّام الحلقة المدارية ───────────────────────────────────────────────── +// ── Orbital ring ── class _OrbitalRingPainter extends CustomPainter { final double radius; final Color dotColor; @@ -407,8 +382,8 @@ class _OrbitalRingPainter extends CustomPainter { const _OrbitalRingPainter({ this.radius = 95, - this.dotColor = const Color(0xFF1DA1F2), - this.lineOpacity = 0.25, + this.dotColor = const Color(0xFF8C9CF8), + this.lineOpacity = 0.20, this.dotSize = 5.5, this.dashCount = 0, }); @@ -418,50 +393,38 @@ class _OrbitalRingPainter extends CustomPainter { final center = Offset(size.width / 2, size.height / 2); if (dashCount > 0) { - // حلقة متقطعة (dashed) final dashPaint = Paint() - ..color = const Color(0xFF1DA1F2).withOpacity(lineOpacity) + ..color = dotColor.withOpacity(lineOpacity) ..strokeWidth = 1 ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; - - const dashAngle = 2 * pi / 40; - final gapRatio = 0.45; - for (int i = 0; i < dashCount; i++) { - final startAngle = i * (2 * pi / dashCount); - final sweepAngle = (2 * pi / dashCount) * (1 - gapRatio); - canvas.drawArc( - Rect.fromCircle(center: center, radius: radius), - startAngle, - sweepAngle, - false, - dashPaint, - ); + final start = i * (2 * math.pi / dashCount); + final sweep = (2 * math.pi / dashCount) * 0.55; + canvas.drawArc(Rect.fromCircle(center: center, radius: radius), start, + sweep, false, dashPaint); } } else { - // حلقة متصلة final ringPaint = Paint() - ..color = const Color(0xFF1DA1F2).withOpacity(lineOpacity) + ..color = dotColor.withOpacity(lineOpacity) ..strokeWidth = 1 ..style = PaintingStyle.stroke; canvas.drawCircle(center, radius, ringPaint); } - // النقطة البراقة — دائماً في القمة (before rotation) final dotPos = Offset(center.dx, center.dy - radius); - // توهج خلف النقطة final glowPaint = Paint() - ..color = dotColor.withOpacity(0.35) + ..color = dotColor.withOpacity(0.30) ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 9); canvas.drawCircle(dotPos, dotSize + 2, glowPaint); - // النقطة نفسها - final dotPaint = Paint() - ..color = dotColor - ..style = PaintingStyle.fill; - canvas.drawCircle(dotPos, dotSize, dotPaint); + canvas.drawCircle( + dotPos, + dotSize, + Paint() + ..color = dotColor + ..style = PaintingStyle.fill); } @override diff --git a/siro_rider/lib/views/home/HomePage/share_app_page.dart b/siro_rider/lib/views/home/HomePage/share_app_page.dart index a57e3ba..dd81f21 100644 --- a/siro_rider/lib/views/home/HomePage/share_app_page.dart +++ b/siro_rider/lib/views/home/HomePage/share_app_page.dart @@ -158,10 +158,7 @@ class ShareAppPage extends StatelessWidget { _showContactsDialog(Get .context!); // Show contacts dialog after loading contacts } else { - Get.snackbar( - 'No contacts available'.tr, - 'Please add contacts to your phone.'.tr, - ); + mySnackbarInfo('Please add contacts to your phone.'.tr); } }, ), diff --git a/siro_rider/lib/views/home/map_widget.dart/map_menu_widget.dart b/siro_rider/lib/views/home/map_widget.dart/map_menu_widget.dart index a5059d4..43fef1e 100644 --- a/siro_rider/lib/views/home/map_widget.dart/map_menu_widget.dart +++ b/siro_rider/lib/views/home/map_widget.dart/map_menu_widget.dart @@ -16,6 +16,7 @@ import 'package:url_launcher/url_launcher.dart'; import '../../../constant/colors.dart'; import '../../../constant/links.dart'; import '../../../controller/home/map/map_engine_controller.dart'; +import '../../widgets/error_snakbar.dart'; import '../../notification/notification_page.dart'; import '../HomePage/contact_us.dart'; import '../HomePage/share_app_page.dart'; @@ -386,10 +387,10 @@ class MapMenuWidget extends StatelessWidget { if (await canLaunchUrl(url)) { await launchUrl(url); } else { - Get.snackbar('Error', 'Could not launch driver app store.'); + mySnackeBarError('Could not launch driver app store.'); } } catch (e) { - Get.snackbar('Error', 'Could not open the link.'); + mySnackeBarError('Could not open the link.'); } } } diff --git a/siro_rider/lib/views/home/map_widget.dart/picker_animation_container.dart b/siro_rider/lib/views/home/map_widget.dart/picker_animation_container.dart index 92358f7..72cea18 100644 --- a/siro_rider/lib/views/home/map_widget.dart/picker_animation_container.dart +++ b/siro_rider/lib/views/home/map_widget.dart/picker_animation_container.dart @@ -8,6 +8,7 @@ import '../../../controller/home/map/location_search_controller.dart'; import '../../../controller/home/map/map_engine_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart'; import '../../../main.dart'; +import '../../widgets/error_snakbar.dart'; import '../../widgets/elevated_btn.dart'; import 'form_search_places_destenation.dart'; @@ -165,12 +166,7 @@ class PickerAnimtionContainerFormPlaces extends StatelessWidget { index] ['id']); Get.back(); - Get.snackbar( - 'Deleted ', - '${'You are Delete'.tr} ${favoritePlaces[index]['name']} from your list', - backgroundColor: - AppColor - .accentColor); + mySnackbarInfo('${'You are Delete'.tr} ${favoritePlaces[index]['name']} from your list'); }, icon: const Icon(Icons .favorite_outlined), diff --git a/siro_rider/lib/views/home/map_widget.dart/ride_from_start_app.dart b/siro_rider/lib/views/home/map_widget.dart/ride_from_start_app.dart index e15aaf6..90927ba 100644 --- a/siro_rider/lib/views/home/map_widget.dart/ride_from_start_app.dart +++ b/siro_rider/lib/views/home/map_widget.dart/ride_from_start_app.dart @@ -15,6 +15,7 @@ import '../../../controller/home/map/ui_interactions_controller.dart'; import '../../../controller/home/map/ride_state.dart'; import '../../../controller/profile/profile_controller.dart'; import '../../../main.dart'; +import '../../widgets/error_snakbar.dart'; class RideFromStartApp extends StatelessWidget { const RideFromStartApp({super.key}); @@ -319,8 +320,7 @@ class RideFromStartApp extends StatelessWidget { if (sosPhone != null && sosPhone != 'sos') { action(sosPhone); } else { - Get.snackbar('Warning'.tr, 'Please set a valid SOS phone number.'.tr, - backgroundColor: AppColor.redColor, colorText: Colors.white); + mySnackbarWarning('Please set a valid SOS phone number.'.tr); } } } diff --git a/siro_rider/lib/views/home/my_wallet/cliq_payment_sheet.dart b/siro_rider/lib/views/home/my_wallet/cliq_payment_sheet.dart new file mode 100644 index 0000000..fbf95b9 --- /dev/null +++ b/siro_rider/lib/views/home/my_wallet/cliq_payment_sheet.dart @@ -0,0 +1,173 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:siro_rider/constant/box_name.dart'; +import 'package:siro_rider/constant/colors.dart'; +import 'package:siro_rider/constant/currency.dart'; +import 'package:siro_rider/constant/payment_tiers.dart'; +import 'package:siro_rider/constant/style.dart'; +import 'package:siro_rider/controller/functions/country_logic.dart'; +import 'package:siro_rider/controller/payment/payment_controller.dart'; +import 'package:siro_rider/main.dart'; + +class CliqPaymentSheet extends StatefulWidget { + final double amount; + const CliqPaymentSheet({super.key, required this.amount}); + + @override + State createState() => _CliqPaymentSheetState(); +} + +class _CliqPaymentSheetState extends State { + late final TextEditingController _phoneCtrl; + final _formKey = GlobalKey(); + bool _saving = false; + + @override + void initState() { + super.initState(); + _phoneCtrl = TextEditingController( + text: box.read(BoxName.phoneWallet) ?? '', + ); + } + + @override + void dispose() { + _phoneCtrl.dispose(); + super.dispose(); + } + + void _submit() { + if (!_formKey.currentState!.validate()) return; + setState(() => _saving = true); + box.write(BoxName.phoneWallet, _phoneCtrl.text.trim()); + final ctrl = Get.find(); + Get.back(); + ctrl.payWithClickWallet( + Get.context!, + widget.amount.toString(), + CurrencyHelper.currency, + ); + } + + @override + Widget build(BuildContext context) { + final accent = AppColor.secondaryColorStatic; + return Padding( + padding: EdgeInsets.only( + left: 24, right: 24, top: 20, + bottom: MediaQuery.of(context).viewInsets.bottom + 32, + ), + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Handle + Center( + child: Container( + width: 36, height: 4, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(2), + ), + ), + ), + const SizedBox(height: 20), + + // Title + Text('Pay by Cliq'.tr, style: AppStyle.headTitle2), + const SizedBox(height: 4), + Text( + 'Enter your Cliq wallet details'.tr, + style: TextStyle( + color: Colors.white.withOpacity(0.5), + fontSize: 13, + ), + ), + const SizedBox(height: 24), + + // Phone field + TextFormField( + controller: _phoneCtrl, + keyboardType: TextInputType.phone, + style: const TextStyle(color: Colors.white, fontSize: 16, letterSpacing: 1.2), + decoration: InputDecoration( + labelText: 'Wallet Phone Number'.tr, + hintText: CountryLogic.getPhoneHint(box.read(BoxName.countryCode) ?? 'Jordan'), + hintStyle: TextStyle(color: Colors.white.withOpacity(0.3)), + labelStyle: TextStyle(color: accent.withOpacity(0.7)), + filled: true, + fillColor: Colors.white.withOpacity(0.06), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: accent, width: 1.5), + ), + prefixIcon: Icon(Icons.phone_android, color: accent.withOpacity(0.6)), + suffixIcon: _phoneCtrl.text.isNotEmpty + ? IconButton( + icon: Icon(Icons.clear, color: Colors.white.withOpacity(0.4)), + onPressed: () => setState(() => _phoneCtrl.clear()), + ) + : null, + ), + validator: (v) => (v == null || v.trim().isEmpty) + ? 'Please enter a phone number'.tr + : null, + onChanged: (_) => setState(() {}), + ), + const SizedBox(height: 20), + + // Amount summary + Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: accent.withOpacity(0.08), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: accent.withOpacity(0.15)), + ), + child: Row( + children: [ + Icon(Icons.receipt_long, color: accent, size: 22), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Invoice Amount'.tr, + style: TextStyle(fontSize: 11, color: Colors.white.withOpacity(0.5))), + const SizedBox(height: 2), + Text('${PaymentTierConfig.formatAmount(widget.amount)} ${CurrencyHelper.currency}', + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700, color: Colors.white)), + ], + ), + ], + ), + ), + const SizedBox(height: 24), + + // Submit + SizedBox( + height: 50, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: accent, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + elevation: 0, + ), + onPressed: _saving ? null : _submit, + child: _saving + ? const SizedBox(width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) + : Text('Create Invoice'.tr, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), + ), + ), + ], + ), + ), + ); + } +} diff --git a/siro_rider/lib/views/home/my_wallet/passenger_wallet_dialoge.dart b/siro_rider/lib/views/home/my_wallet/passenger_wallet_dialoge.dart index 4845d83..923983a 100644 --- a/siro_rider/lib/views/home/my_wallet/passenger_wallet_dialoge.dart +++ b/siro_rider/lib/views/home/my_wallet/passenger_wallet_dialoge.dart @@ -1,7 +1,7 @@ import 'package:siro_rider/constant/currency.dart'; +import 'package:siro_rider/constant/payment_tiers.dart'; import 'package:siro_rider/print.dart'; import 'package:siro_rider/constant/style.dart'; -// import 'package:siro_rider/controller/functions/encrypt_decrypt.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -14,6 +14,7 @@ import 'package:local_auth/local_auth.dart'; import '../../../main.dart'; import '../../widgets/elevated_btn.dart'; import '../../widgets/my_textField.dart'; +import 'cliq_payment_sheet.dart'; import 'payment_screen_sham.dart'; class PassengerWalletDialog extends StatelessWidget { @@ -33,58 +34,16 @@ class PassengerWalletDialog extends StatelessWidget { ? CupertinoActionSheet( title: Text('Select Payment Amount'.tr), actions: [ - CupertinoActionSheetAction( - onPressed: () { - controller.updateSelectedAmount( - box.read(BoxName.countryCode) == 'Syria' ? 10000 : 10, - ); - showPaymentOptions(context, controller); - }, - child: Text( - box.read(BoxName.countryCode) == 'Syria' - ? '10000 ${'LE'.tr}' - : '10 ${CurrencyHelper.currency}', + for (int i = 0; i < PaymentTierConfig.currentTiers.length; i++) + CupertinoActionSheetAction( + onPressed: () { + controller.updateSelectedAmount( + PaymentTierConfig.currentTiers[i].amount, + ); + showPaymentOptions(context, controller); + }, + child: Text(PaymentTierConfig.tierLabel(i)), ), - ), - CupertinoActionSheetAction( - onPressed: () { - controller.updateSelectedAmount( - box.read(BoxName.countryCode) == 'Syria' ? 20000 : 20, - ); - showPaymentOptions(context, controller); - }, - child: Text( - box.read(BoxName.countryCode) == 'Syria' - ? '20000 ${'LE'.tr} = 2050 ${'LE'.tr}' - : '20 ${CurrencyHelper.currency}', - ), - ), - CupertinoActionSheetAction( - onPressed: () { - controller.updateSelectedAmount( - box.read(BoxName.countryCode) == 'Syria' ? 40000 : 40, - ); - showPaymentOptions(context, controller); - }, - child: Text( - box.read(BoxName.countryCode) == 'Syria' - ? '40000 ${'LE'.tr} = 4150 ${'LE'.tr}' - : '40 ${CurrencyHelper.currency}', - ), - ), - CupertinoActionSheetAction( - onPressed: () { - controller.updateSelectedAmount( - box.read(BoxName.countryCode) == 'Syria' ? 100000 : 50, - ); - showPaymentOptions(context, controller); - }, - child: Text( - box.read(BoxName.countryCode) == 'Syria' - ? '100000 ${'LE'.tr} = 11000 ${'LE'.tr}' - : '50 ${CurrencyHelper.currency}', - ), - ), ], cancelButton: CupertinoActionSheetAction( onPressed: () { @@ -145,40 +104,7 @@ void showPaymentBottomSheet(BuildContext context) { const SizedBox(height: 16.0), // Payment Options List - _buildPaymentOption( - context: context, - controller: controller, - amount: 500, - bonusAmount: 30, - currency: CurrencyHelper.currency, - ), - - const SizedBox(height: 8.0), - _buildPaymentOption( - context: context, - controller: controller, - amount: 1000, - bonusAmount: 70, - currency: CurrencyHelper.currency, - ), - - const SizedBox(height: 8.0), - _buildPaymentOption( - context: context, - controller: controller, - amount: 2000, - bonusAmount: 180, - currency: CurrencyHelper.currency, - ), - - const SizedBox(height: 8.0), - _buildPaymentOption( - context: context, - controller: controller, - amount: 5000, - bonusAmount: 700, - currency: CurrencyHelper.currency, - ), + ..._buildPaymentTiers(context, controller), const SizedBox(height: 16.0), TextButton( @@ -192,18 +118,43 @@ void showPaymentBottomSheet(BuildContext context) { ); } +List _buildPaymentTiers(BuildContext context, PaymentController controller) { + final tiers = PaymentTierConfig.currentTiers; + final currency = PaymentTierConfig.currentCurrency; + + return List.generate(tiers.length, (i) { + final t = tiers[i]; + final separator = i < tiers.length - 1 + ? const SizedBox(height: 8) + : const SizedBox.shrink(); + return Column( + children: [ + _buildPaymentOption( + context: context, + controller: controller, + tier: t, + currency: currency, + ), + separator, + ], + ); + }); +} + Widget _buildPaymentOption({ required BuildContext context, required PaymentController controller, - required int amount, - required double bonusAmount, + required PaymentTier tier, required String currency, }) { + final label = PaymentTierConfig.tierLabel( + PaymentTierConfig.currentTiers.indexOf(tier), + ); return Material( color: Colors.transparent, child: InkWell( onTap: () { - controller.updateSelectedAmount(amount); + controller.updateSelectedAmount(tier.amount); Get.back(); showPaymentOptions(context, controller); }, @@ -213,13 +164,7 @@ Widget _buildPaymentOption({ border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(8.0), ), - child: Text( - bonusAmount > 0 - ? '${'Pay'.tr} $amount $currency, ${'Get'.tr} ${amount + bonusAmount} $currency' - : '$amount $currency', - style: AppStyle.title, - textAlign: TextAlign.center, - ), + child: Text(label, style: AppStyle.title, textAlign: TextAlign.center), ), ), ); @@ -394,44 +339,19 @@ void showPaymentOptions(BuildContext context, PaymentController controller) { GestureDetector( onTap: () async { Get.back(); - Get.defaultDialog( - barrierDismissible: false, - title: 'Insert Wallet phone number'.tr, - content: Form( - key: controller.formKey, - child: MyTextForm( - controller: controller.walletphoneController, - label: 'Insert Wallet phone number'.tr, - hint: '962791234567', - type: TextInputType.phone)), - confirm: MyElevatedButton( - title: 'OK'.tr, - onPressed: () async { - Get.back(); - if (controller.formKey.currentState!.validate()) { - if (controller.selectedAmount != 0) { - controller.isLoading = true; - controller.update(); - box.write(BoxName.phoneWallet, - (controller.walletphoneController.text)); - await controller.payWithClickWallet( - context, - controller.selectedAmount.toString(), - CurrencyHelper.currency, - ); - await controller.getPassengerWallet(); - - controller.isLoading = false; - controller.update(); - } else { - Toast.show( - context, - '⚠️ You need to choose an amount!'.tr, - AppColor.redColor, - ); - } - } - })); + if (controller.selectedAmount == 0) { + Toast.show(context, '⚠️ You need to choose an amount!'.tr, AppColor.redColor); + return; + } + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: AppColor.primaryColor, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (_) => CliqPaymentSheet(amount: controller.selectedAmount!), + ); }, child: Container( margin: const EdgeInsets.only(top: 10), diff --git a/siro_rider/lib/views/home/my_wallet/payment_screen_cliq.dart b/siro_rider/lib/views/home/my_wallet/payment_screen_cliq.dart index 97f1c83..a3a3922 100644 --- a/siro_rider/lib/views/home/my_wallet/payment_screen_cliq.dart +++ b/siro_rider/lib/views/home/my_wallet/payment_screen_cliq.dart @@ -3,8 +3,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; +import '../../../constant/colors.dart'; import '../../../constant/links.dart'; +import '../../../constant/style.dart'; import '../../../controller/functions/crud.dart'; +import '../../widgets/error_snakbar.dart'; class PaymentScreenCliq extends StatefulWidget { final double amount; @@ -24,20 +27,17 @@ class PaymentScreenCliq extends StatefulWidget { class _PaymentScreenCliqState extends State with SingleTickerProviderStateMixin { Timer? _pollingTimer; - String _status = 'waiting'; // waiting, uploading, verifying, success, error + String _status = 'waiting'; final TextEditingController _proofController = TextEditingController(); - + late AnimationController _blinkController; - late Animation _colorAnimation; - late Animation _shadowAnimation; + late Animation _pulseAnimation; @override void initState() { super.initState(); - _blinkController = AnimationController(duration: const Duration(milliseconds: 800), vsync: this)..repeat(reverse: true); - _colorAnimation = ColorTween(begin: Colors.red.shade700, end: Colors.red.shade100).animate(_blinkController); - _shadowAnimation = Tween(begin: 2.0, end: 15.0).animate(CurvedAnimation(parent: _blinkController, curve: Curves.easeInOut)); - + _blinkController = AnimationController(duration: const Duration(milliseconds: 1000), vsync: this)..repeat(reverse: true); + _pulseAnimation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: _blinkController, curve: Curves.easeInOut)); _startPolling(); } @@ -64,17 +64,15 @@ class _PaymentScreenCliqState extends State with SingleTicker Future _submitProof() async { if (_proofController.text.trim().isEmpty) { - Get.snackbar('Error'.tr, 'Please paste the transfer message'.tr, backgroundColor: Colors.red); + mySnackeBarError('Please paste the transfer message'.tr); return; } setState(() => _status = 'verifying'); - try { final res = await CRUD().postWallet(link: AppLink.uploadCliqProof, payload: { 'invoice_number': widget.invoiceNumber, 'proof_text': _proofController.text.trim(), }); - if (res != 'failure' && res['status'] == 'success') { if (mounted) setState(() => _status = 'success'); } else { @@ -89,9 +87,16 @@ class _PaymentScreenCliqState extends State with SingleTicker @override Widget build(BuildContext context) { + final accent = AppColor.secondaryColorStatic; return Scaffold( - backgroundColor: Colors.grey[50], - appBar: AppBar(title: const Text("Cliq Payment"), centerTitle: true, backgroundColor: Colors.white, foregroundColor: Colors.black), + backgroundColor: AppColor.primaryColor, + appBar: AppBar( + title: const Text('Cliq Payment'), + centerTitle: true, + backgroundColor: Colors.transparent, + foregroundColor: Colors.white, + elevation: 0, + ), body: SafeArea( child: Padding( padding: const EdgeInsets.all(20.0), @@ -102,48 +107,71 @@ class _PaymentScreenCliqState extends State with SingleTicker } Widget _buildWaitingUI() { - final currencyFormat = NumberFormat.decimalPattern('ar_SY'); - + final accent = AppColor.secondaryColorStatic; return SingleChildScrollView( child: Column( children: [ + // Amount card Container( width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15), - decoration: BoxDecoration(gradient: LinearGradient(colors: [Colors.blue.shade800, Colors.blue.shade600]), borderRadius: BorderRadius.circular(16)), + padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 20), + decoration: BoxDecoration( + gradient: LinearGradient(colors: [accent.withOpacity(0.2), accent.withOpacity(0.05)]), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: accent.withOpacity(0.15)), + ), child: Column( children: [ - const Text("المبلغ المطلوب", style: TextStyle(color: Colors.white70, fontSize: 14)), - const SizedBox(height: 5), - Text("${currencyFormat.format(widget.amount)} ل.س", style: const TextStyle(color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold)), + Text('Invoice Amount'.tr, style: TextStyle(color: Colors.white.withOpacity(0.6), fontSize: 13)), + const SizedBox(height: 6), + Text('${widget.amount.toStringAsFixed(widget.amount == widget.amount.roundToDouble() ? 0 : 1)} ${'SYP'}', + style: const TextStyle(color: Colors.white, fontSize: 32, fontWeight: FontWeight.w700, letterSpacing: 1)), ], ), ), - const SizedBox(height: 25), - + const SizedBox(height: 28), + + // Cliq Alias AnimatedBuilder( animation: _blinkController, - builder: (context, child) { + builder: (_, child) { return Container( padding: const EdgeInsets.all(20), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: _colorAnimation.value ?? Colors.red, width: 3.0), boxShadow: [BoxShadow(color: (_colorAnimation.value ?? Colors.red).withOpacity(0.4), blurRadius: _shadowAnimation.value, spreadRadius: 2)]), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.05), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: accent.withOpacity(0.3 + _pulseAnimation.value * 0.3), width: 2), + boxShadow: [ + BoxShadow( + color: accent.withOpacity(0.1 + _pulseAnimation.value * 0.15), + blurRadius: 12 + _pulseAnimation.value * 10, + spreadRadius: 1, + ), + ], + ), child: Column( children: [ - const Text("يرجى تحويل المبلغ إلى الاسم المستعار التالي (Alias):", textAlign: TextAlign.center, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), - const SizedBox(height: 15), + Text('Send payment to this Cliq alias'.tr, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Colors.white.withOpacity(0.85))), + const SizedBox(height: 16), InkWell( onTap: () { Clipboard.setData(ClipboardData(text: widget.cliqAlias)); - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: const Text("تم نسخ الاسم ✅", textAlign: TextAlign.center), backgroundColor: Colors.green.shade600)); + mySnackbarSuccess('Alias copied'.tr); }, child: Container( - padding: const EdgeInsets.all(15), - decoration: BoxDecoration(color: Colors.blue.shade50, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.blue.shade200)), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14), + decoration: BoxDecoration( + color: accent.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: accent.withOpacity(0.3)), + ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(widget.cliqAlias, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 2.0)), - const Icon(Icons.copy, color: Colors.blue), + Text(widget.cliqAlias, style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold, letterSpacing: 2.0, color: Colors.white)), + Icon(Icons.copy_rounded, color: accent, size: 22), ], ), ), @@ -153,34 +181,49 @@ class _PaymentScreenCliqState extends State with SingleTicker ); }, ), - - const SizedBox(height: 30), - const Text("بعد إتمام التحويل، يرجى نسخ رسالة البنك النصية ولصقها هنا للتحقق التلقائي:", textAlign: TextAlign.center, style: TextStyle(fontSize: 14)), - const SizedBox(height: 10), + const SizedBox(height: 28), + + // Proof input + Text('After payment, paste the bank SMS here for auto-verification:'.tr, + textAlign: TextAlign.center, style: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 13)), + const SizedBox(height: 12), TextField( controller: _proofController, maxLines: 4, + style: const TextStyle(color: Colors.white), decoration: InputDecoration( - hintText: "قم بلصق نص رسالة التحويل هنا...", - border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + hintText: 'Paste transfer message...'.tr, + hintStyle: TextStyle(color: Colors.white.withOpacity(0.3)), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), filled: true, - fillColor: Colors.white, + fillColor: Colors.white.withOpacity(0.06), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: accent, width: 1.5), + ), ), ), - const SizedBox(height: 15), + const SizedBox(height: 16), + SizedBox( width: double.infinity, height: 50, child: ElevatedButton( - style: ElevatedButton.styleFrom(backgroundColor: Colors.blue.shade800, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))), + style: ElevatedButton.styleFrom( + backgroundColor: accent, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + elevation: 0, + ), onPressed: _status == 'verifying' ? null : _submitProof, - child: _status == 'verifying' - ? const CircularProgressIndicator(color: Colors.white) - : const Text("تحقق من الدفع", style: TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold)), + child: _status == 'verifying' + ? const SizedBox(width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) + : Text('Verify Payment'.tr, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), ), ), - const SizedBox(height: 20), - const Text("جاري فحص الفاتورة تلقائياً كل 5 ثوانٍ...", style: TextStyle(color: Colors.grey, fontSize: 12)), + const SizedBox(height: 16), + Text('Auto-checking invoice every 5s...'.tr, + style: TextStyle(color: Colors.white.withOpacity(0.3), fontSize: 11)), ], ), ); @@ -190,16 +233,29 @@ class _PaymentScreenCliqState extends State with SingleTicker return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.verified_rounded, color: Colors.green, size: 100), + Container( + width: 90, height: 90, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.green.withOpacity(0.1), + ), + child: const Icon(Icons.verified_rounded, color: Colors.green, size: 56), + ), const SizedBox(height: 20), - const Text("تم الدفع بنجاح!", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)), - const SizedBox(height: 40), + Text('Payment Successful!'.tr, style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white)), + const SizedBox(height: 32), SizedBox( width: double.infinity, child: ElevatedButton( - style: ElevatedButton.styleFrom(backgroundColor: Colors.green, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16)), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + elevation: 0, + ), onPressed: () { Get.back(); Get.back(); }, - child: const Text("متابعة", style: TextStyle(fontSize: 18)), + child: Text('Continue'.tr, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), ), ), ], diff --git a/siro_rider/lib/views/home/my_wallet/payment_screen_mtn.dart b/siro_rider/lib/views/home/my_wallet/payment_screen_mtn.dart index 4687602..911c03b 100644 --- a/siro_rider/lib/views/home/my_wallet/payment_screen_mtn.dart +++ b/siro_rider/lib/views/home/my_wallet/payment_screen_mtn.dart @@ -5,6 +5,7 @@ import 'package:get/get.dart'; import 'package:intl/intl.dart'; import '../../../constant/links.dart'; import '../../../controller/functions/crud.dart'; +import '../../widgets/error_snakbar.dart'; class PaymentScreenMtn extends StatefulWidget { final double amount; @@ -64,7 +65,7 @@ class _PaymentScreenMtnState extends State with SingleTickerPr Future _submitProof() async { if (_proofController.text.trim().isEmpty) { - Get.snackbar('Error'.tr, 'Please paste the transfer message'.tr, backgroundColor: Colors.red); + mySnackeBarError('Please paste the transfer message'.tr); return; } setState(() => _status = 'verifying'); diff --git a/siro_rider/lib/views/home/profile/passenger_profile_page.dart b/siro_rider/lib/views/home/profile/passenger_profile_page.dart index 44aac7d..0c77a0d 100644 --- a/siro_rider/lib/views/home/profile/passenger_profile_page.dart +++ b/siro_rider/lib/views/home/profile/passenger_profile_page.dart @@ -11,6 +11,7 @@ import 'package:siro_rider/constant/colors.dart'; import 'package:siro_rider/controller/profile/profile_controller.dart'; import 'package:siro_rider/main.dart'; import 'package:siro_rider/views/widgets/mycircular.dart'; +import 'package:siro_rider/views/widgets/error_snakbar.dart'; import '../../../constant/style.dart'; import '../../../controller/functions/country_logic.dart'; @@ -87,15 +88,7 @@ class _PassengerProfilePageState extends State { final response = await request.send(); if (response.statusCode == 200) { - Get.snackbar( - 'Success'.tr, - 'Profile photo updated'.tr, - backgroundColor: Colors.green.withOpacity(0.9), - colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); + mySnackbarSuccess('Profile photo updated'.tr); } else { _showUploadError(); } @@ -107,15 +100,7 @@ class _PassengerProfilePageState extends State { } void _showUploadError() { - Get.snackbar( - 'Error'.tr, - 'Failed to upload photo'.tr, - backgroundColor: Colors.red.withOpacity(0.9), - colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); + mySnackeBarError('Failed to upload photo'.tr); } Future _showImageSourceSheet() async { diff --git a/siro_rider/lib/views/home/profile/promos_passenger_page.dart b/siro_rider/lib/views/home/profile/promos_passenger_page.dart index b095765..661fcb8 100644 --- a/siro_rider/lib/views/home/profile/promos_passenger_page.dart +++ b/siro_rider/lib/views/home/profile/promos_passenger_page.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:siro_rider/controller/home/profile/promos_controller.dart'; import 'package:siro_rider/views/widgets/my_scafold.dart'; +import 'package:siro_rider/views/widgets/error_snakbar.dart'; import 'dart:ui'; // لاستخدامه في الفاصل import '../../../constant/colors.dart'; @@ -132,13 +133,7 @@ class PromosPassengerPage extends StatelessWidget { // --- نفس منطقك القديم للنسخ --- Clipboard.setData( ClipboardData(text: promo['promo_code'])); - Get.snackbar( - 'Promo Copied!'.tr, - '${'Code'.tr} ${promo['promo_code']} ${'copied to clipboard'.tr}', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: AppColor.greenColor, - colorText: Colors.white, - ); + mySnackbarSuccess('${'Code'.tr} ${promo['promo_code']} ${'copied to clipboard'.tr}'); }, child: Container( color: AppColor.primaryColor.withOpacity(0.1), diff --git a/siro_rider/packages/get/lib/get_navigation/src/snackbar/snackbar_controller.dart b/siro_rider/packages/get/lib/get_navigation/src/snackbar/snackbar_controller.dart index bcd7f9f..a93e87a 100644 --- a/siro_rider/packages/get/lib/get_navigation/src/snackbar/snackbar_controller.dart +++ b/siro_rider/packages/get/lib/get_navigation/src/snackbar/snackbar_controller.dart @@ -34,7 +34,7 @@ class SnackbarController { /// The animation controller that the route uses to drive the transitions. /// /// The animation itself is exposed by the [animation] property. - late final AnimationController _controller; + AnimationController? _controller; SnackbarStatus? _currentStatus; @@ -88,7 +88,9 @@ class SnackbarController { } void _configureOverlay() { - _overlayState = Overlay.of(Get.overlayContext!); + final ctx = Get.overlayContext; + if (ctx == null) return; + _overlayState = Overlay.of(ctx); _overlayEntries.clear(); _overlayEntries.addAll(_createOverlayEntries(_getBodyWidget())); _overlayState!.insertAll(_overlayEntries); @@ -96,8 +98,7 @@ class SnackbarController { } void _configureSnackBarDisplay() { - assert(!_transitionCompleter.isCompleted, - 'Cannot configure a snackbar after disposing it.'); + if (_transitionCompleter.isCompleted) return; _controller = _createAnimationController(); _configureAlignment(snackbar.snackPosition); _snackbarStatus = snackbar.snackbarStatus; @@ -106,7 +107,7 @@ class SnackbarController { _animation = _createAnimation(); _animation.addStatusListener(_handleStatusChanged); _configureTimer(); - _controller.forward(); + _controller?.forward(); } void _configureTimer() { @@ -126,11 +127,15 @@ class SnackbarController { /// the transition controlled by the animation controller created by /// `createAnimationController()`. Animation _createAnimation() { - assert(!_transitionCompleter.isCompleted, - 'Cannot create a animation from a disposed snackbar'); + final ctrl = _controller; + if (ctrl == null) { + return AlignmentTween(begin: _initialAlignment, end: _endAlignment).animate( + kAlwaysCompleteAnimation, + ); + } return AlignmentTween(begin: _initialAlignment, end: _endAlignment).animate( CurvedAnimation( - parent: _controller, + parent: ctrl, curve: snackbar.forwardAnimationCurve, reverseCurve: snackbar.reverseAnimationCurve, ), @@ -152,9 +157,10 @@ class SnackbarController { } Animation _createBlurFilterAnimation() { + final ctrl = _controller!; return Tween(begin: 0.0, end: snackbar.overlayBlur).animate( CurvedAnimation( - parent: _controller, + parent: ctrl, curve: const Interval( 0.0, 0.35, @@ -165,11 +171,12 @@ class SnackbarController { } Animation _createColorOverlayColor() { + final ctrl = _controller!; return ColorTween( begin: const Color(0x00000000), end: snackbar.overlayColor) .animate( CurvedAnimation( - parent: _controller, + parent: ctrl, curve: const Interval( 0.0, 0.35, @@ -300,18 +307,21 @@ class SnackbarController { } void _removeEntry() { - assert( - !_transitionCompleter.isCompleted, - 'Cannot remove entry from a disposed snackbar', - ); + if (_transitionCompleter.isCompleted) return; _cancelTimer(); + final ctrl = _controller; + if (ctrl == null) { + _removeOverlay(); + return; + } + if (_wasDismissedBySwipe) { - Timer(const Duration(milliseconds: 200), _controller.reset); + Timer(const Duration(milliseconds: 200), ctrl.reset); _wasDismissedBySwipe = false; } else { - _controller.reverse(); + ctrl.reverse(); } } @@ -320,11 +330,13 @@ class SnackbarController { element.remove(); } - assert(!_transitionCompleter.isCompleted, - 'Cannot remove overlay from a disposed snackbar'); - _controller.dispose(); + if (_controller != null) { + _controller!.dispose(); + } _overlayEntries.clear(); - _transitionCompleter.complete(); + if (!_transitionCompleter.isCompleted) { + _transitionCompleter.complete(); + } } Future _show() { @@ -332,6 +344,8 @@ class SnackbarController { return future; } + bool get _isConfigured => _controller != null; + static void cancelAllSnackbars() { _snackBarQueue._cancelAllJobs(); } @@ -366,7 +380,8 @@ class _SnackBarQueue { } Future _closeCurrentJob() async { - if (_currentSnackbar == null) return; - await _currentSnackbar!.close(); + final current = _currentSnackbar; + if (current == null || !current._isConfigured) return; + await current.close(); } }