Update: 2026-06-25 02:28:33

This commit is contained in:
Hamza-Ayed
2026-06-25 02:28:33 +03:00
parent 1ff13f09ac
commit 98a8a2ae3d
43 changed files with 992 additions and 812 deletions

View File

@@ -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<String, dynamic> jsonData = {};
for (var i = 0; i < decod['message'].length; i++) {

View File

@@ -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<PaymentTier> tiers;
const PaymentTierConfig({required this.currencyCode, required this.tiers});
static const Map<String, PaymentTierConfig> _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<PaymentTier> 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';
}
}

View File

@@ -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();

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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<dynamic> passengerDialog(String message) {

View File

@@ -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'

View File

@@ -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);
}
}
}

View File

@@ -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<Map<String, dynamic>>.from(jsonDecode(res)['message']);
links = List<Map<String, dynamic>>.from(res['message']);
await box.remove(BoxName.locationName);
await box.remove(BoxName.basicLink);
await box.remove(links[4]['name']);

View File

@@ -20,7 +20,7 @@ class SmsEgyptController extends GetxController {
Future<String> 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";

View File

@@ -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();

View File

@@ -29,7 +29,7 @@ class BlinkingController extends GetxController {
},
));
}
var decode = jsonDecode(value);
var decode = value;
// if (decode["status"] == "success") {
// var firstElement = decode["message"][0];

View File

@@ -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');

View File

@@ -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 = [];

View File

@@ -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<void> 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(

View File

@@ -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) {

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);
}
}
},

View File

@@ -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);
}
}
}

View File

@@ -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()));

View File

@@ -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');

View File

@@ -31,7 +31,7 @@ class NotificationCaptainController extends GetxController {
Get.back();
}));
}
notificationData = jsonDecode(res);
notificationData = res;
// sql.insertData(notificationData['message'], TableName.captainNotification);
isLoading = false;

View File

@@ -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 {
// التأكد أننا نخزن قائمة

View File

@@ -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 {

View File

@@ -30,7 +30,7 @@ class DriverWalletHistoryController extends GetxController {
},
));
}
archive = jsonDecode(res)['message'];
archive = res['message'];
isLoading = false;
update();
}

View File

@@ -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<RideLifecycleController>().totalPassenger.toString());
int? selectedAmount = 0;
double? selectedAmount = 0;
List<dynamic> 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<String> generateTokenDriver(String amount) async {
@@ -89,8 +89,7 @@ class PaymentController extends GetxController {
'driverID': Get.find<RideLifecycleController>().driverId,
'amount': amount.toString(),
});
var d = jsonDecode(res);
return d['message'];
return res['message'];
}
Future<void> payToDriverForCancelAfterAppliedAndHeNearYou(
@@ -430,14 +429,14 @@ class PaymentController extends GetxController {
Future<void> 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<String, dynamic> resMap;
if (res is Map<String, dynamic>) {
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());
}
}

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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<String, dynamic> 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<String, dynamic> 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(),
};
}

View File

@@ -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

View File

@@ -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);
}
},
),

View File

@@ -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.');
}
}
}

View File

@@ -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),

View File

@@ -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);
}
}
}

View File

@@ -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<CliqPaymentSheet> createState() => _CliqPaymentSheetState();
}
class _CliqPaymentSheetState extends State<CliqPaymentSheet> {
late final TextEditingController _phoneCtrl;
final _formKey = GlobalKey<FormState>();
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<PaymentController>();
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)),
),
),
],
),
),
);
}
}

View File

@@ -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<Widget> _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),

View File

@@ -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<PaymentScreenCliq> with SingleTickerProviderStateMixin {
Timer? _pollingTimer;
String _status = 'waiting'; // waiting, uploading, verifying, success, error
String _status = 'waiting';
final TextEditingController _proofController = TextEditingController();
late AnimationController _blinkController;
late Animation<Color?> _colorAnimation;
late Animation<double> _shadowAnimation;
late Animation<double> _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<double>(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<double>(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: _blinkController, curve: Curves.easeInOut));
_startPolling();
}
@@ -64,17 +64,15 @@ class _PaymentScreenCliqState extends State<PaymentScreenCliq> with SingleTicker
Future<void> _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<PaymentScreenCliq> 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<PaymentScreenCliq> 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<PaymentScreenCliq> 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<PaymentScreenCliq> 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)),
),
),
],

View File

@@ -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<PaymentScreenMtn> with SingleTickerPr
Future<void> _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');

View File

@@ -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<PassengerProfilePage> {
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<PassengerProfilePage> {
}
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<ImageSource?> _showImageSourceSheet() async {

View File

@@ -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),

View File

@@ -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<Alignment> _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<double> _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<Color?> _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<void> _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<void> _closeCurrentJob() async {
if (_currentSnackbar == null) return;
await _currentSnackbar!.close();
final current = _currentSnackbar;
if (current == null || !current._isConfigured) return;
await current.close();
}
}