Update: 2026-06-25 02:28:33
This commit is contained in:
@@ -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++) {
|
||||
|
||||
66
siro_rider/lib/constant/payment_tiers.dart
Normal file
66
siro_rider/lib/constant/payment_tiers.dart
Normal 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';
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -29,7 +29,7 @@ class BlinkingController extends GetxController {
|
||||
},
|
||||
));
|
||||
}
|
||||
var decode = jsonDecode(value);
|
||||
var decode = value;
|
||||
|
||||
// if (decode["status"] == "success") {
|
||||
// var firstElement = decode["message"][0];
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -31,7 +31,7 @@ class NotificationCaptainController extends GetxController {
|
||||
Get.back();
|
||||
}));
|
||||
}
|
||||
notificationData = jsonDecode(res);
|
||||
notificationData = res;
|
||||
// sql.insertData(notificationData['message'], TableName.captainNotification);
|
||||
|
||||
isLoading = false;
|
||||
|
||||
@@ -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 {
|
||||
// التأكد أننا نخزن قائمة
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -30,7 +30,7 @@ class DriverWalletHistoryController extends GetxController {
|
||||
},
|
||||
));
|
||||
}
|
||||
archive = jsonDecode(res)['message'];
|
||||
archive = res['message'];
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -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,9 +429,9 @@ 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;
|
||||
}
|
||||
|
||||
@@ -448,7 +447,7 @@ class PaymentController extends GetxController {
|
||||
},
|
||||
);
|
||||
|
||||
Get.back(); // close loading
|
||||
if (Get.isDialogOpen ?? false) Get.back();
|
||||
|
||||
late final Map<String, dynamic> resMap;
|
||||
if (res is Map<String, dynamic>) {
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
124
siro_rider/lib/models/click_payment_schema.dart
Normal file
124
siro_rider/lib/models/click_payment_schema.dart
Normal 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(),
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
173
siro_rider/lib/views/home/my_wallet/cliq_payment_sheet.dart
Normal file
173
siro_rider/lib/views/home/my_wallet/cliq_payment_sheet.dart
Normal 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)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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: 28),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
const Text("بعد إتمام التحويل، يرجى نسخ رسالة البنك النصية ولصقها هنا للتحقق التلقائي:", textAlign: TextAlign.center, style: TextStyle(fontSize: 14)),
|
||||
const SizedBox(height: 10),
|
||||
// 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)),
|
||||
? 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)),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user